webpack原理解析
webpack原理解析
webpack打包原理解析
一开始,对webpack打包原理很不熟悉,看了不少资料,但是讲的都不是很清楚,现在来梳理一遍。
所有资源统一入口
这个是什么意思呢?就是webpack是通过js来获取和操作其他文件资源的,比如webpack想处理less,但是它并不会直接从本地的文件夹中直接通过路径去读取css文件,而且通过执行入口js文件,如果入口文件中,或者入口文件相关联的js文件中含有 require(xx.less) 这个less文件,那么它就会通过对应的loader去处理这个less文件
打包中的文件管理
重点来了: webpack是如何进行资源的打包的呢?
总结:
- 每个文件都是一个资源,可以用require导入js
- 每个入口文件会把自己所依赖(即require)的资源全部打包在一起,一个资源多次引用的话,只会打包一份
- 对于多个入口的情况,其实就是分别独立的执行单个入口情况,每个入口文件不相干(可用CommonsChunkPlugin优化)
js单文件入口的情况
比如整个应用的入口为 entry.js
entry.js引用 util1.js util2.js, 同时util1.js又引用了util2.js
关键问题是: 它打包的会不会将 util2.js打包两份?
其实不会的,webpack打包的原理为,在入口文件中,对每个require资源文件进行配置一个id, 也就是说,对于同一个资源,就算是require多次的话,它的id也是一样的,所以无论在多少个文件中require,它都只会打包一分
对于和上面一致拓扑图,打包后的js代码为
注:
/* 1 */
代码打包模块为 id 为 1
通过上面的图片我们看到,
- entry.js 入口文件对应的id为 1
- util1.js的id为 2
- util2.js的id为 3
entry.js引用了 2 和 3, util1.js引用了 3,说明了entry和util1引用的是同一份,util2.js不会重复打包
css单文件入口的情况
上面分析了js的情况,其实css也是一个道理。它同样也会为每个css资源分配一个id, 每个资源同样也只会导入一次。
可以看到, entry.js 和 util1.js共同引用了 style2.less,那么它们作用的结果是怎么样的呢?
可以看到 entry.js 和 util1.js使用的是同一个 css资源
注意: /* 1 */
注释表示id为1的模块
听说 ExtractTextPlugin 导出css的插件比较火, 让我们来看看它到底帮我们干了啥?配置好相关引用后
newExtractTextPlugin('[name].[chunkhash].css')
运行,在static下面生成了 一个.css 一个.js
生成的css代码为
-
.style2 {
-
color: green;
-
}
-
.style3 {
-
color: blue;
-
}
-
.style1 {
-
color: red;
-
}
实际就是把三个css导出到一个css文件,而且同一个入口中,多次引用同一个css,实际只会打包一份
生成的.js为
可以看到 /* 2 */
实际就是 util1.js模块,还是引用 id为3的模块 即css2, 但是我们看 3 模块的定义,为空函数, 它给出的解释是 removed by extract-text-webpack-plugin
实际就是newExtractTextPlugin把相关的css全部移出去了
多文件入口的情况
讲完了单入口,还需讲讲多入口,很多时候我们项目是需要多入口
修改webpack config
可以看到 输出为两个js文件
先对 entry.js 对应的输出文件进行分析
其实可以看到, util1.js util2.js的内容都打包进 对应的js里面去了
在对 entry2.js 输出的文件进行分析
可以看到,把entry2.js依赖的 util2.js也打包进去了
所以多 入口 实际就是 分别执行多个单入口,彼此之间不影响
问题来了,多入口对应的css呢? 还是上面的原则,css也是分别打包的,对于每个入口所依赖的css全部打包,输出就是这个入口对应的css
最后讨论 CommonsChunkPlugin
之前提到过,每个入口文件,都会独立打包自己依赖的模块,那就会造成很多重复打包的模块,有没有一种方法能把多个入口文件中,共同依赖的部分给独立出来呢? 肯定是有的 CommonsChunkPlugin
这个插件使用非常简单,它原理就是把多个入口共同的依赖都给定义成 一个新入口
为何我这里说是定义成新入口呢,因为这个名字不仅仅对应着js 而且对于着和它相关的css等,比如HtmlWebpackPlugin 中 就能体现出来,可以它的 chunks中 加入 common 新入口,它会自动把common的css也导入html
可以看到, 不仅仅js公共模块独立出来,连css同样也是,感受到了 webpack强大了吧
我们可以大概对 common.js index.xxxxx.js(entry.js对应的代码) index2.xxxx.js(entry2.js对应的代码)打包出来的代码分析一下
提前知识:
webpack的id 有两种 一种为 chunkid 一种为moduleId
- 每个chunkid 对应的是一个js文件
- 每个moduleid对应的是一个个js文件的内容的模块(一个js文件里面可以require多个资源,每 个资源分配一个moduleid)
为何要出来一个chunkid呢? 这个chunkid的作用就是,标记这个js文件是否已经加载过了
-
/******/ // object to store loaded and loading chunks
-
/******/ // "0" means "already loaded"
-
/******/ // Array means "loading", array contains callbacks
-
/******/ var installedChunks = {
-
/******/ 2:0
-
/******/ };
installedChunks 是记录一个chunkid是否已经加载过了
我们先从 common.js分析
common.js是公共分离出来的模块,所以按理来说,它应该是一个顶层的模块,实际上,确实如此
看 common.js代码
-
/******/ (function(modules) { // webpackBootstrap
-
/******/ // install a JSONP callback for chunk loading
-
-
-
/******/ var parentJsonpFunction = window["webpackJsonp"];
-
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
-
/******/ // add "moreModules" to the modules object,
-
/******/ // then flag all "chunkIds" as loaded and fire callback
定义了一个 webpackJsonp 函数
看 index.xxxx.js代码
-
webpackJsonp([0],[
-
/* 0 */
-
/***/ function(module, exports, __webpack_require__) {
-
-
module.exports = __webpack_require__(1);
-
-
/***/ },
使用 common.js中定义的函数 webpackJsonp, 所以在浏览器中加载的话,必须先加载 common.js
这个 webpackJsonp([0],[]) 第一个参数 [0] 是什么意思呢? 它就是 chunkid ,我们知道一个chunkidid对应一个 js 文件,它对应的js文件就是它的入口文件 entry.js, 那为何这里还要是一个数组
因为一个入口文件的话,可以依赖多个js文件,其他的 id 就是它所依赖的, 其实就是配置webpack的时候
那个入口js对应的 数组
index: ['./src/js/entry.js'],
[0] 和 现在的id数组一一对应
在 index2.xxx.js中,
-
-
webpackJsonp([1],{
-
-
/***/ 0:
-
/***/ function(module, exports, __webpack_require__) {
-
-
module.exports = __webpack_require__(8);
[1] 就是 webpack.config配置的数组 js 对应的 thunkid
index2: ['./src/js/entry2.js']
即 entry2.js 就是 thunkid 1
上面说完了thunkid, 下面就说 moduleid了, common.js为顶层js文件,通过调用webpackJsonp对其他文件进行处理,
common.js中
-
/******/ var parentJsonpFunction = window["webpackJsonp"];
-
/******/ window["webpackJsonp"] = function webpackJsonpCallback(chunkIds, moreModules) {
-
/******/ // add "moreModules" to the modules object,
-
/******/ // then flag all "chunkIds" as loaded and fire callback
-
-
/******/ for(moduleId in moreModules) {
-
-
/******/ modules[moduleId] = moreModules[moduleId];
-
/******/ }
webpackJsonpCallback(chunkIds, moreModules)
定义为 chunkIds, moreModules,chunkIds 说了,就是入口文件对应的 js 文件的 thunkid, 那么这个moreModules是什么呢? 就是一个js文件中,一个个依赖的和 导出本身的 module
可以看index2.xxxx.js
-
-
webpackJsonp([1],{
-
-
/***/ 0:
-
/***/ function(module, exports, __webpack_require__) {
-
-
module.exports = __webpack_require__(8);
-
-
-
/***/ },
-
-
/***/ 8:
-
/***/ function(module, exports, __webpack_require__) {
-
-
var util2 = __webpack_require__(3)
-
var css1 = __webpack_require__(4)
-
-
-
/***/ }
-
-
});
这里 moreModules 为一个对象,key 为 moduleid, value 就是一个个 module定义
比如 function(module, exports, __webpack_require__) { module.exports = __webpack_require__(8);}
最重要的来了, 这里的话, utils2 是公共的module,它被定义在commong.js中,
common.js
-
-
/* 0 */,
-
/* 1 */,
-
/* 2 */,
-
/* 3 */
-
/***/ function(module, exports) {
-
-
module.exports = {"name": "util2.js"}
-
-
-
/***/ },
-
/* 4 */
可以看到, index.xxx.js 的 调用 和 index2.xxx.js的调用都是一样的,都是下面这句
var util2 = __webpack_require__(3)
它会寻找 moduleid 为3的模块,然后返回
如何寻找呢 看看 webpack_require 定义
-
/******/ 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
-
/******/ };
if(installedModules[moduleId]) 如果缓存中存在的话,就直接返回。
由于 index.xxx.js 和 index2.xxx.js都是返回的同一个 id 的模块,所以实际上 它们使用的是同一个对象
这和nodejs里面的require是一样的,所以整个项目中,require公共模块中的资源的话,实际返回的都是同一个对象