前言
在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 的主要实现是基于configureBuild
Hook 实现的,而configureBuild
Hook 是在 vite 的1.0.0-rc.8
版本以及之后才有的。
那么,进入今天的正题,我将带大家一起欣赏一番 configureBuild
的内部景象(源码)~
configureBuild Hook 介绍
configureBuild
Hook 将会在构建功能进行任何操作之前调用。初始化构建配置时,它会被深度拷贝,便于 configureBuild
Hook 可以更轻松地对其进行检查和变异(而不需要检查所有未定义的地方)。默认会将 Rollup 的输入(input)选项暴露给 configureBuild
,使得 vite 的插件(plugin)能更好地操纵构建过程,并且,意味着更强大的 vite 插件将成为可能。
vite 打包的大致过程如下:
通过图例,我们可以简单了解到
configureBuild
Hook 会在打包的最后阶段
生成文件到硬盘 时被调用。
使用 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~
从源码角度认识 configureBuild Hook
configureBuild
Hook 会在 build
构建方法的开始被调用,首先会创建一个 builds
数组用于保存每个 configuredBuild
Hook 操作结果的 bundle
,并且 vite 默认打包生成的 bundle
也会放到 builds
中。
// src/node/build/index build()
const builds: Build[] = []
需要注意的是,并不是configuredBuild
Hook 必须要返回bundle
,也可以不返回。
然后,会对用户传入 config
的 options
深度拷贝并填充默认值,这是为了保证后续逻辑的正常进行,而不需要处理不存在某个选项的逻辑,类似于当初 webpack 4 增加了配置默认值一样。
const config = prepareConfig(options)
深度拷贝并填充默认值的实现很简单,首先通过 klona npm 模块来完成对 config
的 options
的深度拷贝,然后通过解构赋值的方式设置默认值。
import klona from "klona/json";
function prepareConfig(config: Partial): 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
数组暴露给了configureBuild
Hook,我们可以选择性地操作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
中的 ,加载 polyfill.io 提供的垫片文件地址,从而实现对旧浏览器的兼容,这也是 aleclarson 大佬的 vite-plugin-legacy 的实现思路。最后,如果文中存在表达错误或不当的地方,欢迎各位同学提 Issue。
❤️ 爱心三连击
通过阅读,如果你觉得有收获的话,可以爱心三连击!!!