Shims 与运行时兼容

为什么需要 shims

库源码可能混用 ESM 和 CJS 的运行时变量:

import.meta.url;
import.meta.dirname;
import.meta.filename;
__dirname;
__filename;
require;

当源码输出为另一种模块格式时,这些变量可能不存在。Rslib 的 shims 用来弥合部分差异。

默认值

默认 shims:

{
  cjs: {
    'import.meta.url': true,
    'import.meta.dirname': true,
    'import.meta.filename': true,
  },
  esm: {
    __filename: false,
    __dirname: false,
    require: false,
  },
}

这个默认值体现了一个判断:CJS 输出兼容 ESM 源码中的 import.meta.* 是常见需求;ESM 输出主动模拟 CJS 全局变量更激进,所以默认关闭。

CJS 中的 import.meta.url

CJS 没有 import.meta.url。Rslib 会通过 EntryChunkPlugin 注入:

const __rslib_import_meta_url__ = (function () {
  return typeof document === 'undefined'
    ? new (require('url').URL)('file:' + __filename).href
    : (document.currentScript && document.currentScript.src) ||
        new URL('main.js', document.baseURI).href;
})();

这段 shim 同时考虑 Node 和浏览器脚本环境。

为什么注入位置要小心

JS 文件可能以 shebang 开头:

#!/usr/bin/env node

也可能以 strict directive 开头:

'use strict';

shim 插入不能破坏它们。EntryChunkPlugin 会:

  • shebang 后插入。
  • "use strict" 后插入。
  • 其他情况插到文件开头。

这个细节如果处理错,可执行 CLI 或严格模式语义会坏。

ESM 中的 require

如果用户开启:

shims: {
  esm: {
    require: true,
  },
}

Rslib 会注入基于 module.createRequire 的 shim:

import * as __rslib_shim_module__ from 'module';
const require = __rslib_shim_module__.createRequire(import.meta.url);

这让 ESM 产物中可以使用 CommonJS require,但它会引入 Node 运行时假设,因此默认不开。

ESM 中的 **dirname 和 **filename

Rspack 在 output.module = true 时会自动涉及 __dirname__filename 的处理。Rslib 只有在用户开启对应 shim 时设置 node 选项为 node-module,否则保持 false。

这避免 ESM 产物无意中包含 CJS 兼容层。

disable URL parse

Rslib 还有一个 disableUrlParseRsbuildPlugin,用于阻止 ESM format 中 new URL() 被解析并尝试打包。这个修补来自具体 issue,目的是避免库产物中一些 URL 构造被应用构建式 asset 解析误处理。

它和 import.meta.url shim 都属于 runtime 兼容修补,但关注点不同。

修改风险

Shims 相关改动要检查:

  • CJS 输出。
  • ESM 输出。
  • shebang。
  • "use strict"
  • Node target。
  • web target。
  • bundleless runtime chunk。
  • import.meta.url asset 用法。

这类问题通常表现为运行时错误,不一定构建失败。