詳解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 的話是打包的時候生成的文件鏈接,如果是在生產(chǎn)環(huán)境當然是用服務器地址,如果是開發(fā)環(huán)境就是用本地靜態(tài)服務器的地址
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定義了一個全局函數(shù)webpackJsonp.這個全局函數(shù)在項目一啟動后就定義好。
局部函數(shù)__webpack_require__用以在某一個模塊中初始化或者調用其他的模塊方法。同時這個函數(shù)還有一個靜態(tài)方法__webpack_require__.e這個方法就是用來異步加載js文件的。
接下來一步一步的看:
//common.js
(function(modules) {
//modules用來保存所有的分包,它是一個數(shù)組,數(shù)組每個元素對應的都是callback,每個分包都是通過數(shù)字來進行標識的
//定義好的全局函數(shù)webpackJsonp
//大家可以看看其他打包好的文件,例如index.js, path1.js和path2.js文件.都是webpackJsonp()這種的形式,大家用過JSONP應該會很好理解。首先在前端定義好函數(shù),然后后端下發(fā)組裝好的函數(shù)js文件,前端獲取到這個文件后就可以立即進行執(zhí)行了
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;
}
//這個全局函數(shù)會將各個分包緩存到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
//通過數(shù)字標識的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
//異步加載函數(shù)
__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 {
//創(chuàng)建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后加載,加載完后即開始執(zhí)行.大家還記得webpackJsonp這個全局函數(shù)里面的倒數(shù)3行代碼吧。就是用以調用這里:
/* 0 */
function(module, exports, __webpack_require__) {
'use strict';
__webpack_require__(1);
__webpack_require__(8);
}
其中模塊Id為1和8的內容請查看相應文件, 其中模塊1為我定義的路由文件,在執(zhí)行模塊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();
//定義好的路由規(guī)則
Router.home('path1').addRoute({
path: 'path1',
viewBox: '.public-container',
//模板文件,為模塊4
template: __webpack_require__(4),
pageInit: function pageInit() {
//這個方法是在common.js中__webpack_require__的靜態(tài)方法,用來異步加載js。
//異步加載js的文件(即chunk)用來數(shù)字來標識,chunk的順序從0開始.
//這里path1.js的chunk num為1,大家可以回過頭到common.js的__webpack_require__.e方法里面看看,里面已經(jīng)做好了chunk num和模塊文件的映射, chunk 1對應的模塊文件為path1.js,chunk 2對用的模塊文件為path2.js
//__webpack_require__.e()接收的第二個參數(shù)為異步加載模塊后的回調. 當path1.js被加載完后,在modules里面進行了緩存.這時就可以通過模塊id去獲取這個模塊。然后進行初始化等后續(xù)的操作
__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;
/***/ },
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
使用typescript+webpack構建一個js庫的示例詳解
這篇文章主要介紹了typescript+webpack構建一個js庫,本文主要記錄使用typescript配合webpack打包一個javascript library的配置過程,需要的朋友可以參考下2022-07-07

