3

利用gulp+requirejs解决了目前项目的两个主要问题。

静态资源缓存问题

requirejs对于js文件的版本号是通过config.js配置文件中的urlArgs参数统一管理的。

requirejs.config({
        ...
        urlArgs:"v=1.0.0"
        path:{
            "module1":"./module1",
            "module2":"./module2"
        }
        ...
})

requirejs会在装载module1和module2时,在请求后面拼接上"?v=1.0.0"。
问题在于,每次发布,不管哪个环境,都需要手动去修改urlArgs这个参数,否则就会出现缓存问题。
而希望实现的目标是,所有静态资源根据MD5码生成版本号。
解决思路:
一种是利用gulp将配置文件调整成以下样子:

requirejs.config({
        ...
        urlArgs:""
        path:{
            "module1":"./module1.js?v=dFe82Pzk",
            "module2":"./module2.js?v=1a0Ak9pY"
        }
        ...
})

大致实现流程:
1.读取配置文件,将文件中path解析成JSON对象,前提条件是配置文件中path的键值都是string且都用引号引起来,否则无法转换成json对象;

//获取requireCOnfig中的paths JSON对象
const __getRequireConfigPaths = function (file) {
    let configContents = file.contents.toString();
    let matches = /"paths": (\{[^}]*}),/.exec(configContents);
    return JSON.parse(matches[1]);
};

2.遍历所有require管理的js文件进行MD5编码,建立moduleName:md5键值对构成的对象verMap;

//根据文件内容计算md5串
const __getDataMd5 = function (data) {
    return crypto.createHash('md5').update(data).digest('base64');
};

//根据paths JSON对象生成[{moduleName:moduleNamev?=MD5}]对照关系verMap
gulp.task("createMD5VerMap", function (cb) {
    gulp.src("./config/common-config.js")
        .pipe(through2.obj(function (file, encoding, done) {
                let paths = __getRequireConfigPaths(file);
                //将through2异步操作封装成promise对象,利用Promise.all等待所有through2异步任务执行完毕后调用gulp.task的callback
                let promises = [];
                for (let moduleName in paths) {
                    let filePath = './' + paths[moduleName];
                    let suffix = '';
                    if(/([Cc]ss$)/.test(moduleName)){
                        //需约定moduleName以css或者Css结尾对应的都是css文件
                        filePath = './' + paths[moduleName]+'.css';
                        suffix = '.css'
                    }else if(!/(\.html$)/.test(filePath)){
                        //需约定文件名不以.html结尾的都是js文件
                        filePath = './' + paths[moduleName]+'.js';
                        suffix = '.js'
                    }
                    promises.push(
                        new Promise(function (resolve) {
                            gulp.src(filePath)
                                .pipe(through2.obj(function (file) {
                                        verMap[moduleName] = paths[moduleName]+suffix+"?v="+__getDataMd5(file.contents).slice(0, 6);
                                        resolve();
                                    }, function () {
                                        //不处理失败任务
                                        console.log("未获取到文件:"+paths[moduleName])
                                        verMap[moduleName] = paths[moduleName];
                                        resolve();
                                    })
                                )
                        })
                    );
                }
                Promise.all(promises).then(cb());
            })
        );
});
这里利用Promise.all来保证全部文件完成MD5编码后再进行后续处理,防止异步问题导致verMap未完整生成就被拿去做其他处理。

3.读取配置文件,根据verMap改写path中的值("module1":"./module1" => "module1":"./module1?v=dFe82Pzk")

//将[{moduleName:MD5}]对照关系verMap写入requireConfig.js配置文件中
gulp.task("modifyRequireConfig", function () {
    return gulp.src("js/requirejs-config.js")
        .pipe(through2.obj(function (file, encoding, done) {
            let contents = file.contents.toString();
            contents = contents.replace(/"paths": (\{[^}]*}),/, '"paths": '+JSON.stringify(verMap));
            file.contents = new Buffer(contents);
            this.push(file);
            done();
        }))
        .pipe(rename("requirejs-config.js"))
        .pipe(gulp.dest("./js"));
});

这种方法的弊端在于静态资源的覆盖率,对于未在path中配置的静态文件,是没办法打上md5版本号的,所以需要对所有用require或者define引入的js文件,全部写到path里去,导致配置文件臃肿。

网上还有种思路是修改require.js源码,将urlArgs改为允许传入Function,在根据所有静态文件生成moduleName:MD5键值对以后,通过动态获取MD5码来拼接url。(参考:http://www.tuicool.com/articl...)

如果对修改源码没有限制,可以采用这种方式。

代码合并、压缩

利用requirejs的optimize方法,可以将存在相互依赖关系的几个js合并成单个js文件,并提供uglify压缩。

module1:
    define("module1", function(){
        return {
            key: "value"
        }
    });

module2:
    require(["module1"], function(module1){
        console.log(module1.key);
    })
    
gulpfile.js
    gulp.task("concat", function(){
        rjs.optimize({
            baseUrl: "./",
            name: "./test/module2.js",
            out: "./test/app.js",
            optimize: "uglify"//压缩
        }, function () {
            console.log(name + ":" + out + " is OK!");
        });
    });

执行gulp任务后会将module1和module2合并生成app.js。

app.js:
    define("module1",[],function(){return{key:"value"}}),define("test/module1.js",function(){}),require(["./module1.js"],function(e){console.log(e.key)}),define("test/module2.js",function(){});

而对于没有相互依赖关系的js,可以用gulp-concat进行合并,再用gulp-uglify压缩

//公共模块合并
gulp.task('buildCommModule', function () {
    return gulp.src(["./js/a.js", "./js/b.js"])
        .pipe(concat("common.js"))
        .pipe(uglify())
        .pipe(gulp.dest('./dist/js/'))
})

最后,对于入口html文件,引入js的代码如下

<script src="../js/requirejs/require.min.js"></script>
<script>
    //禁用requirejs-config.js缓存
    require.s.contexts._.config.urlArgs = new Date().getTime().toString();
    
    require(['../js/requirejs-config'], function () {
        require(['task_main']);
    });
</script>

梦梦她爹
1.8k 声望122 粉丝

引用和评论

0 条评论