一:Loader
概念:Loader
就是将Webpack
不认识的内容转化为认识的内容
原因:webpack
默认支持处理JS
与 JSON
文件,其他类型都处理不了,这里必须借助Loader
来对不同类型的文件的进行处理。
举例一下我们常用的loader
-
file-loader
:把文件输出到一个文件夹中,在代码中通过相对 URL 去引用输出的文件 (处理图片和字体)- 在webpack5,内置了资源处理模块,可以不用装
-
url-loader
:与 file-loader 类似,区别是用户可以设置一个阈值,大于阈值会交给 file-loader 处理,小于阈值时返回文件 base64 形式编码 (处理图片和字体)- 在webpack5,内置了资源处理模块,可以不用装
-
svg-inline-loader
:将压缩后的 SVG 内容注入代码中 -
image-loader
:加载并且压缩图片文件 -
babel-loader
:把 ES6 转换成 ES5 -
sass-loader
:将SCSS/SASS代码转换成CSS -
css-loader
:加载 CSS,支持模块化、压缩、文件导入等特性 -
style-loader
:把 CSS 代码注入到 JavaScript 中,通过 DOM 操作去加载 CSSstyle-loader
就是将通过css-loader
处理好的 css 通过 style 标签的形式添加到页面上
-
eslint-loader
:通过 ESLint 检查 JavaScript 代码 -
vue-loader
:加载 Vue.js 单文件组件 -
i18n-loader
: 国际化 -
cache-loader
: 可以在一些性能开销较大的 Loader 之前添加,目的是将结果缓存到磁盘里 -
thread-loader
: 多线程打包,提高构建、打包速度。 -
postcss-loader
:自动添加 CSS3 部分属性的浏览器前缀。- 比如:
transform: translateX(-50%);
- 解析成:
-webkit-translateX(-50%); transform: translateX(-50%);
- 比如:
二:插件(plugin)
概念:Loader
用于转换特定类型的文件不同,插件(Plugin)可以贯穿 Webpack 打包的生命周期,执行不同的任务
举例一下我们常用的plugin
-
happyPack
:多线程打包,提高构建、打包速度。HappyPack
对file-loader、url-loader
支持的不友好,所以不建议对该loader
使用。webpack4
中官方文档的极力推荐thread-loader
,并且HappyPack
将不再被维护,基本废了。
-
webpack-parallel-uglify-plugin
:多进程执行代码压缩,提升构建速度。- 用于生产环境
-
clean-webpack-plugin
: 目录清理- 每次打包的时候,打包目录都会遗留上次打包的文件,为了保持打包目录的纯净,我们需要在打包前将打包目录清空
-
speed-measure-webpack-plugin
: 简称 SMP;可以看到每个 Loader 和 Plugin 执行耗时 (整个打包耗时、每个 Plugin 和 Loader 耗时) -
webpack-bundle-analyzer
: 可视化 Webpack 输出文件的体积 (业务组件、依赖第三方模块) -
mini-css-extract-plugin
:分离样式文件- 一般情况下我们都是使用
style-loader
将样式通过style
方式添加到页面上,但是我们有时候希望通过css文件的形式引入到页面上
。这时候就可以使用这个插件。
- 一般情况下我们都是使用
-
hard-source-webpack-plugin
:模块提供了中间缓存,重复构建时间大约可以减少 80%;- webpack5 中已经内置了模块缓存,不需要再使用此插件
-
purgecss-webpack-plugin
:单独提取 CSS 并清除用不到的 CSS
三:总结一下loader 和 plugin 的区别;
Loader
本质就是一个函数,对接受的内容进行转换,然后返回转换后的结构。所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作。
Plugin
就是插件,用于拓展webpack
的功。在 Webpack
运行的生命周期中会广播出许多事件,Plugin
可以监听这些事件,在合适的时机通过 Webpack
提供的 API
改变输出结果。
四:本地开发和部署线上环境区分
本地环境
- 需要更快的构建速度
- 需要打印 debug 信息
- 需要 live reload 或 hot reload 功能
生产环境
- 需要更小的包体积,代码压缩+tree-shaking
- 需要进行代码分割
- 需要压缩图片体积
针对不同的需求,要做的就是做好环境的区分
安装:
npm install cross-env -D
配置:
"scripts": {
"dev": "cross-env NODE_ENV=dev webpack serve --mode development",
"test": "cross-env NODE_ENV=test webpack --mode production",
"build": "cross-env NODE_ENV=prod webpack --mode production"
},
五:SourceMap
为什么要使用sourceMap :
线上的代码多是压缩后的,如果线上有报错却只能调试那个代码多半是个噩梦。因此我们需要有一个桥梁帮助我们搭建起源代码及压缩后代码的联系,source map
就是起了这个作用。
一般来说 source map
的应用都是在监控系统中,开发者构建完应用后,通过插件将源代码及 source map 上传至平台中。一旦客户端上报错误后,我们就可以通过soure map
来还原源代码的报错位置(具体 API 看文档即可),方便开发者快速定位线上问题。
总结:想要调试源码就需要 soucre map。
监控系统是怎么获取项目的source-map文件呢?
发者构建完应用后,通过插件将源代码及 source map 上传至平台中
六:构建优化
(一)重点:使用高版本
的 Webpack 和 Node.js
(二)压缩 CSS:
(三)压缩 JS:
- 在生成环境下打包
默认会开启
js 压缩,但是当我们手动配置optimization
选项之后,就不再默认对 js 进行压缩,需要我们手动去配置。 - webpack5 内置了terser-webpack-plugin 插件,所以我们不需重复安装,直接引用就可以了
(四)清除无用的 CSS:
- purgecss-webpack-plugin 会单独提取 CSS 并清除用不到的 CSS
(五)Tree-shaking:
Tree-shaking
作用是剔除没有使用的代码,以降低包的体积webpack
默认支持,需要在.bablerc
里面设置model:false
,即可在生产环境下默认开启
(六)Scope Hoisting
- 作用域提升,原理是将多个模块放在同一个作用域下,并重命名防止命名冲突,通过这种方式可以减少函数声明和内存开销。
- webpack 默认支持,在生产环境下默认开启
- 只支持 es6 代码
(七)图片压缩
- 使用基于 Node 库的
imagemin
(很多定制选项、可以处理多种图片格式) - 配置
image-webpack-loader
(八)缩小打包作用域:
-
exclude/include (确定 loader 规则范围)
-
resolve.modules 指明第三方模块的绝对路径 (减少不必要的查找)
-
resolve.mainFields 只采用 main 字段作为入口文件描述字段 (减少搜索步骤,需要考虑到所有运行时依赖的第三方模块的入口文件描述字段)
-
resolve.extensions 尽可能减少后缀尝试的可能性
-
noParse 对完全不需要解析的库进行忽略 (不去解析但仍会打包到 bundle 中,注意被忽略掉的文件里不应该包含 import、require、define 等模块化语句)
-
IgnorePlugin (完全排除模块)
-
合理使用alias
(九)提取页面公共资源:基础包分离:
- 使用
html-webpack-externals-plugin
,将基础包通过 CDN 引入,不打入bundle
中 - 使用
SplitChunksPlugin
进行(公共脚本、基础包、页面公共文件)分离(Webpack4内置
) ,替代了 CommonsChunkPlugin 插件
(十)充分利用缓存提升二次构建速度:
- babel-loader 开启缓存
- terser-webpack-plugin 开启缓存
- 使用 cache-loader 或者 hard-source-webpack-plugin
七:优化运行时体验
运行时优化的核心就是提升首屏的加载速度,主要的方式就是:降低首屏加载文件体积,首屏不需要的文件进行预加载或者按需加载
(一):入口点分割
- 配置多个打包入口,多页打包
(二):splitChunks 分包配置
optimization.splitChunks 是基于 SplitChunksPlugin 插件实现的。
默认情况下,它只会影响到按需加载的 chunks,因为修改 initial chunks 会影响到项目的 HTML 文件中的脚本标签。
webpack 将根据以下条件自动拆分 chunks:
- 新的 chunk 可以被共享,或者模块来自于
node_modules
文件夹 - 新的 chunk 体积大于 20kb(在进行 min+gz 之前的体积)
- 当按需加载 chunks 时,并行请求的最大数量小于或等于 30
- 当加载初始化页面时,并发请求的最大数量小于或等于 30
(三):代码懒加载
首屏加载不太需要的一些资源,我们可以通过懒加载的方式去实现
(四):prefetch 与 preload
当我们使用异步加载的方式引入图片的描述或许是没什么问题的,但是如果需要异步加载的文件比较大时,在点击的时候去加载也会影响到我们的体验,这个时候我们就可以考虑使用 prefetch 来进行预拉取
- prefetch (预获取):浏览器空闲的时候进行资源的拉取
- preload (预加载):提前加载后面会用到的关键资源
八:模块打包原理知道吗?
Webpack 实际上为每个模块创造了一个可以导出和导入的环境,本质上并没有修改 代码的执行逻辑,代码执行顺序与模块加载顺序也完全一致。
九:说一下 Webpack 的热更新原理吧
HMR的核心就是客户端从服务端拉去更新后的文件,准确的说是 chunk diff (chunk 需要更新的部分),实际上 WDS 与浏览器之间维护了一个 Websocket
,当本地资源发生变化时,WDS 会向浏览器推送更新,并带上构建时的 hash,让客户端与上一次资源进行对比。客户端对比出差异后会向 WDS 发起 Ajax
请求来获取更改内容(文件列表、hash),这样客户端就可以再借助这些信息继续向 WDS 发起 jsonp
请求获取该chunk的增量更新。
总结:其实就是有个中间人进行监听,如果代码发生变化就给浏览器发送更新通知。
十:Bundle 、 Chunk 、Module 的认知
- “模块”
(module)
的概念大家都比较熟悉,如CommonJS 模块
、AMD
、ES6 Modules
模块 chunk
表示打包的时候产生得模块,由他来组成bundle
总结:
module
就是没有被编译之前的代码,通过webpack
的根据文件引用关系生成chunk
文件,webpack 处理好chunk
文件后,生成运行在浏览器中的代码bundle
。
十一:Webpack构建流程简单说一下
简单的说就是:
-
初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler
-
编译:从 Entry 出发,针对每个 Module 串行调用对应的 Loader 去翻译文件的内容,再找到该 Module 依赖的 Module,递归地进行编译处理
-
输出:将编译后的 Module 组合成 Chunk,将 Chunk 转换成文件,输出到文件系统中
参考文档:
- Webpack面试题
- 构建 webpack5.x 知识体系