前端工程化二(requirejs + gulp)

利用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>
相關文章
相關標籤/搜索