首页
/ Vite SSR 构建配置详解:ssr.external、noExternal、target 与 resolve 策略

Vite SSR 构建配置详解:ssr.external、noExternal、target 与 resolve 策略

2026-09-06 19:56:00作者:魏侃纯Zoe

本文基于 Vite 官方文档 docs/config/ssr-options.md,系统讲解 SSR 专属配置项 ssr.externalssr.noExternalssr.target 以及 ssr.resolve 下的 conditionsexternalConditionsmainFields 六个选项的完整语义、取值、默认值与优先级规则,并结合 packages/vite/src/node 中的源码实现(external 判定逻辑、条件常量、默认值合并流程)说明每个选项在 dev 与 build 两种模式下的实际行为,帮助你在编写 SSR 应用或插件时精确控制依赖的外部化与解析路径。

以下选项除特别注明外,均同时作用于开发(dev)和构建(build)两个阶段。

SSR 依赖外部化的背景:为什么需要这些选项

在 Node.js 环境中运行 SSR 时,npm 依赖通常不需要像浏览器那样被打包,而是由 Node.js 原生 import/require 加载——这被称为“外部化”(externalize)。Vite 的默认策略是:除链接依赖(linked dependencies,即通过 file: 或 workspace 软链接等方式链接、不在 node_modules 内部署的包)外,所有依赖默认全部外部化;链接依赖默认被打包进产物,目的是让开发时修改本地源码包也能触发 HMR。

这一策略的判定入口在 external.tsshouldExternalize 为每个环境创建并缓存一个 isExternal 函数,判断逻辑由 createIsConfiguredAsExternal 实现。理解下面各选项前,可以先记住这条从源码中可见的判定优先级链(见 external.ts):

  1. ssr.external 中显式列出的 id(或包名)强制外部化,优先级最高
  2. ssr.noExternalboolean 之外的写法)命中则强制打包;
  3. 其余裸导入(bare import)按“是否可外部化”自动判定。

此外,从源码结构看,只有无扩展名或 .js/.mjs/.cjs 后缀的文件才允许被外部化(canExternalizeFile,见 external.ts),这保证了不会把一个 CSS 或 WASM 文件误判为可外部化的 JS 依赖。

ssr.external

将给定依赖及其传递依赖在 SSR 中外部化。默认情况下,除链接依赖外所有依赖都会被外部化;如果你希望把某个默认被打包的链接依赖也外部化,可以把它(或它的包名)的名字传给该选项。

当取值为 true 时,包括链接依赖在内的所有依赖全部外部化

需要注意两条与 ssr.noExternal 的交互规则:

  • string[] 显式列出的依赖,即使同时出现在 ssr.noExternal(任意类型)中,也总是以 external 为准——源码中这正是先检查 external.includes(id) / external.includes(pkgName) 并提前返回 true 的实现(external.ts);
  • external: true 且链接包未显式列出,链接包也会被外部化(源码注释 “If external is true, all will be externalized by default, regardless if it's a linked package”)。

string[] 支持单个包名(会命中该包的所有可外部化入口),也允许列出包内的具体子入口。

ssr.noExternal

  • Type: string | RegExp | (string | RegExp)[] | true
  • Related: SSR Externals

阻止列出的依赖被外部化——这些依赖会在构建时被打进 bundle。默认情况下,只有链接依赖不被外部化(为 HMR 服务);如果你希望反过来把链接依赖外部化,把名字传给 ssr.external 即可。

noExternal 支持字符串、正则及二者的数组混合,底层复用 Vite 的 createFilter 机制对包名做匹配(见 external.ts)。常见用途是强制把依赖了 Node 原生模块、使用了 require 或带有本地资源路径的包打进去,例如:

// vite.config.ts
import { defineConfig } from 'vite'

export default defineConfig({
  ssr: {
    // 包名精确匹配
    noExternal: ['some-legacy-pkg'],
    // 正则批量匹配,支持数组混合写法
    noExternal: [/^@acme\//, 'another-pkg'],
    // true:完全不外部化任何依赖(全部打包)
    // noExternal: true,
  },
})

truessr.external 的优先级关系:

  • noExternal: true 时,没有任何依赖被外部化;但显式列在 ssr.externalstring[] 形式)中的依赖仍然会被外部化,即 external 显式列表优先;
  • 若同时配置 ssr.noExternal: truessr.external: true,则以 ssr.noExternal 为准,最终没有任何依赖被外部化(源码中 typeof noExternal === 'boolean' 分支直接返回 !noExternal,位于显式 external 检查之后、自动判定之前,见 external.ts)。

另一个从源码可确认的细节:当 ssr.target: 'node'(默认)时,Node.js 内建模块(node:path 等)会默认被外部化;而在 ssr.target: 'webworker'noExternal: true 的组合下,内建模块列表被置空、不再视为 external(见 config.tsbuiltins 的解析逻辑)。

ssr.target

  • Type: 'node' | 'webworker'
  • Default: node

SSR 服务器的构建目标,类型为 SSRTarget,默认值 'node'ssr/index.tsSSROptionsssrConfigDefaults 中定义,并经 resolveSSROptionsssr/index.ts)与用户配置合并。

该选项影响两条解析默认值的选择(见 config.ts):

  • target: 'node'(默认):ssr.resolve.conditions 使用服务端条件(不含 browser 条件,package.jsonbrowser 字段被忽略),内建模块默认外部化;
  • target: 'webworker':改用客户端条件(包含 browser 条件),mainFields 也回到客户端默认值,适合为 Web Worker 构建 SSR 形态的 bundle。

ssr.resolve.conditions

  • Type: string[]
  • Default: ['module', 'node', 'development|production'](即 defaultServerConditions;当 ssr.target === 'webworker' 时为 ['module', 'browser', 'development|production'],即 defaultClientConditions
  • Related: Resolve Conditions

这些条件作用于插件管线中的解析,并且只影响 SSR 构建中未外部化的依赖;要影响已外部化的 import,请使用 ssr.resolve.externalConditions

默认常量定义在 constants.tsDEFAULT_SERVER_CONDITIONS['module', 'browser', 'node', 'development|production'] 去掉 browser 得到,DEFAULT_CLIENT_CONDITIONS 则是去掉 node 的版本。development|production 为占位条件,在实际解析时按 dev/build 模式替换为对应值,保证同一配置在两种模式下得到各自环境下的 export 条件。

这些常量也通过 index.tsdefaultServerConditionsdefaultClientConditionsdefaultExternalConditions 的名字对外导出,插件作者可以基于它们派生自己的默认值。

典型用法是为 SSR 环境追加框架自用的条件,让包在 exports 字段中区分的服务端入口生效:

// vite.config.ts
export default defineConfig({
  ssr: {
    resolve: {
      conditions: [
        'module', 'node', 'production',
        'my-custom-condition', // 供包 exports 中自定义条件使用
      ],
    },
  },
})

仓库中的 playground/ssr-conditions 测试工程专门用于验证不同 resolve.conditionsexports 条件的命中行为,可结合其 vite.config.ts 与测试用例对照阅读。

ssr.resolve.externalConditions

  • Type: string[]
  • Default: ['node', 'module-sync']

该条件用于外部化直接依赖在 SSR import 期间(包括 ssrLoadModule)的解析,即由 Node.js 自身(而非 Vite 插件管线)加载的依赖所使用的 conditions。默认值来自 constants.tsDEFAULT_EXTERNAL_CONDITIONS;在 external 判定流程中,它会被作为 tryNodeResolveconditions 传入,用来模拟“Vite 之外 Node 如何解析这个依赖”,从而决定是否允许外部化(见 external.ts)。

使用自定义值时,官方文档给出的实践要点是:在 dev 与 build 两端的 Node 进程上都通过 --conditions 启动参数传入相同值,才能得到一致的行为。例如设置 ['node', 'custom'] 时:

# dev:启动开发服务器时
NODE_OPTIONS='--conditions custom' vite

# build:运行构建产物时
NODE_OPTIONS='--conditions custom' node ./dist/server.js

这里的前提是 Node 版本支持 --conditions 标志(参见 Node.js CLI 文档);若依赖包未声明对应条件,传不传该参数不会产生差别。

ssr.resolve.mainFields

  • Type: string[]
  • Default: ['module', 'jsnext:main', 'jsnext']

解析包入口时尝试读取的 package.json 字段列表。注意它的优先级低于 exports 字段:如果入口已成功从 exports 解析出来,main field 会被忽略;同时该设置只影响未外部化的依赖

默认值按 target 区分:node 目标使用 DEFAULT_SERVER_MAIN_FIELDS['module', 'jsnext:main', 'jsnext']),webworker 目标退回客户端默认值(包含 browser),见 config.tsmainFields 的三元选择。

如果某个包同时提供 ESM 与 CJS 入口,你可以通过调整该列表控制 SSR 优先命中哪一个字段;而想让 Node 侧(外部化依赖)也遵循同样偏好,则需依赖包本身的 exports 声明或 ssr.resolve.externalConditions 来约束。

配置落地:在 Vite 中的解析流程与验证方式

将上述选项串起来看,ssr 配置在启动时的处理路径是:

  1. ssrConfigDefaultstarget: 'node'optimizeDeps: {} 等)与用户 ssr 配置经 mergeWithDefaults 合并为 ResolvedSSROptionsssr/index.ts);
  2. ssr.external / ssr.noExternal / ssr.resolve.externalConditions 被并入环境级 resolve 选项(config.ts),供 external 判定与各解析器使用;
  3. 每次模块解析时,external.ts 中的 isExternal 按“显式 external → 布尔 noExternal → 列表 noExternal 过滤 → 自动判定”的顺序输出外部化结论,且结果按 id 缓存在 WeakMap 中,避免重复解析。

验证配置是否生效的建议路径:

  • 查看仓库内 playground/ssrplayground/ssr-depsplayground/ssr-conditions 等 e2e 测试工程,它们覆盖了 SSR 依赖处理、noExternal 场景与 conditions 命中的真实用例;
  • 开启 debug 模式(vite:external 调试器)观察某个 import 是否被跳过外部化,对应 external.ts 中 “Failed to node resolve ... Skipping externalizing it by default” 的日志分支;
  • 构建产物中确认目标依赖是否出现在 bundle 内(外部化则应只保留 import 语句)。

小结

选项 类型 默认值 作用范围
ssr.external string[] | true 未设置时按默认策略 dev + build;显式项优先级最高
ssr.noExternal string | RegExp | (string | RegExp)[] | true [] dev + build;true 时 external 显式项仍优先
ssr.target 'node' | 'webworker' node 影响 conditions / mainFields / 内建模块外部化
ssr.resolve.conditions string[] ['module', 'node', 'development|production'](webworker 为客户端条件) 仅未外部化依赖的插件管线解析
ssr.resolve.externalConditions string[] ['node', 'module-sync'] 外部化依赖在 Node 侧 import 时的条件
ssr.resolve.mainFields string[] ['module', 'jsnext:main', 'jsnext'](webworker 为客户端默认) 仅未外部化依赖的入口字段解析

核心记忆点:ssr.external 的显式列表永远压过 ssr.noExternalconditions 管插件管线内的解析,externalConditions 管 Node 加载外部依赖时的解析;mainFields 只在 exports 无法解析入口时才起作用

登录后查看全文
热门项目推荐
相关项目推荐