利用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>