03 【资源处理】
03 【资源处理】
在上一章,我们讲解了四种资源模块引入外部资源。除了资源模块,我们还可以通过 loader引入其他类型的文件。
什么是loader
webpack
只能理解 JavaScript
和JSON
文件,这是 webpack 开箱可用的自带能力。 loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供 应用程序使用,以及被添加到依赖图中。
在 webpack 的配置中,loader
有两个属性:
test
属性,识别出哪些文件会被转换。use
属性,定义出在进行转换时,应该使用哪个 loader。
1.HTML资源
在index.html
中是采用手动修改路径,然后才引入的,这样并不方便。我们想要实现的是webpack打包完成后自动帮助我们引入相关的资源。 于是就可以使用社区插件中的HtmlWebpackPlugin
插件。
自动引入资源
1、下载 html-webpack-plugin 插件
pnpm i html-webpack-plugin -D
2、在 webpack.config.js 文件中引入插件并调用
使用,当直接new HtmlWebpackPlugin()
的时候,会在dist文件夹下生成一个index.html
,但是它只有一些基础的配置。如果想要自行配置,则需要一个自己给定的模板index.html,然后增加一些配置,配置的参考文档:https://github.com/jantimon/html-webpack-plugin#options
该插件将为你生成一个 HTML5 文件, 在 body 中使用 script
标签引入你所有 webpack 生成的 bundle。 只需添加该插件到你的 webpack 配置中
不传参的情况:
new HTMLWebpackPlugin()
:会在配置的output文件夹创建一个空的html, 自动引入打包输出的所有资源,包括js, css...;设置参数template:复制设置的
'./src/index.html'
文件到配置的output文件夹,并自动引入打包输出的所有资源。
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
/*
html-webpack-plugin 没有任何配置会生成一个自动引入js文件的index.html
*/
plugins: [
new HtmlWebpackPlugin({
// 可以用于html模板的title标签内容
title: 'dselegent',
template: './index.html', // 打包生成的文件的模板
filename: 'app.html', // 打包生成的文件名称。默认为index.html
// 设置所有资源文件注入模板的位置。可以设置的值
// true|'head'|'body'|false,默认值为 true
inject: 'body',
压缩html代码 production环境使用
minify: {
// 移除空格
collapseWhitespace: true,
// 移除注释
removeComment: true
}
}),
],
};
模板index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body></body>
</html>
如果你有多个 webpack 入口,他们都会在已生成 HTML 文件中的 <script>
标签内引入。
如果在 webpack 的输出中有任何 CSS 资源(例如,使用 MiniCssExtractPlugin 提取的 CSS),那么这些资源也会在 HTML 文件 <head>
元素中的 <link>
标签内引入。
打包:
查看 app.html 内容:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>我是dselegent</title>
</head>
<body>
<script defer src="bundle.js"></script>
</body>
</html>
这次打包应用到了我们的模板文件 index.html , 并且生成了新的文件 app.html , 文 件里自动引用的 bundle.js 也从 迁移到了里。
Webpack 会在输出目录中新创建一个 HTML 文件,在原始的 HTML 文件中无需引入 JS 文件,通过 Webpack 编译后的 HTML 文件会自动引入。
官方说明:https://webpack.docschina.org/plugins/html-webpack-plugin/
配置选项:https://github.com/jantimon/html-webpack-plugin#options
2.样式资源
Webpack 本身是不能识别样式资源的,所以我们需要借助 Loader 来帮助 Webpack 解析样式资源
我们找 Loader 都应该去官方文档中找到对应的 Loader,然后使用
官方文档找不到的话,可以从社区 Github 中搜索查询
.css文件:style-loader, css-loader
;
css-loader
:将css文件转换成commonjs模块加载到js中,css代码被转换成了样式字符串。(转换后得到的commonjs模块可以理解为:用js给元素动态添加样式的那种代码);style-loader
:创建style标签
,将css-loader
生成的样式资源插入进去,添加到head中,使样式生效。
.less文件:style-loader, css-loader, less-loader
;
less-loader
:将less文件编译成css文件;- 注:要想
less-loader
生效,还需要下载less
(npm install less -D
)。
.scss/.sass文件:style-loader, css-loader, sass-loader
;
sass-loader
:将sass或scss文件编译成css文件;- 注:要想
sass-loader
生效,还需要下载node-sass
(npm install node-sass -D
)。
2.1 css
下载包
pnpm add css-loader style-loader -D
注意:需要下载两个 loader
功能介绍
css-loader
:负责将 Css 文件编译成 Webpack 能识别的模块style-loader
:会动态创建一个 Style 标签,里面放置 Webpack 中 Css 模块内容
此时样式就会以 Style 标签的形式在页面上生效
配置
module.exports = {
...
module: {
rules: [
{
// 用来匹配 .css 结尾的文件
test: /\.css$/i,
// 写法一:
// use 数组里面 Loader 执行顺序是从右到左
use: [
'style-loader', // 创建style标签,将'css-loader'整合到js中的样式字符串放到style标签中。
'css-loader' // 将css文件转换成commonjs模块加载到js中,css代码被转换成了样式字符串。
],
// 写法二:
use: [
{ loader: 'style-loader' },
{ loader: 'css-loader' }
]
}
]
}
}
模块 loader
可以链式调用。链中的每个 loader
都将对资源进行转换。链会逆序执 行。第一个 loader
将其结果(被转换后的资源)传递给下一个 loader
,依此类推。 最后,webpack
期望链中的最后的 loader
返回 JavaScript
。
应保证 loader 的先后顺序: 'style-loader'
在前,而 'css-loader'
在后。如果 不遵守此约定,webpack 可能会抛出错误。webpack
根据正则表达式,来确定应该 查找哪些文件,并将其提供给指定的 loader
。在这个示例中,所有以 .css
结尾的 文件,都将被提供给 style-loader
和css-loader
。
这使你可以在依赖于此样式的 js 文件中 import './style.css'
。现在,在此模块执行过程中,含有 CSS 字符串的 <style>
标签,将被插入到html
文件的<head>
中。
通过在项目中添加一个新的style.css
文件,并将其import
到我 们的 index.js
中:
07-manage-assets/src/style.css
hello {
color: #f9efd4;
}
在入口文件index.js
里导入.css
文件:
// 引入 Css 资源,Webpack才会对其打包
import './style.css'
document.body.classList.add('hello')
你应该看到页面背景颜色是浅黄色。要查看 webpack 做了什么,请检查页面(不要 查看页面源代码,它不会显示结果,因为<style>
标签是由 JavaScript 动态创建 的),并查看页面的 head 标签,包含 style
块元素,也就是我们在 index.js
中 import 的 css 文件
中的样式。
2.2 less&sass
下载样式处理解析器
pnpm i sass-loader sass -D
pnpm i less-loader less -D
sass-loader
:负责将 Sass 文件编译成 css 文件sass
:sass-loader
依赖sass
进行编译
在配置文件中添加解析器
module.exports = {
...
module: {
rules: [
{
test: /\.s[ac]ss$/i,
use: [
// 在 head 中创建 style 标签
'style-loader',
// 将 css 文件整合到 js 文件中
'css-loader',
// 编译 sass 文件为 css 文件
'sass-loader'
]
},
{
test: /\.less$/,
use: ["style-loader", "css-loader", "less-loader"],
},
]
}
}
在项目 src
目录下创建 style.less
文件:
@color: red;
body {
color: @color;
}
在 index.js
文件中导入 less
文件
import './style.less'
document.body.classList.add('world')
由预览的效果可见,页面的文字都添加了“红色”的样式。
不同的样式文件会新建一个
style
标签
2.3 抽离CSS
Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式,这样对于网站来说,会出现闪屏现象,用户体验不好,我们应该是单独的 Css 文件,通过 link 标签加载性能才好。
在多数情况下,我们也可以进行压缩CSS,以便在生产环境
中节省加载时间,同时还 可以将CSS文件抽离成一个单独的文件。实现这个功能,需要 mini-css-extractplugin
这个插件来帮忙。安装插件:
下载包
pnpm i mini-css-extract-plugin -D
本插件会将取代style-loader
, 将 CSS 提取到单独的文件中,为每个包含 CSS 的 JS 文件创建一个 CSS 文件,并且支持 CSS 和 SourceMaps 的按需加载。
本插件基于 webpack v5
的新特性构建,并且需要 webpack 5 才能正常工作。
之后将 loader
与plugin
添加到你的 webpack
配置文件中:
配置
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
...
module: {
rules: [
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
// 用这个loader取代style-loader,因为想将css单独输出为一个文件
use: [MiniCssExtractPlugin.loader, "css-loader"],
},
{
test: /\.less$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "less-loader"],
},
{
test: /\.s[ac]ss$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"],
},
{
test: /\.styl$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "stylus-loader"],
},
]
},
plugins: [
// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和目录
filename: "static/css/main.css",
}),
]
}
⚠️ 注意,如果你从 webpack 入口处导入 CSS 或者在 初始 chunk 中引入 style,
mini-css-extract-plugin
则不会将这些 CSS 加载到页面中。请使用html-webpack-plugin
自动生成link
标签或者在创建index.html
文件时使用link
标签。
查看打包完成后的目录和文件:
07-manage-assets/dist/app.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initialscale=1.0">
<title>dselegent</title>
<link href="styles/c8d1b95f617a81aa500c.css"
rel="stylesheet"></head>
<body>
<script defer src="bundle.js"></script></body>
</html>
如果是生成模式,将自动压缩css文件,无需额外配置。
官方文档:https://webpack.docschina.org/plugins/mini-css-extract-plugin
2.4 压缩css(生产模式)
下载包
pnpm i css-minimizer-webpack-plugin -D
在配置文件中进行配置
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin")
module.exports = {
...
optimization: {
minimizer: [
// 使用插件优化 css 代码
new CssMinimizerPlugin()
],
},
// 模式
mode: 'production'
}
压缩 CSS 代码,仅在生产模式下有效
官方文档:https://webpack.docschina.org/plugins/css-minimizer-webpack-plugin/
3.图片资源
过去在 Webpack4 时,我们处理图片资源通过 file-loader
和 url-loader
进行处理
现在 Webpack5 已经将两个 Loader 功能内置到 Webpack 里了,我们只需要简单配置即可处理图片资源
配置
const path = require("path");
module.exports = {
...
module: {
rules: [
{
test: /\.(png|jpe?g|gif|webp)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024 // 小于10kb的图片会被base64处理
}
}
}
],
},
plugins: [],
mode: "development",
};
对图片资源进行优化
将小于某个大小的图片转化成 data URI 形式(Base64 格式)
- 优点:减少请求数量
- 缺点:体积变得更大
此时输出的图片文件就只有两张,有一张图片以 data URI 形式内置到 js 中了 (注意:需要将上次打包生成的文件清空,再重新打包才有效果)
添加图片资源
- src/images/1.jpeg
- src/images/2.png
- src/images/3.gif
使用图片资源
- src/less/index.less
block-bg {
background-image: url(./assets/webpack-logo.svg) ;
}
07-manage-assets/src/index.js
import './style.less'
const block = document.createElement('div')
block.style.cssText = 'width: 200px; height: 200px; background: aliceblue'
block.classList.add('block-bg')
block.textContent = exampleTxt
document.body.appendChild(block)
启动服务,打开浏览器:
我们看到,通过样式把背景图片加到了页面中。
4.字体资源
同样可以使用webpack内置的asset module模块来加载引入字体文件。 这里我使用阿里矢量图标库的字体图标来演示。
下载字体图标文件
- 打开阿里巴巴矢量图标库open in new window
- 选择想要的图标添加到购物车,统一下载到本地
解压之后,是这么一大坨东西,而我要修改的其实仅仅是这个文件:
具体的使用方法可以查看这个地址:https://www.iconfont.cn/help/detail?spm=a313x.7781069.1998910419.d8cf4382a&helptype=code
添加字体图标资源
src/fonts/iconfont.ttf
src/fonts/iconfont.woff
src/fonts/iconfont.woff2
src/css/iconfont.css
- 注意字体文件路径需要修改
src/index.js
import "./css/iconfont.css";
const span = document.createElement('span')
span.classList.add('testIcon')
span.innerHTML=''//这个是下载时对应图标的unicode
document.body.appendChild(span)
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>webpack5</title>
</head>
<body>
<!-- 使用字体图标 -->
<i class="iconfont icon-arrow-down"></i>
<i class="iconfont icon-ashbin"></i>
<i class="iconfont icon-browse"></i>
<script src="../dist/static/js/main.js"></script>
</body>
</html>
配置
const path = require("path");
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
clean: true, // 自动将上次打包目录资源清空
},
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: "asset/resource",
generator: {
filename: "static/font/[hash:8][ext][query]",
},
},
],
},
plugins: [],
mode: "development",
};
运行指令
npx webpack
5.数据资源
此外,可以加载的有用资源还有数据,如 JSON
文件,CSV
、TSV
和 XML
。类似于 NodeJS,JSON 支持实际上是内置的,也就是说 import Data from './data.json'
默认将正常运行。
要导入 CSV
、TSV
和 XML
,你可以使用 csv-loader
和 xml-loader
。让我们处理加载这三类文件:
安装包
pnpm install --save-dev csv-loader xml-loader
添加配置:
module: {
rules: [
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
]
}
现在,你可以 import 这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种,所 导入的 Data 变量,将包含可直接使用的已解析 JSON:
创建两个文件:
07-manage-assets/src/assets/data.xml
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Mary</to>
<from>John</from>
<heading>Reminder</heading>
<body>Call Cindy on Tuesday</body>
</note>
07-manage-assets/src/assets/data.csv
to,from,heading,body
Mary,John,Reminder,Call Cindy on Tuesday
Zoe,Bill,Reminder,Buy orange juice
Autumn,Lindsey,Letter,I miss you
在入口文件里加载数据模块,并在控制台上打印导入内容:
07-manage-assets/src/index.js
import Data from './assets/data.xml'
import Notes from './assets/data.csv'
console.log(Data)
console.log(Notes)
由此可见, data.xml 文件转化为一个JS对象, data.cvs 转化为一个数组。
6.自定义 JSON 模块 parser
通过使用 自定义 parser
替代特定的 webpack loader,可以将任何 toml
、 yaml
或 json5
文件作为 JSON 模块导入。
假设你在 src 文件夹下有一个 data.toml 、一个 data.yaml 以及一个 data.json5 文件
下载包
pnpm install toml yaml json5 --save-dev
配置
const toml = require('toml')
const yaml = require('yaml')
const json5 = require('json5')
module.exports = {
devServer: {
static: './dist'
},
module: {
rules: [
{
test: /\.toml$/,
type: 'json',
parser: {
parse: toml.parse
}
},
{
test: /\.yaml$/,
type: 'json',
parser: {
parse: yaml.parse
}
},
{
test: /\.json5$/,
type: 'json',
parser: {
parse: json5.parse
}
}
]
},
}
在主文件中引入模块,并打印内容:
import toml from './data.toml';
import yaml from './data.yaml';
import json from './data.json5';
console.log(toml.title); // output `TOML Example`
console.log(toml.owner.name); // output `Tom Preston-Werner`
console.log(yaml.title); // output `YAML Example`
console.log(yaml.owner.name); // output `Tom Preston-Werner`
console.log(json.title); // output `JSON5 Example`
console.log(json.owner.name); // output `Tom Preston-Werner`
现在, toml 、 yaml 和 json5 几个类型的文件都正常输出了结果。
7.js规范化
可组装的 JavaScript 和 JSX 检查工具。
这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能
我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查
7.1 配置文件
配置文件由很多种写法:
.eslintrc.*
:新建文件,位于项目根目录.eslintrc
.eslintrc.js
.eslintrc.json
区别在于配置格式不一样
package.json
中eslintConfig
:不需要创建文件,在原有文件基础上写
ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可
7.2 具体配置
我们以 .eslintrc.js
配置文件为例:
module.exports = {
"env": { // 指定脚本的运行环境。每种环境都有一组特定的预定义全局变量。
"browser": true, // 运行在浏览器
"es2021": true // 支持es2021
},
"extends": [ // 使用的外部代码格式化配置文件
"airbnb-base"
],
"parserOptions": {
"ecmaVersion": 12, // ecmaVersion 用来指定支持的 ECMAScript 版本 。默认为 5,即仅支持es5
"sourceType": "module"
},
globals: {}, // 脚本在执行期间访问的额外的全局变量
"rules": { // 启用的规则及其各自的错误级别。0(off) 1(warning) 2(error)
}
};
- parserOptions 解析选项
parserOptions: {
ecmaVersion: 6, // ES 语法版本
sourceType: "module", // ES 模块化
ecmaFeatures: { // ES 其他特性
jsx: true // 如果是 React 项目,就需要开启 jsx 语法
}
}
rules 具体规则
"off"
或0
- 关闭规则"warn"
或1
- 开启规则,使用警告级别的错误:warn
(不会导致程序退出)"error"
或2
- 开启规则,使用错误级别的错误:error
(当被触发的时候,程序会退出)
rules: { semi: "error", // 禁止使用分号 'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告 'default-case': [ 'warn', // 要求 switch 语句中有 default 分支,否则警告 { commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了 ], eqeqeq: [ 'warn', // 强制使用 === 和 !==,否则警告 'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告 ], }
更多规则详见:规则文档open in new window
extends 继承
开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。
现有以下较为有名的规则:
- Eslint 官方的规则open in new window:
eslint:recommended
- Vue Cli 官方的规则open in new window:
plugin:vue/essential
- React Cli 官方的规则open in new window:
react-app
// 例如在React项目中,我们可以这样写配置
module.exports = {
extends: ["react-app"],
rules: {
// 我们的规则会覆盖掉react-app的规则
// 所以想要修改规则直接改就是了
eqeqeq: ["warn", "smart"],
},
};
7.3 在 Webpack 中使用
下载包
pnpm i eslint-webpack-plugin eslint -D
定义 Eslint 配置文件
.eslintrc.js
module.exports = { // 继承 Eslint 规则 extends: ["eslint:recommended"], env: { node: true, // 启用node中全局变量 browser: true, // 启用浏览器中全局变量 }, parserOptions: { ecmaVersion: 6, sourceType: "module", }, rules: { "no-var": 2, // 不能使用 var 定义变量 }, };
也可以 npx eslint --init 生成
Airbnb: https://github.com/airbnb/javascript Standard: https://github.com/standard/standard Google: https://github.com/google/eslint-config-google XO: https://github.com/xojs/eslint-config-xo
修改 js 文件代码
index.js
import 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"; var result1 = count(2, 1); console.log(result1); var result2 = sum(1, 2, 3, 4); console.log(result2);
配置
执行
npx eslint ./src/
,可以看到会输出一些error或者waring我们期望
eslint
能够实时提示报错而不必等待执行命令。 这个功能可以通过给自己的IDE(代码编辑器)安装对应的eslint插件来实现。webpack.config.js
module.exports = { plugins: [ new ESLintWebpackPlugin({ // 指定检查文件的根目录 context: path.resolve(__dirname, "src"), }), ], };
运行指令
npx webpack
7.4 VSCode Eslint 插件
打开 VSCode,下载 Eslint 插件,即可不用编译就能看到错误,可以提前解决
但是此时就会对项目所有文件默认进行 Eslint 检查了,我们 dist 目录下的打包后文件就会报错。但是我们只需要检查 src 下面的文件,不需要检查 dist 下面的文件。
所以可以使用 Eslint 忽略文件解决。在项目根目录新建下面文件:
.eslintignore
build/*.js
src/assets
public
// 忽略dist目录下所有文件
dist
vscode设置eslint保存文件时自动修复eslint错误_五虎战画戟的博客-CSDN博客
启动 vscode 打开项目,
windows: ctrl + shift + p
打开 搜索面板
MacBook: command + shift + p
打开 搜索面板
输入 settings.json
,选择 .vscode
下的 settings.json
加入以下代码:
{
// #每次保存的时候将代码按eslint格式进行修复
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
这个规则是在保存时按照你的项目中 eslint 配置 fix
注意你~/Library/Application Support/Code/User/settings.json 中的规则尽量不要与项目中的规则产生冲突
7.5 overlay
devServer.client.overlay
控制当出现编译错误或警告时,在浏览器中是否显示全屏覆盖。默认显示boolean = true
object: { errors boolean = true, warnings boolean = true }
关闭浏览器全屏覆盖提示
webpack.config.js
module.exports = {
//...
devServer: {
client: {
overlay: false,
},
},
};
如果你只想显示错误信息: webpack.config.js
module.exports = {
//...
devServer: {
client: {
overlay: {
errors: true,
warnings: false,
},
},
},
};
8.js兼容处理
8.1 什么是JS兼容性处理?
其实就是把ES6及以上(后边简写为ES6+)新增的语法、API处理成ES5及以下的版本,解决某些浏览器(ie)上的兼容性报错问题。
8.2 babel的介绍
babel的出现,就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言。
由于语言的转换工作灵活多样,babel的做法和postcss、webpack差不多,它本身仅提供一些分析功能,真正的转换需要依托于插件完成。
8.3 babel的安装
babel可以和构建工具联合使用,也可以独立使用
如果要独立的使用babel,需要安装下面两个库:
- babel-loader: 在 webpack 里应用 babel 解析 ES6 的桥梁
- @babel/core: babel 核心模块
- @babel/preset-env: 简单理解:就是一组 Babel 插件, 扩展 Babel 功能
@babel/preset-env
: 一个智能预设,允许您使用最新的 JavaScript。@babel/preset-react
:一个用来编译 React jsx 语法的预设@babel/preset-typescript
:一个用来编译 TypeScript 语法的预设
pnpm i -D babel-loader @babel/core @babel/preset-env
regeneratorRuntime
这里有时还会有一些特殊的语法 例如 async await
等,这样的语法就不好转换,因此还需要一个新的 库来处理,就需要 regenerator-runtime
来进行处理转换。
pnpm i @babel/runtime @babel/plugin-transform-runtime -D
8.4 webpack配置
配置文件
配置文件由很多种写法:
babel.config.*
:新建文件,位于项目根目录
babel.config.js
babel.config.json
.babelrc.*
:新建文件,位于项目根目录
.babelrc
.babelrc.js
.babelrc.json
package.json
中babel
:不需要创建文件,在原有文件基础上写
Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可
babel.config.js
module.exports = {
// 预设
presets: ['@babel/preset-env'],
plugins: [['@babel/plugin-transform-runtime']],
};
webpack.config.js
module: {
/*
js 兼容性处理:babel-loader @babel/core @babel/preset-env
1. 基本js兼容性处理 --> @babel/preset-env
问题:只能转换基本语法,如Promise不能转换
2. 全部js兼容性处理 --> @babel/polyfill 使用时直接在js文件里面引入:import '@babel/polyfill'
问题:我只要解决部分兼容性问题,但是将所有兼容性代码全部引入,体积太大了
3. 需要做兼容性处理的就做:按需加载 --> core-js
*/
rules: [{
test: /\.js$/, // 处理后缀名为js的文件
exclude: /node_modules/, //排除node_modules下的文件
// 加载 loader
loader: 'babel-loader',
}]
}
然后运行就可以了,会把对应的es6语法转换成es5语法。
9.代码分离
我们的 js 都是打包输出到一个文件中的,当内容越来越多的时候,会导致单个文件体积十分巨大,所以我们就需要对代码进行分割,将一个巨大的文件,分割成多个中小型文件,然后可以按需加载或并行加载这些文件
代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的bundle,以及控制资源加载优先级,如果使用合理,会极大缩减加载时间。
常用的代码分离方法有三种:
- 入口起点:使用 entry 配置手动地分离代码。
- 动态导入:通过模块的内联函数调用来分离代码
- 防止重复:使用 SplitChunksPlugin 去重和分离 chunk。
9.1 入口起点( 不推荐 )
使用 entry 配置手动分离代码,如果多个入口共享的文件,会分别在每个包里重复打包。
module.exports = {
entry: {
main: './assets/js/main.js',
other: './assets/js/add.js'
},
output: {
filename: 'js/[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
}
这种方式的确存在一些隐患:
- 如果入口
chunk
之间包含一些重复的模块,那些重复模块都会被引入到各个bundle
中。 - 这种方法不够灵活,并且不能动态地将核心应用程序逻辑中的代码拆分出来。
9.2 防止重复
配置 entry 提取公用依赖
webpack.config.js
module.exports = {
entry: {
main: {
import: './assets/js/main.js',// 启动时需加载的模块
dependOn: 'shared'// 当前入口所依赖的入口
},
other: {
import: './assets/js/add.js',
dependOn: 'shared'
},
shared: 'lodash' // 当上面两个模块有lodash这个模块时,就提取出来并命名为shared chunk
},
output: {
filename: 'js/[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
}
执行webpack
命令,可以看到打包结果
已经提取出来shared.bundle.js,
即为提取打包了lodash公用模块
index.bundle.js other.bundle.js
体积也变小
查看dist/index.html
可以看到三个文件都被加载了
SplitChunksPlugin
SplitChunksPlugin
插件可以能自动的帮助我们做公共模块的抽离。让我们使用这个插件,将之前的示例中重 复的 lodash 模块去除:
webpack 中 splitChunks 的默认配置
module.exports = {
entry: {
main: './assets/js/main.js',
other: './assets/js/add.js'
},
output: {
filename: 'js/[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
splitChunks: {
chunks: 'all', // 动态导入的模块其依赖会根据规则分离
minSize: 30000, // 文件体积要大于 30k
minChunks: 1, // 文件至少被 1 个chunk 引用
maxAsyncRequests: 5, // 动态导入文件最大并发请求数为 5
maxInitialRequests: 3, // 入口文件最大并发请求数为 3
automaticNameDelimiter: '~', // 文件名中的分隔符
name: true, // 自动命名
cacheGroups: {
vendors: { // 分离第三方库
test: /[\\/]node_modules[\\/]/,
priority: -10 // 权重
},
default: { // 分离公共的文件
minChunks: 2, // 文件至少被 2 个 chunk 引用
priority: -20,
reuseExistingChunk: true // 复用存在的 chunk
}
}
}
}
}
chunks
该参数有四种取值
async
:动态导入的文件其静态依赖会根据规则分离initial
:入口文件的静态依赖会根据规则分离all
:所有的都会根据规则分离chunk => Boolean
:返回 true 表示根据规则分离,false 则不分离
更多配置查看: SplitChunksPlugin
可以通过 import 方法对文件名进行自定义
import(/* webpackChunkName: '自定义文件名' */'文件路径')
9.3 动态导入
当涉及到动态代码拆分时,webpack 提供了两个类似的技术。
- 第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案 的
import()
语法 来实现动态导入。 - 第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure 。
这里让我们尝试使用第一种方式,使用import()
来进行动态导入
我们分别在 index.html、other.js、index.js
中添加如下代码
// index.html body 中添加
<button id="btn">点我</button>
// other.js
console.log("我被加载了")
// index.js
let btn = document.querySelector("#btn");
btn.addEventListener("click", () => {
// import() 返回的是一个 promise,在 then() 方法中执行导入后的操作,也可以使用 async/await
import("./other").then(res => {
console.log(res);
});
});
可以看到,打包文件多生成了一个叫 0.js
的文件,并且 html 中并没有引入该文件,点击按钮再看看会发生什么
生成了一个 script 标签,并加载了 0.js
文件,这就是动态导入。对于这个文件名称,不是很直观,我们使用魔法注释修改一下
import(/* webpackChunkName: "other" */ "./other").then(res => { console.log(res); });
最终打包生成的文件名就叫 other.js,如果希望加上 hash 值,可以在配置文件里添加一个参数
output: {
filename: "[name].[chunkhash:10].js", // 入口文件打包生成的文件名
chunkFilename: "[name].[chunkhash:10].js" // 动态模块打包生成的文件名,name 默认为数字,如果使用了魔法注释则为魔法注释的名字
}
9.4 懒加载
懒加载或者按需加载,是一种很好的优化网页或应用的方式。这种方式实际上是先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。
math.js
export function add (x, y) {
return x + y
}
export function reduce (x, y) {
return x - y
}
src/index.js
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
// 魔法注释 webpackChunkName 修改懒加载打包文件名
// 即使不使用 webpackChunkName,webpack 5 也会自动在 development 模式下分配有意义的文件名。
import(/* webpackChunkName: 'math' */ './math.js').then(({ add }) => {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
效果:
点击按钮后才加载math.bundle.js并执行了函数打印输出结果
第一次加载完页面, math.bundle.js 不会加载,当点击按钮后,才加载 math.bundle.js 文件。
问题:
这样做可能会让用户的交互长时间没有响应的。原因就是待到交互时才进行模块的加载,可能时间会比较长。由此,我们引入prefetch,即预取。
9.5 预获取、预加载
Webpack v4.6.0+ 增加了对预获取和预加载的支持。
在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 “resource hint(资源提示)”,来告知浏览器:
- prefetch(预获取):将来某些导航下可能需要的资源(当页面所有内容都加载完毕后,在网络空闲的时候,加载资源)
- preload(预加载):当前导航下可能需要资源
prefetch
src/index.js
const button = document.createElement('button')
button.textContent = '点击执行加法运算'
button.addEventListener('click', () => {
// webpackPrefetch: true 在动态引入时开始预获取
import(/* webpackChunkName: 'math', webpackPrefetch: true */ './math.js').then(({ add }) => {
console.log(add(4, 5))
})
})
document.body.appendChild(button)
添加第二句魔法注释:webpackPrefetch: true
告诉 webpack 执行预获取。这会生成 并追加到页面头部,指示着浏览器在闲置时间预取 math.js 文件。
效果 可以看到math.bundle.js已经预先获取了
加载完成之后,浏览器又会去自动加载
我们发现,在还没有点击按钮时, math.bundle.js
就已经下载下来了。同时,在 app.html 里webpack自动添加了一句:
点击按钮,会立即调用已经下载好的 math.bundle.js
文件中的 add 方法:
preload
preload和prefetch在用法上相差不大,效果上的差别如下(引自官方文档):
- preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
- preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
- preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。 浏览器支持程度不同。
10.缓存
以上,我们使用 webpack 来打包我们的模块化后的应用程序,webpack 会生成一个可部署的 /dist
目录,然后把打包后的内容放置在此目录中。只要 /dist
目录中的内容部署到服务器上,客户端(通常是浏览器)就能够访问网站此服务器的网站及其资源。而最后一步获取资源是比较耗费时间的,这就是为什么浏览器使用一种名为 缓存 的技术。可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。
此指南的重点在于通过必要的配置,以确保 webpack 编译生成的文件能够被客户端缓存,而在文件内容变化后,能够请求到新的文件。
10.1 输出文件的文件名
通过使用 output.filename
进行文件名替换,可以确保浏览器获取到修改后的文件。[hash]
替换可以用于在文件名中包含一个构建相关(build-specific)的 hash,但是更好的方式是使用 [chunkhash]
替换,在文件名中包含一个 chunk 相关(chunk-specific)的哈希。
让我们使用起步 中的示例,以及管理输出 中的 plugins
来作为项目的基础,所以我们不必手动处理维护 index.html
文件:
project
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
|- /node_modules
webpack.config.js
module.exports = {
//...
output: {
filename: 'scripts/[name].[contenthash].js',
//...
},
//...
}
执行打包编译:
可以看到,bundle 的名称是它内容(通过 hash)的映射。如果我们不做修改,然后再次运行构建,文件名会保持不变。
10.2 缓存第三方库
将第三方库(library)(例如 lodash
或 react
)提取到单独的 vendor
chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。 我们在 optimization.splitChunks 添加如下 cacheGroups
参数并构建:
module.exports = {
//...
optimization: {
//...
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
}
},
}
执行编译:
截止目前,我们已经把 JS 文件、样式文件及图片等资源文件分别放到了 scripts
、styles
、images
三个文件夹中。
11.js压缩
安装插件 terser-webpack-plugin
pnpm i terser-webpack-plugin -D
配置
const TerserWebpackPlugin = require("terser-webpack-plugin")
module.exports = {
...
optimization: {
minimizer: [
// 使用插件压缩 js 代码 (生产模式)
new TerserWebpackPlugin({
parallel: true, //多线程压缩
extractComments: false //不要注释-因为默认会对每个压缩的文件生成一个txt的注释文本。没必要
})
]
}
}
这个插件,当mode是production的时候,执行这个配置打包成js压缩的形式,而开发环境时,则是打包成原始的js。 插件的使用文档:https://webpack.docschina.org/plugins/terser-webpack-plugin/