前言
使用Vite
(版本:"5.4.9")搭建React
(版本:"^18.3.1")项目,针对大体积的三方依赖做打包优化~
打包优化
使用插件rollup-plugin-visualizer
- 安装插件
rollup-plugin-visualizer
(版本: "^5.12.0"):
# 安装插件:对打包体积分析
pnpm i rollup-plugin-visualizer -D
该插件的功能:对打包体积输出分析报告,生产一个stats.html
。
- 配置
vite.config.js
:
import { visualizer } from 'rollup-plugin-visualizer'
export default defineConfig({
plugins: [
// ==打包后的文件体积分析==
visualizer({
open:true, // 打包完成后自动打开浏览器
})
]
})
在终端执行打包命令pnpm build
:
vite v5.4.9 building for production...
✓ 34 modules transformed.
dist/index.html 0.87 kB │ gzip: 0.43 kB
dist/assets/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/index-J-a4xQHu.js 203.16 kB │ gzip: 66.59 kB
✓ built in 2.31s
可以看到,所有的js都被打包进一个文件index-J-a4xQHu.js
,该文件大小203.16 kB。
浏览器自动打开stats.html
,分析结果:
体积大的就是react-dom
、react
和react-router-dom
,下面对此做优化~。
配置第三方依赖资源单独打包
build.rollupOptions.external
该字段作用:指定依赖不被打包
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==外部化依赖:指定依赖不打进入口的index包==
external: ["react", "react-dom", "react-router-dom"],
}
}
})
再次输入pnpm build
打包,结果如下,发现index.js
文件明显小了很多,由原来的203.16KB到现在的3.56KB
vite v5.4.9 building for production...
✓ 17 modules transformed.
dist/index.html 0.87 kB │ gzip: 0.43 kB
dist/assets/bg-CyIxRLAg.png 28.28 kB
dist/assets/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/index-C9xEeSm6.js 2.24 kB │ gzip: 1.08 kB
✓ built in 743ms
但是打包目录只有一个js文件,设置外部化的依赖文件到哪里去了?
再次看刚配置的vite.config.js
,发现,我只是设置了external字段,该字段是指定哪些依赖不打进bundule即最后的入口文件index-hash.js
,这样指定的依赖react
、react-dom
和react-react-dom
确实没有被打进包,所以这个输出是正常的~
默认情况下:vite将样式和图片抽离出来了(
dist/assets/index-BHdWsPZQ.css
和dist/assets/bg-CyIxRLAg.png
)~
build.rollupOptions.output.manualChunks
该字段作用:配置手动分包策略
修改配置文件:
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==外部化依赖:指定依赖不打进入口的index包==
external: ["react", "react-dom", "react-router-dom"],
// ==新增:输出配置==
output: {
// ==新增:配置手动分包==
manualChunks: {
react: ["react", "react-dom", "react-router-dom"],
}
}
}
}
})
执行打包命令pnpm build
,报错了:
vite v5.4.9 building for production...
✓ 12 modules transformed.
x Build failed in 715ms
error during build:
"react" cannot be included in manualChunks because it is resolved as an external module by the "external" option or plugins.
at getRollupError (file:///F:/test/monorepo-test/node_modules/.pnpm/rollup@4.24.3/node_modules/rollup/dist/es/shared/parseAst.js:395:41)
at error (file:///F:/test/monorepo-test/node_modules/.pnpm/rollup@4.24.3/node_modules/rollup/dist/es/shared/parseAst.js:391:42)
at ModuleLoader.loadEntryModule (file:///F:/test/monorepo-test/node_modules/.pnpm/rollup@4.24.3/node_modules/rollup/dist/es/shared/node-entry.js:20063:20)
at async Promise.all (index 0)
at async Promise.all (index 0)
ELIFECYCLE Command failed with exit code 1.
看报错信息:react在external
被配置了不打包,而manualChunks
又是配置单独打包。react一边被设置了不打包,一边设置了打包,两者冲突。将external
字段去掉,再次打包即可。
修改配置文件:
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==新增:注释external==
// ==外部化依赖:指定依赖不打进入口的index包==
// external: ["react", "react-dom", "react-router-dom"],
// ==输出配置==
output: {
// ==配置手动分包==
manualChunks: {
react: ["react", "react-dom", "react-router-dom"], // 被打包进react开头的js文件,vite默认的chunk文件名是outDir/[name]-[hash:8].js
}
}
}
}
})
vite v5.4.9 building for production...
✓ 37 modules transformed.
dist/index.html 0.94 kB │ gzip: 0.46 kB
dist/assets/bg-CyIxRLAg.png 28.28 kB
dist/assets/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/index-BkrjLTIM.js 2.21 kB │ gzip: 1.08 kB
dist/assets/react-CNZNb05N.js 140.87 kB │ gzip: 45.25 kB
✓ built in 1.38s
这次成功了,并且将react
、react-dom
和react-react-dom
三个依赖单独打进react-hash.js
里了。
build.rollupOptions.output.chunkFileNames
该字段作用:配置chunk的打包输出路径+名称。
Vite默认的chunk打包输出是outDir/[name]-[hash].js,若是不想这样的输出路径或文件名称,则可使用字段chunkFileNames进行个性化配置。
但是我想让js在outDir/js/[name]-[hash].js
,就需要配置chunkFileNames
字段:
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==输出配置==
output: {
// ==配置手动分包==
manualChunks: {
react: ["react", "react-dom", "react-router-dom"], // 被打包进react开头的js文件,vite默认的chunk文件名是outDir/[name]-[hash:8].js
},
// ==新增:代码分割后的文件输出配置(即manualChunks配置了手动分包,这些包的输出路径+名称)==
chunkFileNames: `${assetsDir}/js/[name]-[hash].js`,
}
}
}
})
再次执行打包命令pnpm build
,
vite v5.4.9 building for production...
✓ 37 modules transformed.
dist/index.html 0.94 kB │ gzip: 0.46 kB
dist/assets/bg-CyIxRLAg.png 28.28 kB
dist/assets/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/index-t51NycYU.js 2.22 kB │ gzip: 1.08 kB
dist/assets/js/react-CNZNb05N.js 140.87 kB │ gzip: 45.25 kB
✓ built in 1.63s
这样就完成了react依赖的单独打包配置~
build.rollupOptions.output.entryFileNames
该字段作用:配置入口js文件的输出名称,类似chunkFileNames字段。
Vite默认的入口js打包输出是outDir/index-[hash].js,若是不想这样的输出路径或文件名称,则可使用字段entryFileNames进行个性化配置。例如,我想要让他被打包到outDir/js目录下。
修改配置文件:
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==输出配置==
output: {
// ==配置手动分包==
manualChunks: {
react: ["react", "react-dom", "react-router-dom"], // 被打包进react开头的js文件,vite默认的chunk文件名是outDir/[name]-[hash:8].js
},
// ==代码分割后的文件输出配置(即manualChunks配置了手动分包,这些包的输出路径+名称)==
chunkFileNames: `${assetsDir}/js/[name]-[hash].js`,
// ==新增:配置入口js文件的名称==
entryFileNames: `${assetsDir}/js/[name]-[hash].js`,
}
}
}
})
重新打包pnpm build
:
vite v5.4.9 building for production...
✓ 37 modules transformed.
dist/index.html 0.95 kB │ gzip: 0.46 kB
dist/assets/bg-CyIxRLAg.png 28.28 kB
dist/assets/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/js/index-BkrjLTIM.js 2.21 kB │ gzip: 1.08 kB
dist/assets/js/react-CNZNb05N.js 140.87 kB │ gzip: 45.25 kB
✓ built in 1.43s
js文件都被打包进dist/assets/js目录下了,但是样式文件css是vite默认抽离出来的,我想让它在styles文件夹下。
build.rollupOptions.output.assetFileNames
该字段作用:静态资源输出配置,除了入口文件(entryFileNames)和设置的chunk(chunkFileNames)的静态资源,如字体、样式、图片等。
修改配置文件,使样式文件被打包到styles目录下:
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==输出配置==
output: {
// ==配置手动分包==
manualChunks: {
react: ["react", "react-dom", "react-router-dom"], // 被打包进react开头的js文件,vite默认的chunk文件名是outDir/[name]-[hash:8].js
},
// ==代码分割后的文件输出配置(即manualChunks配置了手动分包,这些包的输出路径+名称)==
chunkFileNames: `${assetsDir}/js/[name]-[hash].js`,
// ==配置入口js文件的名称==
entryFileNames: `${assetsDir}/js/[name]-[hash].js`,
// ==新增:配置静态资源的输出(静态资源按类型输出)==
assetFileNames: (assetInfo)=> {
const { names } = assetInfo
const name = names && names[0]
const suffix = "[name]-[hash].[ext]" // 文件名称格式
if (!name) {
console.log(1, assetInfo.name)
return `${assetsDir}/${suffix}`
} else if (name.endsWith(".css")) {
console.log(2, name)
return `${assetsDir}/${styleDir}/${suffix}` // 样式输出到styles
} else if (checkFileExtension(name, imageExtensions)) {
console.log(3, name)
return `${assetsDir}/${imagesDir}/${suffix}` // 图片输出到images
} else {
console.log(4, name)
return `${assetsDir}/${suffix}`
}
}
}
}
}
}
})
重新打包pnpm build
:
vite v5.4.9 building for production...
✓ 37 modules transformed.
3 bg.png
1 index.css
2 index.css
dist/index.html 0.95 kB │ gzip: 0.46 kB
dist/assets/images/bg-CyIxRLAg.png 28.28 kB
dist/assets/styles/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/js/index-CgbsK4No.js 2.22 kB │ gzip: 1.08 kB
dist/assets/js/react-CNZNb05N.js 140.87 kB │ gzip: 45.25 kB
✓ built in 1.53s
图片被打包到dist/assets/images
、样式在dist/assets/styles
,脚本在dist/assets/js
,这样就做到了资源的分门别类~
配置不打包依赖, 使用CDN链接
由上面的打包结果可知,可以将第三方依赖单独打包,节省页面的二次加载时间。
但是,也可以在生产环境使用CDN来优化~
配置生产环境下的CDN引用,有两种方式:
- 方式一:在
index.html
中增加script标签对依赖库的CDN链接。需要配合external
字段设置依赖外化和插件vite-plugin-externals
作变量转换。 - 方式二:修改配置文件,使用插件
vite-plugin-cdn-import
动态生成对依赖库的CDN链接的引入,同时使用插件vite-plugin-externals
作变量转换。
两者区别:一个是使用插件vite-plugin-cdn-import
动态生成的script标签到打包后的index.html
,一个是手动在index.html
中配置的script标签。
build.rollupOptions.external
修改配置文件,将react
、react-dom
和react-router-dom
依赖外化,设置不打包:
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==新增:外部化依赖:指定依赖不打进入口包==
external: ["react", "react-dom", "react-router-dom"],
// ==输出配置==
output: {
// ==新增:注释手动分包==
// ==配置手动分包==
// manualChunks: {
// react: ["react", "react-dom", "react-router-dom"], // 被打包进react开头的js文件,vite默认的chunk文件名是outDir/[name]-[hash:8].js
// },
// ==代码分割后的文件输出配置(即manualChunks配置了手动分包,这些包的输出路径+名称)==
// chunkFileNames: `${assetsDir}/js/[name]-[hash].js`,
// ==配置入口js文件的名称==
entryFileNames: `${assetsDir}/js/[name]-[hash].js`,
// ==配置静态资源的输出(静态资源按类型输出)==
assetFileNames: (assetInfo)=> {
const { names } = assetInfo
const name = names && names[0]
const suffix = "[name]-[hash].[ext]" // 文件名称格式
if (!name) {
console.log(1, assetInfo.name)
return `${assetsDir}/${suffix}`
} else if (name.endsWith(".css")) {
console.log(2, name)
return `${assetsDir}/${styleDir}/${suffix}` // 样式输出到styles
} else if (checkFileExtension(name, imageExtensions)) {
console.log(3, name)
return `${assetsDir}/${imagesDir}/${suffix}` // 图片输出到images
} else {
console.log(4, name)
return `${assetsDir}/${suffix}`
}
}
}
}
}
}
})
执行打包命令pnpm build
:
vite v5.4.9 building for production...
✓ 17 modules transformed.
3 bg.png
1 index.css
2 index.css
dist/index.html 0.88 kB │ gzip: 0.43 kB
dist/assets/images/bg-CyIxRLAg.png 28.28 kB
dist/assets/styles/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/js/index-BMimJrdv.js 2.24 kB │ gzip: 1.08 kB
✓ built in 587ms
发现没有了三方依赖文件的打包,因为将三方依赖的库设置在external
字段里即不被打包~
但是上面的配置也有问题:将react-router-dom
也设置在external
字段里,打包后(使用live-server
在本地运行打包后的项目)运行报错:
Uncaught TypeError: Cannot read properties of undefined (reading 'Routes')
at Object.get [as Routes] (index.tsx:2062:1)
at index-hYraL5ch.js:1:3353
寻找报错原因:
点开index-hYraL5ch.js
(打包后的入口脚本)看报错的这一行window.ReactRouterDOM.Routes
,继续看react-router-dom
源码,找Routes
,发现它的生成需要依赖第三方库@remix-run/router
。
上面我没有配置@remix-run/router
的打包,所以便报了上面的错误。
为了避免这个问题,将react
和react-dom
排除在打包之外,且将react-router-dom
配置在手动分包。
手动配置CDN引用
- 第一步:去第三方服务网站找到相应的链接并复制(锁定版本),这里笔者用的是jsdelivr:
- 第二步:修改项目根目录下的
index.html
,将第一步复制的结果粘贴到head标签里:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<!-- 新增:react和react-dom的引用 -->
<!-- 引入CDN,加载react -->
<script src="https://cdn.jsdelivr.net/npm/react@18.3.1/umd/react.production.min.js"></script>
<!-- 引入CDN,加载react-dom -->
<script src="https://cdn.jsdelivr.net/npm/react-dom@18.3.1/umd/react-dom.production.min.js"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="index.js"></script>
<script>
console.log(this)
</script>
</body>
</html
注意:因为使用CDN链接便不再是通过ESM来导入使用,而是通过全局变量来使用。
为了知道引入的CDN文件的全局变量是谁,注释掉<script type="module" src="index.js"></script>
,打印当前作用域console.log(this)
,浏览器打开该页面看打印信息:
发现,引入的CDN在window上绑定了相应的库,react
被绑定到了window.React
,react-dom
被绑定到了window.ReactDOM
。
现在已经知道了全局变量名称,下面就需要做全局变量的转换~
- 第三步:设置
react
和react-dom
外部化和react-router-dom
手动分包:
修改配置文件:
export default defineConfig({
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==新增:配置外化依赖==
// ==输出配置==
output: {
// ==修改:配置手动分包==
external: ["react", "react-dom"],
// ==修改:配置手动分包==
manualChunks: {
"react-router": ["react-router-dom"], // 被打包进react-router开头的js文件,vite默认的chunk文件名是outDir/[name]-[hash:8].js
},
...
}
}
}
})
- 第四步:全局变量的转换,使用
build.rollupOption.output.globals
字段
继续修改配置文件:
globals: {
"react": "React", // 映射react的全局变量为React
"react-dom": "ReactDOM",
"react-dom/client": "ReactDOM", // 因为项目入口脚本引用了react-dom/client:import { createRoot } from "react-dom/client"
}
执行打包命令pnpm build
:
vite v5.4.9 building for production...
✓ 20 modules transformed.
3 bg.png
1 index.css
2 index.css
dist/index.html 0.87 kB │ gzip: 0.46 kB
dist/assets/images/bg-CyIxRLAg.png 28.28 kB
dist/assets/styles/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/js/index-CYRc7TcT.js 3.65 kB │ gzip: 1.66 kB
dist/assets/js/react-router-MroGPJZ-.js 16.43 kB │ gzip: 6.34 kB
✓ built in 981ms
在打包目录的终端输入命令live-server
,运行本地项目:
报错了,从报错信息看加载react失败,再去看打包后的代码index-CYRc7TcT.js
:
说明使用build.rollupOption.output.globals
无效~
百度了下,使用插件vite-plugin-externals
吧:
- 安装:
pnpm i vite-plugin-externals -D
; - 使用:修改配置文件如下,
// ==新增:导入插件==
import { viteExternalsPlugin } from "vite-plugin-externals"
export default defineConfig({
plugins: [
...,
// ==新增:外部插件的命名映射配置==
viteExternalsPlugin({
"react": "React", // 映射react的全局变量为React
"react-dom": "ReactDOM",
"react-dom/client": "ReactDOM",
},{
// =官方文档:https://github.com/crcong/vite-plugin-externals/blob/HEAD/README.zh-CN.md==
// ==警告: 如果你在开发环境中,引入了生产环境的库, 可能会使得 HMR 失败。==
// ==在server模式下禁止转换external里的代码==
disableInServe: true,
})
]
// ==打包配置==
build: {
// ==修改:删除或注释build.rollupOption.output.globals字段内容==
...
}
})
重新打包pnpm build
:
vite v5.4.9 building for production...
✓ 16 modules transformed.
3 bg.png
1 index.css
2 index.css
dist/index.html 0.87 kB │ gzip: 0.46 kB
dist/assets/images/bg-CyIxRLAg.png 28.28 kB
dist/assets/styles/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/js/index-D_0ulMjz.js 3.82 kB │ gzip: 1.71 kB
dist/assets/js/react-router-BenATCSB.js 17.82 kB │ gzip: 6.70 kB
✓ built in 1.06s
运行打包项目,页面正常显示~
自动配置CDN引用
- 第一步:安装插件
vite-plugin-cdn-import
(终端执行pnpm i vite-plugin-cdn-import -D
, 版本:1.0.1); - 第二步:设置相应的外部化依赖并且配置自动导入CDN插件,修改配置文件如下:
// ==新增:安装插件(pnpm i vite-plugin-cdn-import -D),配置CDN==
import { Plugin as ImportToCDN } from 'vite-plugin-cdn-import'
export default defineConfig({
plugins: [
...
// ==新增:配置CDN==
ImportToCDN({
// ==指定CDN源==
prodUrl: 'https://cdn.jsdelivr.net/npm/{name}@{version}/{path}',
// ==配置第三方依赖使动态生成相应的CDN引用==
modules: [
{
name: "react",
version: "18.3.1",
path: "umd/react.production.min.js",
var: "React", // 这个var是固定的,与在html中通过script标签引入的库会在window对象生成一个对象且绑定到window上,这两个要一致
},
{
name: "react-dom",
version: "18.3.1",
path: "umd/react-dom.production.min.js",
var: "ReactDOM",
},
]
}),
],
// ==打包配置==
build: {
outDir: `${buildDir}`, // 打包输出目录,默认是dist
rollupOptions: {
// ==新增:外部化依赖:指定依赖不打进入口包==
external: ["react", "react-dom"],
// ==输出配置==
output: {
...
}
}
}
})
执行打包命令pnpm build
:
vite v5.4.9 building for production...
✓ 20 modules transformed.
3 bg.png
1 index.css
2 index.css
dist/index.html 0.86 kB │ gzip: 0.43 kB
dist/assets/images/bg-CyIxRLAg.png 28.28 kB
dist/assets/styles/index-BHdWsPZQ.css 0.08 kB │ gzip: 0.09 kB
dist/assets/js/index-C-59qxsq.js 3.76 kB │ gzip: 1.65 kB
dist/assets/js/react-router-BYLXKHLw.js 17.95 kB │ gzip: 6.69 kB
✓ built in 1.11s
再看dist/index.html
,在head标签里增加了这三项依赖的script标签引入:
<!doctype html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/react@18.3.1/umd/react.production.min.js" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18.3.1/umd/react-dom.production.min.js" crossorigin="anonymous"></script>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React</title>
<script type="module" crossorigin src="/assets/js/index-C-59qxsq.js"></script>
<link rel="modulepreload" crossorigin href="/assets/js/react-router-BYLXKHLw.js">
<link rel="stylesheet" crossorigin href="/assets/styles/index-BHdWsPZQ.css">
</head>
<body>
<div id="root"></div>
<script>
console.log(this)
</script>
</body>
</html>
注意:第二步使用的是动态导入+external,还有另外一种方式是动态导入+viteExternalsPlugin
// ==动态导入CDN==
import { Plugin as ImportToCDN } from "vite-plugin-cdn-import"
// ==依赖外部化配置==
import { viteExternalsPlugin } from "vite-plugin-externals"
export default defineConfig(()=> {
return {
plugins: [
...,
// ==配置CDN==
ImportToCDN({
prodUrl: "https://cdn.jsdelivr.net/npm/{name}@{version}/{path}",
modules: [
{
name: "react",
version: "18.3.1",
path: "umd/react.production.min.js",
var: "React", // 这个var是固定的,与在html中通过script标签引入的库会在window对象生成一个对象且绑定到window上,这两个要一致
},
{
name: "react-dom",
version: "18.3.1",
path: "umd/react-dom.production.min.js",
var: "ReactDOM",
},
]
}),
// ==外部模块的全局变量==
viteExternalsPlugin({
"react": "React", // 映射react的全局变量为React
"react-dom": "ReactDOM",
"react-dom/client": "ReactDOM",
},{
// =官方文档:https://github.com/crcong/vite-plugin-externals/blob/HEAD/README.zh-CN.md==
// ==警告: 如果你在开发环境中,引入了生产环境的库, 可能会使得 HMR 失败。==
// ==在server模式下禁止转换external里的代码==
disableInServe: true,
})
],
// ==打包配置==
build: {
// ==打包输出目录,vite默认是dist==
outDir: buildDir,
// ==打包输出配置(vite打包使用的是rollup)==
rollupOptions: {
// ==修改:注释外部依赖配置==
// ==配置某个依赖不打包==
// external: ["react", "react-dom"],
output: {
...
}
}
}
}
})
所以,插件vite-plugin-externals
中的viteExternalsPlugin
可以替代build.rollupOptions.external
字段来使用。方式一也可以直接分两步:手动导入CDN引用和viteExternalsPlugin
配置便可实现,无需再配置build.rollupOptions.external
。
注意:在本地开发过程中,使用的还是本地的依赖包(还是要安装
react
和react-dom
);但是在生产环境下,使用的react
和react-dom
是通过CDN获取到的。
好了,完成【针对大体积的三方依赖打包优化】任务~
总结
主要介绍了针对【大体积的三方依赖】的两种处理方式:
- 配置手动分包(单独打包):降低页面二次加载时间(第一次加载后会存入缓存,再次请求从缓存中拿)。
- 外部化依赖(不打包):在开发环境使用本地包,在生产环境使用CDN链接(极大减少请求时间和提升用户体验度)。
字段
- build.rollupOptions.output.manualChunks:配置手动分包策略。
- build.rollupOptions.output.chunkFileNames:配置chunk的输出路径和文件名称。
- build.rollupOptions.output.entryFileNames:配置入口脚本的输出路径和文件名称。
- build.rollupOptions.output.assetFileNames:配置静态资源的输出路径和文件名称。
- build.rollupOptions.external:配置三方依赖不被打包。
插件
- rollup-plugin-visualizer:打包体积分析。
- vite-plugin-cdn-import:自动导入CDN链接到index.html。
- vite-plugin-externals:配置外部化依赖。
CDN
CDN英文名称为Content Delivery Network,内容分发网络,其节点遍布各地,用户的访问到的CDN链接对应的内容会从最近节点拿,从而加快了加载速度,提高用户的体验。
其第三方服务网站主要有两个:例如 jsDelivr 或者 cdnjs。
两种实现方式:
- 手动配置CDN引用:
- 手动在
index.html
页面配置对应CDN链接的script标签的引用; - 使用插件
viteExternalsPlugin
配置外部化依赖,定义全局变量的映射。
- 手动在
- 自动配置CDN引用:
- 使用插件
vite-plugin-cdn-import
配置动态导入; - 配置外部化依赖:
build.rollupOptions.external
或者使用插件vite-plugin-externals
。
- 使用插件
live-server
使用live-server
在本地运行打包后的项目:
- 先全局安装
live-server
:npm install live-server -g
; - 再在打包目录的终端输入命令
live-server
:默认启动http://127.0.0.1:8080/
。
源代码
完整配置
// vite.conifg.js
import { defineConfig } from "vite"
import React from "@vitejs/plugin-react"
import { visualizer } from "rollup-plugin-visualizer"
import { Plugin as ImportToCDN } from "vite-plugin-cdn-import"
import { viteExternalsPlugin } from "vite-plugin-externals"
import path from "path"
// ==路径合成,返回绝对路径==
const resolve = function () {
return arguments.length ? path.resolve(...arguments) : ""
}
// ==路径配置==
// ==项目根目录==
const projectDir = process.cwd()
// ==源代码目录==
const srcDir = "src"
// ==指定打包目录,默认dist==
const buildDir = "dist"
// ==指定静态资源存放路径(相对于build.outDir)==
const assetsDir = "assets"
// ==指定字体的输出目录==
const fontDir = "font"
// ==指定图片的输出目录==
const imagesDir = "images"
// ==指定js的输出目录==
const jsDir = "js"
// ==指定样式文件的输出目录==
const styleDir = "styles"
// ==指定静态资源的后缀名称==
const imageExtensions = [".jpg", ".jpeg", ".png", ".gif", ".webp", ".bmp"]
const fontExtensions = [".ttf", ".otf", ".woff", ".woff2"]
// ==校验文件名称是否是指定类型的==
function checkFileExtension(filename, extensionsArray) {
// ==校验是否是以指定名称的后缀==
const regex = new RegExp(`\(${extensionsArray.join('|')})$`, 'i')
return regex.test(filename)
}
export default defineConfig(({
command,
// mode,
...args
})=> {
// console.log("command", command, args, process.env.NODE_ENV)
return {
plugins: [
React({
babel: {
plugins: ["@babel/plugin-transform-react-jsx"], // 转换jsx,使可以在js文件里写jsx
}
}),
// ==对打包体积分析==
visualizer({
open: true,
}),
// ==配置CDN:动态导入==
ImportToCDN({
prodUrl: "https://cdn.jsdelivr.net/npm/{name}@{version}/{path}",
modules: [
{
name: "react",
version: "18.3.1",
path: "umd/react.production.min.js",
var: "React", // 这个var是固定的,与在html中通过script标签引入的库会在window对象生成一个对象且绑定到window上,这两个要一致
},
{
name: "react-dom",
version: "18.3.1",
path: "umd/react-dom.production.min.js",
var: "ReactDOM",
},
]
}),
// ==外部模块的全局变量==
viteExternalsPlugin({
"react": "React", // 映射react的全局变量为React
"react-dom": "ReactDOM",
"react-dom/client": "ReactDOM",
},{
// =官方文档:https://github.com/crcong/vite-plugin-externals/blob/HEAD/README.zh-CN.md==
// ==警告: 如果你在开发环境中,引入了生产环境的库, 可能会使得 HMR 失败。==
// ==在server模式下禁止转换external里的代码==
disableInServe: true,
})
],
// ==解析配置==
resolve: {
// ==配置别名==
alias: {
"@": resolve(projectDir, srcDir),
"@components": resolve(projectDir, `${srcDir}/components`),
"@utils": resolve(projectDir, `${srcDir}/utils`),
"@assets": resolve(projectDir, `${srcDir}/assets`),
},
// ==配置扩展名==
extensions: [".jsx", ".tsx", ".js", ".ts"],
},
// ==打包配置==
build: {
// ==打包输出目录,vite默认是dist==
outDir: buildDir,
// ==打包输出配置(vite打包使用的是rollup)==
rollupOptions: {
// ==配置某个依赖不打包==
// external: ["react", "react-dom"],
output: {
// ==配置手动分包策略==
manualChunks: {
"react-router": ["react-router-dom"],
},
// ==代码分割后的文件输出配置(即manualChunks配置了手动分包,这些包的输出名称)==
chunkFileNames: `${assetsDir}/${jsDir}/[name]-[hash].js`,
// ==配置入口js文件的名称==
entryFileNames: `${assetsDir}/${jsDir}/[name]-[hash].js`,
// ==配置静态资源输出(指的是非chunk[chunkFileNames]、非入口文件[entryFileNames])==
assetFileNames: (assetInfo) => {
const { names } = assetInfo
const name = names && names[0]
const suffix = "[name]-[hash].[ext]"
if (!name) {
console.log(1, assetInfo.name)
return `${assetsDir}/${suffix}`
} else if (name.endsWith(".css")) {
console.log(2, name)
return `${assetsDir}/${styleDir}/${suffix}`
} else if (checkFileExtension(name, imageExtensions)) {
console.log(3, name)
return `${assetsDir}/${imagesDir}/${suffix}`
} else {
console.log(4, name)
return `${assetsDir}/${suffix}`
}
}
}
}
}
}
})
package.json
{
"name": "@monorepo-test/react-project",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host --mode development",
"build": "vite build --mode production",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"live-server": "^1.2.2",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0"
},
"devDependencies": {
"@babel/plugin-transform-react-jsx": "^7.25.9",
"@eslint/js": "^9.13.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.3",
"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"rollup-plugin-visualizer": "^5.12.0",
"vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-externals": "^0.6.2"
}
}