前言
通过 官网构建优化流程(3) - grunt静态页面预编译插件 grunt-staticfy 可以看到通过这个 grunt-staticfy 组件可以实现 模板静态化预编译,但是会有几个问题。
后面决定采用gulp来重写这个组件:
github 传送门 gulp-staticfy
npm 组件传送门 gulp-staticfy
用gulp 的几个好处就是:
- gulp 是基于流的方式,不需要像grunt那样进行那么多的io操作
- npm 有一个 phantom 的插件: phantomjs-node,在安装这个npm 插件的时候,不用单独再去安装phantomjs 客户端。而是插件在安装的时候,就会去下载phantomJS。而且整个调用非常方便。可以用 promise, 也可以用 co同步(还有最新的async await语法), 而如果是之前grunt-staticfy 那种处理,就比较麻烦,还要用到 exec 去执行 phantom 的脚本, 非常不友好。
代码分析
新版gulp组件的原理跟旧版grunt差不多,但是会简洁很多。
文件目录如下:
主要的文件就两个,都在src目录里面,其中index 是主要逻辑,server是创建简易http server的逻辑。
src/index.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 ;
var gutil = require('gulp-util');
var path = require('path');
var phantom = require("phantom");
var SimpleServer = require('./server.js');
var map = require('map-stream');
module.exports = function(options) {
var totalFileSize = 0;
var finishFileSize = 0;
var ph;
var createPhPage = function(){
if(ph){
return ph.createPage();
}else{
return phantom.create().then(_ph => {
ph = _ph;
return ph.createPage();
})
}
};
// close all http server
var doAllFinish = () => {
ph.exit();
SimpleServer.closeAll();
console.log(gutil.colors.green("staticfy success"));
};
var doFinish = () => {
finishFileSize += 1;
if(finishFileSize === totalFileSize){
doAllFinish();
}
};
return map(function (file, callback) {
if (file.isNull()) {
console.log("null");
return callback(null, file);
}
if (file.isStream()) {
console.log("stream");
return callback(null, file);
}
if (file.isBuffer()) {
totalFileSize += 1;
// custom server url
var wwwDir = options.server_url || file.cwd;
var baseName = path.relative(wwwDir, file.path);
var url = 'http://localhost:{{port}}/';
var query_string = options.query_string ? `?${options.query_string}` : '';
console.log(gutil.colors.blue(`${baseName} loading ...`));
SimpleServer.start(wwwDir, server => {
// Replace {{port}} with server.port
url = url.replace('{{port}}', server.port) + baseName + query_string;
var ph_page;
createPhPage().then(page => {
ph_page = page;
return ph_page.open(url);
}).then(status => {
//console.log(status);
return ph_page.property('content')
}).then(content => {
console.log(gutil.colors.blue(baseName) + gutil.colors.green(" load success"));
ph_page.close();
doFinish();
file.contents = new Buffer(content);
callback(null, file);
}).catch(e => {
ph_page && ph_page.close();
doFinish();
console.log(e)
});
});
}
});
};
src/server.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
28
29
30
31
32
33
34
35var http = require('http');
var nodeStatic = require('node-static');
var portfinder = require('portfinder');
var _ = require("lodash");
var fileServerList = [];
function startServer(wwwDir, callback) {
if(fileServerList[wwwDir]){
callback(fileServerList[wwwDir]);
}else{
var fileServer = new nodeStatic.Server(wwwDir);
portfinder.getPort(function (err, port) {
var server = http.createServer(function (request, response) {
request.addListener('end', function () {
fileServer.serve(request, response);
}).resume();
}).listen(port);
server.port = port;
fileServerList[wwwDir] = server;
callback(server);
});
}
}
function closeServer(){
//console.log("close http server");
_.each(_.keys(fileServerList), function(key){
fileServerList[key].close();
delete fileServerList[key];
});
}
module.exports = {
start: startServer,
closeAll: closeServer
};
这边要注意几个细节:
- 当前任务的file都加载完成之后,http server 要记得关闭, 即调用 SimpleServer.closeAll() ,因为 gulp 进程不会主动退出,会导致资源浪费;
- phantom exe 程序调用的时候,只需要开启一次,当所有的file都预编译完之后,才关掉,即调用 ph.exit(),因为每一次开启都要耗时间,这样做重复利用才能省时间;
- 虽然phantom 的主进程只需要开启一次,但是加载页面的每一个子线程,在加载结束的时候,都要关掉。 即调用 ph_page.close();
举个栗子
example 的 gulpfile 实例: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
28
29
30
31
32var gulp = require('gulp');
var staticfy = require('../src/index');
var del = require('del');
var _ = require('lodash');
var seq = require("gulp-sequence");
// 多语言列表
var langArr = ['', 'zh-cn', 'ja', 'en'];
var langTaskListMap = {};
var fileArr = [
"src/simple.html",
"src/simple2.html",
"src/tmp/query_string.html"
];
gulp.task('clean', function(cb) {
return del(['build'], cb);
});
// 批量注册任务
langArr.forEach(lang => {
langTaskListMap[`${lang}-staticfy`] = gulp.task(`${lang}-staticfy`, function() {
// 这边要指定路径,dest才会复制目录结构
// 这边一定要return,不然就会变成异步,而不会同步
return gulp.src(fileArr, {base: '.'})
.pipe(staticfy({
query_string: `lang=${lang}`
}))
.pipe(gulp.dest(`build/${lang}`));
});
});
gulp.task('default', seq.apply(null,['clean'].concat( _.keys(langTaskListMap))));
在官网新版gulp打包的应用
1 | var langArr = _.keys(global.config.supportLangs); |
这时候就要把语言参数当做url参数传进去,这时候 html 页面的js就会得到这个语言参数,从而加载对应的语言json文件,然后进行语种渲染,最后得到该语种的html静态文件。
传server_url是为了指定开启静态web server的位置。它虽然是一个语种一个语种是同步串行的,但是一个语种里面有多个html文件,这时候是并行编译的,所以会开很多的web server,这个是为了更快的进行页面预编译。这也是为什么后面编译完之后,要记得关闭web server的原因,因为如果开那么多的web server,最后却没有关闭的话,会导致资源泄露
这边需要注意的一点是:在进行gulp构建的时候,如果是同步的话,最后一定要有return,不然任务就不能按顺序执行, 因为我们要同步按顺序将一门一门语言都预编译出来,如果异步的话,有时候会漏掉一些。1
2
3
4
5
6
7
8
9langTaskListMap[`${lang}-staticfy`] = gulp.task(`${lang}-staticfy`, function() {
// 这边要指定路径,dest才会复制目录结构
// 这边一定要return,不然就会变成异步,而不会同步
return gulp.src(fileArr, {base: '.'})
.pipe(staticfy({
query_string: `lang=${lang}`
}))
.pipe(gulp.dest(`build/${lang}`));
});
也就是说,在这个任务中,要让每一个语言都串行的执行,那么就要有这个return。不然就会全部走异步并行,导致出错。
ps:这地方当时在做的时候,踩过坑,当时搞了好久,所以这边也着重再提下系列文章
官网构建优化流程(1) - 简介
官网构建优化流程(2) - 旧版grunt打包构建流程
官网构建优化流程(3) - grunt静态页面预编译插件 grunt-staticfy
官网构建优化流程(4) - gulp 静态页面预编译插件 gulp-staticfy
官网构建优化流程(5) - gulp jst模板联合组件 gulp-concat-js
官网构建优化流程(6) - 把原先的grunt换成gulp构建
官网构建优化流程(7) - 官网的多语言跳转规则
官网构建优化流程(8) - gulp打包构建在ie8会报错
官网构建优化流程(9) - windows 下打zip包到服务器没有执行权限的问题
官网构建优化流程(10) - gulp-staticfy进行多语言预编译的时候,会随机出现乱码
官网构建优化流程(11) - 部署转为用Jenkins自动化部署
官网构建优化流程(12) - 优化加载速度,资源分开存放
官网构建优化流程(13) - 字体文件的跨域问题 和 S3/CloudFront/COS 设置跨域CORS