前言
本文主要介绍webpack打包原理。webpack这个项目还是比较复杂的,现在已经到了webpack5的版本了。但是webpack打包原理并不是很复杂。本文的所有内容基于webpack0.1版本,github上能够找到的webapck的第一个版本;
基础概念
- chunk: webpack打包生成的文件是chunk
- module: 模块,可以是common.js模块,也可以是es6模块,还可以是amd的模块
- chunkId: chunkId的标识
- moduleId: 模块的标识
- require.ensure: commonjs规范中按需加载代码的方法,webpack实现了这个方法;其他按需加载的方法包括amd, es6中的import方法;
require.ensure(dependencies, callback)
参考文章: https://github.com/webpack/do...
这里我只解释下require.ensure函数的第一个参数,在执行第二个参数(回调函数)之前,第一个参数的依赖都已经被加载,但是加载后不会被执行。这个和amd规范不同,amd规范依赖加载后就执行了;
一个例子
下文所有内容基于webpack 0.1版本,如果你采用其他版本,打包出来的结果大同小异,不影响对webpack打包原理的理解
目录结构:
代码
webpack.config.js
var path = require("path");
module.exports = {
mode: 'development',
entry: path.resolve(__dirname, './index.js'),
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].js'
}
}
index.js
var a = require("./src/a");
var b = require("./src/b");
require.ensure([], function(require) {
require("./src/b").xyz();
var d = require("./src/d");
});
a.js
//module a
b.js
//module b
c.js
//module c
d.js
//module d
打包后的代码:
main.js
(function (modules) { // webpackBootstrap
// The module cache
var installedModules = {};
// object to store loaded and loading chunks
// "0" means "already loaded"
// Array means "loading", array contains callbacks
var installedChunks = { 0: 0 };
// The require function
function 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(null, module, module.exports, 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
require.e = function requireEnsure(chunkId, callback) {
// "0" is the signal for "already loaded"
if (installedChunks[chunkId] === 0)
return callback.call(null, require);
// an array means "currently loading".
if (installedChunks[chunkId] !== undefined) {
installedChunks[chunkId].push(callback);
} else {
// 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.src = modules.c + "" + chunkId + ".js";
head.appendChild(script);
}
};
// expose the modules object (__webpack_modules__)
require.modules = modules;
// expose the module cache
require.cache = installedModules;
// install a JSONP callback for chunk loading
window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
// add "moreModules" to the modules object,
// then flag all "chunkIds" as loaded and fire callback
var moduleId, chunkId, callbacks = [];
while (chunkIds.length) {
chunkId = chunkIds.shift();
if (installedChunks[chunkId])
callbacks.push.apply(callbacks, installedChunks[chunkId]);
installedChunks[chunkId] = 0;
}
for (moduleId in moreModules) {
modules[moduleId] = moreModules[moduleId];
}
while (callbacks.length)
callbacks.shift().call(null, require);
};
// Load entry module and return exports
return require(0);
})
/************************************************************************/
({
// __webpack_public_path__
c: "",
0:function (module, exports, require) {
var a = require(1);
var b = require(2);
require.e/*nsure*/(1, function (require) {
require(2).xyz();
var d = require(3);
});
},
1:
function (module, exports, require) {
// module a
},
2: function (module, exports, require) {
// module b
}
})
1.js
webpackJsonp([1],
{
/***/ 3:
/***/ function(module, exports, require) {
exports.lzy = 123;
// module d
/***/ }
}
)
两个文件一共130多行代码,看懂这些代码基本就明白webpack打包原理了,下面对打包后的文件进行分析:打包后生成了2个chunk, 1.js和main.js,其中1.js是异步加载的chunk;
chunk分析
main.js是一个自执行函数,自执行函数的参数是一个对象;对象的key是moduleId, 对象的值是一个函数,这个函数对应了一个模块;webpack将所有的模块打包成一个函数,可以看到moduleId为0,1,2依次对应index.js, a.js, b.js;那么d.js对应的模块呢,别急,请看异步1.js chunk,包括了d.js,说明d.js是异步加载的;
既然webpack将每一个文件打包成一个模块,我们对几个关键的函数进行分析require(模块加载), 模块异步加载require.e, 加载成功后执行的回调函数webpackJsonp
require方法
很简单,没有几行代码,应该可以看懂,传入moduleId, 根据moduleId查找对应的模块,并调用
require.e方法
这个方法可以说一下,可以看到通过插入script,异步加载文件,如何避免重复加载呢,比如说d.js已经引入了,再次require.ensure如何避免重复加载呢?带着问题就明白了
installedChunks[chunkId]有三种状态:
- installedChunks[chunkId] == 0; 说明脚本已经加载完成,webpackJsonp方法中有将installedChunks[chunkId]置0的操作
- installedChunks[chunkId] !== undefined,说明脚本正在加载,还没执行,这个时候在数组中添加回调函数;
- installedChunks[chunkId] === undefined,说明第一次加载,需要创建一个script引入脚本,即为异步chunk
webpackJsonp
脚本加载成功后,执行的回调函数;可以看出,主要逻辑是执行回调函数,为啥这样命名:webpackJsnop,我想跟jsnop的回调函数真的很像啊,webpackJsonp中执行require.ensure的回调函数,jsonp的回调函数接收服务端返回的数据
webpack打包原理说完了,如果你还是不理解,从头跑一遍上面的代码吧;
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。