webpack5
基础
基本配置
webpack 五个核心概念
- Entry
入口(Entry)指示 webpack 以哪个文件为入口起点开始打包,分析构建内部依赖图
- Output
输出(Output)指示 webpack 打包后的资源 bundles 输出到哪里去,以及如何命名
- Loader
Loader 让 webpack 能 够 去 处 理 那 些 非 JavaScript 文 件 (webpack 自 身 只 理 解 JavaScript)
- Plugins
插件(Plugins)可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩, 一直到重新定义环境中的变量等
- Mode
development
- 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 development。启用 NamedChunksPlugin 和 NamedModulesPlugin
- 特点:能让代码本地调试 运行的环境
production
- 会将 DefinePlugin 中 process.env.NODE_ENV 的值设置 为 production。启用FlagDependencyUsagePlugin, FlagIncludedChunksPlugin,ModuleConcatenationPlugin, NoEmitOnErrorsPlugin, OccurrenceOrderPlugin, SideEffectsFlagPlugin 和 TerserPlugin
- 能让代码优化上线 运行的环境
1 |
|
安装
1 |
|
编译
1 |
|
开发模式
- 就是写代码
处理样式资源
处理css资源
安装css-loader
和style-loader
1 |
|
使用
1 |
|
webpack.config.js
1 |
|
处理less资源
安装less
和less-loader
1 |
|
webpack.config.js
1 |
|
处理sass资源
安装sass
和sass-loader
1 |
|
webpack.config.js
1 |
|
处理图片资源
1 |
|
处理字体图标资源
1 |
|
处理其他资源
1 |
|
处理js资源
- eslint——代码格式,语法
- Babel——兼容性
eslint
配置文件
.eslintrc.js
1 |
|
.eslintignore
- 忽略打包文件
去官网看
使用
安装
1 |
|
webpack.config.js
1 |
|
Babel
安装
1 |
|
webpack.config.js
1 |
|
处理HTMl资源
安装
1 |
|
使用
1 |
|
开发服务器&自动化
自动化编译
安装
1 |
|
配置
1 |
|
使用
1 |
|
生产模式
- 主要对代码进行优化
区分开发模式和生产模式
创建config
文件夹,用来存放不同模式的配置文件
webpack.dev.js
开发模式的配置文件webpack.prod.js
生产模式的配置文件
使用
- 开发模式
1 |
|
- 生产模式
1 |
|
在package.json
配置快捷
1 |
|
后面直接 npm start
就是开发环境的了
npm run build
就是生产环境的了
Css处理
提取Css成单独文件
Css文件目前被打包到js文件中,当js文件加载时,会创建一个style标签来生成样式
这样对于网站来说,会出现闪屏现象
- 应该时单独的Css文件,通过link标签加载性能才好
安装
1 |
|
webpack.config.js
1 |
|
Css兼容性处理
安装
1 |
|
webpack.config.js
1 |
|
封装样式loader函数
Css压缩
安装
1 |
|
webpack.config.js
1 |
|
高级
提升开发体验
SourceMap
- SourceMap(源代码映射)是一个用来生成源代码与构建后代码一一映射的文件的方案
- 它会生成一个
xxx.map
文件,里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了,会通过xxx.map
文件,从构建后代码出错位置找到映射后源代码出错位置,从而让浏览器提示源代码文件出错位置,帮助我们更快找到错误根源
开发模式:cheap-module-source-map
- 优点:打包编译速度快,只包含行映射
- 缺点:没有列映射
1 |
|
生成模式:source-map
- 优点:包含行/列映射
- 缺点:打包编译速度更慢
1 |
|
提升打包构建速度
HotModuleReplacement 模块热替换
- webpack默认会将所有模块全部重新编译打包
- 在程序运行中,替换、添加或删除模块,而无需重新加载整个页面
OneOf
- 每个文件只能被其中一个loader配置处理
Include/Exclude
- 只能选择一种使用
- Include——只运行这个
- Exclude——不运行这个
Cache 缓存
每次打包时js文件都要经Eslint检查和Babel编译,速度比较慢
我们可以缓存之前的Eslint检查和Babel编译结果,这样第二次打包时速度就更快
1 |
|
Thead
对js文件处理主要是eslint、babel、Terser三个工具
多进程打包:开启电脑的多个进程同时干一件事
安装
1 |
|
webpack.config.js
1 |
|
1 |
|
减少代码体积
Tree Shaking
Babel
- Babel为编译的每个文件都插入了辅助代码,使代码体积过大
- 将这些辅助代码作为一个独立模块,为避免重复引入
安装
1 |
|
Image Minimizer
- 对图片进行压缩,减少图片体积
- 本地项目静态图片才需要进行压缩
安装
1 |
|
无损压缩
1 |
|
优化代码运行性能
Code Split
打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。
所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。
代码分割(Code Split)主要做了两件事:
分割文件:将打包生成的文件进行分割,生成多个 js 文件。
按需加载:需要哪个文件就加载哪个文件。
1、多入口
安装
1 |
|
webpack.config.js
1 |
|
- 配置了几个入口,至少输出几个 js 文件
2、提取重复代码
如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。
我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。
webpack.config.js
1 |
|
- 此时我们会发现生成 3 个 js 文件,其中有一个就是提取的公共模块
3、按需加载,动态导入
想要实现按需加载,动态导入模块。还需要额外配置:
- 修改文件
- main.js
1 |
|
- app.js
1 |
|
- public/index.html
1 |
|
- 运行指令
1 |
|
我们可以发现,一旦通过 import 动态导入语法导入模块,模块就被代码分割,同时也能按需加载了
4、单入口
开发时我们可能是单页面应用(SPA),只有一个入口(单入口)。那么我们需要这样配置:
1 |
|
5、更新配置
最终我们会使用单入口+代码分割+动态导入方式来进行配置。更新之前的配置文件。
1 |
|
6、给动态导入文件取名称
1、修改文件
- main.js
1 |
|
2、eslint 配置
- 下载包
1 |
|
- 配置
1 |
|
- 统一命名配置
1 |
|
- 运行指令
1 |
|
观察打包输出 js 文件名称
Preload / Prefetch
为什么
我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。
但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。
我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 Preload
或 Prefetch
技术
是什么
reload
:告诉浏览器立即加载资源。Prefetch
:告诉浏览器在空闲时才开始加载资源。
它们共同点:
- 都只会加载资源,并不执行。
- 都有缓存。
它们区别:
Preload
加载优先级高,Prefetch
加载优先级低。Preload
只能加载当前页面需要使用的资源,Prefetch
可以加载当前页面资源,也可以加载下一个页面需要使用的资源。
总结:
- 当前页面优先级高的资源用
Preload
加载。 - 下一个页面需要使用的资源用
Prefetch
加载。
它们的问题:兼容性较差。
- 我们可以去 Can I Useopen in new window 网站查询 API 的兼容性问题。
Preload
相对于Prefetch
兼容性好一点。
怎么用
1、下载包
1 |
|
2、配置 webpack.prod.js
1 |
|
Network Cache
为什么
将来开发时我们对静态资源会使用缓存来优化,这样浏览器第二次请求资源就能读取缓存了,速度很快。
但是这样的话就会有一个问题, 因为前后输出的文件名是一样的,都叫 main.js,一旦将来发布新版本,因为文件名没有变化导致浏览器会直接读取缓存,不会加载新资源,项目也就没法更新了。
所以我们从文件名入手,确保更新前后文件名不一样,这样就可以做缓存了
是什么
们都会生成一个唯一的 hash 值。
- fullhash(webpack4 是 hash)
每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。
- chunkhash
根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。
- contenthash
根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的
怎么用
1 |
|
- 问题:
当我们修改 math.js 文件再重新打包的时候,因为 contenthash 原因,math.js 文件 hash 值发生了变化(这是正常的)。
但是 main.js 文件的 hash 值也发生了变化,这会导致 main.js 的缓存失效。明明我们只修改 math.js, 为什么 main.js 也会变身变化呢?
- 原因:
- 更新前:math.xxx.js, main.js 引用的 math.xxx.js
- 更新后:math.yyy.js, main.js 引用的 math.yyy.js, 文件名发生了变化,间接导致 main.js 也发生了变化
- 解决:
将 hash 值单独保管在一个 runtime 文件中。
我们最终输出三个文件:main、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,main 不变。
runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。
1 |
|
Core-js
为什么
过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。
它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。
所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决
是什么
core-js
是专门用来做 ES6 以及以上 API 的 polyfill
。
polyfill
翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性
怎么用
1、修改 main.js
1 |
|
- 此时 Eslint 会对 Promise 报错。
2、修改配置文件
- 下载包
1 |
|
- .eslintrc.js
1 |
|
3、运行指令
1 |
|
此时观察打包输出的 js 文件,我们发现 Promise 语法并没有编译转换,所以我们需要使用 core-js
来进行 polyfill
。
4、使用core-js
- 下载包
1 |
|
- 手动全部引入
1 |
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这样引入会将所有兼容性代码全部引入,体积太大了。我们只想引入 promise 的 polyfill
。
- 手动按需引入
1 |
|
只引入打包 promise 的 polyfill
,打包体积更小。但是将来如果还想使用其他语法,我需要手动引入库很麻烦。
自动按需引入
- main.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./styl/index.styl";
const result1 = count(2, 1);
console.log(result1);
const result2 = sum(1, 2, 3, 4);
console.log(result2);
// 添加promise代码
const promise = Promise.resolve();
promise.then(() => {
console.log("hello promise");
});- babel.config.js
1
2
3
4
5
6
7
8
9
10module.exports = {
// 智能预设:能够编译ES6语法
presets: [
[
"@babel/preset-env",
// 按需加载core-js的polyfill
{ useBuiltIns: "usage", corejs: { version: "3", proposals: true } },
],
],
};
此时就会自动根据我们代码中使用的语法,来按需加载相应的 polyfill
了
PWA
为什么
开发 Web App 项目,项目一旦处于网络离线情况,就没法访问了。
我们希望给项目提供离线体验。
是什么
渐进式网络应用程序(progressive web application - PWA):是一种可以提供类似于 native app(原生应用程序) 体验的 Web App 的技术。
其中最重要的是,在 离线(offline) 时应用程序能够继续运行功能。
内部通过 Service Workers 技术实现的。
怎么用
1、下载包
1 |
|
2、修改配置文件
1 |
|
3、修改 main.js
1 |
|
4、运行指令
1 |
|
此时如果直接通过 VSCode 访问打包后页面,在浏览器控制台会发现 SW registration failed
。
因为我们打开的访问路径是:http://127.0.0.1:5500/dist/index.html
。此时页面会去请求 service-worker.js
文件,请求路径是:http://127.0.0.1:5500/service-worker.js
,这样找不到会 404。
实际 service-worker.js
文件路径是:http://127.0.0.1:5500/dist/service-worker.js
。
5、解决路径问题
- 下载包
1 |
|
serve 也是用来启动开发服务器来部署代码查看效果的。
- 运行指令
1 |
|
此时通过 serve 启动的服务器我们 service-worker 就能注册成功了。
总结
我们从 4 个角度对 webpack 和代码进行了优化:
- 提升开发体验
- 使用
Source Map
让开发或上线时代码报错能有更加准确的错误提示。
- 提升 webpack 提升打包构建速度
- 使用
HotModuleReplacement
让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。 - 使用
OneOf
让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。 - 使用
Include/Exclude
排除或只检测某些文件,处理的文件更少,速度更快。 - 使用
Cache
对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。 - 使用
Thead
多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
- 减少代码体积
- 使用
Tree Shaking
剔除了没有使用的多余代码,让代码体积更小。 - 使用
@babel/plugin-transform-runtime
插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。 - 使用
Image Minimizer
对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
- 优化代码运行性能
- 使用
Code Split
对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。 - 使用
Preload / Prefetch
对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。 - 使用
Network Cache
能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。 - 使用
Core-js
对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。 - 使用
PWA
能让代码离线也能访问,从而提升用户体验。
项目
Vue 脚手架
开发模式配置
- webpack.dev.js
下载包
1 |
|
webpack.config.js
1 |
|
开发模式配置
- webpack.dev.js
1 |
|
生产模式配置
- webpack.prod.js
1 |
|
其他配置
- package.json
1 |
|
- .eslintrc.js
1 |
|
- babel.config.js
1 |
|
合并开发和生产配置
- webpack.config.js
1 |
|
优化配置
1 |
|