跳转到内容

一文看懂 Vite 是如何加载 vite.config.ts 配置文件的?

❓ 有没有好奇过:

Vite 作为一个 npm 依赖库,它是如何加载我们项目中的vite.config.ts文件的?

你可能会觉得没什么吧,不就是通过文件路径 requireimport 一下吗?或者使用readFile不也能读取到文件内容吗?

但是,如果你站在 Vite 库的开发者角度思考,你会发现,事情没有这么简单。

  1. 首先,readFile肯定是不可以的。因为我们不是要拿到文件的内容,而是要执行里面的代码,并且拿到执行后的结果。
  2. 还需要考虑配置文件使用的是 cjs 还是 esm,以此来决定使用require还是import来进行加载。
  3. 如果配置文件是 js 或者 JSON 类型,直接require 自然是没问题的;但是如果配置文件是 ts,那就没法直接require了。

这篇文章主要讨论配置文件是 ts 的情况。

先看一个 ts 的配置文件:

ts
import { defineConfig } from 'vite';

export default defineConfig({
  // ...
});

对于 Vite 来说,vite.config.ts配置文件其实就是一个模块。只不过这个模块不是库内部的文件模块,而是通过用户提供的一个文件模块。

自然,要使用一个模块,当然是require这个模块就行了;如果是 ES Module,就使用import

但是,由于 node 本身无法直接执行 ts 代码。因此,我们在执行这个模块代码之前,必须要先将此模块代码转成 js 代码,然后再requireimport

那么 Vite 是如何将 ts 转成 js 代码的呢?

从 Vite 官网可以找到这样一段话:

我们只需要关注第一句话:默认情况下,Vite 会使用 esbuild 将配置文件打包成一个临时文件,然后再加载该临时文件

关键词:esbuild,我们前往 Vite 源码,就能看到:

其中build函数就是从esbuild库中导入的。它的作用就是将vite.config.ts配置文件编译并打包成一个js 模块,然后写入一个临时的 js 文件中并进行加载。

👉 我们可以写一个简化版的实现:

ts
/**
 * 导出一个定义配置文件的函数
 */
export function defineConfig(config: UserConfig) {
  return config;
}

// 加载配置文件
export async funcrion loadConfig() {
  const configFile = path.resolve('vite.config.ts');
  const tmpFile = path.resolve('vite.config.js');

  // 1. 编译打包
  await esbuild.build({
    entryPoints: [configFile],
    platform: 'node',
    format: 'cjs',
    outfile: tmpFile,
  });

  // 2. 加载模块
  const mod = require(tmpFile);
  fs.rm(tmpFile);
  return mod.default || mod;
}

很简单,就是先编译,再加载。

也许强迫症的你会对产生的“临时文件”耿耿于怀,有没有什么办法不产生临时文件呢?

如果你使用的是cjs(上面的例子),那么恭喜你,是可以做到的。

ts
// 加载配置文件
export async funcrion loadConfig() {
  // 1. 编译打包
  const result = await esbuild.build({
    entryPoints: [path.resolve('vite.config.ts')],
    platform: 'node',
    format: 'cjs',
    write: false,
  });

  // 2. 加载模块
  const module = new Module('vite.config.ts');
  module.paths = Module._nodeModulePaths(process.cwd());
  module._compile(result.outputFiles[0].text, 'vite.config.ts');

  return module.exports.default;
}

如果是esm模块,网上说可以使用data URL实现(import('data:...')),这里就不去探讨了。

以上的方法都是基于esbuild来实现的,如果你觉得使用麻烦的话,那么你或许可以试试ts-node

ts
// 加载配置文件
export async function loadConfig() {
  const tsNode = await import('ts-node');
  tsNode.register({
    transpileOnly: true,
    compilerOptions: {
      module: 'commonjs'
    }
  });

  const mod = await import(path.resolve('vite.config.ts'));
  return mod.default ?? mod;
}

ts-node这个库可以让你的 Nodejs 支持加载 ts 模块,它是在运行时动态将 ts 代码转译成 js,然后再交给 Node 来执行

感谢阅读

👇️👇️👇️