构建流程
Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程:
- 初始化参数:从配置文件和 Shell 语句中读取与合井参数,得出最终的参数;
- 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译;
- 确定入口:根据配置中的 entry 找出所有的入口文件;
- 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系;
- 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会;
- 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。
在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。
常用 loader 和 plugin
参考:吐血整理的webpack入门知识及常用loader和plugin - 掘金
loader
编译方面:babel-loader、ts-loader、markdown-loader
静态资源方面:file-loader、url-loader(处理图片)、svg-sprite-loader
css 样式方面:style-loader、css-loader、postcss-loader、less-loader
plugin
html 资源整合方面:html-webpack-plugin、clean-webpack-plugin
开发环境:webpack.HotModuleReplacePlugin、webpack.definePlugin、
分包:SplitChunksPlugin
压缩:mini-css-extract-plugin
加快打包速度: ParallelUglifyPlugin(uglifyjs-webpack-plugin)、happyPack->threadLoader、
缓存:cache-loader、DllPlugin
dll 动态链接库
所谓动态链接,就是把一些经常会共享的代码制作成 DLL 档,当可执行文件调用到 DLL 档内的函数时,Windows 操作系统才会把 DLL 档加载存储器内,DLL 档本身的结构就是可执行档,当程序有需求时函数才进行链接。透过动态链接方式,存储器浪费的情形将可大幅降低。

HMR
热资源替换
相比于自动更新,该方式可以做到不刷新浏览器实现 热插拔式 的更新资源
css 文件在 lloader 的基础上无需额外配置,而 js 文件还需要手动配置
原理
1.首先 webpack-dev-server 会建立一个服务器,并且和浏览器建立 websocket 通信。
2.服务器监听文件变化,当文件变化的时候,会重新打包相应的 chunk,然后向浏览器发射 hash 和 ok 事件,通知浏览器对应的 chunkid 等信息。
3.浏览器监听 hash 和 ok 事件,再接受信息之后,通过 jsonp 向服务端请求对应的热更新代码。
4.最后浏览器把 jsonp 获得的代码注入到 html 的 head 里面去执行,从而实现了对应的模块替换
Code Splitting
按需加载,降低启动成本,提高响应速度
HtmlWebpackPlugin 插件 的 chunks 属性来实现打包出来的模块分割
optimization 选项的 splitChunks 属性的 chunks 设置提取公共第三方库
- 使用 webpackChunkName 的魔法注释,可以指定两个组件打包到一个模块当中
split-chunks
提取或分离代码的插件,主要作用是提取公共代码,防止代码被重复打包,拆分过大的 js 文件,合并零散的 js 文件
splitChunks: {
chunks: "all", //指定打包同步加载还是异步加载
minSize: 30000, //构建出来的chunk大于30000才会被分割
minRemainingSize: 0,
maxSize: 0, //会尝试根据这个大小进行代码分割
minChunks: 1, //制定用了几次才进行代码分割
maxAsyncRequests: 6,
maxInitialRequests: 4,
automaticNameDelimiter: "~", //文件生成的连接符
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/, //符合组的要求就给构建venders
priority: -10, //优先级用来判断打包到哪个里面去
filename: "vendors", //指定chunks名称
},
default: {
minChunks: 2, //被引用两次就提取出来
priority: -20,
reuseExistingChunk: true, //检查之前是否被引用过有的话就不被打包了
},
},
}async: 适合只将异步加载模块分离成单独 chunk 而其他模块合并成为一个 chunk。
all: 适合在正常项目中提取同步异步公用模块到一个 chunk 中减少代码加载和代码量。
initial: 这个配置的特性是:如果自己当前模块的引用模块引用过自己正在使用的库那么这个库会被提取到公用的 chunks 中去,如果没有被引用过,就会构建到自己生成的 chunk 当中去,如果自己的子模块引用过这个库,也会被提取成相应的以自身为起点的公用 chunk,所以这个代码分割可以尽可能的减小初始化的时候的代码量
——属于有需要再提取,而不是 all 下的发现了就提取
三种 hash
hash是针对项目级别的,所有文件的哈希值都相同chunkhash是文件级别的,根据不同的入口文件进行依赖文件解析,构建对应的chunk,生成对应的哈希值contenthash是针对文件内容级别的,只有模块的内容改变,hash 就会改变(主要用于CSS抽离CSS文件
loader:css-loader
plugin:mini-css-extract-plugin
——css 为什么使用 contentHash
webpack 的 Tree shaking
- 配置 usedExports 让打包出来的模块只导出被使用的成员
- 配置 minimize 压缩输出结果,删去上一步中未被使用的模块代码

与 babel-loader 的结合问题
tree shaking 实现的前提是 ES Modules
新版本的 babel-loader,会根据当前代码的所处环境来判断是否使用 ES Modules 的转换(也可以使用 preset 来强制转换 ES Modules 模块)
原理
- 标记出哪些模块导出值没被用过
- 收集导出变量构建到模块依赖图中
- 遍历模块依赖图来看导出变量有没有被使用
- 删除掉没有用到的导出语句
Treeshaking 原理:
实现原理大致有以下三步
1、收集模块导出的内容
(1)ESM 导出语句会转换为 Dependency 对象,记录到 module 对象的 dependencies 集合。
(2)webpack 使用 FlagDependencyExportsPlugin 插件从 entry 入口开始,遍历所有 module 对象,将其 dependencies 集合中的导出值,放入 ModuleGraph 中存储,完成模块导出内容的收集。
2、标记模块导出的内容
模块导出信息收集完毕后,Webpack 需要标记出各个模块哪些导出值有被其它模块用到,哪些没有:
webpack 使用在 FlagDependencyUsagePlugin 插件中,逐步遍历 ModuleGraph 存储的所有 moudle 的导出值,每个导出值会被编译器方法(compilation.getDependencyReferencedExports 方法)所检验,确定其对应是否被其它模块使用,如果被其他模块所使用,将该导出值放入到 webpack 导出对象中( webpack_exports),未被使用的值都不会定义在 webpack_exports 对象中,形成一段不可能被执行的 Dead Code 效果。
3、删除无效代码
由 Terser、UglifyJS 等 DCE 工具 " 摇 " 掉这部分无效代码,构成完整的 Tree Shaking 操作。
副作用代码不可被删除
是指当调用函数时,除了返回函数值之外,还会对调用函数产生附加的影响
删除代码失效可能是打包出来的代码有副作用