构建原因
- 规范代码所有代码构建前均会先进行规范审核,根据不同语言预设判断。
- 提高效率将需要重复输入的有规律的设置,用自动化的方式添加。
- 避免疏忽把默认需要操作的动作用程序代替,避免人为疏忽。
- 提高性能把开发时的易于编辑的源码转化为访问时性能较高的结果。
- 减少重复将重复调用的内容用程序的方式生成结果,避免访问负荷。
- 板块化按板块开发,生成整体页面,用组装的方式实现。
构建内容
- 脚本*.js --> *.js, *.min.js, map/js.json
- 样式*.css --> *.less, *.min.css, map/css.json
- 模板views/.json --> views/.hbs, views/*.min.hbs, map/html.json
- 图片*.[png|jpg|gif|svg|ico] --> *.[png|jpg|gif|svg|ico], map/img.json
- 字体font/.[eot|ttf|woff|woff2|svg] --> font/.[eot|ttf|woff|woff2|svg]
- 第三方bower.json --> bower.json, bower_components/*
构建配置
npm install gulp-cli -g
npm install bower -g
cd node-m/static/auto
npm install --no-save // 安装但不更新 package.json 和 package-lock.json
"bower": "^1.7.9",
"browser-sync": "^2.11.0",
"del": "^2.2.0",
"gulp": "^3.9.0",
"gulp-changed": "^1.3.0", // 用于判断文件是否更新
"gulp-clean-css": "^2.0.9",
"gulp-csslint": "^0.3.1",
"gulp-eslint": "^4.0.0", // 更新至 4.0.0
"gulp-hasher": "^0.1.0", // 生成文件 md5
"gulp-htmllint": "0.0.11",
"gulp-htmlmin": "^1.2.0",
"gulp-include": "^2.0.2",
"gulp-less": "^3.0.1",
"gulp-lintspaces": "^0.4.1",
"gulp-rename": "^1.2.2", // 更改后缀/增加前缀
"gulp-uglify": "^1.4.1",
"gulp-util": "^3.0.7", // 主要用于输出错误信息
"gulp-watch": "^4.3.6",
"htmllint": "^0.3.0",
"merge-stream": "^1.0.0",
"q": "^1.4.1",
"run-sequence": "^1.1.5",
"svgo": "^0.7.2",
"through2": "^2.0.0" // 可读、可写流生成
构建方式
gulp watch // 监听所有资源
gulp all // 生成所有资源(除第三方资源外)
gulp tpl // 生成修改或新增模板
gulp css // 生成修改或新增样式
gulp js // 生成修改或新增脚本
gulp img // 生成修改或新增图片
gulp font // 生成修改或新增字体
gulp bower // 生成修改或新增第三方资源
gulp tplAll // 生成所有模板
gulp cssAll // 生成所有样式
gulp jsAll // 生成所有脚本
gulp imgAll // 生成所有图片
gulp fontAll // 生成所有字体
gulp bowerAll // 生成所有第三方资源
构建原理(图片)
//pipe直接复制(svg 除外)
.pipe(svgProcess())
.pipe(gulp.dest('../dest'))
.pipe(hasher()) // 计算文件 md5
.pipe(resouceMap(hasher.hashes, 'img')); // 生成后缀文件
//svgProcesssvg 压缩
return through.obj(function(file, enc, cb) {
if(file.path.match(/.svg$/)){
(new svgo()).optimize(String(file.contents), function (result) { // 使用 svgo 对象
if (result.error) {
return cb(new gutil.PluginError('svgProcess', result.error));
}
file.contents = new Buffer(result.data); // 对文件内容重新赋值
cb(null, file); // 继续往下流
});
}else{
cb(null, file);
}
});
//resouceMap对后缀文件对象进行排序生成
for(x in hashesRead){
keyTemp.push(x); // 已有文件
}
for(y in hashesWrite){
keyTemp.push(y); // 新增文件
}
keyTemp.sort(); // 文件名排序
keyTemp.forEach(function(v,k){
hashesTemp[v] = 'undefined' !== typeof hashesWrite[v] ? hashesWrite[v] : hashesRead[v]; // 排序后的对象
});
构建原理(脚本)
//pipe生成未压缩和已压缩版本
.pipe(eslint({
configFile: '.eslintrc' // eslint 配置文件
}))
.pipe(eslintReportor()) // 审核错误报告
.on('error',errorProcess)
.pipe(gulp.dest('../dest')) // 直接复制生成未压缩文件
.pipe(browserSync.stream())
.pipe(uglify({
preserveComments: 'license' // 压缩,保留许可注释
}))
.on('error',errorProcess)
.pipe(rename(function (path) {
path.basename += ".min"; // 压缩文件增加前缀
}))
.pipe(gulp.dest('../dest'))
.pipe(rename(function (path) {
path.basename = path.basename.replace(/.min$/, ''); // 恢复原名
}))
.pipe(hasher()) // 以压缩版本生成后缀
.pipe(resouceMap(hasher.hashes, 'js'));
//eslintReportor审核通过继续,不通过输出错误信息
if(result.errorCount > 0 || result.warningCount > 0){ // 错误或警告数大于 0
result.messages.forEach(function (issue) { // 错误信息循环输出
lintReporter('eslint', result.filePath, issue.line, issue.column, issue.ruleId, issue.message, type[issue.severity]);
});
}
if(result.errorCount < 1){
cb(null, file); // 没有错误,继续往下走
}else{
cb(); // 有错误,当前文件不再往下走
}
构建原理(样式)
//pipe生成未压缩和已压缩版本
.pipe(lintspaces({
indentation: 'spaces', // 统一缩进
spaces: 4
}))
.pipe(lintspacesReporter()) // 缩进错误输出
.pipe(less()) // less 编译
.on('error',errorProcess)
.pipe(csslint('.csslintrc')) // csslint 配置文件
.pipe(csslintReporter()) // 审核错误报告
.on('error',errorProcess)
.pipe(cssImgHash()) // 增加图片后缀
.pipe(gulp.dest('../dest')) // 生成 css 文件
.pipe(browserSync.stream())
.pipe(hasher())
.pipe(resouceMap(hasher.hashes, 'css'))
.pipe(minifyCss({rebase: false})) // 压缩样式,不处理路径
.pipe(rename(function (path) {
path.basename += ".min";
}))
//cssImgHash对所有引用图片添加后缀
contents = contents.replace(/url((["']?)([^)"']+?)1)/ig, function(match, quote, url){ // 匹配引用图片
var result;
var hashUrl = url.replace(/^//, '');
hashUrl = hashUrl.substring(0, -1 != hashUrl.indexOf('?') ? hashUrl.indexOf('?') : hashUrl.length).replace(//|.|-/g, '_'); // 后缀字段
if('undefined' != typeof hashes[hashUrl]){
result = 'url(' + url + '?' + hashes[hashUrl] + ')';
}else{
result = match;
}
return result;
});
构建原理(模板组装)
//pipe将 json 中的板块合并为 hbs
.pipe(concatLibs()) // 组装板块
.pipe(htmlParser(false)) // 处理文档
.pipe(rename({extname:".hbs"}))
.pipe(htmllint({}, function(){}))
.on('error',errorProcess)
.pipe(htmllintReporter())
.pipe(gulp.dest('../dest'))
.pipe(htmlParser(true))
.pipe(htmlmin({ // 压缩 html
collapseWhitespace: true, // 空格处理
processScripts: ['text/x-handlebars-template'], // 脚本标签中的模板
removeComments: true, // 去除注释
ignoreCustomFragments: [/\?{{.+?}}/] // 忽略变量
}))
.on('error',errorProcess)
.pipe(rename(function (path) {
path.basename += ".min";
}))
//concatLibs把板块连接,并更新相对路径
data = data.replace(/(['"])((?:..?/)+)/g, function(match, quote, url){
return quote + path.relative(file.base, path.join(path.dirname(libFile), url)).replace(/\/g, '/') + '/';
});
构建原理(地址处理)
//图片地址增加域名与后缀
reg.img = /<img[^>]*src="([^"]*)"[^>]*>/igm; // 普通图片
reg.imgData = /<img[^>]*data="([^"]*)"[^>]*>/igm; // 延迟加载图片
reg.ico = /<link[^>]*href="([^"]*.(ico|png))"[^>]*>/igm; // ico 图片
tempUrl = tempUrl.substring(0, -1 !== tempUrl.indexOf('?') ? tempUrl.indexOf('?') : tempUrl.length); // 去掉原有后缀
var hashUrl = tempUrl.replace(//|.|-/g, '_'); // 统一字段字符
tempUrl = '{{constant.url.resource}}' + tempUrl + '?{{hash.img.' + hashUrl+ '}}'; // 添加域名后缀
//样式地址增加域名与后缀,放置在 head 里,压缩版本串联
var stylesReg = /({{#[^}]*?}}(?:
?
)?)?<link(?:.*)rel="stylesheet"(?:[^>]*)/?>(?:
?
)?({{/[^}]*?}}(?:
?
)?)?/gim // 提取样式标签
contents = contents.replace(/({{{w+}}}s*)?(</head>)/, stylesTag + '$1$2'); // 放到 head 结束标签前
stylesUrl += styles[x].replace(stylesReg, '$1').trim()
+ minUrl + '?{{hash.' + hashKey + '.' + hashUrl + '}}' + (x >= stylesLength-1 ? '' : ',')
+ styles[x].replace(stylesReg, '$2').trim(); //连接为一条地址
//脚本地址增加域名与后缀,放置在 body 最后面,压缩版本串联
var scriptsReg = /<script[^>]*>[^<]*</script>(
?
)?/gim; // 提取脚本标签
var orderReg = /order="(-?d+)/i // 脚本顺序提取
if(!toMin){
var order = 10000;
scripts = scripts.map(function(v){
if(!v.match(orderReg)){
v = v.replace('<script', '<script set-order="' + order + '"'); // 设置默认顺序
order++;
}
return v;
});
scripts.sort(function(a,b){
return parseInt(orderReg.exec(a)[1]) - parseInt(orderReg.exec(b)[1]); // 从小到大排序
});
scripts = scripts.map(function(v){
v = v.replace(/sset-order="(d+)"/i, ''); // 删除增加的顺序属性
return v;
});
}