详解webpack分包及异步加载套路

最近一个小项目是用webpack来进行构建的。其中用到了webpack分包异步加载的功能。今天抽时间看了下webpack打包后的文件,大致弄明白了webpack分包及异步加载的套路。

由于这个小项目是用自己写的一个路由,路由定义好了不同路径对应下的模板及逻辑代码:

webpack配置文件:

rush:js;"> 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$/,'css')
},{
test: /.json$/,loader: 'json'
}
]
},resolve: {
alias: {
src: path.join(dirname,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
})
]
}

接下来是定义好的路由文件:

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',template: require('modules/path2/index.html'),pageInit() {
require.ensure([],() => {
let controller = require('modules/path2/controller');
Router.registerCtrl('path2','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文件的。

接下来一步一步的看:

rush: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);
}
};
})([]);

rush:js;"> // 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.

rush: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行代码吧。就是用以调用这里:

rush:js;"> /* 0 */ function(module,__webpack_require__) {

'use strict';

__webpack_require__(1);

__webpack_require__(8);

}

其中模块Id为1和8的内容请查看相应文件,其中模块1为我定义的路由文件,在执行模块1的代码前,会加载模块2的内容,模块2的内容为我定义的路由库。

接下来就看下模块1中路由定义的具体内容

rush:js;"> /* 1 */ /***/ function(module,__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',//模板文件,为模块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',为模块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;

/***/ },

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程之家。

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...