vite 不支持 ie 11?configureBuild Hook 帮你定制 bundle 打包过程

前言

vite 不支持 ie 11?configureBuild Hook 帮你定制 bundle 打包过程_第1张图片
5 分钟带你快速读懂 vite 打包过程,源码分析一文中,我们介绍了 vite 打包的代码转化是通过 esbuild 来完成的。而 esbuild 是不支持 ie11 的。所以 vite 的 GitHub 上经常会出现一些 issue 问如何支持 ie11,例如下面这个 issue

vite 不支持 ie 11?configureBuild Hook 帮你定制 bundle 打包过程_第2张图片

在这个 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 打包的大致过程如下:

vite 不支持 ie 11?configureBuild Hook 帮你定制 bundle 打包过程_第3张图片

通过图例,我们可以简单了解到 configureBuild Hook 会在打包的最后阶段 生成文件到硬盘 时被调用。

使用 configureBuild Hook 自定义插件

首先,我们来看一个使用 configureBuild Hook 的插件示例:

定义插件 myPlugin.ts:

export default (): Plugin => ({
  configureBuild(viteConfig) {
    ...

    return async build => {
      ....
    }
  }
})

这里,我们定义了一个箭头函数,它会返回 configureBuild 函数,也就是 configureBuild Hook,它会在打包的开始被调用,并且会传入两个参数 configbuilds(在后面,我会一一介绍它们)。而 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 的参数 buildconfig 是什么、以及如何应用抱有疑问。

那么,接下来,我们带着这些疑问从源码的角度深入了解一下 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,也可以不返回。

然后,会对用户传入 configoptions 深度拷贝并填充默认值,这是为了保证后续逻辑的正常进行,而不需要处理不存在某个选项的逻辑,类似于当初 webpack 4 增加了配置默认值一样。

const config = prepareConfig(options)

深度拷贝并填充默认值的实现很简单,首先通过 klona npm 模块来完成对 configoptions 的深度拷贝,然后通过解构赋值的方式设置默认值。

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,将用户的 configbuilds 暴露給它。

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 的调用顺序。

并且,我想大家应该也注意到,当我们设置 writefalse 的时候,此时调用的是 bundle.generate,输出的只是 codesourcemap,如果这样,我们就需要通过 configureBuild Hook 进行后续的文件写的操作!而且,我们还可以在 Hook 中进行一些代码转化操作,例如使用 bable-coretransform(),这样一来就很轻松地实现了对 ie11 的支持。

写在最后

正如 configureBuild Hook 的介绍一样,它的出现会给 vite 带来更多强大的插件。例如,我们可以在 configureBuild Hook 中修改 index.html 中的

你可能感兴趣的:(前端,源码分析)