利用gulp+requirejs解决了目前项目的两个主要问题。css
requirejs对于js文件的版本号是经过config.js配置文件中的urlArgs参数统一管理的。html
requirejs.config({ ... urlArgs:"v=1.0.0" path:{ "module1":"./module1", "module2":"./module2" } ... })
requirejs会在装载module1和module2时,在请求后面拼接上"?v=1.0.0"。
问题在于,每次发布,无论哪一个环境,都须要手动去修改urlArgs这个参数,不然就会出现缓存问题。
而但愿实现的目标是,全部静态资源根据MD5码生成版本号。
解决思路:
一种是利用gulp将配置文件调整成如下样子:json
requirejs.config({ ... urlArgs:"" path:{ "module1":"./module1.js?v=dFe82Pzk", "module2":"./module2.js?v=1a0Ak9pY" } ... })
大体实现流程:
1.读取配置文件,将文件中path解析成JSON对象,前提条件是配置文件中path的键值都是string且都用引号引发来,不然没法转换成json对象;gulp
//获取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;promise
//根据文件内容计算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里去,致使配置文件臃肿。app
网上还有种思路是修改require.js源码,将urlArgs改成容许传入Function,在根据全部静态文件生成moduleName:MD5键值对之后,经过动态获取MD5码来拼接url。(参考:http://www.tuicool.com/articl...)异步
若是对修改源码没有限制,能够采用这种方式。requirejs
利用requirejs的optimize方法,能够将存在相互依赖关系的几个js合并成单个js文件,并提供uglify压缩。ui
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>