vite 源码 - 配置

上篇文章可以知道,vitedev 环境启动了一个服务器。这篇文章主要分析如何处理相关的配置信息。

信息源

vite 的配置信息有两个方式配置:

  1. 命令行参数
  2. 配置文件
  3. 插件

这里不具体讨论配置的具体参数,如何配置可以在 vite 官方文档中查看。

这篇文章从运行 vite 命令开始,查看配置数据的流转。

js 复制代码
"scripts": {
    "dev": "vite",
}

初始化

命令行参数

在上篇文章中,指出 vite 是通过 createServer 函数创建的 server 服务。这个函数接收一个参数,叫做 inlineConfig,这个参数在执行vite命令时,就是由命令行参数转换而来(也接收 vite 处理过的最终配置)。

js 复制代码
const server = await createServer({
    root,
    base: options.base,
    mode: options.mode,
    configFile: options.config,
    configLoader: options.configLoader,
    logLevel: options.logLevel,
    clearScreen: options.clearScreen,
    server: cleanGlobalCLIOptions(options),
    forceOptimizeDeps: options.force,
  })

这里的 options 就是解析的命令行参数,由于当前并没有传入,所有为空对象,由此,最终传入 createServer 函数的参数对象的值都为 undefined

yaml 复制代码
{
    base: undefined
    clearScreen: undefined
    configFile: undefined
    configLoader: undefined
    forceOptimizeDeps: undefined 
    logLevel: undefined
    mode: undefined
    root: undefined
    server: {}
}

如果传入命令行参数,则会被解析到具体的配置中。

最终配置

上面传入的配置数据,会被透传到内部函数 _createServer,这个函数接收两个参数:

  • inlineConfig: vite 配置
  • options: 函数选项

在这个函数中,做的第一件事就是处理配置。

js 复制代码
const config = isResolvedConfig(inlineConfig)
    ? inlineConfig
    : await resolveConfig(inlineConfig, 'serve')

核心就是以上代码,判断传入配置是否为最终配置,如果是就直接用,不是就调用 resolveConfig 函数进行处理。

resolveConfig

javascript 复制代码
async function resolveConfig(
  /** "内联配置":调用方以对象形式传进来的 Vite 配置(等价于 CLI 参数/配置文件里的同名项,会覆盖配置文件) */
  inlineConfig: InlineConfig,                
  /** 当前运行命令:影响插件的 apply('build'|'serve')、默认值选择,以及使用 server/build/preview 哪一支配置 */
  command: 'build' | 'serve',                // 
  /** 默认 mode:当没有显式传入 config.mode/--mode 时使用。通常 serve=development,build=production */
  defaultMode = 'development',               
   /** 默认的 process.env.NODE_ENV:没显式设置时给它一个默认值(serve 通常是 development;build 通常是 production) */
  defaultNodeEnv = 'development',           
  /** 是否处于 preview 流程:vite preview 调用解析时会置 true,用于走 config.preview 分支而不是 config.server */
  isPreview = false,                        
  /** @internal 内部钩子:解析出 ResolvedConfig 后,允许最后一次"就地修改"配置 */
  patchConfig: ((config: ResolvedConfig) => void) | undefined = undefined,
  /** @internal 内部钩子:插件数组解析完成后,允许调整/插入/重排插件 */
  patchPlugins: ((resolvedPlugins: Plugin[]) => void) | undefined = undefined,
): Promise<ResolvedConfig> { /* ... */ }

上面这个图是这个函数的大概流程,其中忽略了一些逻辑,比如日志打印,声明变量等。主要功能就是处理配置信息,其中需要注意的是对插件的处理,在 vite 中,插件也是可以修改配置信息的,所以需要判断并执行对应的插件。

最终得到的数据如下所示:

yaml 复制代码
const resolvedConfig = {
  // ------ 顶层基础 ------ //
  configFile: "/.../vite.config.ts",        // 实际使用的配置文件绝对路径
  configFileDependencies: ["/.../vite.config.ts"], // 配置文件的依赖(变更时触发重载)
  inlineConfig: { server: {} },             // 通过 Node API/CLI 合并进来的"内联配置"
  root: "/abs/path/to/project",             // 规范化后的项目根目录
  base: "/",                                // 公共基础路径(注入到 import/HTML 资源前缀)
  decodedBase: "/",                         // base 的解码版(处理过 % 编码)
  rawBase: "/",                             // 未加工前的 base,用于分支判断
  publicDir: "/abs/.../public",             // 原样拷贝到产物的静态资源目录(false/"" 表示禁用)
  cacheDir: "/abs/.../node_modules/.vite",  // 缓存目录(依赖预构建、元数据缓存等)
  command: "serve",                         // 当前命令:'serve' | 'build'
  mode: "development",                      // 模式:影响 .env 选择与 define 替换
  isWorker: false,                          // 本配置是否用于 worker 环境
  mainConfig: null,                         // 多配置串联时的根配置(默认 null)
  bundleChain: [],                          // 内部构建链信息(调试/实验特性)
  isProduction: false,                      // 基于 NODE_ENV 推断的生产/开发布尔值

  // ------ 插件 ------ //
  plugins: [/* 内置+用户插件,已按 enforce 段位与顺序排好 */],

  // ------ 资源与工具链选项 ------ //
  css: {
    transformer: "postcss",                 // CSS 处理器:'postcss'
    preprocessorMaxWorkers: true,           // 预处理器并行工作线程
    devSourcemap: false,                    // 开发时是否给 CSS 产出 sourcemap
  },
  json: {
    namedExports: true,                     // JSON 是否导出具名成员
    stringify: "auto",                      // 何时把 JSON 作为字符串处理
  },
  esbuild: {                                // esbuild 相关(false 表示禁用某些环节)
    jsxDev: true,                           // 开发 JSX 转换(影响开发体验/警告)
  },

  // ------ 预览服务器(vite preview)默认 ------ //
  preview: {
    port: 4173,
    strictPort: false,                      // 端口被占用是否报错
    allowedHosts: [],
    open: false,
    cors: { origin: {} },
    headers: {},
  },

  // ------ 环境变量 ------ //
  envDir: "/abs/path/to/project",           // 加载 .env 的目录
  env: {
    BASE_URL: "/",                          // 注入到客户端可读:import.meta.env.BASE_URL
    MODE: "development",                    // import.meta.env.MODE
    DEV: true,                              // import.meta.env.DEV
    PROD: false,                            // import.meta.env.PROD
  },

  // ------ 日志与包缓存 ------ //
  logger: { hasWarned: false },             // 简化的 logger 状态
  packageCache: {},                         // 包信息缓存(加速/去重)

  // ------ Web Worker 默认 ------ //
  worker: {
    format: "iife",                         // worker 打包格式
    rollupOptions: {},                      // 透传到 rollup 的自定义配置
  },

  appType: "spa",                           // 应用类型:'spa' | 'mpa' | 'custom'
  experimental: {                           // 实验性开关(不稳定)
    importGlobRestoreExtension: false,      // import.meta.glob 恢复扩展名行为
    hmrPartialAccept: true,                 // HMR 部分接受(更细粒度热更)
  },

  // ------ SSR 顶层(为生态兼容回灌自 environments.ssr) ------ //
  ssr: {
    target: "node",                         // SSR 运行目标:'node' | 'webworker'
    optimizeDeps: {                         // SSR 下依赖预构建策略
      esbuildOptions: { preserveSymlinks: false },
      include: [], exclude: [], needsInterop: [],
      extensions: [], holdUntilCrawlEnd: true, force: false, noDiscovery: true,
    },
    external: [],                           // 强制 external 的依赖名单
    noExternal: [],                         // 强制内联(不 external)的依赖名单
    resolve: {
      conditions: ["module", "node", "development|production"], // SSR 解析条件
      externalConditions: ["node", "module-sync"],              // SSR 外部条件
    },
  },

  // ------ 依赖预构建(客户端为主;与 environments.client.optimizeDeps 共用同一引用) ------ //
  optimizeDeps: {
    include: [], exclude: [], needsInterop: [],
    extensions: [], holdUntilCrawlEnd: true,
    force: false,                           // 强制重新预构建
    noDiscovery: false,                     // 关闭自动扫描(完全手动 include)
    esbuildOptions: { preserveSymlinks: false },
  },

  // ------ 开发服务器(vite dev)运行时选项 ------ //
  server: {
    port: 5173, strictPort: false,          // 端口与占用处理
    allowedHosts: [],                       // 允许的 Host 白名单
    open: false,                            // 启动是否自动打开浏览器
    cors: { origin: {} },                   // CORS 配置
    headers: {},                            // 自定义响应头
    warmup: { clientFiles: [], ssrFiles: [] }, // 预热文件列表
    middlewareMode: false,                  // 中间件模式(与自定义服务器整合)
    fs: {
      strict: true,                         // 严格文件系统访问(限制越界)
      deny: [".env", ".env.*", "*.{crt,pem}", "**/.git/**"], // 拒绝访问规则
      allow: ["/abs/path/to/project/vite"], // 允许访问白名单
    },
    preTransformRequests: true,             // 预变换请求(加速 dev 首屏)
    perEnvironmentStartEndDuringDev: false, // 多环境分阶段启动(实验)
  },

  // ------ 模块解析策略(顶层;源自 client 环境并已解析) ------ //
  resolve: {
    externalConditions: ["node", "module-sync"], // 参与外部依赖解析的条件
    extensions: [".mjs",".js",".mts",".ts",".jsx",".tsx",".json"], // 解析扩展
    dedupe: [],                                  // 强制去重的依赖(共享单实例)
    noExternal: [], external: [],                // 构建/SSR external 策略
    preserveSymlinks: false,                     // 是否保留符号链接
    alias: [
      { find: /*特征*/, replacement: "/@fs/.../env.mjs" }, // 路径别名
      { find: /*特征*/, replacement: "/@fs/.../client.mjs" },
    ],
    mainFields: ["browser","module","jsnext:main","jsnext"], // 优先入口字段
    conditions: ["module","browser","development|production"], // 条件导出
    builtins: [],                               // 内建模块别名(供 polyfill 等)
  },

  // ------ 多环境解析结果(client / ssr ...) ------ //
  environments: {
    client: {
      resolve: { /* 与顶层 resolve 类似,但针对浏览器环境的细化 */ },
      keepProcessEnv: false,                   // 是否保留 process.env 访问(客户端默认 false)
      consumer: "client",                      // 消费者标识:客户端
      optimizeDeps: { /* 客户端依赖预构建设置 */ },
      dev: {
        warmup: [],                            // dev 预热文件
        sourcemap: { js: true },               // js sourcemap
        preTransformRequests: true,            // 预变换
        recoverable: true,                     // dev 期间可恢复错误
        moduleRunnerTransform: false,          // Runner 级别变换
      },
      build: {
        target: ["chrome107","edge107","firefox104","safari16"], // 产物 JS/CSS 目标
        polyfillModulePreload: true,           // 是否注入 modulepreload polyfill
        modulePreload: { polyfill: true },     // 同上细化
        outDir: "dist", assetsDir: "assets",
        sourcemap: false,
        terserOptions: {}, rollupOptions: {},  // 透传给 Rollup/Terser
        commonjsOptions: { include: [{}], extensions: [".js",".cjs"] },
        dynamicImportVarsOptions: { warnOnError: true, exclude: [{}] },
        write: true, emptyOutDir: null, copyPublicDir: true,
        manifest: false, lib: false, ssrManifest: false, ssrEmitAssets: false,
        reportCompressedSize: true, chunkSizeWarningLimit: 500, watch: null,
        cssCodeSplit: true, minify: "esbuild",
        ssr: false, emitAssets: true,          // 客户端构建会产出静态资源
        cssTarget: ["chrome107","edge107","firefox104","safari16"],
        cssMinify: true,
      },
      plugins: [/* 客户端生效的插件清单(已排序) */],
    },

    ssr: {
      resolve: { /* 针对 Node/WebWorker SSR 的解析条件/别名等 */ },
      keepProcessEnv: true,                    // SSR 允许直接使用 process.env
      consumer: "server",
      optimizeDeps: { noDiscovery: true, ... },// SSR 下通常不自动扫描
      dev: {
        warmup: [], sourcemap: { js: true },
        preTransformRequests: false,           // SSR dev 不预变换请求
        recoverable: false,                    // 更严格
        moduleRunnerTransform: true,           // 运行器级变换
      },
      build: {
        // 与 client.build 相似,但:
        minify: false,                         // SSR 一般不压缩,便于调试
        ssr: true, emitAssets: false,          // 不产出静态资源(由 client 负责)
        cssMinify: "esbuild",
      },
      plugins: [/* SSR 生效的插件清单(已排序) */],
    },
  },

  // ------ 运行期工具 ------ //
  webSocketToken: "oTQiPWuOywdb",             // HMR/WS 鉴别 token(随机 72bits)
  safeModulePaths: new Set(),                 // 标记为"安全可加载"的模块路径集合
}

具体的配置处理逻辑由于篇幅限制就不一行一行的说明了,有兴趣可看一下 vite 源码中的 resolveConfig 这个函数即可。

相关推荐
Keepreal4963 小时前
谈谈对javascript原型链的理解以及原型链的作用
前端·javascript
Keepreal4963 小时前
Typescript中type和interface的区别
前端·typescript
RJiazhen3 小时前
从迁移至 Rsbuild 说起,前端为什么要工程化
前端·架构·前端工程化
小红3 小时前
网络通信核心协议详解:从ARP到TCP三次握手与四次挥手
前端·神经网络
麦兜*3 小时前
Redis多租户资源隔离方案:基于ACL的权限控制与管理
java·javascript·spring boot·redis·python·spring·缓存
rggrgerj4 小时前
VUE3+element plus 实现表格行合并
javascript·vue.js·elementui
影子信息4 小时前
uniapp 日历组件 uni-datetime-picker
前端·uni-app
fxshy4 小时前
Vue3和element plus在el-table中使用el-tree-select遇到的change事件坑
javascript·vue.js·elementui