使用Node.js需要与ES6导入/导出

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

  1. 使用require导入模块,使用module.exportsexports.foo导出。
  2. 使用ES6import导入模块,使用ES6export导出

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

774189 次浏览

主要优点是句法:

  • 更具声明性/紧凑的语法
  • ES6模块基本上会使UMD(通用模块定义)过时-基本上消除了Common JS和AMD(服务器与浏览器)之间的分裂。

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

您可能需要考虑以下几种用法/功能:

要求:

  • 您可以动态加载,其中加载的模块名称不是预定义的 /static,或者仅在以下情况下有条件地加载模块它是“真正必需的”(取决于某些代码流)。
  • 正在加载同步。这意味着如果您有多个require,它们是一个接一个地加载和处理。

ES6进口:

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

此外,需要模块系统不是基于标准的。现在ES6模块存在,它不太可能成为标准。未来将在各种实现中原生支持ES6模块,这在性能方面将是有利的。

更新

自Node v12(2019年4月)以来,默认启用了对ES模块的支持,自Node v15(2020年10月)以来,它是稳定的(参见这里)。包括节点模块的文件必须以.mjs结尾,或者最近的package.json文件必须包含"type": "module"节点留档有更多信息,也是关于Common JS和ES模块之间的互操作。

性能方面总是有可能新功能不如现有功能优化得好。然而,由于模块文件只评估一次,性能方面可能可以忽略。最终你必须运行基准测试来获得明确的答案。

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


以前的答复

使用一个比另一个有什么性能优势吗?

请记住,目前还没有原生支持ES6模块的JavaScript引擎。你自己说你使用的是Babel。默认情况下,Babel会将importexport声明转换为Common JS(require/module.exports)。所以即使你使用ES6模块语法,如果你在Node中运行代码,你也将在幕后使用Common JS。

有一些技术上的差异,例如,在ES6中,ES6不允许动态加载模块。

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

使用ES6模块对“树摇动”很有用;即启用Webpack 2、Rollup(或其他捆绑程序)来识别未使用/导入的代码路径,因此不会将其放入生成的bundle中。这可以通过消除您永远不需要的代码来显着减少其文件大小,但默认情况下是捆绑的,因为Webpack等人无法知道是否需要它。

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

例如,使用:

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

…给捆绑器一个提示package.anotherPart不是必需的(如果它没有导入,它就不能使用-对吗?),所以它不会打扰捆绑它。

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

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

汇总和其他人的工作方式可能不同-如果你感兴趣,请查看文档。

使用一个比另一个有什么性能优势吗?

目前的答案是否定的,因为目前的浏览器引擎都没有实现ES6标准中的import/export

一些比较图表http://kangax.github.io/compat-table/es6/没有考虑到这一点,所以当你看到几乎所有的绿色Chrome时,要小心。

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

(在V8根据ES6规范实现之前,我们可能仍然只有几个虫子就行了或几年的时间。)

文档是我们需要的,这文档是我们必须遵守的。

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文件/模块。

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

在此处输入图片描述

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

import {foo, bar} from "dep";

文件名dep.js

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

图片来源:保罗·山更多信息

当涉及到异步或延迟加载时,那么import ()要强大得多。看看当我们以异步方式需要组件时,然后我们以某种异步方式使用import,就像使用awaitconst变量一样。

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

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

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

事情是import()实际上是异步的。正如响应配置中的Neehar venugopal所提到的,您可以使用它来动态加载客户端架构的响应组件。

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

export也是如此:ES6export与Common JSmodule.exports完全相同。

-如果你正在开发一个node.js项目,那么你必须严格使用#0,因为如果你使用#2,节点将抛出异常错误#1。所以节点不支持导入语句。

更新-根据Dan Dascalescu的建议:自v8.5.0(2017年9月发布)以来,node --experimental-modules index.mjs允许您在没有Babel的情况下使用import。您还可以(并且应该)将您的npm包发布为本机ESModul,并向后兼容旧的require方式。

有关在何处使用异步导入的更多信息,请参阅此处-https://www.youtube.com/watch?v=bb6RCrDaxhw

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

在2019年,ES6模块得到了84%浏览器的支持。虽然Node.js将它们放在--实验-模块标志后面,但还有一个名为esm的方便节点包,这使得集成变得顺畅。

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

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

以讨厌的Foo类作为我们的示例依赖项。

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导入,导出是总是编译到Common JS,所以有没有好处使用一个或另一个。尽管建议使用ES6,因为当浏览器的本机支持发布时它应该是有利的。原因是,您可以从一个文件导入部分,而使用Common JS,您必须需要所有文件。

ES6→import, export default, export

使用JSrequire, module.exports, exports.foo

下面是它们的常见用法。

ES6导出默认值

// hello.jsfunction hello() {return 'hello'}export default hello
// app.jsimport hello from './hello'hello() // returns hello

ES6导出倍数和导入倍数

// hello.jsfunction hello1() {return 'hello1'}function hello2() {return 'hello2'}export { hello1, hello2 }
// app.jsimport { hello1, hello2 } from './hello'hello1()  // returns hello1hello2()  // returns hello2

module.exports

// hello.jsfunction hello() {return 'hello'}module.exports = hello
// app.jsconst hello = require('./hello')hello()   // returns hello

module.exports多个

// hello.jsfunction hello1() {return 'hello1'}function hello2() {return 'hello2'}module.exports = {hello1,hello2}
// app.jsconst hello = require('./hello')hello.hello1()   // returns hello1hello.hello2()   // returns hello2
  • ES模块是静态的,这意味着导入在每个模块的顶层和任何控制流语句之外进行描述。这将不起作用:

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

但在普通情况下,它是允许的:

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

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

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

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

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

  • ESM不能直接将JSON文件作为模块导入,这是一个在Common JS中经常使用的功能。这就是为什么在reactjsfetch中使用api。

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