webpack打包的实现原理及方法
webpack打包原理是根据文件间的依赖关系对其进行静态分析,然后将这些模块按指定规则生成静态资源,当webpack处理程序时,它会递归的构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将这些模块打包成一个或多个bundle;
- Entry:入口起点,指示webpack应该使用哪个模块,来作为内部构建依赖的开始;
- Ouput:ouput属性告诉webpack在哪里输出它所创建的bundles,以及如何命名这些文件,默认为./dist;
- Module: 模块,在webpack里一切皆模块,一个模块对应着一个文件。webpack会从配置的entry开始递归找出所有依赖的模块;
- Chunk:代码块,一个chunk由多个代码块组合而成,用于代码合并与分割;
- bundle: 多个chunk合在一起就是bundle,一个bundle可以理解为一个大的js打包之后生成的文件,而多个bundle里可能有公共的部分,或者一个bundle里的东西并不需要一次性加载,需要按照路由按需加载,拆分成不同的chunk;
- loader: lodaer让webpack能够去处理那些非JavaScript文件(webpack自身只理解JavaScript)
webpack的构建流程是什么
- 初始化参数:解析webpack配置参数,合并shell传入的webpack.config.js文件配置的参数,形成最后的配置结果。
- 开始编译:上一步得到的参数初始化compiler对象,注册所有配置的插件,插件监听webpack构建生命周期的事件节点,做出相应的反应,执行对象的run方法开始执行编译;
- 确定入口: 从配置的entry入口,开始解析文件构建AST语法树,找出依赖,递归下去;
- 编译模块: 递归中根据文件类型和loader配置,调用所有配置的loader对文件进行转换,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理;
- 完成模块编译并输出:递归完事后,得到每个文件的结果,包含每个模块以及他们之间的依赖关系,根据entry或分包配置生成代码块chunk;
- 输出完成:输出所有的chunk到文件系统;
webpack的热更新原理
- 其实是自己开启了express应用,添加了对webpack编译的监听,添加了和浏览器的websocket长连接,当文件变化触发webpack进行编译完成后,会通过sokcet消息告知浏览器准备刷新。而为了减少刷新的代价,就是不用刷新网页,而是刷新某个模块,webpack-dev-server可以支持热更新,通过生成文件的hash值来对比需要跟新的模块,浏览器再进行热替换。
Websocket是一种双向协议,它最大的特点就是 服务器可以主动向客户端推送消息,客户端也可以主动向服务器发送信息。这是HTTP不具备的,热更新实际上就是服务器端的更新通知到客户端,所以选择了Websocket
babel-loader的原理
在Babel-loader中es6转换为es5实际上是使用Babel-core的transform方法来进行代码转换的。
其实主要核心原理有三步:
- 解析:将代码解析生成抽象语法树(即AST),即词法分析与语法分析的过程;
- 转换:对AST进行变换一系列的操作,babel接受得到AST并通过babel-traverse对其进行遍历,在此过程中进行添加、更新及移除;
- 生成: 将变换后的AST再转换成JS代码,使用到的模块是babel-generator;
webpack Loader和Plugin的区别
- Loader:用于对模块源码的转换,Loader描述了webpack如何处理非JavaScript模块,并在build中引入这些模块。Loader可以将文件从不同语言(如TypeScript)转换为JavaScript,或者将内联图像转换为data URL。比如说: CSS-Loader, Style-Loader
编写一个 loader - Plugin:目的在于解决loader无法实现的其他事,它直接作用于webpack,扩展了它的功能。在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。plugin是一个扩展器,在webpack打包的过程中,基于事件驱动的机制,监听webpack打包过程中的某些节点,从而执行广泛的任务。 编写一个插件
常用loader
- style-loader:用于将
css编译完成的样式,挂载到页面style标签上。需要注意loader执行顺序,style-loader放到第一位,因为loader都是从下往上执行,最后全部编译完成挂载到style上 - css-loader:用于识别
.css文件, 处理css必须配合style-loader共同使用,只安装css-loader样式不会生效。 - sass-loader:
css预处理器,我们在项目开发中经常会使用到的 - postcss-loader:用于补充css样式各种浏览器内核前缀
- babel-loader:将Es6+ 语法转换为Es5语法
- ts-loader:用于配置项目typescript
- html-loader:我们有时候想引入一个
html页面代码片段赋值给DOM元素内容使用,这时就用到html-loader - file-loader:用于处理文件类型资源,如
jpg,png等图片 - url-loader:
url-loader也是处理图片类型资源,只不过它与file-loader有一点不同,url-loader可以设置一个根据图片大小进行不同的操作,如果该图片大小大于指定的大小,则将图片进行打包资源,否则将图片转换为base64字符串合并到js文件里 - html-withimg-loader:我们在编译图片时,都是使用
file-loader和url-loader,这两个loader都是查找js文件里的相关图片资源,但是html里面的文件不会查找所以我们html里的图片也想打包进去,这时使用html-withimg-loader - vue-loader:用于编译
.vue文件 - eslint-loader:用于检查代码是否符合规范,是否存在语法错误
常用plugin
- copy-webpack-plugin:将已经存在的单个文件或整个目录复制到构建目录。
- html-webpack-plugin:基本作用是生成html文件
- 单页应用可以生成一个html入口,多页应用可以配置多个html-webpack-plugin实例来生成多个页面入口
- 为html引入外部资源如script、link,将entry配置的相关入口chunk以及mini-css-extract-plugin抽取的css文件插入到基于该插件设置的template文件生成的html文件里面,具体的方式是link插入到head中,script插入到head或body中。
- mini-css-extract-plugin:本插件会将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件
- webpack.HotModuleReplacementPlugin:模块热替换插件,除此之外还被称为 HMR。
- 该功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
注意:HMR 绝对不能被用在生产环境。
- webpack.DefinePlugin:创建一个在编译时可以配置的全局常量。这会对开发模式和生产模式的构建允许不同的行为非常有用。
- 因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。
- 通常,有两种方式来达到这个效果,使用'"production"', 或者使用 JSON.stringify('production')
- webpack-bundle-analyzer: 可以看到项目各模块的大小,可以按需优化.一个webpack的bundle文件分析工具,将bundle文件以可交互缩放的treemap的形式展示。
- SplitChunksPlugin:代码分割
webpack优化
优化效率工具
- progress-bar-webpack-plugin: 查看编译进度;
- speed-measure-webpack-plugin: 查看编译速度;
- webpack-bundle-analyzer: 打包体积分析。
优化开发体验
- 自动更新: webpack-dev-server
- 热更新: 使用 webpack 内置的 HMR 插件,更新 webpack-dev-server 配置。
module.export = {
devServer: {
contentBase: './dist',
hot: true, // 热更新
},
}
加快构建速度
缓存:
- cache: 通过配置webpack持久化缓存cache: filesystem, 来缓存生成的webpack模块和chunk,改善构建速度。引入缓存后,首次构建时间会增加15%,二次构建时间减少90%
module.exports = {
cache: {
type: 'filesystem', // 使用文件缓存
},
}
- DLL: 配置麻烦且webpack5 开箱即用的持久缓存是比 dll 更优的解决方案
- cache-loader: 不需要引入了,上面的 cache 已经帮助我们缓存了
- 减少 loader、plugins: 每个的 loader、plugin 都有其启动时间。尽量少地使用工具,将非必须的 loader、plugins 删除。
- 指定include: loader 指定 include,减少 loader 应用范围,仅应用于最少数量的必要模块。
- 管理资源: 使用 webpack 资源模块 (asset module) 代替旧的 assets loader(如 file-loader/url-loader/raw-loader 等),减少 loader 配置数量。
- 优化 resolve 配置:
- alias: alias 可以创建 import 或 require 的别名,用来简化模块引入
- extensions: extensions 表示需要解析的文件类型列表。根据项目中的文件类型,定义 extensions,以覆盖 webpack 默认的 extensions,加快解析速度。由于 webpack 的解析顺序是从左到右,因此要将使用频率高的文件类型放在左侧,如下我将 tsx 放在最左侧。
module.exports = {
resolve: {
extensions: ['.tsx', '.js'], // 因为我的项目只有这两种类型的文件,如果有其他类型,需要添加进去。
}
}
- noParse: 如果一些第三方模块没有AMD、CommonJS规范版本,可以使用noParse来识别这个模块,这样webpack引入这些模块,但是不会进行转化和编译,从而提升webpack的构建性能;例如:
jquery、lodash
module: {
noParse: /jquery|lodash/
}
- 多进程:
- thread-loader: 通过 thread-loader 将耗时的 loader 放在一个独立的 worker 池中运行,加快 loader 构建速度。
- happypack: 弃用
减小打包体积
- 代码压缩:
- JS 压缩: webpack5 自带最新的 terser-webpack-plugin,无需手动安装。
- CSS 压缩: css-minimizer-webpack-plugin压缩 CSS 文件。
- 代码分离:
- 抽离重复代码: SplitChunksPlugin 插件开箱即用,可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。
- CSS 文件分离: mini-css-extract-plugin 插件将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
- Tree Shaking(摇树):
- webpack5 sideEffects: 通过 package.json 的 "sideEffects" 属性,来实现这种方式。
{
"name": "your-project",
"sideEffects": false
}
- CDN: 将一部分大的静态资源手动上传至 CDN,并修改本地引入路径。将大的静态资源上传至 CDN: 字体:压缩并上传至 CDN; 图片:压缩并上传至 CDN。
加快加载速度
按需加载: webpack 提供的 import() 语法 动态导入 功能进行代码分离,通过按需加载,大大提升网页加载速度。
<button onClick={() => import('lodash')}>加载lodash</button>