gulp + browser-sync + django 实现livereload开发

Prerequisites

nodejs ruby

node package dev Dependencies

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"devDependencies": {
"bower": "^1.8.0",
"browser-sync": "^2.18.8",
"gulp": "^3.9.1",
"gulp-copy": "^1.0.0",
"gulp-cssmin": "^0.1.7",
"gulp-group-files": "^2.0.0",
"gulp-ignore": "^2.0.2",
"gulp-rename": "^1.2.2",
"gulp-ruby-sass": "^2.1.1",
"gulp-sourcemaps": "^2.6.0",
"gulp-uglify": "^2.1.2",
"iniparser": "^1.0.5",
"main-bower-files": "^2.13.1",
"ncp": "^2.0.0",
"pdf.js": "bung87/pdf.js#master"
}

pdf.js 只为演示build项目依赖包,下面说明gulpfile.js

1
2
3
4
5
6
7
8
9
var gulp = require('gulp');
// var sass = require('gulp-sass');
var sass = require('gulp-ruby-sass');
var group = require('gulp-group-files');

var bower = require('main-bower-files');
var sourcemaps = require('gulp-sourcemaps');
var browserSync = require('browser-sync').create();
var fs = require('fs');

此处注释gulp-sass而采用gulp-ruby-sass是因为gulp-sass依赖node-sass而node-sass依赖node-gyp,鉴于我有很多次node-gyp安装失败的经历而且本身sass最初的compiler就是ruby实现的我就采用了更为稳妥的做法。 precompile sass files

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var sassFiles = {
"style" : {
src: "./static/scss/style.scss",
dest: "./build/css"
}
};

gulp.task('sass:compile',function (){
return group(sassFiles,function (key,fileset){
// group(sassFiles,function (key,fileset){
// return gulp.src(fileset.src)
// .pipe(sass().on('error', sass.logError))
console.log(fileset.src)
return sass(fileset.src)
.pipe(sourcemaps.init())
.pipe(sourcemaps.write('maps', {
includeContent: true,
sourceRoot: 'source'
}))
.on('error', sass.logError)
.pipe(gulp.dest(fileset.dest)).pipe(browserSync.stream());
})();
});

pipe到browserSync.stream()是实现precompile后浏览器自动刷新的关键,下面不再赘述。 copy前端ui框架的字体到build目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
gulp.task('copy',function (){
var dest = './build/fonts';
if (!fs.existsSync(dest)) {
var sassFiles = {
"fonts" : {
src: ['bower_components/foundation-icons/foundation_icons_general/fonts/*' ],
dest: "./build/fonts"
}
};
return group(sassFiles,function (key,fileset){
return gulp.src(fileset.src)
.pipe(gulp.dest(fileset.dest)).pipe(browserSync.stream());
})();
}
});

使用main-bower-files将bower依赖包 bower.json main 属性里静态资源copy到build目录

1
2
3
4
gulp.task('bower', function() {
//.pipe(gulpIgnore.exclude("pdf.js/"))
return gulp.src(bower()).pipe(gulp.dest('build/js')).pipe(browserSync.stream());
});

watch bower sass的变化

1
2
3
4
5
6
7
8
9
gulp.task('bower:watch',function (){
// gulp.watch('static/**/*.scss',['sass:compile']);
gulp.watch('bower.json',['copy','bower']);
});

gulp.task('sass:watch',function (){
gulp.watch('static/**/*.scss',['sass:compile']);
// gulp.watch('bower.json',['build']);
});

将由collectstatic 和build后输出到assets目录的js和css都minify 并加.min.js .min.css后缀

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var cssmin = require('gulp-cssmin');
var rename = require('gulp-rename');
var uglify = require('gulp-uglify');
var gulpIgnore = require('gulp-ignore');

gulp.task('css:min', ["collectstatic"],function () {
return gulp.src('assets/**/*.css')
.pipe(gulpIgnore.exclude("*.min.css"))
.pipe(cssmin())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('assets'));
});

gulp.task('js:min', ["collectstatic"],function () {
return gulp.src('assets/**/*.js')
.pipe(gulpIgnore.exclude("*.min.js"))
.pipe(gulpIgnore.exclude("**/grappelli/js/*.js"))
.pipe(uglify())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('assets'));
});

gulp.task('min', ['css:min','js:min']);

根据virtualenv 环境变量或者uwsgi.ini获取python执行环境后调用./manage.py collectstatic。 –no-post-process 是因为后面还有其他gulp task,需要确定同步执行完毕。 –noinput >/dev/null 是因为collectstatic输出内容太多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var iniparser = require('iniparser');
var execSync = require('child_process').execSync;
var exec = require('child_process').exec;
gulp.task('collectstatic', function (cb) {
iniparser.parse('./uwsgi.ini', function(err,data){
var uwsgi = data.uwsgi,home = "";
if(process.env.VIRTUAL_ENV){
home = process.env.VIRTUAL_ENV
}else{
home = uwsgi.home;
}
var python = home +"/bin/python";
execSync(python + ' ./manage.py collectstatic --no-post-process --noinput >/dev/null ');
cb();
});
});

单独build pdf.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
gulp.task("pdfbuild",function(){
var path = 'build/js/pdf.js/';
var process = require('process');
if (!fs.existsSync(path)) {
process.chdir('./node_modules/pdf.js');
execSync("yarn install",function(err, stdout, stderr){
console.log(err, stdout, stderr);
process.chdir(__dirname);
});
}
});

var ncp = require('ncp').ncp;
gulp.task("pdf",function(){
var process = require('process');
var path = 'build/js/pdf.js/'
// process.chdir('./node_modules/pdf.js');

if (!fs.existsSync(path)) {
// Do something

execSync("gulp minified --gulpfile ./node_modules/pdf.js/gulpfile.js",function(err, stdout, stderr){
console.log(err, stdout, stderr);
ncp('./node_modules/pdf.js/build/',path);
});
}
});

启动本地server,我是mac环境所以这里启动mysql server 是”mysql.server start”。 这里依赖django-pserver requirements.txt 增加 git+https://github.com/bung87/django-pserver django-pserver 是因为bower sync proxy socket而修改python代码后django默认不会重用同一个socket,而是新开一个socket这时候bower sync监听的已经关闭,新开的又未知。而django-pserver是复用同一个socket所以不存在这个问题。 spawn 这里设置了选项stdio,经过尝试发现django console log都是走stderr所以配置后browser sync的std outputs会和django的混在一起,之前使用execSync导致django的log只有到process终止才输出,而且每次到超出默认的buffer限制。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const { spawn } = require('child_process');
gulp.task('build',['sass:compile',"copy","bower","pdf"]);
gulp.task('develop',['sass:watch',"bower:watch"]);
gulp.task('server',function(cb){
execSync("mysql.server start");

if(process.env.VIRTUAL_ENV){
home = process.env.VIRTUAL_ENV;
const path = require('path');
var python = home +"/bin/python";
var manage = './manage.py'
const server = spawn(python , [`${manage}`,"runserverp","--nostatic"], {
cwd:path.resolve('./'),
stdio: ['ignore', 'ignore', process.stderr]
});

server.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});

cb();
}
});

urls.py 里serve media文件 并阻止static file cache因为没有文件名hash浏览器会缓存。

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.contrib.staticfiles.views import serve as serve_static
from django.views.decorators.cache import never_cache
from django.conf.urls.static import static

if settings.DEBUG:

urlpatterns = (tuple) (static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT)) +urlpatterns

if settings.DEBUG:
urlpatterns += (
url(r'^static/(?P.*)$', never_cache(serve_static)),

)

最后最主要的gulp task proxyReq.setHeader(‘X-BrowserSync-External-Url’, externalUrl);此处设置brower sync的externalUrl到http header以供后端调试之用。 watch pyc pyo $py.class 此三类python的缓存文件,python code编辑后生成缓存文件有一段间隔所以这里并不直接watch .py.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gulp.task("dev",['server'],function(){

browserSync.init({
// logLevel: "debug",
logConnections: true,
proxy: {
target:"localhost:8000",
proxyReq: [
function(proxyReq) {
externalUrl = browserSync.getOption('urls').get('external');
proxyReq.setHeader('X-BrowserSync-External-Url', externalUrl);
}
]
},
});
gulp.watch('bower.json',['copy','bower']);
gulp.watch('static/**/*.scss',['sass:compile']);
gulp.watch("**/*.html").on('change', browserSync.reload);
gulp.watch("**/*.pyc").on('change', browserSync.reload);
gulp.watch("**/*.pyo").on('change', browserSync.reload);
gulp.watch("**/*$py.class").on('change', browserSync.reload);
gulp.watch("**/*.mo").on('change', browserSync.reload);
});

从WordPress迁移到hexo 面向前端开发人员的rails教程
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×