External Modules
When a module is marked as external, Rolldown will not bundle it. Instead, import or require statements are preserved in the output, and the module is expected to be available at runtime.
// 输入
import lodash from 'lodash';
console.log(lodash);
// 输出(lodash 为 external)
import lodash from 'lodash';
console.log(lodash);This page will explain the end-to-end workings of externals: how a module becomes external, how its import path is determined in the output, and how the relevant options and plugin hooks interact.
模块如何变为 External
一个模块可以通过三种方式被标记为 external:
external选项 — 一个配置级模式(字符串、正则、数组或函数),用于测试每个 import specifier。有关模式语法、示例和注意事项,请参见 选项参考。插件的
resolveId钩子 — 插件可以返回{ id, external: true }(或"relative"/"absolute")来显式地将模块标记为 external。插件也可以return false,以与external选项相同的规范化方式将原始 specifier 标记为 external。未解析的模块 — 如果没有插件或内部解析器能找到某个模块,并且
external选项匹配该 specifier,Rolldown 会将其视为 external,而不是抛出错误。
完整的解析流程
下面是 Rolldown 遇到一个 import 时所遵循的分步过程:
1. 首次 external 检查
原始 import specifier(例如 './utils'、'lodash')会使用 isResolved: false 与 external 选项进行测试。如果匹配,则该模块会立即被标记为 external——插件和内部解析器会被完全跳过。
2. 插件 resolveId
如果第一次检查未匹配,插件就有机会解析该 import:
| 插件返回值 | 影响 |
|---|---|
return false | External。使用原始 specifier 作为模块 ID(与步骤 1 的规范化相同)。 |
return { id, external: true } | External。使用 id 作为模块 ID。 |
return { id, external: "relative" } | External。路径始终相对化(覆盖配置)。 |
return { id, external: "absolute" } | External。路径始终保持原样(覆盖配置)。 |
return { id }(没有 external) | 已解析,带着解析后的 ID 继续到步骤 3。 |
return null | 没有插件处理它,继续到步骤 3。 |
3. 内部解析器
Rolldown 内置的解析器会尝试在磁盘上找到该模块。
4. 第二次 external 检查
解析后的 ID(例如 '/project/node_modules/vue/dist/vue.runtime.esm-bundler.js')会使用 isResolved: true 与 external 选项进行测试。如果匹配,则该 specifier 会被标记为 external。
5. 输出路径确定
无论是哪个步骤将模块标记为 external(首次检查、插件或第二次检查),makeAbsoluteExternalsRelative 都会统一应用,以确定输出中的导入路径:
裸 specifier(例如
'lodash'、'node:fs')——如果在第一次检查中匹配,则会原样出现。如果在第二次检查中匹配(已解析路径),则会显示完整的解析路径(参见关于/node_modules/的 注意事项)。相对和绝对 specifier —— 会发生两件事:
解析时规范化 — 对于第一次检查和
return false,当启用makeAbsoluteExternalsRelative时(默认就是启用的),相对 specifier(原始 import specifier)会通过相对于导入者目录进行解析而被规范化为绝对路径。这可确保从不同目录导入的'./utils'能正确映射到不同的 external 模块。对于第二次检查和return { id, external: true },解析后的模块 ID 已经是绝对路径。渲染时输出 — 绝对的已解析模块 ID 可能会从输出 chunk 的位置重新转换为相对路径(例如
'/project/src/utils.js'→'./utils.js')。是否发生取决于makeAbsoluteExternalsRelative的值,以及原始 import specifier 是否为相对路径。
插件覆盖(external: "relative" / "absolute")会完全绕过这套逻辑。有关每个值如何控制此行为及示例,请参见 makeAbsoluteExternalsRelative 参考。
特殊情况
Data URLs
带有有效 data: URL 的 specifier(例如 data:text/javascript,export default 42),且文件格式受支持时,会由 Rolldown 的内部 dataurl 插件处理,该插件会打包内联内容。它们不会自动被视为 external。
不过,其他 data: URLs 会自动被视为 external,除非由自定义插件处理。
HTTP URLs
以 http://、https:// 或 // 开头的 specifier 会自动被视为 external,无论 external 选项如何,除非由自定义插件处理。这些 ID 会原样输出,不受 makeAbsoluteExternalsRelative 影响。
import lib from 'https://cdn.example.com/lib.js';
// 始终为 external,原样输出未使用的导入会被移除
如果外部模块中的某个导入没有被使用,Rolldown 会将其移除。
// 输入
import { used, unused } from 'ext-pkg';
console.log(used);
// 输出
import { used } from 'ext-pkg';
console.log(used);请注意,即使所有导入都被移除了,语句本身通常仍会保留。外部模块被假定具有副作用,因此它会变成一个裸的 import 'ext-pkg';。只有当外部模块也被标记为无副作用时,这条语句才会被完全删除。
与打包模块的区别
如果某个打包模块实际上并没有导出 unused,Rolldown 会在构建时发出 MISSING_EXPORT 错误,不管这个导入是否被使用。
对于外部模块,Rolldown 并不知道实际存在哪些导出,因此无法检查。如果 unused 不存在,导入它会在运行时抛错,而移除这个导入也会连同这个错误一起移除。无声地引入语义变化通常不是好主意,但 Rolldown 在这里做了一个例外。未使用的导入通常来自死代码消除,无论是 Rolldown 自身还是某个插件,而不是手动编写的,因此这个错误很少是你真正想看到的。