webpack分包及異步加載套路

JenLangham 7年前發布 | 31K 次閱讀 前端技術 webpack

最近一個小項目是用 webpack 來進行構建的。其中用到了 webpack 分包異步加載的功能。今天抽時間看了下 webpack 打包后的文件,大致弄明白了 webpack 分包及異步加載的套路。

由于這個小項目是用自己寫的一個路由,路由定義好了不同路徑對應下的模板及邏輯代碼:

webpack 配置文件:

var path = require('path'),
    DashboardPlugin = require('webpack-dashboard/plugin'),
    HtmlWebpackPlugin = require('html-webpack-plugin'),
    webpack = require('webpack'),
    ExtractTextPlugin = require('extract-text-webpack-plugin');

var PATHS = {
    app: path.join(__dirname, 'src'),
    dist: path.join(__dirname, 'dist')
}

var PKG = require('./package.json');
var TARGET = process.env.npm_lifecycle_event;   //獲取當前正在運行的腳本名稱

var isProduction = function() {
    return process.env.NODE_ENV === 'production';
}



module.exports ={
    entry: {
        'index': path.join(__dirname, 'src/index.js'),
        'lib': ['./src/lib/js/index.js'],
    },
    //filename是主入口文件的名稱,即對應的entry
    //chunkFilename對應的是非主入口文件的名稱,chunk
    output: {
        path: PATHS.dist,
        publicPath: '/static/taxi-driver/',    //publicPath 的話是打包的時候生成的文件鏈接,如果是在生產環境當然是用服務器地址,如果是開發環境就是用本地靜態服務器的地址
        filename: 'js/register/[name].js',
        chunkFilename: 'js/register/[name].js',
        //TODO: build文件中加入hash值
    },
    //生成source-map文件
    devtool: isProduction ? null : 'source-map',
    devServer: {
        proxy: {
            '/api/*': {
                target: 'http://localhost:3000',
                secure: false
            }
        }
    },
    module: {
        loaders: [
            {
                test: /\.js$/,
                exclude: /node_modules|picker.min.js/,
                loader: 'babel'
            },
            {
                test: /\.less$/,
                loader: ExtractTextPlugin.extract('style', 'css!less')
            },
            {
                test: /\.html$/,
                loader: 'raw'
            },
            {
                test: /\.css$/,
                loader: ExtractTextPlugin.extract('style', 'css')
            },
            {
                test: /\.json$/,
                loader: 'json'
            }
        ]
    },
    resolve: {
        alias: {
            src: path.join(__dirname, 'src'),
            modules: path.join(__dirname, 'src/modules'),
            lessLib: path.join(__dirname, 'src/lib/less'),  
            jsLib: path.join(__dirname, 'src/lib/js'),
            components: path.join(__dirname, 'src/components')
        },
        extensions: ['', '.js', '.less', '.html', '.json'],
    },
    plugins: [
        new HtmlWebpackPlugin({
            title: '認證資料',
            template: './dist/assets/info.html',
            inject: 'body',
            filename: 'pages/register/index.html'   //輸出html文件的位置
        }),
        new DashboardPlugin(),
        new ExtractTextPlugin('css/register/style.css'),     //將引入的樣式文件單獨抽成style.css文件并插入到head標簽當中,帶有路徑時,最后打包
        new webpack.optimize.CommonsChunkPlugin({
            name: 'common',
            filename: 'js/register/common.js',
            minChunks: 3
        })
    ]
}

接下來是定義好的路由文件:

const Router = new Route();

    Route
        .addRoute({
            path: 'path1',
            viewBox: '.public-container',
            template: require('modules/path1/index.html'),
            pageInit() {
            //webpack提供的分包的API. require.ensure
                require.ensure([], () => {
                    let controller = require('modules/path1/controller');
                    Router.registerCtrl('path1', new controller('.public-container'));
                }, 'path1');
            }
        })
        .addRoute({
            path: 'path2',
            viewBox: '.public-container',
            template: require('modules/path2/index.html'),
            pageInit() {
                require.ensure([], () => {
                    let controller = require('modules/path2/controller');
                    Router.registerCtrl('path2', new controller('.public-container'));
                }, 'path2');
            }
        });

最后 webpack 會將這2個需要異步加載的模塊,分別打包成 path1.js 和 path2.js .

當頁面的路徑為:

http://localhost:8080/pages/register/#/path1 時,會加載 path1.js 文件

http://localhost:8080/pages/register/#/path2 時,會加載 path2.js 文件.

再來看看 webpack 打包后的文件:

其中在 common.js 中, webpack 定義了一個全局函數 webpackJsonp .這個全局函數在項目一啟動后就定義好。

局部函數 __webpack_require__ 用以在某一個模塊中初始化或者調用其他的模塊方法。同時這個函數還有一個靜態方法 __webpack_require__.e 這個方法就是用來異步加載 js 文件的。

接下來一步一步的看:

//common.js
    (function(modules) {
        //modules用來保存所有的分包,它是一個數組,數組每個元素對應的都是callback,每個分包都是通過數字來進行標識的

        //定義好的全局函數webpackJsonp
        //大家可以看看其他打包好的文件,例如index.js, path1.js和path2.js文件.都是webpackJsonp()這種的形式,大家用過JSONP應該會很好理解。首先在前端定義好函數,然后后端下發組裝好的函數js文件,前端獲取到這個文件后就可以立即進行執行了
        var parentJsonpFunction = window["webpackJsonp"];
        window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
            var moduleId, chunkId, i = 0, callbacks = [];
/******/         for(;i < chunkIds.length; i++) {
/******/             chunkId = chunkIds[i];
/******/             if(installedChunks[chunkId])
/******/                 callbacks.push.apply(callbacks, installedChunks[chunkId]);
/******/             installedChunks[chunkId] = 0;
/******/         }
                //這個全局函數會將各個分包緩存到modules
/******/         for(moduleId in moreModules) {
/******/             modules[moduleId] = moreModules[moduleId];
/******/         }
/******/         if(parentJsonpFunction) parentJsonpFunction(chunkIds, moreModules);
/******/         while(callbacks.length)
/******/             callbacks.shift().call(null, __webpack_require__);
                //用以啟動整個應用
/******/         if(moreModules[0]) {
/******/             installedModules[0] = 0;
/******/             return __webpack_require__(0);
/******/         }
        };
    })([]);
// The require function
            //通過數字標識的moduleId
/******/     function __webpack_require__(moduleId) {

/******/         // Check if module is in cache
/******/         if(installedModules[moduleId])
/******/             return installedModules[moduleId].exports;

/******/         // Create a new module (and put it into the cache)
/******/         var module = installedModules[moduleId] = {
/******/             exports: {},
/******/             id: moduleId,
/******/             loaded: false
/******/         };

/******/         // Execute the module function
/******/         modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/         // Flag the module as loaded
/******/         module.loaded = true;

/******/         // Return the exports of the module
/******/         return module.exports;
/******/     }

/******/     // This file contains only the entry chunk.
/******/     // The chunk loading function for additional chunks
            //異步加載函數
/******/     __webpack_require__.e = function requireEnsure(chunkId, callback) {
/******/         // "0" is the signal for "already loaded"
/******/         if(installedChunks[chunkId] === 0)
/******/             return callback.call(null, __webpack_require__);

/******/         // an array means "currently loading".
/******/         if(installedChunks[chunkId] !== undefined) {
/******/             installedChunks[chunkId].push(callback);
/******/         } else {
                    //創建script表情,請求js文件
/******/             // start chunk loading
/******/             installedChunks[chunkId] = [callback];
/******/             var head = document.getElementsByTagName('head')[0];
/******/             var script = document.createElement('script');
/******/             script.type = 'text/javascript';
/******/             script.charset = 'utf-8';
/******/             script.async = true;

/******/             script.src = __webpack_require__.p + "js/register/" + ({"0":"index","1":"path1","2":"path2"}[chunkId]||chunkId) + ".js";
/******/             head.appendChild(script);
/******/         }
/******/     };

/******/     // expose the modules object (__webpack_modules__)
/******/     __webpack_require__.m = modules;

/******/     // expose the module cache
/******/     __webpack_require__.c = installedModules;

/******/     // __webpack_public_path__
            //配置文件中定義的publicPath,build完后加載文件的路徑
/******/     __webpack_require__.p = "/static/taxi-driver/";
/******/ })

在最后輸出的 index.html 文件中首先加載的是這個 common.js 文件,然后是入口文件 index.js 。因為這個實例代碼里面沒有很多共用文件,因此 webpack 自己提供的 commonChunkPlugin 這個插件并沒有起到作用,本來作為共用文件的 xRoute.js 因此也被打包進入了 index.js .

webpackJsonp([0, 3], [
        /* 0 */
/***/ function(module, exports, __webpack_require__) {

    'use strict';

    __webpack_require__(1);

    __webpack_require__(8);

/***/ },
/* 1 */
/* 2 */
/* 3 */
//....
/* 8 */
    ])

index.js 文件在 common.js 后加載,加載完后即開始執行.大家還記得 webpackJsonp 這個全局函數里面的倒數3行代碼吧。就是用以調用這里:

/* 0 */
    function(module, exports, __webpack_require__) {

    'use strict';

    __webpack_require__(1);

    __webpack_require__(8);

}

其中模塊 Id 為 1 和 8 的內容請查看相應文件, 其中 模塊1 為我定義的路由文件,在執行 模塊1 的代碼前,會加載 模塊2 的內容, 模塊2 的內容為我定義的路由庫。

接下來就看下 模塊1 中路由定義的具體內容:

/* 1 */
/***/ function(module, exports, __webpack_require__) {

    'use strict';

    Object.defineProperty(exports, "__esModule", {
        value: true
    });

    //加載路由庫
    var _index = __webpack_require__(2);
    //實例化一個路由
    var Router = new _index.Route();
    //定義好的路由規則
    Router.home('path1').addRoute({
        path: 'path1',
        viewBox: '.public-container',
        //模板文件,為模塊4
        template: __webpack_require__(4),
        pageInit: function pageInit() {
        //這個方法是在common.js中__webpack_require__的靜態方法,用來異步加載js。
        //異步加載js的文件(即chunk)用來數字來標識,chunk的順序從0開始.
        //這里path1.js的chunk num為1,大家可以回過頭到common.js的__webpack_require__.e方法里面看看,里面已經做好了chunk num和模塊文件的映射, chunk 1對應的模塊文件為path1.js,chunk 2對用的模塊文件為path2.js
        //__webpack_require__.e()接收的第二個參數為異步加載模塊后的回調. 當path1.js被加載完后,在modules里面進行了緩存.這時就可以通過模塊id去獲取這個模塊。然后進行初始化等后續的操作
            __webpack_require__.e/* nsure */(1, function () {
                var controller = __webpack_require__(6);
                Router.registerCtrl('path1', new controller('.public-container'));
            });
        }
    }).addRoute({
        path: 'path2',
        viewBox: '.public-container',
        //模板文件,為模塊5
        template: __webpack_require__(5),
        pageInit: function pageInit() {
            __webpack_require__.e/* nsure */(2, function () {
                var controller = __webpack_require__(7);
                Router.registerCtrl('path2', new controller('.public-container'));
            });
        }
    });

    Router.bootstrap();

    exports.default = Router;

/***/ },

 

來自:https://segmentfault.com/a/1190000007962830

 

 本文由用戶 JenLangham 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!