优化 Webpack?肘,跟我进屋聊聊
前言
webpack 官网介绍了很多优化它的方法,虽然量大管饱,但不够体系化。因此我打算将常用的方法归纳起来,从优化开发体验,提升构建速度,减少构建体积,优化应用性能这四个维度逐一介绍 webpack 的优化。
我们承接基础篇的代码进行目录的改造(本篇内容未提出需要安装与配置的 loader 和 plugin 在上一篇文章中已经安装与配置好。若不清楚基础篇仓库内容的请移步查看上一篇文章,有助于理解本篇内容)。
优化开发体验
本章主要介绍开发者可以通过 webpack 的哪些配置,实现定向搜索,源代码追踪,模块热更新,TS 开发,代码校验以及省略文件后缀名等功能,提升自己在项目中的开发体验。
定向搜索 alias
我们在 src/index.js 中引入自己创建的组件,把它渲染出来以此讲解本案例。
import Header from './components/Header/index.js'
Header()配置 components/Header/index.js 的内容。
import '../../css/index.scss'
const Header = () => {
const body = document.body
const div = document.createElement("div")
div.setAttribute("class","cengfan")
div.innerHTML = "<h2>我来组成头部</h2>"
body.append(div)
}
export default Headersrc/css/index.scss 填写如下内容:
$baseCls:"cengfan";
.#{$baseCls} {
background: #4285f4;
color: #fff;
}执行 npx webpack serve 查看结果渲染成功:

现在我们把注意力转移到 components/Header/index.js 中的 import 语句。这个组件引用了外部的样式,路径回退了 2 层。看似问题不大,但如果项目结构复杂,要回退更多层级的路径就很麻烦了。举个例子,如果在 Header 文件夹下还有 a/b/c/index.js 这样的文件层级,我们引用 css 的方式会变成这样 ../../../../../css/index.scss。
当项目结构如此复杂,我们还想达成引用的需求时。有什么方式能优雅地解决这个问题呢?接下来我们请出 webpack 的 resolve 配置。
官方对于 resolve 的解释非常简单:配置模块如何解析。下面我们看看如何配置。
- 步骤 1,配置 webpack.config.js
const path = require('path')
const resolvePath = _path => path.resolve(__dirname, _path)
module.exports = {
// ...
resolve:{
alias: {
'@': resolvePath('./src')
},
},
//...
}alias 创建 import 或 require 的别名,来确保模块引入变得更简单。这里将 @ 的寻址路径指定为 src 的根目录。
- 步骤 2,更改 Header 组件的 import 语句
import '@/css/index.scss'这里相当于在 src 路径下找寻后面的文件。配置好后重启服务,页面正常渲染。
即使项目层级复杂,你也可以指定任何你想寻址的目录。这就是 alias 的使用方法,Vue 中 @ 解析符的寻址也是这么回事。
源代码追踪 sourceMap
本小节开始学习前,我们先搞明白 webapck 中的 module,chunk 和 bundle 分别是什么。
- module:开发者手写的代码模块
- chunk:输入 module 代码后,交由 webpack 正在打包的代码
- bundle:编译后输出的浏览器最终能直接识别的代码
简而言之,这三者是这样婶儿的关系:
module(手写的代码) => chunk(webpack 处理中的代码) => bundle(webpack 处理后的代码)。
客户端在执行程序时,读取的是打包后的 bundle 文件。如果程序执行过程报错,报错信息是 bundle 的内容。无法溯源到源代码 module 中的错误,此时需要借助 sourceMap 来帮忙。
sourceMap(源代码映射)是一个用来生成源代码与构建后代码对应映射文件的方案。下面我们举个例子来看看它的使用。
我们在 components/Header/index.js 中添加依据错误的 console 信息:
console.lo('Halo Header')运行 webpack server 查看页面。

点击第一行的 main.js 报错查看报错信息:

可以看到报错信息追溯的都是编译后的 bundle 代码,没有追溯到源码。下面我们给 webpack.config.js 配置 sourceMap 信息。
module.exports = {
// ...
devtool: 'cheap-module-source-map',
mode: 'development'
//...
}TIP
如果 mode 设置为 production,那么将不会看到追踪到源码的错误信息。
再次查看报错信息,此时错误已经追踪到源码上了:

这就是 sourceMap 的作用,它的官方配置很多。 但总结下就是将错误追踪分成不同的种类。 如是否单独生成 chunk 与 bundle 相互映射的 map 文件;是否以内联形式追加 sourceUrl 信息等排列组合。这里整理了一份表格供参考。
- eval:每个 module 会被封装到 eval 内执行,并且会在末尾追加注释
//sourceURL - source-map:额外生成 sourceMap 文件
- hidden-source-map:同上,但是不会在 bundle 末尾追加注释
- inline-source-map:生成一个 DataUrl 形式的 sourceMap 文件,map 文件不会被单独打包
- eval-source-map:同上,另外每个 module 会被封装到 eval 内执行
- cheap-module-source-map:生成一个没有列信息的 sourceMap 文件,map 文件会被单独打包
但是我们的文章宗旨是通篇大白话,不讲八股文。这么些区别谁也懒得记。实际开发过程中我们只需要关注开发(development)和生产(production)两个环境如何配置 sourceMap 信息即可,直接记结论如下表:
| mode | devtools | 优点 | 缺点 |
|---|---|---|---|
| development | cheap-module-source-map | 打包编译速度快 | 只包含行映射 |
| production | source-map | 包含行、列映射 | 打包速度慢 |
模块热更新 HMR
模块热替换 (HMR - hot module replacement) 功能会在应用程序运行过程中,替换、添加或删除 模块,而无需重新加载整个页面。主要是通过以下几种方式,来显著加快开发速度:
- 保留在完全重新加载页面期间丢失的应用程序状态。
- 只更新变更内容,以节省宝贵的开发时间。
- 在源代码中 CSS/JS 产生修改时,会立刻在浏览器中进行更新,这几乎相当于在浏览器 devtools 直接更改样式。
在 webpack.config.js 中的 devServer 配置 hot: true 即可开启:
module.exports = {
// ...
devServer: {
host: 'localhost',
port: 8080,
open: true,
hot: true,
},
mode: 'development'
}TypeScript 开发 babel-loader
开发 ts 需在安装对应依赖,并额外做一些配置,我们先做好前置工作,调整 src 目录,新增一个 ts 文件:
├─ src
│ ├─ components
│ │ ├─ Add
│ │ │ └── index.ts
│ │ ├─ Header
│ │ │ └── index.js
│ ├─ css
│ │ └── index.scss
│ ├─ index.html
│ └─ index.jsAdd/index.ts 文件中填充的内容如下:
const add = (a: number, b: number):number => a + b
const Hello = () => {
const result = add(2,3)
console.log(result)
}
export default Hellosrc/index.js 引入该组件:
import Hello from './components/Add/index.ts'
Hello()到这里前置工作完成,接下来我们正式开始配置 webpack 使其能正常编译 ts 文件。
- 步骤 1,安装 bebal 预设 @babel/preset-typescript
npm i @babel/preset-typescript- 步骤 2,配置 webpack.config.js
//...
module.exports = {
//...
module: {
rules: [
//...
{
test: /\.(js|ts)$/,
use: 'babel-loader',
}
]
},
//...
}- 步骤 3,项目根目录新建 babel.config.json 文件
├─ src
├─ .gitignore
└─ babel.config.json
└─ README.md
└─ webpack.config.js
└─ package.json- 步骤 4,配置 babel.config.json 文件
{
"presets": ["@babel/preset-env","@babel/preset-typescript"]
}到这里 ts 的前置工作就做完了,运行下 webpack serve 查看结果,Hello 函数正确打印了结果。

校验代码
校验代码分常规的 js 校验和 ts 校验两块内容讲解。
原因是 ts 校验涉及的历史背景较为复杂。
早期涉及 ts 的编译,需要使用到 ts-loader + babel-loader,先将 ts 代码编译成 es 代码,再通过 babel-loader 编译成 es5 代码。
后来 ts 团队与 babel 团队合作带来了全新的 babel 7。使得 babel-loader 能直接编译 ts 代码,但是 babel 官网有如下提示:
务必牢记 Babel 不做类型检查,你仍然需要安装 Flow 或 TypeScript 来执行类型检查的工作。
也就是说虽然配置和编译变得更快更简单,但由于不依赖 tsc 编译代码,编译过程中丢失了 ts 语法的校验功能。
那如何去做 ts 语法的校验呢,后续我们进行详细地讲解,让我们先关注如何使用 eslint 在 webpack 中进行代码的常规校验。
用 eslint 校验 js
常规校验 js es 代码需要在 webpack 中引入 eslint 并做好配置,配置步骤如下:
- 步骤 1,安装 eslint 依赖
npm i eslint eslint-webpack-plugin- 步骤 2,配置 webpack.config.js
// ...
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const resolvePath = _path => path.resolve(__dirname, _path)
module.exports = {
// ...
plugins: [
// new ESLintWebpackPlugin(options)
// 注意传递给 ESLintWebpackPlugin 的 options 参数会传给 ESLint 类,
// 这些选项和 .eslintrc 中所指定的不是同一个东西
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: resolvePath('./src'),
}),
// ...
]
// ...
}- 步骤 3,根目录新增 .eslintrc.js,.eslintignore 文件
├─ src
├─ .gitignore
└─ README.md
└─ .eslintignore
└─ .eslintrc.js
└─ webpack.config.js
└─ package.json这里讲解下这两个文件的作用:
- .eslintrc.js:配置 eslint 校验规则的文件。
- .eslintignore:哪些文件需要忽略校验规则。
eslint 的使用,本质上是对 .eslintrc.js 文件进行相关配置。
有时候我们不希望 eslint 校验所有文件。类似 git 提交时,我们不希望所有文件都被 git 识别并提交,此时就有了 .gitignore 文件去指定 git 应该忽略的文件。.eslintignore 的作用也是如此,指定 eslint 应该忽略哪些文件去做代码校验。
- 步骤 4,配置 .eslintignore 文件
dist
node_modules
webpack.config.js- 步骤 5,配置 .eslintrc.js 文件
module.exports = {
root: true,
env: {
node: true, // 启用 node 中全局变量
browser: true, // 启用浏览器中全局变量
},
parserOptions: {
ecmaVersion: 6,
sourceType: "module",
},
rules: {
"no-var": 2, // 不能使用 var 定义变量
},
extends: ["eslint:recommended"], // 继承 Eslint 规则
};| 字段 | 含义 |
|---|---|
| root | 是否为根目录 |
| env | 指定环境,使用 env 关键字指定你想启用的环境,并设置它们为 true |
| parserOptions | 解析配置选项 |
| rules | 可以使用注释或配置文件修改你项目中要使用的规则,修改对应规则的值即可;"off" 或 0 关闭规则,"warn" 或 1 为开启规则,使用警告级别的错误,"error" 或 2 开启规则,使用错误级别的错误 |
| extends | 可以让 eslint 继承已经配置好的规则 |
eslint 的配置项很多,这里列举了一些字段的含义,详细的配置推荐大家去官网查阅。
在这里我们配置了 eslint 并设置了不允许使用 var 来定义变量这一规则,一旦使用 var 定义变量,eslint 会报 error 级别的错误,并中止程序。
我们在 src/index.js 中用 var 定义一个变量,可以看到还没有运行 webpack,vscode 就显示出错误的提示了。

因为我设置了保存代码自动修复 eslint 错误,所以保存后运行 webpack,会提示这样的错误:

eslint 的校验符合预期,大功告成。接下来我们进行 ts 校验的相关配置。
tsc 检查 ts 文件
下面使用 tsc 来校验 ts。
首先创建 tsconfig.json 文件:
tsc --init然后添加脚本命令:
{
"type-check": "tsc --noEmit"
}最后执行这个脚本命令就可以了。
省略引入文件后缀名 resolve
我们看看开发中“省略引入文件后缀名”这个问题发生的场景,查看 src/index.js 的内容:
import Header from './components/Header/index.js'
import Hello from './components/Add/index.ts'
Header()
Hello()日常开发中我们有很多 import 的使用,为了方便开发我们希望引用时能省略文件后缀名如 .js .ts。如果直接在 import 语句中去掉文件后缀名,编译会报错。
此时可以配置 resolve 的 extensions 属性,它会按顺序解析这些后缀名。
修改 webpack.config.js,之后我们引入文件的时候省略这些文件后缀名也不会出问题。
module.exports = {
//...
resolve:{
// ...
extensions: [".js", ".ts"]
},
// ...
}提升构建速度
本章节我们从规则匹配,排除/包含文件,babel 缓存,缓存其他资源,多进程打包等方面讲解如何提升 webpack 的构建速度。
规则匹配 oneOf
webpack 打包时每个文件都会经过所有 loader 处理,虽然 loader 并不会处理与 test 不匹配的文件。但文件还是会遍历所有的 loader,导致匹配变慢。
解决方式使用 oneOf,让文件匹配上对应的 loader 后,就不与其他 loader 做匹配。修改 webpack.config.js 配置:
{
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
}{
module: {
rules: [
{
oneOf: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
}
]
},
}排除/包含文件 exclude, include
我们使用 loader 处理 js ts 文件时,要排除 node_modules 下面的文件,或直接指定只处理 src 目录下的文件,通过配置 babel-loader 达成效果,注意 exclude/include 为互斥关系,二者只能开启其中的一种。
{
test: /\.(js|ts)$/,
use: 'babel-loader',
exclude: /node_modules/,
},babel 缓存
每次处理 js ts 文件都要经过 eslint 校验和 babel 编译,速度较慢。我们可以开启缓存,将上次编译的结果缓存起来,提升构建速度。
修改 babel-loader 配置以开启 babel 缓存。
{
test: /\.(js|ts)$/,
exclude: /node_modules/,
// include: resolvePath('./src'),
loader: 'babel-loader',
options: {
cacheDirectory: true, // 开启 babel 编译缓存
cacheCompression: false, // 缓存文件不压缩
}
}为何需要关闭缓存压缩?
生产环境的代码无需用上这些压缩缓存文件。如果在开发模式中开启缓存压缩,执行压缩的过程需要耗费一定的时间。为了更快的构建速度,这里选择关闭该功能。
eslint 缓存
修改 plugins 中 ESLintWebpackPlugin 的配置开启 eslint 缓存。
new ESLintWebpackPlugin({
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: resolvePath('./src'),
extensions: ['js'],
// 指定检查文件的根目录
exclude: "node_modules", // 默认值
cache: true, // 开启缓存
// 缓存目录
cacheLocation: resolvePath('../node_modules/.cache/.eslintcache')
}),
})减少构建体积
本章我们将从 treeShaking,资源压缩等方面讲解如何减少 webpack 的构建体积。
treeShaking
https://webpack.docschina.org/guides/tree-shaking/#root
资源压缩
css 压缩
css 压缩需要用到 css-minimizer-webpack-plugin,使用步骤如下:
# 下载
npm i css-minimizer-webpack-plugin然后配置 webpack.config.js。
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'
module.exports = {
// ...
plugins: [
//...
new CssMinimizerPlugin()
],
// ...
}js 压缩
webpack5 的生产模式默认开启了 js 和 html 的压缩。
图片资源压缩
将小于指定体积的图片转化成 data URI 的 Base64 形式的资源。
找到之前处理图片资源的 loader 进行如下配置即可:
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: 'asset',
generator: {
filename: 'assets/img/[hash:10][ext]'
},
parser: {
dataUrlCondition: {
maxSize: 60 * 1024 // 小于 60kb 的图片会被 base64 处理
}
}
},优化应用性能
本章我们将通过代码分割,动态导入模块,缓存文件,CDN 加载资源等方面讲解如何优化 webpack 的应用性能。
代码分割
代码分割起到“化整为零”的作用,它可以把代码分到不同的 bundle 中,以减小单个 bundle 的体积。之后按照使用需求,将这些代码按需加载或并行加载。如果使用合理,它能极大减少应用程序的加载时间以提升应用性能。
举个例子,假设我们的应用程序在打包后生成了一个 10M 的 bundle 文件,用户在进入应用首页时就要加载这个 10M 的 JS 脚本。如果网速不快,加载时间会变得极为漫长,这显然拖垮了应用的整体性能。
理想情况是 webpack 帮我们把应用程序的代码做了分割,每个模块的代码体积都很小。我们进入哪个模块,就加载对应模块的文件,程序空闲时它会异步或并行加载其他资源,这样就能提升我们应用的性能。
分割代码的方式有 2 种,entry 入口分离,splitChunks,下面我们看看二者如何使用。
entry 入口分离
我们现在新增一个 src/main.js 文件,内容如下:
import Hello from './components/Add/index.ts'
console.log('Hello Main!')
Hello()现有的 src/index.js 改成如下:
import Header from './components/Header/index'
import Hello from './components/Add/index.ts'
console.log('Hello Index!')
Header()
Hello()接着我们配置 webpack.config.js 的 entry 选项,入口为 index.js 和 main.js,这样到时候打包出来的 main.html 文件引入的 js 文件就有两个。
entry: {
index: './src/index.js',
main: './src/main.js',
}打包后查看结果,两个入口 js 文件引用的 Add 下的 Hello 模块都能正常运行。
但是我们查看打包后的 dist 文件,发现二者共同引用的模块 Hello,被重复地分别打包进了 index 和 main 这 2 个 bundle 中。 这代码分离了,又好像没分离。我们希望通用的代码只被打包一次然后引用即可,此时就需要使用到 splitChunks。
splitChunks
splitChunks 的配置方式较为复杂,详细介绍请查看官方文档。这里我们挑取最简单的配置展示给大家看。
// ...
module.exports = {
// ...
optimization: {
// ...
splitChunks:{
// 对所有模块进行分割
chunks:'all',
cacheGroups: {
default: {
// chunks 需达到一定体积才能被分割,我们定义的 chunk 体积太小,所以更改生成 chunk 的最小体积(以 bytes 为单位)。
minSize: 0,
minChunks: 2,
priority: -20,
// 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
reuseExistingChunk: true,
}
}
}
},
// ...
}更改分割策略后,打包代码查看结果,发现新生成了一个名为 749 的 bundle 文件。
这个 bundle 的内容,就是我们 Add 文件里的内容。
接下来查看 src/index.js 的 bundle 内容,发现它引入了 749 这个 bundle,原先 Add 中的 Hello 模块的内容并没有被重新定义。
再看看 src/main.js 的 bundle,发现也是如此。
这就是 splitChunks,它帮助我们分割出通用的代码避免重复打包。
动态导入模块
应用在初始化时不会一次加载全部资源,当它需要使用某个功能时加载对应功能的模块,这就是模块的动态导入。动态导入模块的方式有懒加载,预获取/预加载,它的使用方式是用 import 引入需要懒加载的模块,下面我们进行详细讲解。
懒加载
在使用懒加载前,我们调整目录结构,新增一个 Minus 组件。
├─ src
│ ├─ components
│ │ ├─ Add
│ │ │ └── index.ts
│ │ ├─ Header
│ │ │ └── index.js
│ │ ├─ Minus
│ │ │ └── index.js
│ ├─ css
│ │ └── index.scss
│ ├─ index.html
│ └─ index.js
│ └─ main.js组件内容如下:
const Minus = (a, b) => {
return a - b
}
export default Minus接着更新 src/index.js 的内容,使用 import 引入需要懒加载的 Minus 组件,并将它打印出来:
import Header from './components/Header/index'
import Hello from './components/Add/index.ts'
console.log('Hello Index!')
Header()
Hello()
const body = document.body
const btn = document.createElement("button")
btn.innerText = '点击加载 Minus 组件'
body.append(btn)
const btnDom = document.querySelector('button')
btnDom.addEventListener('click',() => {
console.log(import("./components/Minus/index"))
})打包在浏览器中运行,点击按钮查看 Network 面板加载资源的变化。
前后对比发现多了个 375.js 的 bundle 文件,点开文件查看内容,发现这就是 Minus 组件的 bundle。
我们还可以查看控制台日志,了解到 Minus 组件是以 ES Module 为形式,Promise 为结果被引入到 index.js 中的。
如果想在 index.js 中使用 Minus 组件我们更改下面的内容:
btnDom.addEventListener('click',() => {
import("./components/Minus/index").then((res) => {
// 模块暴露的方式为默认暴露所以调用 default 方法使用
const result = res.default(5,3)
console.log(res)
console.log(result)
})
})如果想给懒加载的 bundle 命名,可以在 import 时添加对应的魔法注释:
import(/* webpackChunkName:"Minus" */ "./components/Minus/index").then((res) => {
// 模块暴露的方式为默认暴露所以调用 default 方法使用
const result = res.default(5,3)
console.log(res)
console.log(result)
})再次点击按钮查看加载的资源文件,此时这个 bundle 名称由原来的 375 变成了我们更改的命名 Minus。
懒加载的使用方法是不是看着格外眼熟?在 Vue 或 React 中,路由的懒加载方式也是如此。
懒加载很好用,但它仍然有一些不足之处,当我们需要懒加载的资源过大,加载时间过长,会导致应用的体验变差。此时我们需要一个更好的解决方法——预加载。
预加载
预获取/预加载的使用很简单,扩展一下魔法注释即可,写法如下:
import(/* webpackChunkName:"Minus", webpackPrefetch: true */ "./components/Minus/index").then((res) => {
// 模块暴露的方式为默认暴露所以调用 default 方法使用
const result = res.default(5,3)
console.log(res)
console.log(result)
})启动服务查看结果,发现在点击按钮前 Minus 组件就被加载了进来。
应用的 head 标签内通过 link 标签将 Minus 以 prefetch 的方式加载了下来。
这就是预加载,它能在浏览器空闲的时候,去加载我们指定的资源。它只会加载资源,并不执行。
缓存文件
浏览器的缓存技术能极大减少客户端访问应用的时间,提升用户体验。但如果新打包的 bundle 文件名称未被修改,会导致浏览器申请资源时总是触发缓存机制,使客户端无法获取到最新的资源。
本小节我们通过配置 output 属性,确保编译的 bundle 能被客户端缓存,在资源发生变动时也能请求到新的文件。配置方式如下:
output: {
// ...
filename: 'scripts/[name].[contenthash:10].js',
}这里获取了对应 bundle 的文件内容,取文件内容的 hash 值前 10 位为扩展后缀名。在文件发生更改时,hash 后缀会产生变化,反之则无变化。
此时 bundle 文件都加上了 hash 后缀,这里我们给 Minus 组件新增一条 console 语句再打包看看前后变化。
查看 dist 文件,可以看出 Minus 和 index 对应的 bundle 和 map 文件命名发生改变,main, 749 命名未被改变。
Minus 因为内容改变,所以 contenthash 被改变,可 index 的 contenthash 为什么也会被改变呢?我们查看 index 的 bundle 文件,可以看出 index 依赖 Minus,在 index 的 bundle 中保存了 Minus 的 hash 值。如果 Minus 发生变化,index 的 bundle 中记录 Minushash 值的这部分也会发生变化。导致 index 的文件内容发生改变,从而引起 index 的 contenthash 被更改。
这里很好理解,假设某个 bundle 发生了改变,这个 bundle 和依赖该模块的 bundle 的 contenthash 都会发生改变。这种“依赖性改变”非常合情合理对吧?......对吗?
显然这是不合理的,我们希望 webpack 只专注于产生了实质性改变的文件,关联文件不受影响,从而让缓存更加持久。处理这种问题就需要配置 runtimeChunk 属性。
runtimeChunk 会把文件之间依赖的映射关系提取成单独的文件保管,这个文件就叫 runtime 文件。如果 Minus 发生改变只有它和依赖它的 runtime 文件会被改变,index 不被改变。具体配置如下:
optimization: {
// ...
runtimeChunk: {
name: entryChunk => `runtime-${entryChunk.name}.js`
}
},这里我们给 runtime 文件加上了 runtime- 的前缀,重新打包后就会新增 runtime- 前缀的文件。
我们更改 Minus 组件,去掉刚刚添加的 console 语句,再次打包查看结果, 发现只有 Minus, runtime-index 文件以及它们的 map 文件发生了变化,index 文件本身并没有发生变化。这样就保证了缓存的持久性,让用户体验更好。
CDN 加载资源 externals
如何让 webpack 使用 CDN 加载资源。这里需要配置 externals 属性,它的作用是防止将某些 import 的资源打包到 bundle 中,在运行时,再去外部获得这些扩展依赖。
这里我们以 Vue 为例,进行 CDN 的加载并使用。
- 步骤 1,src/index.html 中引入 CDN 资源:
<body>
<script src="https://unpkg.com/vue@next"></script>
</body>- 步骤 2,配置 webpack.config.js:
// ...
module.exports = {
// ...
externals: {
// 以 `import aa from 'bb'` 为例,
// key 代表 bb,value 代表 aa
vue: 'Vue',
},
mode: 'production'
}- 步骤 3,main.js 引入 Vue 并使用:
import Hello from './components/Add/index.ts'
import {ref} from 'vue'
console.log('Hello Main!')
Hello()
const a = ref('cengfan')
console.log(a)再举个例子帮助理解,修改 main.js 的 import 语句将 from 'vue' 变成 from 'cengfan'。
import Hello from './components/Add/index.ts'
import {ref} from 'cengfan'
console.log('Hello Main!')
Hello()
const a = ref('cengfan')
console.log(window)
console.log(a)修改 webpack.config.js 的 externals 属性:
externals: {
cengfan: 'Vue',
},启动服务后,仍能正常打印结果。