前言
在5 分钟带你快速读懂 vite 打包过程,源码分析一文中,我们介绍了 vite 打包的代码转化是通过 esbuild 来完成的。而 esbuild 是不支持 ie11 的。所以 vite 的 GitHub 上经常会出现一些 issue 问如何支持 ie11,例如下面这个 issue:
在这个 issue 下,vite 的几位 contributor 分别讲了几种支持 ie11 的解法:
undefin 大佬:我没有这样测过,似乎你需要在 rollupOptions 中添加 babel/polyfill 和添加 umd 格式。
在你的 vite.config.js 中添加以下代码,需要注意的是你要安装 core-js、@babel/core、@babel/preset-env、@babel/runtime。
import babel from '@rollup/plugin-babel';
export default {
rollupInputOptions: {
plugins: [
babel({
presets: [[
"@babel/preset-env",
{
"corejs": 2,
"useBuiltIns": "usage",
"targets": {
"ie": "11"
}
}
]]
})
],
}
}
aleclarson 大佬:我做了一个插件 vite-plugin-legacy 用来简单地支持旧的(浏览器)遗留。请等待 #874 合并!
其实相比较这两位大佬的解法,大体上的思路是一样的,都是围绕 babel 来实现代码的转化、贴片操作。但是,如果较起真来,还是 aleclarson 大佬的 vite-plugin-legacy 比较「贴心」。并且,上面提及的 PR 现在已经合并了,这代表着我们已经可以使用 vite-plugin-legacy 插件了!
❝需要注意的是,vite-plugin-legacy 的主要实现是基于
❞configureBuildHook 实现的,而configureBuildHook 是在 vite 的1.0.0-rc.8版本以及之后才有的。
那么,回到今天的正题,我将带大家一起欣赏一番 configureBuild 的内部景象(源码)~
1 configureBuild Hook 介绍
configureBuild Hook 将会在构建功能进行任何操作之前调用。初始化构建配置时,它会被深度拷贝,便于 configureBuild Hook 可以更轻松地对其进行检查和变异(而不需要检查所有未定义的地方)。默认会将 Rollup 的输入(input)选项暴露给 configureBuild,使得 vite 的插件(plugin)能更好地操纵构建过程,并且,意味着更强大的 vite 插件将成为可能。
vite 打包的大致过程如下:
❝通过图例,我们可以简单了解到
❞configureBuildHook 会在打包的最后阶段「生成文件到硬盘」 时被调用。
2 使用 configureBuild Hook 自定义插件
首先,我们来看一个使用 configureBuild Hook 的插件示例:
定义插件 myPlugin.ts:
export default (): Plugin => ({
configureBuild(viteConfig) {
...
return async build => {
....
}
}
})
这里,我们定义了一个箭头函数,它会返回 configureBuild 函数,也就是 configureBuild Hook,它会在打包的开始被调用,并且会传入两个参数 config 和 builds(在后面,我会一一介绍它们)。而 configureBuild 调用会返回一个 async 函数,它会在打包的结束调用。
配置 vite.config.ts:
import myPlugin from "./myPlugin"
import type { UserConfig } from "vite"
const config: UserConfig = {
plugins: [
myPlugin()
]
}
export default config
两个步骤,我们就完成了一个的使用 configuredHook Plugin 的简单示例。虽然,知道了怎么定义使用 configureBuild Hook 的 Plugin,但是大家可能会对 configureBuild 的参数 build 和 config 是什么、以及如何应用抱有疑问。
那么,接下来,我们带着这些疑问从源码的角度深入了解一下 configureBuild Hook~
3 从源码角度认识 configureBuild Hook
configureBuild Hook 会在 build 构建方法的开始被调用,首先会创建一个 builds 数组用于保存每个 configuredBuild Hook 操作结果的 bundle,并且 vite 默认打包生成的 bundle 也会放到 builds 中。
// src/node/build/index build()
const builds: Build[] = []
❝需要注意的是,并不是
❞configuredBuildHook 必须要返回bundle,也可以不返回。
然后,会对用户传入 config 的 options 深度拷贝并填充默认值,这是为了保证后续逻辑的正常进行,而不需要处理不存在某个选项的逻辑,类似于当初 webpack 4 增加了配置默认值一样。
const config = prepareConfig(options)
深度拷贝并填充默认值的实现很简单,首先通过 klona npm 模块来完成对 config 的 options 的深度拷贝,然后通过解构赋值的方式设置默认值。
import klona from "klona/json";
function prepareConfig(config: Partial<BuildConfig>): BuildConfig {
const {
alias = {},
assetsDir = '_assets',
assetsInclude = isStaticAsset,
assetsInlineLimit = 4096,
...
} = klona(config)
return {
...config,
alias,
assetsDir,
assetsInclude,
assetsInlineLimit,
base,
...
}
}
接下来,会从 config 中获取到 configureBuild,由于同时可能会存在多个使用了 configureBuild Hook
的 plugin,所以这里取到的 configuredBuild 会是一个数组。然后,再通过 map 方法遍历 configureBuild 数组并调用每一个 configureBuild Hook,将用户的 config、builds 暴露給它。
const postBuildHooks = toArray(config.configureBuild)
.map((configureBuild) => configureBuild(config, builds))
.filter(Boolean) as PostBuildHook[]
❝可以看到,这里 vite 只是将
❞builds数组暴露给了configureBuildHook,我们可以选择性地操作builds。
虽然 configBuild Hook 出现的目的是为了让用户能更多地操作 bundle 的打包过程,但这并不意味着它的优先级(Priority)是最高的。 vite 默认打包生成的 bundle 仍然会优先于它被处理,即默认的 bundle 会被放置到 builds 数组的开始。
const rollup = require('rollup').rollup as typeof Rollup
// 默认打包生成的 bundle
const bundle = rollup({...});
builds.unshift({
id: "index",
bundle
});
到这里 builds 数组就构建好了,接下来就是遍历它,进行 bundle 的写操作(即输出到硬盘上),因为 vite 使用的是 Rollup 完成文件的打包,所以这里调用的是 bundle.write 来将文件输出到硬盘上。
for (const build of builds) {
const bundle = await build.bundle;
const { output } = await bundle[write ? 'write' : 'generate']({
dir: resolvedAssetsPath,
format: 'es',
sourcemap,
entryFileNames: `[name].[hash].js`,
chunkFileNames: `[name].[hash].js`,
assetFileNames: `[name].[hash].[ext]`,
...config.rollupOutputOptions
});
build.html = await renderIndex(output);
build.assets = output
await postBuildHooks.reduce(
(queue, hook) => queue.then(() => hook(build as any)),
Promise.resolve();
)
}
默认情况下,write 是为 true,即会调用 bundle.write,将代码写入到硬盘上,这也是为什么我们的打包后的结果会输出到 dist 目录的原因。在每次遍历 builds 的最后,vite 会调用 postBuildHooks 来使用在开始时调用 configureBuild Hook 返回的函数(通常会是一个 Promise),由于 postBuildHooks 是一个 Promise 数组,所以这里使用了 reduce 方法来保证 Hook 的调用顺序。
并且,我想大家应该也注意到,当我们设置 write 为 false 的时候,此时调用的是 bundle.generate,输出的只是 code 和 sourcemap,如果这样,我们就需要通过 configureBuild Hook 进行「后续的文件写的操作」!而且,我们还可以在 Hook 中进行一些代码转化操作,例如使用 bable-core 的 transform(),这样一来就很轻松地实现了对 ie11 的支持。
写在最后
正如 configureBuild Hook 的介绍一样,它的出现会给 vite 带来更多强大的插件。例如,我们可以在 configureBuild Hook 中修改 index.html 中的 <script/>,加载 polyfill.io 提供的垫片文件地址,从而实现对旧浏览器的兼容,这也是 aleclarson 大佬的 vite-plugin-legacy 的实现思路。最后,如果文中存在表达错误或不当的地方,欢迎各位同学提 Issue。
往期文章回顾
❤️ 爱心三连击
通过阅读,如果你觉得有收获的话,可以爱心三连击!!!
❝我是五柳,喜欢创新、捣鼓源码,专注于 Vue3 源码、Vite 源码、前端工程化等技术领域分享,欢迎关注我的「微信公众号:Code center」。
❞