Vite SSR 构建配置详解:ssr.external、noExternal、target 与 resolve 策略
本文基于 Vite 官方文档 docs/config/ssr-options.md,系统讲解 SSR 专属配置项 ssr.external、ssr.noExternal、ssr.target 以及 ssr.resolve 下的 conditions、externalConditions、mainFields 六个选项的完整语义、取值、默认值与优先级规则,并结合 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.ts:shouldExternalize 为每个环境创建并缓存一个 isExternal 函数,判断逻辑由 createIsConfiguredAsExternal 实现。理解下面各选项前,可以先记住这条从源码中可见的判定优先级链(见 external.ts):
ssr.external中显式列出的 id(或包名)强制外部化,优先级最高;ssr.noExternal(boolean之外的写法)命中则强制打包;- 其余裸导入(bare import)按“是否可外部化”自动判定。
此外,从源码结构看,只有无扩展名或 .js/.mjs/.cjs 后缀的文件才允许被外部化(canExternalizeFile,见 external.ts),这保证了不会把一个 CSS 或 WASM 文件误判为可外部化的 JS 依赖。
ssr.external
- Type:
string[] | true - Related: SSR Externals
将给定依赖及其传递依赖在 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,
},
})
true 与 ssr.external 的优先级关系:
- 当
noExternal: true时,没有任何依赖被外部化;但显式列在ssr.external(string[]形式)中的依赖仍然会被外部化,即 external 显式列表优先; - 若同时配置
ssr.noExternal: true与ssr.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.ts 中 builtins 的解析逻辑)。
ssr.target
- Type:
'node' | 'webworker' - Default:
node
SSR 服务器的构建目标,类型为 SSRTarget,默认值 'node' 在 ssr/index.ts 的 SSROptions 与 ssrConfigDefaults 中定义,并经 resolveSSROptions(ssr/index.ts)与用户配置合并。
该选项影响两条解析默认值的选择(见 config.ts):
target: 'node'(默认):ssr.resolve.conditions使用服务端条件(不含browser条件,package.json的browser字段被忽略),内建模块默认外部化;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.ts:DEFAULT_SERVER_CONDITIONS 由 ['module', 'browser', 'node', 'development|production'] 去掉 browser 得到,DEFAULT_CLIENT_CONDITIONS 则是去掉 node 的版本。development|production 为占位条件,在实际解析时按 dev/build 模式替换为对应值,保证同一配置在两种模式下得到各自环境下的 export 条件。
这些常量也通过 index.ts 以 defaultServerConditions、defaultClientConditions、defaultExternalConditions 的名字对外导出,插件作者可以基于它们派生自己的默认值。
典型用法是为 SSR 环境追加框架自用的条件,让包在 exports 字段中区分的服务端入口生效:
// vite.config.ts
export default defineConfig({
ssr: {
resolve: {
conditions: [
'module', 'node', 'production',
'my-custom-condition', // 供包 exports 中自定义条件使用
],
},
},
})
仓库中的 playground/ssr-conditions 测试工程专门用于验证不同 resolve.conditions 下 exports 条件的命中行为,可结合其 vite.config.ts 与测试用例对照阅读。
ssr.resolve.externalConditions
- Type:
string[] - Default:
['node', 'module-sync']
该条件用于外部化直接依赖在 SSR import 期间(包括 ssrLoadModule)的解析,即由 Node.js 自身(而非 Vite 插件管线)加载的依赖所使用的 conditions。默认值来自 constants.ts 的 DEFAULT_EXTERNAL_CONDITIONS;在 external 判定流程中,它会被作为 tryNodeResolve 的 conditions 传入,用来模拟“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.ts 中 mainFields 的三元选择。
如果某个包同时提供 ESM 与 CJS 入口,你可以通过调整该列表控制 SSR 优先命中哪一个字段;而想让 Node 侧(外部化依赖)也遵循同样偏好,则需依赖包本身的 exports 声明或 ssr.resolve.externalConditions 来约束。
配置落地:在 Vite 中的解析流程与验证方式
将上述选项串起来看,ssr 配置在启动时的处理路径是:
ssrConfigDefaults(target: 'node'、optimizeDeps: {}等)与用户ssr配置经mergeWithDefaults合并为ResolvedSSROptions(ssr/index.ts);ssr.external/ssr.noExternal/ssr.resolve.externalConditions被并入环境级resolve选项(config.ts),供 external 判定与各解析器使用;- 每次模块解析时,external.ts 中的
isExternal按“显式 external → 布尔 noExternal → 列表 noExternal 过滤 → 自动判定”的顺序输出外部化结论,且结果按 id 缓存在WeakMap中,避免重复解析。
验证配置是否生效的建议路径:
- 查看仓库内 playground/ssr、playground/ssr-deps、playground/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.noExternal;conditions 管插件管线内的解析,externalConditions 管 Node 加载外部依赖时的解析;mainFields 只在 exports 无法解析入口时才起作用。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00