基本概念:chunk & module
module:每一个源码js文件其实都可以看成一个module
chunk:根据module引用关系生成chunk
bundle:一般来说一个 chunk 对应一个 bundle;但可以通过引入插件 拆分chunk,如分离出了bundle.css
按照同一份源码对应不同阶段理解:(生命周期层面)
我们直接写出来的是 module,webpack 处理时是 chunk,最后生成浏览器可以直接运行的 bundle。
webpack 处理时是 chunk:
当我们写的 module 源文件传到 webpack 进行打包时,webpack 会根据文件引用关系生成 chunk 文件;
如上图引用的多的utils就会生成单独的chunk,
配置动态加载或者提取公共包的话,也会生成新的bundle。
基本概念:runtime&manifest
在使用 webpack 构建的典型应用程序或站点中,有三种主要的代码类型:
- 业务代码
- vendor.js: 将第三方引用库 统一打包在这里 因为几乎不怎么变化 独立出来可以充分利用浏览器缓存
- webpack 的 runtime 和 manifest,管理所有模块的交互(在模块交互时,连接模块所需的加载和解析逻辑)
runtime:连接模块化应用程序所需的所有代码, 包括:已经加载到浏览器中的连接模块逻辑,以及尚未加载模块的延迟加载逻辑。
manifest:存储了 chunks 映射关系(跟fis里面静态资源映射表的概念一致)。有了chunks 的映射,我们才知道要加载的chunk的真实地址。
runtime读取manifest解析和加载模块
(无论你选择哪种模块语法,那些 import 或 require 语句现在都已经转换为 webpack_require 方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块)
P.S: mainfest比较小,可以inline的方式接入html 由此可以节省一次网络请求(借鉴fis的inline思想
inline-manifest-webpack-plugin插件
分包拆分合理利用缓存的前置知识:必须弄懂runtime和mainfest。
怎么配置构建优化产出的runtime和mainfest,才能使每次改动之后的新增修改文件最小从而合理利用缓存。
关键点:compiler、Compilation
1、初始化编译:生成Compiler
对象,加载所有的插件,为webpack
事件流挂上插件的自定义hooks
调用compiler.run/watch 执行编译
2、compilation
是compiler里具体负责编译的
依次进入每一个入口文件(entry
),利用配置的各种Loader来编译不同模块,递归编译;
Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容
再将编译好的文件内容使用acorn
解析生成AST静态语法树
所有模块和和依赖分析完成后,执行 compilation
的 seal
方法对每个 chunk 进行整理、优化、封装;封装到__webpack_require__
来实现模块化操作.
3、输出emit
:所有文件的编译及转化都已经完成,包含了最终输出的资源,我们可以在传入事件回调的compilation.assets
上拿到所需数据,其中包括即将输出的资源、代码块Chunk等等信息。
- 你刚说的输出列表是什么? - 其实是在问chunks是挂在哪个对象上。
- 入口模块是怎么处理的? - 其实在问如何生成入口模块的
- 最后是怎么把文件内容写入到文件系统的? - 其实是在问compiler是如何具备文件读写能力的
- 什么时候构建阶段开始? - 其实是在问webpack开始构建之前做了哪些构建准备
- 你说的调用Loader对模块进行翻译是如何做到的? - 其实是在问在哪个阶段处理loader的
1、.run(): 创建compile对象,继承自tapable
compile创建compilation,负责组织整个编译过程
事件(钩子):
compile -> make(分析入口文件创建模块对象) -> build-module -> after-compile -> emit -> after-emit
初始化插件? loader执行
step1: 实例化compiler
1、初始化 NodeEnvironmentPlugin(让compiler具体文件读写能力) // 源码里是对 node 的 fs 模块进行了二次封装的
2、挂载所有 plugins 插件至 compiler 对象身上; plugin.apply(compiler) // 所以写插件的时候可以看到apply(comiler)拿到当前的编译实例
3、挂载所有 webpack 内置的插件(入口)
step2: compiler.run
this.hooks.beforeRun.callAsync -> this.hooks.run.callAsync -> this.compile
step3: compile方法做的事情
调用this.hooks.beforeRun.callAsync
- this.newCompilation(params) 实例化Compilation对象
- this.hooks.make.callAsync 触发make钩子监听
- compilation.seal 开始处理 chunk
- this.hooks.afterCompile.callAsync(compilation,…)
- 流程进入compilation了。。。
step4: 完成模块编译操作
- addEntry
- _addModuleChain
- createModule:定义一个创建模块的方法,达到复用的目的
- module = normalModuleFactory.create(data) : 创建普通模块,目的是用来加载js模块
- afterBuild
- this.processDependencies : 找到模块与模块之间的依赖关系
- this.buildModule(module, afterBuild)
- module.build : 到这里就意味着当前 Module 的编译完成了
- createModule:定义一个创建模块的方法,达到复用的目的
- seal: 生成代码内容,输出文件
webpack = 构建核心流程 + loader + plugin
- Plugin:webpack构建过程中,会在特定的时机广播对应的事件,插件监听这些事件,在特定时间点介入编译过程
经过构建阶段后,compilation 会获知资源模块的内容与依赖关系
经过 seal 阶段处理后, compilation 则获知资源输出的图谱,知道如何输出:哪些模块跟那些模块“绑定”在一起输出到哪里
weback架构:compiler + compilation + plugins
webpack 运行过程中只会有一个 compiler ;
而每次编译 —— 包括调用 compiler.run 函数或者 watch = true 时文件发生变更,都会创建一个 compilation 对象
一步一步分析
静态引入 和 动态载入的代码 webpack打包生成了什么
src
---main.js # 分别静态引入A.js, 动态引入B.js
---moduleA.js
---moduleB.js
/**
* main.js
*/
import testA from './moduleA';
testA();
import('./moduleB').then(module => {
});
最终生成的文件就是两个bundle,分别是:
- main.js和moduleA.js组成的bundle.js
- moduleB.js组成的0.bundle.js // 动态加载的
静态bundle产生:mainTemplate 渲染出来的
动态:chunkTemplate渲染出来
打包产物: import 解析成了啥
mainjs打包后的代码:
(function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */
var _moduleA__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(
/*! ./moduleA */ "./src/moduleA.js" # 静态加载的A.js
);
Object(_moduleA__WEBPACK_IMPORTED_MODULE_0__["default"])();
__webpack_require__.e( /*! import() */ 0)
# 动态加载的B.js
.then(__webpack_require__.bind(null, /*! ./moduleB */ "./src/moduleB.js"))
.then(module => {});
})
# 核心 __webpack_require__函数
// import moduleA => __webpack_require__('./src/moduleA.js');
所以接下来看 webpack_require函数
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)
// 如果一个import的模块是第一次加载,那之前必然没有加载过,就会去执行加载过程
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
// Flag the module as loaded
module.l = true;
// Return the exports of the module
return module.exports;
}
如果没有加载过,那就从modules这个所有模块的map里去加载
静态引入:modules的map?
mainjs + moduleA 生成的bundlejs 简化:
# modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
(function (modules) {})({
"./src/main.js": (function (module, __webpack_exports__, __webpack_require__) {}),
"./src/moduleA.js": (function (module, __webpack_exports__, __webpack_require__) {})
});
modules就是函数的入参,具体值就是我们包含的所有module,到此,
一个chunk是如何加载的,以及chunk如何包含module
动态引入:类jsonp
那这个新的js文件0.bundle.js里面是不是也有自己的modules呢?
那bundle.js如何知道0.bundle.js里面的modules呢?
动态模块载入后,webpack就知道了bundle.js里面的modules就一定会有了0.bundle.js包含的那些modules,这是如何做到的呢?
翻译解释一下:(猜测)
动态载入后,动态模块里引用的commonjs代码,一定会被包含在了静态生成的bundlejs里
这样,当动态按需载入的时候,不是重复载入commonjs代码,而是仅仅载入动态分离的那部分逻辑
这样可以保证,动态载入 但是 引入的资源确实不是重复引用的
可以看看动态引入生成的0.bundle.js代码,里面是啥
# 向一个全局数组里面push了自己的模块id以及对应的modules
(window["webpackJsonp"] = window["webpackJsonp"] || []).push(
[
[0],
{
"./src/moduleB.js": (function (module, __webpack_exports__, __webpack_require__) {})
}
]
);
所以,动态载入的代码的控制逻辑还是在静态生成的bundle里
// bundle.js
# 劫持了push函数
var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || [];
var oldJsonpFunction = jsonpArray.push.bind(jsonpArray);
jsonpArray.push = webpackJsonpCallback; # 劫持
jsonpArray = jsonpArray.slice();
for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);
var parentJsonpFunction = oldJsonpFunction;
所以,当window[“webpackJsonp”] || []).push时,会执行bundlejs代码,bundlejs拿到动态的所有的参数
进而把 0.bundle.js里面的所有module加到静态的modules里面去
至此 静态bundlejs 拥有了 动态产生的0.bundle.js引用的那些module