Auto external 与 externals type

问题背景

库构建默认不应该把所有依赖都打进产物。对于 npm library,很多依赖应该留给消费者安装和解析。Rslib 的 autoExternal 就是把 package.json 中的依赖自动转成 bundler external。

但 external 不是只有“包名是否 external”这一维。它还有:

  • 哪类 dependency 默认 external。
  • 用户 external 与 autoExternal 的优先级。
  • subpath import 如何匹配。
  • 不同 format 用什么 externalsType。
  • ESM 下 commonjs issuer external 是否需要 warning。

这些细节不理解,产物很容易“构建成功但发布后坏”。

默认 external 哪些依赖

默认策略:

dependency 类型默认 external
dependencies
peerDependencies
optionalDependencies
devDependencies

原因:

  • dependencies 是发布后消费者会安装的运行时依赖。
  • peerDependencies 通常由消费者提供,例如 React、Vue。
  • optionalDependencies 也属于运行时依赖,只是可选安装。
  • devDependencies 默认只用于构建或测试,不应假设消费者环境有它。

如果用户确实希望 external devDependency,可以显式配置 autoExternal.devDependenciesoutput.externals

哪些 format 默认启用 autoExternal

Rslib 中 getAutoExternalDefaultValue 根据 format 判断默认值。中间库产物 format 更倾向 external,最终 bundle format 不一定 external。

通常:

  • ESM/CJS 这类 library intermediate format 默认 external。
  • UMD/IIFE/MF 这类最终运行产物默认更倾向 bundle,除非用户显式 external。

这是因为 ESM/CJS 常发布到 npm,被下游 bundler 或 Node 消费;UMD/IIFE 常直接给浏览器消费,默认把依赖打进去更符合预期。

subpath import

Auto external 不只匹配包根:

import React from 'react';
import { jsx } from 'react/jsx-runtime';

如果 react 被 external,react/jsx-runtime 也应该 external。Rslib 为每个依赖生成:

  • 字符串 external:react
  • 正则 external:^react($|/|\\)

这样 subpath import 能一起匹配。

用户 externals 优先

用户显式配置 output.externals 时,优先级高于 autoExternal。典型例子:

output: {
  externals: {
    react: 'react-custom',
  },
}

如果 autoExternal 仍然再生成 react,就会有冲突。Rslib 在用户 externals 是 object 时会把这些 key 从 autoExternal 里排除。

externalsType 按 format 变化

同样 external 一个 react,不同 format 的输出语义不同:

formatexternalsType大致产物
ESMmodule-importimport react from "react"
CJScommonjs-importrequire("react")
UMDumdUMD wrapper external
IIFEglobal从 globalThis 查
MFglobalfederation 场景下 global

所以 external 不只是包名列表,还要配合 format。

ESM 下 commonjs issuer warning

Rslib 有一个看起来很特殊的 warning:当 ESM format 中,CommonJS issuer external 了某个 request,可能提示用户设置 external type。

背景是,源码可能包含:

const React = require('react');

但输出是 ESM format,external type 是 module-import。这会让 CommonJS request 以 ESM external 方式处理,可能不符合用户预期。

Rslib 不直接禁止,而是 warning,提示用户如果需要其他 external type,就显式设置 output.externals

Phantom dependency 风险

Bundleless external 中有一段注释提到 phantom dependency。场景是:

import React from 'react';

如果 react 能从本地 node_modules resolve,但 package.json 没有声明它,构建时可能看起来成功。发布后消费者安装你的包,不一定有 react,产物就坏了。

Rslib 在 resolver 失败时会保留原 request 并 debug 提示用户把 npm 包加到 dependencies 或 peerDependencies。这是“发布后正确性”的防护。

修改风险

改 autoExternal 时必须检查:

  • dependencies、peerDependencies、optionalDependencies、devDependencies。
  • subpath import。
  • 用户 externals object/string/array。
  • ESM/CJS output。
  • bundleless 与 bundle。
  • dts bundle 的 bundledPackages。

尤其要注意:JS external 和 dts external 应保持语义一致,否则 JS 产物和类型产物边界不同。