超详细Gulp打包seajs模块压缩合并,绝对对你项目有帮助 ~

前言

  • 项目背景:
    • 项目存在于n+1年前的php旧框架中,其中使用的技术大家不用猜应该也知道了;
    • 由于其中许多业务一直都存在这个php旧框架中,而且暂时性的不存在做整体迁移的情况,所以只能在php旧框架中动手了 [😭 伤心太平洋];
  • 现阶段需解决的点:
    • php旧框架版本戳问题 [😭 暗自伤心]:由于浏览器缓存机制,每次修改文件,需手动更改对应的版本戳,修改成本较高,涉及文件变更也较多;
    • ES6语法支持问题 [😭 猛男落泪]:了解过javascript的同学应该知道,ES6语法可以更精简你的代码,从而提升你的开发效率;
    • js、css 文件未压缩 [😒 用户难受]
    • 由于我们都是有追求技术的Team,同时也秉承用户至上的理念,所以为了解决上述问题,我们的技术革命刻不容缓;

技术调研

  • Grunt VS Gulp VS Webapck
  • Grunt:
    • 配置与生态没有达到我们的要求
  • Webapck:
    • 不支持CMD模式,需下载对应的转换成AMD或CommonJs的插件;
    • 由于项目的seajs写法没有统一性,故可能存在转换后踩坑的情况;
  • Gulp:
    • 与seajs的生态配合较好;
    • 针对seajs打包的一系列流程与插件都有实例;
    • 与其它项目的打包构建一致性;
  • 终上所述,我们选择了gulp [😩 ]

php旧框架现有目录介绍

└── 项目名
		├── ....		php文件
		├── images  静态图片
        ├── include 前端页面静态js、css、images等(gulp运行后主要操作的目录一)
    	│		├── ...
    	│		├── javascript
    	│		├── ...
	    │		├── css
	    │		├── ...
	    │		├── static
	    │		├── ...
	    │		├── gulpfile.js
	    │		├── package.json
	    │   └── ...
	    ├── lib 		php文件
	    ├── ... 		其它php文件
	    ├── tmpl 		前端页面模板文件(gulp运行后主要操作的目录二)
	    └── ... 		其它配置文件

Gulp打包流程图

Gulp打包流程图

  • 看了上面的流程图,你是否在怀疑自己,是不是感觉脑壳还是懵的;
  • 你不用怀疑,因为这是专门针对我们旧框架项目定做的打包流程(ps:其它地方大概也许应该就不合适了);
  • 那我们来详细介绍下吧 ~

gulp打包依赖

gulp运行

 由于前端资源与模板主要存在于include目录与tmpl目录,故只会对该两个目录进行操作;

1. 操作include目录:

该文件目录主要保存的是js、css、images静态资源
1.1 复制include目录下的文件
  • 主要是为了区分php的变量访问,防止出现访问错误;
  • on('end', fun) :主要为了区分gulp异步打包机制,防止某些文件复制了被替换的现象;
// 复制include文件下的某些文件至.build,去做构建处理
gulp.task('copyInclude', function (cb) {
    gulp.src(copyPaths.paths) // paths为路径处理
        .pipe(gulp.dest('.build'))
        .on('end',function() {
            console.log('copyInclude 执行完成了');
            cb();
        })
});
1.2 压缩合并js, css文件
  • 压缩css的同时,转义css文件中的相对路径引用,例如:../../../../../

     // 处理并压缩css文件;
     gulp.task('minifyCSS', function () {
         minifyCSS('{css,javascript,static}/**/*.css', '.build')
     })
     
     // ......
     
     /**
      * 壓縮 CSS 公共方法
      * replace(/(..\/){3,5}images\//gi’替换图片路径的正則
      * 匹配 ../../../images/ 字符串內容,../ 是 3~5 個
      * @param {String} path 目標路徑
      * @param {String} destPath 輸出路徑
      */
     function minifyCSS (path, destPath) {
         var cssFsCache = fsCache('.gulpCache/csscache');
         gulp.src(path)
             .pipe(replace(/(..\/){3,5}images\//gi, `//images${env_name}.591.com.tw/`))
             .pipe(cssFsCache)
             .pipe(minifycss({
                 compatibility: 'ie9'
             }))
             .pipe(cssFsCache.restore)
             .pipe(gulp.dest(destPath));
     }
    
  • 压缩js文件同时,做babeles6语法的转义;

     // 处理并压缩 JS 文件
     gulp.task('uglifyJS', ['seaCombo'], function (cb) {
         jsPaths.paths.forEach(function(item,index){
             // index === (jsPaths.paths.length - 1) 解决回调函数次数过多问题,只能回调一次
             uglifyJS(item.entryPath, item.outputPath, index === (jsPaths.paths.length - 1) && cb)
         })
     });
     
     /**
      * JS 壓縮
      * @param {String} path
      * @param {String} destPath
      * @param {回調函數} cb
      */
     function uglifyJS (path, destPath, cb) {
         var jsFsCache = fsCache('.gulpCache/jscache');
         pump([
             gulp.src(path),
             jsFsCache,
             babel({
                 presets: ['@babel/env'],
                 sourceType: 'script' // 簡單理解,不會添加 'use strict'
             }),
             uglify({
                 mangle: { reserved: ['require', 'exports', 'module'] }
             }),
             jsFsCache.restore,
             gulp.dest(destPath)
         ],
         cb)
     }
    
  • 压缩文件时忽略 require, exports, module是为了seajs合并时做处理;

  • seaCombo 任务合并seajs:

     // seajs 的 alias config
     const staticConfig = require('./static/alias/static');
     const houseConfig = require('./static/alias/house');
     const userCenterConfig = require('./static/alias/userCenter');
     const configAlias = Object.assign({}, staticConfig, userCenterConfig, houseConfig);
     
     // 合併 sea.config 中的alias 的 JS
     gulp.task('seaCombo', function (cb) {
     
         const configAliasArr = Object.keys(configAlias);
     
         configAliasArr.forEach(function (item, index) {
     
             // 切割路径有 ? 的时间戳, 为了使seajsCombo插件识别
             let nowItem = configAlias[item].split('?')[0];
     
             // 从相对路径变成绝对路径,为了使seajsCombo插件识别
             configAlias[item] = path.resolve(__dirname, nowItem)
             let srcString = configAlias[item];
             let _base;
     
             const hasStatic = srcString.includes('static');
             const hasJavascript = srcString.includes('javascript');
     
     
             if (hasStatic || hasJavascript) {
                 _base = (hasStatic && 'static') || (hasJavascript && 'javascript')
             } else {
                 return;
             }
             return gulp.src(srcString, { base: _base })
                 .pipe(seajsCombo({
                     map: configAlias,
                     ignore: jsPaths.ignoreConfig
                 }))
                 .pipe(gulp.dest('.build/' + _base))
                 .on('end',function() {
                     index === (configAliasArr.length - 1) && cb();
                 })
         })
     
     });
    
    • 自定义插件**gulp-combo-seajs**主要解决问题:

      • 支持require("xx.css")引入问题
      • seajs.config的alias别名设置文件名命名模块id,而不是以当前文件名命名模块id;
      • 在部分文件不能识别成正确的.js文件,导致会在 xx.js 文件后面加上 .jsxx.js.js
1.3 生成js, css文件的hash映射文件
  • 生成hash映射主要为了区分版本戳

    //js生成文件hash编码并生成 rev-manifest.json文件名对照映射
    gulp.task('revJson', function(){
        return gulp.src(['.build/**/*.{js,css}'])
            .pipe(rev())
            .pipe(rev.manifest('rev-manifest.json'))
            .pipe(gulp.dest('.build'));
    });
    
1.4 更改需要映射的js文件的版本戳(主要针对seajs中的配置)
  • 针对seajs中的配置文件路径添加版本戳

     // 更改需要映射的js文件的版本戳
     gulp.task('revJs', function (cb) {
         jsPaths.seaConfig.forEach(function (item, index) {
             return gulp.src(['.build/rev-manifest.json'].concat(item.entryPath))
                 .pipe(revCollector({
                     replaceReved: true
                 }))
                 .pipe(gulp.dest(item.outputPath))
                 .on('end',function() {
                     if (index === (jsPaths.seaConfig.length - 1) ) {
                         console.log( 'revJs 执行完成了' )
                         cb();
                     };
                 })
         })
     })
    
1.5 複製.build目录文件到 build 目錄
// 複製.build目录文件到 build 目錄
gulp.task('copyBuild', function () {
    return gulp.src('.build/**/*.*')
        .pipe(gulp.dest('build'));
});

2. 操作tmpl目录:

  • 该文件目录主要保存的是页面模板文件,包括php和html模板
2.1 复制tmpl模板中的文件 至 tmpl_gulp 文件中
// 复制tmpl模板中的文件 至 tmpl_gulp 文件中
gulp.task('copyTmpl', function () {
    // 1、先忽略需要更改的文件模板,以免发生覆盖
    let nowTmpls = ignorePaths(tmplPaths)
    // 2、再进行copy
    return gulp.src(['../tmpl/**/*.*'].concat(nowTmpls))
        .pipe(gulp.dest('../tmpl_gulp'));
});
2.2 更改tmpl內对应模板文件的CSS,JS版本戳
gulp.task('revHtml', function () {
    buildSlicePaths(tmplPaths, function (path, destPath) {
        return gulp.src(['.build/rev-manifest.json', path])
            // 1.先把文件直接转成utf-8格式
            .pipe(convertEncoding({
                from: 'GBK',
                to: 'utf8'
            }))
            // 2.再去映射修改的文件
            .pipe(revCollector({
                replaceReved: true
            }))
            // 3.再通过utf-8转换成原来的编码格式
            .pipe(convertEncoding({
                from: 'utf8',
                to: 'GBK'
            }))
            // 4.最后生成文件模板
            .pipe(gulp.dest('../tmpl_gulp/' + destPath));
    })
})

打包后目录

打包后目录对比

完整gulpfile.js配置

/**
* @description: 舊框架自動構建配置 - 基於 gulp
* @author:      https://github.com/jeddygong
* @dateTime:    2021-02-29 14:45:02
*/

// 引入 gulp及组件
var gulp    = require('gulp'),
    uglify = require('gulp-uglify'),
    fsCache = require('gulp-fs-cache'),
    pump = require('pump'),
    babel = require('gulp-babel'),
    replace = require('gulp-replace'),
    runSequence  = require('run-sequence')
    minifycss = require('gulp-minify-css');

const path = require('path');

const rev = require('gulp-591-rev');
const revCollector = require('gulp-591-collect');
const seajsCombo = require('gulp-combo-seajs');
const convertEncoding = require("gulp-convert-encoding");

// seajs 的 alias config
const staticConfig = require('./static/alias/static');
const houseConfig = require('./static/alias/house');
const userCenterConfig = require('./static/alias/userCenter');
const configAlias = Object.assign({}, staticConfig, userCenterConfig, houseConfig);

// 當前環境變量
const env = process.env.NODE_ENV

// 環境變量判斷
const env_name = (env === 'develop' ? '.debug' : '')

// 需要copy的include目录下的文件
const copyPaths = {
    paths: [
        '{ajax,bootstrap,css,javascript,static}/**/*.*',
    ]
}

// 需要映射的 php 或 html 文件中的版本戳 文件,添加到paths中即可
const tmplPaths = {
    base:  '../tmpl/',
    paths: [
        'index/house/postRent/index.tpl.php'
    ]
}

// js路径配置
const jsPaths = {
    // 需要添加合并的seajs文件, 只针对页面是使用include/javascript/public/config.js的配置
    manifestPaths: [
        '.build/javascript/newUserCenter/newMedium/index.js'
    ],

    // 基本js文件路径,需要压缩编译的文件
    paths: [
        // javascript 路径下的js文件暂不压缩,要不然会有问题,例子javascript/housing/newList.js
        {
            entryPath: [
                '.build/javascript/**/*.js',
                '!.build/javascript/mobile/preloadData.js',
                '!.build/javascript/house/list.js',
                '!.build/javascript/seajs/**/*.*',
                '!.build/javascript/plugin/*.*',
                '!.build/javascript/newPlugin/*.*'
            ],
            outputPath: '.build/javascript'
        },
        {
            entryPath: ['.build/static/**/*.js'],
            outputPath: '.build/static'
        }
    ],
    // 需要合并添加版本戳替换seajs配置
    seaConfig: [
        {
            entryPath: ['.build/static/alias/static.js', '.build/static/alias/userCenter.js', '.build/static/alias/house.js'],
            outputPath: '.build/static/alias'
        },
        {
            entryPath: ['.build/javascript/public/config.js'],
            outputPath: '.build/javascript/public'
        },
        {
            entryPath: ['.build/javascript/newPublic/dsConfig.js'],
            outputPath: '.build/javascript/newPublic'
        }
    ],
    ignoreConfig: [
        '/include/javascript/jquery.validate.js',
        ...Object.keys(staticConfig)
    ]
}

// 复制include文件下的某些文件至.build,去做构建处理
gulp.task('copyInclude', function (cb) {
    gulp.src(copyPaths.paths)
        .pipe(gulp.dest('.build'))
        .on('end',function() {
            console.log('copyInclude 执行完成了');
            cb();
        })
});

// 处理并压缩css文件;
gulp.task('minifyCSS', function () {
    minifyCSS('{css,javascript,static}/**/*.css', '.build')
})

// 处理并压缩 JS 文件
gulp.task('uglifyJS', ['seaCombo'], function (cb) {
    jsPaths.paths.forEach(function(item,index){
        // index === (jsPaths.paths.length - 1) 解决回调函数次数过多问题,只能回调一次
        uglifyJS(item.entryPath, item.outputPath, index === (jsPaths.paths.length - 1) && cb)
    })
});

// 合併 sea.config 中的alias 的 JS
gulp.task('seaCombo', function (cb) {

    const configAliasArr = Object.keys(configAlias);

    configAliasArr.forEach(function (item, index) {

        // 切割路径有 ? 的时间戳
        let nowItem = configAlias[item].split('?')[0];

        // 从相对路径变成绝对路径
        configAlias[item] = path.resolve(__dirname, nowItem)
        let srcString = configAlias[item];
        let _base;

        const hasStatic = srcString.includes('static');
        const hasJavascript = srcString.includes('javascript');


        if (hasStatic || hasJavascript) {
            _base = (hasStatic && 'static') || (hasJavascript && 'javascript')
        } else {
            return;
        }
        return gulp.src(srcString, { base: _base })
            .pipe(seajsCombo({
                map: configAlias,
                ignore: jsPaths.ignoreConfig
            }))
            .pipe(gulp.dest('.build/' + _base))
            .on('end',function() {
                // console.log( index === (configAliasArr.length - 1) && 'seaCombo 执行完成了' );
                index === (configAliasArr.length - 1) && cb();
            })
    })

});

//js生成文件hash编码并生成 rev-manifest.json文件名对照映射
gulp.task('revJson', function(){
    return gulp.src(['.build/**/*.{js,css}'])
        .pipe(rev())
        .pipe(rev.manifest('rev-manifest.json'))
        .pipe(gulp.dest('.build'));
});

// 更改需要映射的js文件的版本戳
gulp.task('revJs', function (cb) {
    jsPaths.seaConfig.forEach(function (item, index) {
        return gulp.src(['.build/rev-manifest.json'].concat(item.entryPath))
            .pipe(revCollector({
                replaceReved: true
            }))
            .pipe(gulp.dest(item.outputPath))
            .on('end',function() {
                if (index === (jsPaths.seaConfig.length - 1) ) {
                    console.log( 'revJs 执行完成了' )
                    cb();
                };
            })
    })
})

// 複製.build目录文件到 build 目錄
gulp.task('copyBuild', function () {
    return gulp.src('.build/**/*.*')
        .pipe(gulp.dest('build'));
});

// 复制tmpl模板中的文件 至 tmpl_gulp 文件中
gulp.task('copyTmpl', function () {
    // 1、先忽略需要更改的文件模板,以免发生覆盖
    let nowTmpls = ignorePaths(tmplPaths)
    // 2、再进行copy
    return gulp.src(['../tmpl/**/*.*'].concat(nowTmpls))
        .pipe(gulp.dest('../tmpl_gulp'));
});

// 更改tmpl內对应模板文件的CSS,JS版本戳
gulp.task('revHtml', function () {
    buildSlicePaths(tmplPaths, function (path, destPath) {
        return gulp.src(['.build/rev-manifest.json', path])
            // 1.先把文件直接转成utf-8格式
            .pipe(convertEncoding({
                from: 'GBK',
                to: 'utf8'
            }))
            // 2.再去映射修改的文件
            .pipe(revCollector({
                replaceReved: true
            }))
            // 3.再通过utf-8转换成原来的编码格式
            .pipe(convertEncoding({
                from: 'utf8',
                to: 'GBK'
            }))
            // 4.最后生成文件模板
            .pipe(gulp.dest('../tmpl_gulp/' + destPath));
    })
})


// 默认任务 清空图片、样式、js并重建 运行语句 gulp
gulp.task('default', ['start']);

gulp.task('start', function () {
    console.log('/********************************', new Date().toLocaleString(), '*******************************************/');
    runSequence(
        ['copyInclude'],
        ['uglifyJS', 'minifyCSS'],
        ['revJson'],
        ['revJs'],
        /**
         * 由于更改了revJs任务 jsPaths.seaConfig文件中的版本戳 并且是在revJson之后修改的
         * 导致更改后的rev-manifest.json没有更新到,所以需要再次生成版本戳
         */
        ['revJson'],
        ['copyBuild'],
        ['copyTmpl'],
        ['revHtml']
    );
});

/**
 * JS 壓縮
 * @param {String} path
 * @param {String} destPath
 * @param {回調函數} cb
 */
function uglifyJS (path, destPath, cb) {
    var jsFsCache = fsCache('.gulpCache/jscache');
    pump([
        gulp.src(path),
        jsFsCache,
        babel({
            presets: ['@babel/env'],
            sourceType: 'script' // 簡單理解,不會添加 'use strict'
        }),
        uglify({
            mangle: { reserved: ['require', 'exports', 'module'] }
        }),
        jsFsCache.restore,
        gulp.dest(destPath)
    ],
    cb)
}

/**
 * 壓縮 CSS 公共方法
 * replace(/(..\/){3,5}images\//gi’替换图片路径的正則
 * 匹配 ../../../images/ 字符串內容,../ 是 3~5 個
 * @param {String} path 目標路徑
 * @param {String} destPath 輸出路徑
 */
function minifyCSS (path, destPath) {
    var cssFsCache = fsCache('.gulpCache/csscache');
    gulp.src(path)
        .pipe(replace(/(..\/){3,5}images\//gi, `//images${env_name}.591.com.tw/`))
        .pipe(cssFsCache)
        .pipe(minifycss({
            compatibility: 'ie9'
        }))
        .pipe(cssFsCache.restore)
        .pipe(gulp.dest(destPath));
}

/**
 * @description 需要忽略的文件不被copy,解决gulp的task任务异步问题
 * @param   {Array}     manifestPaths    所有忽略的文件路径
 * @return  {Array}
 */
function ignorePaths (manifestPaths, base) {
    return manifestPaths.paths.map(function(item,index){
        return '!'+ manifestPaths.base + item;
    })
}

/**
 * @description 需要打包的文件路径做切割
 * @Example 'javascript/newUserCenter/newMedium/index.js' => 'javascript/newUserCenter/newMedium' 和 'index.js'
 * @param   {Array}     manifestPaths    所有忽略的文件路径
 * @param   {Function}  cb
 */
function buildSlicePaths (manifestPaths, cb) {
    manifestPaths.paths.forEach(function(item,index){
        let name = item.split('/').length - 1;
        // 查找最后一个/的位置
        let sub = item.lastIndexOf('\/');
        let destPath = '';
        if (sub) {
            destPath = item.slice(0, sub);
        }
        cb(manifestPaths.base + item, destPath, index);
    })

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值