使用 Node.js require 与 ES6 import/export

Using Node.js require vs. ES6 import/export

在我正在合作的项目中,我们有两种选择可以使用的模块系统:

  1. 使用 require 导入模块,使用 module.exportsexports.foo 导出模块。
  2. 使用 ES6 导入模块 import,并使用 ES6 导出 export

使用一个比另一个有任何性能优势吗?如果我们要使用 ES6 模块而不是 Node 模块,还有什么我们应该知道的吗?

语法上的主要优点:

  • 更多 declarative/compact 语法
  • ES6 模块基本上将使 UMD(通用模块定义)过时 - 基本上消除了 CommonJS 和 AMD(服务器与浏览器)之间的分裂。

您不太可能看到 ES6 模块的任何性能优势。即使浏览器完全支持 ES6 功能,您仍然需要一个额外的库来捆绑模块。

您可能需要考虑几种用途/功能:

要求:

  • 您可以在没有加载模块名称的情况下进行动态加载 预定义的/静态的,或者只有在以下情况下才有条件地加载模块的地方 它是 "truly required"(取决于特定的代码流)。
  • 正在加载 同步。这意味着如果您有多个 require,它们是 一个一个加载处理。

ES6 导入:

  • 你可以使用 命名导入以选择性地仅加载您需要的部分。这样可以 节省内存。
  • 导入可以是异步的(在当前的 ES6 模块加载器中,它实际上是异步的)并且可以执行得更好一些。

此外,Require 模块系统不是基于标准的。现在 ES6 模块已经存在,它不太可能成为标准。未来各种实现都会原生支持ES6 Modules,在性能上会有优势

更新

自 Node v12(2019 年 4 月)起,默认启用对 ES 模块的支持,自 Node v15(2020 年 10 月)起,它是稳定的(参见 here). Files including node modules must either end in .mjs or the nearest package.json file must contain "type": "module". The Node documentation 有更多信息,还有关于 CommonJS 之间的互操作和 ES 模块。

在性能方面,新功能总是有可能不如现有功能优化得那么好。然而,由于模块文件只被评估一次,性能方面可能会被忽略。最后你必须 运行 基准测试才能得到一个明确的答案。

ES模块可以通过import()函数动态加载。与 require 不同,这 returns 是一个承诺。


上一个回答

Are there any performance benefits to using one over the other?

请记住,目前还没有 JavaScript 原生支持 ES6 模块的引擎。你自己说你正在使用 Babel。无论如何,Babel 默认将 importexport 声明转换为 CommonJS (require/module.exports)。因此,即使您使用 ES6 模块语法,如果您 运行 Node.

中的代码,您也会在幕后使用 CommonJS

CommonJS 和 ES6 模块之间存在技术差异,例如CommonJS 允许您动态加载模块。 ES6 不允许这样做,but there is an API in development for that.

由于 ES6 模块是标准的一部分,我会使用它们。

使用 ES6 模块对 'tree shaking' 很有用;即启用 Webpack 2、Rollup(或其他捆绑器)来识别不是 used/imported 的代码路径,因此不要将其放入生成的捆绑包中。这可以通过消除您永远不需要的代码来显着减小其文件大小,但默认情况下与 CommonJS 捆绑在一起,因为 Webpack 等人无法知道是否需要它。

这是使用代码路径的静态分析完成的。

例如,使用:

import { somePart } 'of/a/package';

... 向捆绑器提示不需要 package.anotherPart(如果未导入,则无法使用 - 对吗?),因此它不会打扰捆绑它。

要为 Webpack 2 启用此功能,您需要确保您的转译器不会吐出 CommonJS 模块。如果你在 babel 中使用 es2015 插件,你可以在你的 .babelrc 中禁用它,如下所示:

{
  "presets": [
    ["es2015", { modules: false }],
  ]
}

Rollup 和其他人的工作方式可能有所不同 - 如果您有兴趣,请查看文档。

Are there any performance benefits to using one over the other?

目前的答案是否定的,因为 none 当前的浏览器引擎实现了 import/export ES6 标准。

一些比较图表 http://kangax.github.io/compat-table/es6/ 没有考虑到这一点,所以当您看到 Chrome 几乎所有果岭时,请小心。 import ES6 中的关键字未被考虑在内。

换句话说,包括V8在内的当前浏览器引擎无法从主JavaScript文件[=59]导入新JavaScript文件 =] 通过任何 JavaScript 指令。

(距离 V8 根据 ES6 规范实现它可能还需要 a few bugs away 或几年的时间。)

这个document is what we need, and this document是我们必须遵守的

ES6 标准规定,在我们读取模块之前,模块依赖项应该存在,就像在编程语言 C 中一样,我们有(头文件).h 个文件。

这是一个经过充分测试的好结构,我相信创建 ES6 标准的专家已经牢记这一点。

这使得 Webpack 或其他包打包器能够在某些特殊 情况下优化包,并减少包中不需要的一些依赖项。但在我们有完美依赖的情况下,这永远不会发生。

import/export 原生支持上线还需要一些时间,而且 require 关键字很长一段时间都不会去任何地方。

什么是 require

这是node.js加载模块的方式。 ( https://github.com/nodejs/node )

Node 使用系统级方法读取文件。使用 require 时,您基本上依赖于它。 require 将在某些系统调用中结束,例如 uv_fs_open(取决于最终系统,Linux、Mac、Windows)以加载 JavaScript file/module.

要检查这是真的,请尝试使用 Babel.js,您会看到 import 关键字将转换为 require

我个人使用 import,因为我们可以使用 import 来导入所需的方法和成员。

import {foo, bar} from "dep";

文件名: dep.js

export foo function(){};
export const bar = 22

感谢 Paul Shan。 More info.

当谈到异步或延迟加载时,import () 更强大。看看我们何时以异步方式需要组件,然后我们以某种异步方式使用 import 它,就像在 const 变量中使用 await.

const module = await import('./module.js');

或者如果你想使用 require() 那么,

const converter = require('./converter');

事情是 import() 实际上是异步的。正如 neehar venugopal 在 ReactConf 中提到的,您可以使用它为客户端架构动态加载 React 组件。

在路由方面也更好。当用户连接到特定网站到其特定组件时,这是使网络日志下载必要部分的一件特殊事情。例如仪表板之前的登录页面不会下载仪表板的所有组件。因为当前需要的是登录组件,所以只会下载它。

export 也是如此:ES6 export 与 CommonJS module.exports.

完全相同

注意 - 如果您正在开发一个 node.js 项目,那么您必须严格使用 require() 因为节点会抛出异常如果您将使用 import ,则错误为 invalid token 'import' 。所以节点不支持导入语句。

更新 - 正如 Dan Dascalescu: Since v8.5.0 (released Sep 2017), node --experimental-modules index.mjs lets you use import without Babel. You can (and should) also publish your npm packages as native ESModule, with backwards compatibility 对旧 require 方式的建议。

查看此内容以获得更多使用异步导入的许可 - https://www.youtube.com/watch?v=bb6RCrDaxhw

最重要的是要知道 ES6 模块确实是官方标准,而 CommonJS (Node.js) 模块不是。

2019年84% of browsers. While Node.js puts them behind an --experimental-modules flag, there is also a convenient node package called esm支持ES6模块,集成顺畅

您可能 运行 在这些模块系统之间遇到的另一个问题是代码位置。 Node.js 假定源代码保存在 node_modules 目录中,而大多数 ES6 模块部署在平面目录结构中。这些不容易调和,但可以通过使用预安装脚本和 post 安装脚本破解您的 package.json 文件来完成。这是一个示例 isomorphic module and an article 解释它是如何工作的。

不确定为什么(可能是优化 - 延迟加载?)它是这样工作的,但我注意到如果不使用导入的模块,import 可能无法解析代码。
在某些情况下,这可能不是预期的行为。

将讨厌的 Foo class 作为我们的示例依赖。

foo.ts

export default class Foo {}
console.log('Foo loaded');

例如:

index.ts

import Foo from './foo'
// prints nothing

index.ts

const Foo = require('./foo').default;
// prints "Foo loaded"

index.ts

(async () => {
    const FooPack = await import('./foo');
    // prints "Foo loaded"
})();

另一方面:

index.ts

import Foo from './foo'
typeof Foo; // any use case
// prints "Foo loaded"

截至目前 ES6 导入、导出为 always compiled to CommonJS,因此使用其中一个 没有任何好处。尽管推荐使用 ES6,因为当浏览器发布本机支持时它应该是有利的。原因是,您可以从一个文件导入部分,而使用 CommonJS 则必须要求所有文件。

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

以下是它们的常见用法。

ES6 导出默认值

// hello.js
function hello() {
  return 'hello'
}
export default hello

// app.js
import hello from './hello'
hello() // returns hello

ES6导出多个并导入多个

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
export { hello1, hello2 }

// app.js
import { hello1, hello2 } from './hello'
hello1()  // returns hello1
hello2()  // returns hello2

CommonJS module.exports

// hello.js
function hello() {
  return 'hello'
}
module.exports = hello

// app.js
const hello = require('./hello')
hello()   // returns hello

CommonJS module.exports 多个

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
module.exports = {
  hello1,
  hello2
}

// app.js
const hello = require('./hello')
hello.hello1()   // returns hello1
hello.hello2()   // returns hello2
  • ES 模块是静态的,这意味着导入是在每个模块的顶层描述的,并且在任何控制流语句之外。这行不通:

    if (condition) {
       import module1 from 'module1'
    }
    

但是在commonjs中,是允许的:

if (condition) {
    module = require('module1')
}
  • ES 模块 运行 隐含在 strict mode 中。这意味着我们不必在每个文件的开头显式添加“use strict”语句。不能禁用严格模式;因此,我们不能使用未声明的变量或 with 语句或具有仅在 non-strict 模式下可用的其他功能。 strict mode是更安全的执行方式。

  • 在 ESM 中,一些重要的 CommonJS 引用没有定义。这些包括 require , exports , module.exports , __filename,__dirname.

  • 我们可以使用标准导入语法从 ESM 导入 CommonJS 模块。但只有 default exports 有效:

       import packageName from 'commonjs-package' // Works
      import { moduleName } from 'commonjs-package' // Errors
    

但是,无法从 CommonJS 模块导入 ES 模块。

  • ESM 无法将 JSON 文件直接作为模块导入,这是 CommonJS 经常使用的功能。这就是为什么在 reactjs 中使用 fetch api。

    import data from './data.json' //fails