外部依赖与包支持

Externals 机制

概念

Externals 告诉 ncc 跳过指定模块的打包,保留为运行时 require()import 调用。典型用例:

  • 原生模块(.node 文件)无法打包
  • 可选依赖不希望包含在产物中
  • 运行时才确定的模块

实现

externals 处理分为两层:

1. ExternalMap(src/index.js:164-201

const externalMap = (() => {
  const regexps = [];
  const aliasMap = new Map();
  const regexCache = new Map();
  return { get, set };
})();

支持精确匹配和正则匹配,正则结果有缓存。

2. Webpack externals 函数(src/index.js:329-333

externals({ context, request, dependencyType }, callback) {
  const external = externalMap.get(request);
  if (external) return callback(null, `${type} ${external}`);
  return callback();
}

根据 dependencyType 决定输出为 module 还是 node-commonjs

使用示例

# CLI
ncc build input.js -e express -e pg

# 正则模式(在 API 中)
externals: { "/^@aws-sdk\\/(.*)$/": "@aws-sdk/$1" }

未找到模块处理

当模块在构建时无法解析(不在 node_modules 中、路径错误等),ncc 不会报错,而是将其转为运行时 require()

构建时: require('some-optional-dep')

解析失败 → 重定向到 @@notfound.js?some-optional-dep

notfound-loader 处理

运行时: __non_webpack_require__('some-optional-dep')

这与 externals 的区别:externals 是显式声明的,未找到模块是隐式的(解析失败时自动处理)。

常见包适配

项目包含 package-support.md 文件,记录了需要特殊处理的常见包。以下是从集成测试中提取的信息:

已验证的包

test/integration/ 中可以看到 ncc 成功测试了以下包的编译:

类别包名
Web 框架express, koa, apollo-server-express
数据库mongoose, pg, mysql, mariadb, sequelize, ioredis
云服务@aws-sdk/client-s3, firebase, firebase-admin, @google-cloud/bigquery
认证auth0, passport
HTTP 客户端axios, got, isomorphic-unfetch
消息/通信socket.io, twilio, @slack/web-api
监控@sentry/node, @bugsnag/js, hot-shots
支付stripe
图像处理sharp, jimp, canvas
模板引擎pug, consolidate
工具库rxjs, core-js, esm

需要 external 的包

某些包因为使用原生模块或极度动态的加载模式,需要标记为 external:

# 原生模块
ncc build app.js -e sharp -e canvas

# 动态加载
ncc build app.js -e oracledb

被自动过滤的包

empty-loader.js 中硬编码了以下包的过滤规则:

  • uglify-js — 不可静态分析
  • uglify-es — 同上

这些包在构建时会被替换为空模块,并输出警告建议使用 --external

@vercel/webpack-asset-relocator-loader

这是 ncc 的核心依赖之一,负责:

  1. 静态分析文件引用__dirname__filenamepath.join(__dirname, ...)
  2. 资源重定位:将引用的文件复制到输出目录,更新引用路径
  3. 动态 require 处理:尝试分析动态 require() 表达式
  4. 二进制文件处理.node 原生模块的复制和引用

配置选项

选项说明
filterAssetBase只处理此目录内的资源
existingAssetNames避免文件名冲突
escapeNonAnalyzableRequires无法分析的 require 保留为运行时调用
wrapperCompatibility兼容各种模块包装模式
customEmit用户自定义资源发射逻辑

API

方法说明
initAssetCache(compilation)初始化资源缓存(每次编译调用)
getAssetMeta(path, compilation?)获取资源的元数据(路径、权限)
getSymlinks()获取所有检测到的符号链接

pnpm patch

# pnpm-workspace.yaml
patchedDependencies:
  unfetch@5.0.0: patches/unfetch@5.0.0.patch

unfetch 包有一个 patch,修复其与 ncc 的兼容性问题。