前言
在下一个时代的打包工具 esbuild 中,我给大家介绍了什么是「esbuild」,以及如何使用它实现一个 bundle 打包。今天,在这个特别的节日(1024),我拎出了「vite」打包实现的核心逻辑,分别进行图解和代码分析,目标只有一个:
5 分钟带大家快速读懂「vite」打包过程!
(正文开始~)
1 npm run vite build 过程发生了什么?
当我们需要打包基于「vite」的项目时,我们需要运行 npm run vite build
命令。实际上,它对应于源码中的 runBuild
方法:
// vite/src/node/cli.ts
async function runBuild(options: UserConfig) {
try {
await require('./build').build(options)
process.exit(0)
} catch (err) {
console.error(chalk.red(`[vite] Build errored out.`))
console.error(err)
process.exit(1)
}
}
可以看到,这里我们会调用 ./build/index.ts
中的 build()
方法来进行打包操作:
// vite/src/node/build/index.ts
async function build(options: BuildConfig): Promise<Result> {
...
}
而总结起来,build()
方法的实现过程会是这样(时序图):
2 逐行分析 build() 方法实现过程
这里,我们从代码的角度来逐行分析一番 build()
方法的实现细节:
1、移除原先的 outDir
目录(默认情况下是 dist
目录)。
await fs.emptyDir(outDir)
2、解析应用的入口 index.html
,创建 buildHtmlPlugin
解析入口 index.html
,可以把它理解成 「Webpack」中的 HtmlWebpackPlugin
。
const { htmlPlugin, renderIndex } = await createBuildHtmlPlugin(
root,
indexPath,
publicBasePath,
assetsDir,
assetsInlineLimit,
resolver,
shouldPreload
)
3、创建 baseRollupPlugin
,它会返回一个 plugin
数组,它包括初始化默认的 plugin
和用户自定义的 plugin
,例如 buildResolvePlugin
、esBuildPlugin
、vuePlugin
等等。
const basePlugins = await createBaseRollupPlugins(root, resolver, options)
而这里的 plugins
实际上就是「Rollup」中的 rollupInputOptions
选项的 plugins
。所以,如果大家需要自定义 plugin
来实现一些功能,可以参考「Rollup」官网。
4、解析 .env
文件,对于 VITE_
开头的会通过 import.meta.env
的方式暴露给我们。
Object.keys(env).forEach((key) => {
if (key.startsWith(`VITE_`)) {
userEnvReplacements[`import.meta.env.${key}`] = JSON.stringify(env[key])
userClientEnv[key] = env[key]
}
})
5、「vite」是通过「Node」的方式使用「Rollup」,所以会调用 rollup.rollup()
生成 bundle
。并且,这里会应用上面创建好的 baseRollupPlugin
、buildHtmlPlugin
以及一些基础的打包选项。
const rollup = require('rollup').rollup as typeof Rollup
const bundle = await rollup({...})
6、调用 bundle.generate
生成 output
(对象),它包含每一个 chunk
的内容,例如文件名、文件内容。最后,通过遍历 output
并调用 fs
模块生成对应的 chunk
文件,从而结束整个打包过程。
const { output } = await bundle.generate({
format: 'es',
sourcemap,
entryFileNames: `[name].[hash].js`,
chunkFileNames: `[name].[hash].js`,
...rollupOutputOptions
})
if (write) {
const cwd = process.cwd()
const writeFile = async (
filepath: string,
content: string | Uint8Array,
type: WriteType
) => {
...
}
await fs.ensureDir(outDir)
for (const chunk of output) {
if (chunk.type === 'chunk') {
// write chunk
const filepath = path.join(resolvedAssetsPath, chunk.fileName)
let code = chunk.code
if (chunk.map) {
code += `\n//# sourceMappingURL=${path.basename(filepath)}.map`
}
await writeFile(filepath, code, WriteType.JS)
if (chunk.map) {
await writeFile(
filepath + '.map',
chunk.map.toString(),
WriteType.SOURCE_MAP
)
}
} else if (emitAssets) {
if (!chunk.source) continue
// write asset
const filepath = path.join(resolvedAssetsPath, chunk.fileName)
await writeFile(
filepath,
chunk.source,
chunk.fileName.endsWith('.css') ? WriteType.CSS : WriteType.ASSET
)
}
}
if (indexHtml && emitIndex) {
await writeFile(
path.join(outDir, 'index.html'),
indexHtml,
WriteType.HTML
)
}
❝需要注意的是,这里我们并没有分析
❞ssr
对应的打包逻辑,有兴趣的同学可以自行了解。
写在最后
「vite」打包实现的核心就是 build()
方法,通过简单分析这个过程,我想大家对「vite」打包实现应该会建立起一个基本认知。并且,对于如何自定义 plugins
也会知道一二,因为它实际上就是「Rollup」的 plugins
。最后,如果文中存在表达不当的地方,欢迎各位同学提 Issue ~
往期文章回顾
深度解读 Vue3 源码 | 内置组件 teleport 是什么“来头”?
❤️ 爱心三连击
通过阅读,如果你觉得有收获的话,可以爱心三连击!!!
❝我是五柳,喜欢创新、捣鼓源码,专注于 Vue3 源码、Vite 源码、前端工程化等技术领域分享,欢迎关注我的微信公众号:Code center。
❞