代码改变世界

前端客户端自动构建

2018-05-02 18:11  @疯狂的迈步  阅读(381)  评论(0编辑  收藏  举报

构建原因

  • 规范代码所有代码构建前均会先进行规范审核,根据不同语言预设判断。
  • 提高效率将需要重复输入的有规律的设置,用自动化的方式添加。
  • 避免疏忽把默认需要操作的动作用程序代替,避免人为疏忽。
  • 提高性能把开发时的易于编辑的源码转化为访问时性能较高的结果。
  • 减少重复将重复调用的内容用程序的方式生成结果,避免访问负荷。
  • 板块化按板块开发,生成整体页面,用组装的方式实现。

构建内容

  • 脚本*.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[^>]*\bsrc="([^"]*)"[^>]*>/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 = /(\{\{#[^}]*?\}\}(?:\r?\n)?)?<link(?:.*)rel="stylesheet"(?:[^>]*)\/?>(?:\r?\n)?(\{\{\/[^}]*?\}\}(?:\r?\n)?)?/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>(\r?\n)?/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;
    });
}