TypeScript 帮助作者创建双CommonJS/ES模块包

bqjvbblv  于 4个月前  发布在  TypeScript
关注(0)|答案(8)|浏览(43)

建议

🔍 搜索词

双 CommonJS / CJS + ES module / ESM 包

✅ 可实现性检查清单

我的建议符合以下准则:

  • 这不会对现有的 TypeScript/JavaScript 代码造成破坏性的改变
  • 这不会改变现有 JavaScript 代码的运行时行为
  • 这可以在不根据表达式的类型生成不同的 JS 的情况下实现
  • 这不是一个运行时特性(例如库功能、带有 JavaScript 输出的非 ECMAScript 语法、新的 JS 语法糖等)
  • 这个特性将与 TypeScript's Design Goals 的其他部分保持一致。

最后一个复选框未勾选,因为这个功能请求是关于扩展 tsc 在构建管道中的作用,并且它是关于与 Node 的行为保持一致,而不是 ECMAScript 的行为,后者没有包的概念。话虽如此,Node 是 TypeScript 的两个主要目标之一(另一个是 Web)。

⭐ 建议

这个建议更抽象,而不是一个精确的实现提案。问题在于,为 Node 编写双 CJS/ESM 包目前无论是有还是没有 TypeScript,都存在相当大的摩擦,而 tsc 可以做得更好。我理解如果这个问题被认为是超出范围并关闭的话。
我在写这个假设阅读此内容的人了解 Node 16 中的 ESM 以及它们的约束条件。
使用 "module": "nodenext",TypeScript 通过使用带有 .js 扩展名的 import specifiers 使 .ts 文件可以导入其他 .ts 文件,以便浏览器和 Node 可以运行生成的代码。也就是说,当 tsc 看到 import './x.js'; 时,它知道要查找 ./x.ts 以解析模块。这样,tsc 就不需要重写 import specifier 并可以在编译后的 JS 代码中发出 import './x.js';
单独地,双 CJS/ESM 包需要它们的一些文件使用 .mjs 或 .cjs 文件扩展名。这是因为包声明了 "type": "module" 或者 "type": "commonjs",而其他类型的文件需要分别使用 .cjs 或 .mjs。(顺便说一下,这个问题无论包是否使用 TypeScript都存在。即使它们没有使用 TypeScript,编写 ESM 作者也需要复制或编译他们的 ESM 以生成 CJS。)
这个功能请求是让 tsc 能够发出带有 .js 文件扩展名重写的编译后的 JS 以及带有类似重写的 import specifiers。这样一来,包的源代码就是它的 .ts 文件,运行例如 tsc && tsc -p tsconfig.commonjs.json 将为双模式包生成 ESM 和 CJS。

📃 有动力的例子

考虑一个由多个具有内部包导入的文件组成的包。

hello-world.ts

import b from './b.js';
export default function helloWorld() { return `${b()} world`; }

hello.ts

export default function hello() { return `hello`; }

作者希望发布该包的 CJS 和 ESM:

package.json

{
  "type": "module",
  "//": "Using longhand notation below for clarity"
  "exports": {
    ".": {
      "import": "./build/esm/hello-world.js",
      "require": "./build/cjs/hello-world.cjs",
      "types": "./build/esm/hello-world.d.ts"
    }
  }
}

即使涉及两个 tsconfig.json 文件(例如 tsconfig.json 和 commonjs.json,后者扩展前者并覆盖一些选项),能够运行两次 tsc 以生成可用的 ESM 和 CJS 将是有用的。换句话说,在这个例子中,发出的 CJS 需要 .cjs 文件扩展名和带有 cjs import specifiers,因为 package.json 声明了 "type": "module"

thtygnil

thtygnil1#

我正在写这篇文章,假设阅读这篇文章的人了解Node 16中的ESM工作及其约束。
我认为你的受众可能比你想象的要小得多🙃
使用"module": "nodenext",TypeScript允许.ts文件通过使用具有.js扩展名的import specifiers来导入其他.ts文件,以便浏览器和Node可以运行生成的代码。也就是说,当tsc看到import './x.js';时,它知道要查找./x.ts来解析模块。这样,tsc不需要重写import specifier,并可以在编译后的JS代码中发出import './x.js';
重要提示:

  • 在每个--module模式下都是如此,只是在nodenext中强制执行,因为在那里我们可以自信地说其他任何事情都是错误的。
  • 在CommonJS中指定扩展名是有效的,但不是必需的。
  • 你可能知道“不需要重写import specifier”不仅仅是一个便利,而是一个设计原则,这让一些人感到失望和愤怒。

所以我认为这个
这个功能请求是让tsc能够发出带有.js文件扩展名重写的已编译JS
是我们不打算涉足的领域。
然而,我认为这个问题是我们应该意识到并有一些指导方针的问题,并理解现有的工具。我的理解是这是Rollup的主要用途之一。你尝试过吗?体验如何?
我还知道@weswigham对此有一些特定的建议(即创建一个最小化的ESM Package 器,围绕CJS源真相,以及考虑是否真的需要特定的ESM入口点)。

zengzsys

zengzsys2#

这是在每个模块模式下都是真实的,只是在nodenext中被强制执行,因为我们可以自信地说其他任何事情都是错误的。
我不知道这个,很有帮助。
说实话,在接触到ESM/CJS情况的细节后,我理解这些是TypeScript团队不得不面对的难题。一个包是否使用TypeScript,它需要一些构建过程来从ESM文件派生CJS文件,以便为在ESM中编写的双模式包提供支持。
考虑如何编写一个没有TypeScript的包。我想用原生ESM编写代码,期望它是JS生态系统的发展方向。我也想让我的包在CJS和ESM项目中都能工作,直到我的包消费者中有足够多的人转向ESM,因此制作了一个双模式包。据我所知,主要源代码用ESM编写的双模式包不能使用CJS Package 器,因为CJS只能异步地将ESM转换为CJS。因此,这类双模式包需要将ESM转换为CJS(并重写.js import s为.cjs require() s),并发布ESM和CJS版本的代码。
这与TypeScript完全无关,如果我是TypeScript团队的话,我会认为这是超出范围的。可以说,将事物建模的方式是tsc负责将TS编译为ESM,问题简化为上述情况:在非TypeScript项目中使用任何工具将ESM编译为CJS。
我没有尝试过Rollup。我的观点是,我希望只有一个工具来处理这个问题。我怀疑一年后有了像Bun或Rome这样的东西会更好,祝好运。

bttbmeg0

bttbmeg03#

我知道你写过,你想用一个工具(tsc)来处理这个问题,但也许https://github.com/AlCalzone/esm2cjs可以解决你当前的问题。这是我在遇到同样问题时创建的一个小型 Package 器——让tsc输出ESM,并以某种方式将其转换为混合的ESM和CJS包。

lfapxunr

lfapxunr4#

我认为Rollup(或者AlCalzone的工具)就像Bun或Rome一样,是“一个工具”,除非你认为罗马也会取代TypeScript的类型检查器。但我认为这些工具的大部分想法是tsc只进行类型检查,而其他东西负责产生输出,无论是简单的逐文件转换还是任何数量的转换和捆绑在一起。

wwwo4jvm

wwwo4jvm5#

如果在"module": "node16""module": "nodenext"中,TypeScript编译器可以根据package.json中的"module"设置输出ESM和CommonJS,而不是仅输出其中一个,那就太好了。

nwlqm0z1

nwlqm0z16#

我认为4.7犯了一个小错误,即如果tsc具有module: nodenext, outputDir: dist/cjs,其中dist/cjs具有一个带有type: commonjspackage.json,但根package.jsontype: module,输出将是esm...

yqhsw0fo

yqhsw0fo7#

更现代的方法是使用package.json中的新"exports"属性来发布双ESM/CommonJS包。(为了获得最广泛的支持,您可能还需要在顶部指定module和/或browser,支持程度差异很大)

i7uaboj4

i7uaboj48#

我原本打算创建一个关于更好支持混合式发射的问题,但现在已经有了。
我在 https://kuhrt.me/logs/hybrid-esm-cjs-node-packages-using-typescript 处记下了我的处理方法。
我为 ESM 编写了库,然后要通过构建脚本将所有文件转换为 .cjs 格式,并相应地更改文件中的导入路径。
这感觉像是一个很大的技巧,TypeScript 应该以某种方式支持这种模式。

相关问题