如何同时支持es模块和commonjs模块

Nor*_*mal 18 javascript node.js

我们从 npm 安装的一些包同时支持 commonjs 和 es 模块,

这些包可以按如下方式导入:

import express from 'express'
// or
const express = require('express')
Run Code Online (Sandbox Code Playgroud)

我创建了一个包,已使用 es 模块将其发布到 npm

因为我正在开发的另一个项目是用 commonjs 构建的,所以我意识到我不能使用以下语法来要求它:

const stackPlayer = require('stack-player')
Run Code Online (Sandbox Code Playgroud)

我怎样才能支持我的包stack-player中的两个模块系统,以便世界各地的每个人都可以使用它?

  • 除了将我的所有项目转换为 es 模块之外,还有其他方法吗(这太复杂了,因为该项目已有 1 年历史,而且足够大,无法拒绝这个想法)。?

Aur*_*ast 21

主要有两种场景:

1.你的包是使用 CommonJS (CJS) 模块加载编写的

这意味着您的包用于require()加载依赖项。对于这种包,不需要特殊的工作来支持在 ES 和 CJS 模块中加载包。ES 模块能够通过该import语句加载 CJS 模块,但需要注意的是仅支持默认导入语法。CJS模块可以通过该require()函数加载其他CJS模块。所以ES模块和CJS模块都能够加载CJS模块。

2.你的包是使用ES模块加载编写的

这意味着您的包用于import加载依赖项。但不要被愚弄 - 有时,尤其是在使用 TypeScript 时,您可能正在编写import代码,但它会在require()幕后编译。

不幸的是,CommonJS 模块不支持加载 ES 模块,除非(在 Node.js 中)使用该import()函数(这有点痛苦,不是一个很好的解决方案)。

为了在这种情况下支持 CommonJS,最好的选择是将包转换为 CommonJS 模块,并同时提供包的 CommonJS 和 ESM 版本。

我在自己的一些包中主要使用 Rollup 来完成此操作,这使得它相对容易。

基本概念是这样的:

  1. 将您的包编写为 ES 模块。
  2. 安装汇总:npm i -D rollup
  3. 运行npx rollup index.js --file index.cjs --format cjs将您的代码转换为 CJS 模块。
  4. 从 package.json 中导出两者:
{
  "name": "my-package",
  "version": "1.0.0",
  "main": "index.js",
  "type": "module",
  "exports": {
    "import": "./index.js",
    "require": "./index.cjs"
  }
}
Run Code Online (Sandbox Code Playgroud)

这样,CJS 模块加载器知道加载您的index.cjs文件,而 ESM 加载器知道加载您的index.js文件,两者都很高兴。


Chr*_*her 7

require() 用法

require()默认情况下,只能在CommonJS Modules中使用。将 ECMAScript 模块导入 CommonJS 的内置方法是使用import(pathToFile).then(module => { }).

支持 require()

如果您想支持require()您的包,则必须提供CommonJS 模块。

这是一个功能示例,演示了何时以及如何使用require()or import()import()与 ECMAScript 模块相比,CommonJS 模块的工作方式存在一些细微的差异。特别是,当在import()导出带有module.exports.

index.js它导入不同的模块类型(来自上面的演示):(
以防 stackblitz 演示将被删除:)

// executed as CommonJS module
console.time('');
import('./lib/example.cjs').then(({ default: example }) => {
  console.timeLog('', 'import cjs', example() == 'Foo'); // true
});
import('./lib/index.mjs').then(({ example }) => {
  console.timeLog('', 'import mjs', example() == 'Foo'); // true
});
try {
  const example = require('./lib/example.cjs');
  console.timeLog('', 'require cjs', example() == 'Foo'); // true
} catch (e) {
  console.timeLog('', 'require cjs', '\n' + e.message);
}
try {
  const example = require('./lib/index.mjs');
  console.timeLog('', 'require mjs', example() == 'Foo');
} catch (e) {
  console.timeLog('', 'require mjs', '\n' + e.message); // Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/lib/index.mjs not supported.
}
Run Code Online (Sandbox Code Playgroud)

lib/example.cjs

module.exports = function example() {
  return 'Foo';
};

Run Code Online (Sandbox Code Playgroud)

lib/index.mjs

import example from './example.cjs';
export { example };
export default example;
Run Code Online (Sandbox Code Playgroud)

包的条件导出

可以为要支持的包提供条件导出require(),例如在require()您的包不再支持 CommonJS 的情况下。请参阅此链接了解更多信息。

“exports”字段允许在通过 node_modules 查找或对其自身名称的自引用加载的名称导入时定义包的入口点。Node.js 12+ 支持它作为“main”的替代方案,可以支持定义子路径导出和条件导出,同时封装内部未导出的模块。

package.json(来自nodejs文档的示例)

{
  "exports": {
    "import": "./index-import.js",
    "require": "./index-require.cjs"
  },
  "type": "module"
}
Run Code Online (Sandbox Code Playgroud)

如果是这样,您必须提供两个脚本:一个用于 CommonJS ( "require": "filename"),另一个用于 ECMAScript 模块 ( "import": "filename")。

虽然必须通过或index-require.js提供脚本,但必须通过 提供脚本。exports = ...module.exports = ...index-import.jsexport default

关键词使用

您只能根据文件模块类型使用特定关键字。

CommonJS 模块
  • module.exports用于定义模块导出并可供其他模块使用的值。它可以设置为任何值,包括对象、函数或简单数据类型(如字符串或数字)。
  • exports,module
    • 如果您在 ECMAScript 模块内使用它们,您将收到未定义的错误。
  • require()
    • require()ECMAScript 模块内部是可能的,但是您必须使用此答案中提到的解决方法或查看以下文档module.createRequire(fileName)

      import { createRequire } from 'node:module';
      const require = createRequire(import.meta.url);
      
      // sibling-module.js is a CommonJS module.
      const siblingModule = require('./sibling-module');
      
      Run Code Online (Sandbox Code Playgroud)
    • 如果您require()从 ECMAScript 模块上的 CommonJS 中调用,则会抛出不支持的错误:

      Error [ERR_REQUIRE_ESM]: require() of ES Module /path/to/script.mjs not supported.
      
      Run Code Online (Sandbox Code Playgroud)

      根据情况提供更详细的错误消息:

      相反,将 /path/to/app.js 中 script.mjs 的 require 更改为所有 CommonJS 模块中都可用的动态 import() 。

      或者:

      /path/to/script.js 被视为 ES 模块文件,因为它是一个 .js 文件,其最近的父 package.json 包含 "type": "module" ,该文件将该包范围内的所有 .js 文件声明为 ES 模块。

      相反,将 /path/to/script.js 重命名为以 .cjs 结尾,更改所需代码以使用所有 CommonJS 模块中可用的动态 import(),或将 "type": "module" 更改为 "type": "commonjs " 在 /path/to/package.json 中将所有 .js 文件视为 CommonJS (对所有 ES 模块使用 .mjs 代替)。

ECMAScript 模块 (ESM)
  • export default用于导出单个值作为模块的默认导出。这允许以更简洁的方式导入值,因为导入语句在导入默认导出时可以省略大括号。
  • 另一方面,命名导出允许从模块导出多个值。命名导出使用export关键字,后跟标识符和值。( export const foo = "bar")
  • import ... from ...
    • 它可以处理 CommonJS 文件并解释它们,就像您使用require().

      基于express的示例:

      import express, { Route, Router } from 'express'; // EJS
      // is similar to:
      var express = require("express"), { Route, Router } = express; // CJS
      
      Run Code Online (Sandbox Code Playgroud)

CommonJS 和 ECMAScript 模块都支持该import()函数,但返回的对象可以具有 ESM 文件的更多属性。

概括:

CJS 模块不需要转换为 ESM,因为可以使用import ... from ...语法将它们导入 ESM,而无需对 CJS 模块进行任何修改。但是,建议使用 ECMAScript 模块语法编写新模块,因为它是 Web 和服务器端应用程序的标准,并且可以在浏览器/客户端和节点/服务器端无缝使用相同的代码。

此外,我发现logrocket.com 上关于 Node.js 中的 CommonJS 与 ES 模块的这篇文章内容非常丰富。它更深入地探讨了 ECMAScript 与 CommonJS 相比的优缺点。

链接: