利用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}]对照关系verMapgulp.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。(参考:)
如果对修改源码没有限制,可以采用这种方式。
代码合并、压缩
利用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的代码如下