最近一个小项目是用
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;
/***/ },