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


使用require导入模块,以及使用module.exportsexports.foo导出。
导入使用ES6 import的模块,以及使用ES6 export导出的模块


使用一个模块对另一个模块是否有性能优势?如果要在Node模块上使用ES6模块,还有其他什么要知道的吗?

评论

node --experimental-modules index.mjs使您可以在不使用Babel的情况下使用导入,并且可以在Node 8.5.0+中使用。您还可以(并且应该)将npm软件包发布为本地ESModule,并与旧的require方法具有向后兼容性。

#1 楼


使用它们之间是否有性能优势?您说自己正在使用Babel。不管怎样,Babel仍默认将importexport声明转换为CommonJS(require / module.exports)。因此,即使您使用ES6模块语法,如果您在Node中运行代码,您也会在后台使用CommonJS。
CommonJS和ES6模块之间存在技术差异,例如CommonJS允许您动态加载模块。 ES6不允许这样做,但是正在为此开发一个API。
由于ES6模块是标准的一部分,所以我会使用它们。

更新2020
以来Node v12对ES模块的支持默认情况下处于启用状态,但在撰写本文时仍处于试验阶段。包含节点模块的文件必须以.mjs结尾,或者最接近的package.json文件必须包含"type": "module"。 Node文档提供了大量有关CommonJS和ES模块之间互操作的信息。
在性能方面,总是存在新功能未如现有功能那样得到最佳优化的机会。但是,由于模块文件仅被评估一次,因此性能方面可能会被忽略。最后,您必须运行基准测试才能获得明确的答案。
ES模块可以通过import()函数动态加载。与require不同,这将返回一个承诺。

评论


我尝试将ES6导入与require一起使用,但是它们的工作方式有所不同。 CommonJS导出类本身,而只有一个类。 ES6导出就像有多个类一样,因此您必须使用.ClassName来获取导出的类。是否还有其他实际影响实施的差异

– Thethereimist
2015年12月19日在1:04
@Entei:好像您要默认导出,而不是命名导出。 module.exports = ...;等价于export default .... exports.foo = ...等价于export var foo = ...;

–费利克斯·克林(Felix Kling)
2015年12月19日在1:08



值得注意的是,即使Babel最终将转储文件导入到Node.com中的CommonJS中,并与Webpack 2 / Rollup(以及允许ES6树摇动的任何其他捆绑程序)一起使用,也可能最终得到一个比等效代码Node小得多的文件正是由于ES6允许对导入/导出进行静态分析这一事实,因此使用使用过程中的紧缩要求。尽管这对Node并没有什么影响,但如果最终将代码作为单个浏览器捆绑销售,肯定可以。

–李·本森(Lee Benson)
16年11月29日在19:17



除非您需要动态导入

– Chulian
17年2月28日在7:03

ES6模块位于最新的V8中,并且还带有其他标志。参见:medium.com/dev-channel/…

– Nexii Malthus
17年5月25日在12:17



#2 楼

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

需求:


您可以动态加载,而加载的模块名称不是
预定义的/ static,或仅在
“确实需要”时才有条件地加载模块(取决于某些代码流)。
加载是
同步的。这意味着如果有多个require,它们将被
加载和处理。

ES6导入:


可以使用
命名为import的商品可以有选择地仅加载您需要的商品。这样可以节省内存。
导入可以是异步的(在当前的ES6 Module Loader中,实际上是这样),并且可以执行得更好。

此外,Require模块系统不是基于标准的。由于存在ES6模块,因此极不可能成为标准。将来,将在各种实现中对ES6模块提供本机支持,这在性能方面将是有利的。

评论


是什么让您认为ES6导入是异步的?

–费利克斯·克林(Felix Kling)
15年7月11日在12:56

@FelixKling-各种观察结果的组合。使用JSPM(ES6 Module Loader ...),我注意到,当导入修改了全局名称空间时,在其他导入中不会观察到这种效果(因为它们是异步发生的。这在转码中也可以看到)。另外,由于这是行为(1个导入不会影响其他行为),因此没有理由不这样做,因此它可能取决于实现

–提交
15年7月11日在13:06

您提到了一个非常重要的东西:模块加载器。 ES6提供了导入和导出语法,但未定义模块的加载方式。重要的是声明是可以静态分析的,因此无需执行代码即可确定依赖关系。这将允许模块加载器同步或异步加载模块。但是ES6模块本身不是同步或异步的。

–费利克斯·克林(Felix Kling)
2015年7月11日14:18



@FelixKling ES6模块加载器在OP中被标记,因此我认为它与答案相关。我还说过,基于观察,异步是当前的行为,以及将来(在任何实现中)的可能性,因此这是需要考虑的相关问题。你认为这是错的吗?

–提交
2015年7月11日14:23



我认为重要的是不要将模块系统/语法与模块加载器混为一谈。例如,如果您为节点开发,那么无论如何您都可能会编译需要的ES6模块,因此无论如何您都在使用Node的模块系统和加载器。

–费利克斯·克林(Felix Kling)
2015年7月11日14:27



#3 楼

主要优点是语法:


更多的声明性/紧凑语法
ES6模块将基本上使UMD(通用模块定义)过时-实质上消除了CommonJS和AMD(服务器)之间的分裂VS浏览器)。

ES6模块不太可能带来任何性能上的好处。即使浏览器完全支持ES6功能,您仍将需要一个额外的库来捆绑模块。

评论


您能否阐明为什么即使浏览器具有完整的ES6模块支持也需要捆绑器吗?

– E. Sundin
16年7月3日在15:15

道歉,进行了更合理的编辑。我的意思是导入/导出模块功能未在任何浏览器中本地实现。仍然需要转译器。

– snozza
16年7月4日在9:23

在我看来似乎有点矛盾。如果有全力支持,那么捆扎机的目的是什么? ES6规范中缺少什么吗?在完全受支持的环境中,捆绑程序实际上会执行哪些操作呢?

– E. Sundin
16年7月4日在22:14

正如@snozza所说的那样……“导入/导出模块功能在任何浏览器中都没有天真的实现。仍然需要编译器”

–robertmain
17-10-13在1:35

您不再需要任何额外的库。从v8.5.0(一年多以前发布)开始,node --experimemntal-modules index.mjs允许您使用不带Babel的导入。您还可以(并且应该)将npm软件包发布为本地ESModule,并与旧的require方法具有向后兼容性。许多浏览器本身也支持动态导入。

– Dan Dascalescu
18-09-26在3:33

#4 楼


使用一个相对于另一个有任何性能上的好处吗?


当前的答案是否定的,因为当前的浏览器引擎都没有实现ES6标准的import/export。 />
一些比较表http://kangax.github.io/compat-table/es6/没有考虑到这一点,因此当您看到Chrome的几乎所有绿色时,请小心。未考虑来自ES6的import关键字。

换句话说,当前的浏览器引擎(包括V8)无法通过任何JavaScript指令从主JavaScript文件中导入新的JavaScript文件。 />(距离V8根据ES6规范实现这一目标可能还有几步之遥,或者距离我们还有数年之遥。)

ES6标准指出,在像读取程序语言C一样,在其中读取.h文件的模块之前,应该先存在模块依赖性。

此这是一个经过良好测试的结构,我相信创建ES6标准的专家会牢记这一点。 ,并减少不需要的依赖关系。但是在万一我们有完美的依赖关系的情况下,这将永远不会发生。

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



评论


实际上,有一个领域可以提高性能-捆绑包大小。在Webpack 2 /汇总构建过程中使用import可能会通过“摇晃”未使用的模块/代码来减少生成的文件大小,否则可能会最终捆绑到最终捆绑包中。较小的文件大小=下载速度更快=在客户端上初始化/执行速度更快。

–李·本森(Lee Benson)
16年11月29日在19:12

原因是地球上当前没有浏览器本地允许使用import关键字。或这意味着您无法从JavaScript文件导入另一个JavaScript文件。这就是为什么您无法比较这两者的性能优势的原因。但是,当然,Webpack1 / 2或Browserify之类的工具可以处理压缩。他们并驾齐驱:gist.github.com/substack/68f8d502be42d5cd4942

–prosti
16年11月29日在19:46



您正在忽略“摇树”。在要点链接的任何地方都没有讨论摇树。使用ES6模块可以启用它,因为导入和导出是静态声明,它们会导入特定的代码路径,而require可以是动态的,因此可以捆绑在未使用的代码中。性能收益是间接的-Webpack 2和/或汇总可以潜在地导致更小的捆绑包大小,从而更快地进行下载,因此对于(浏览器的)最终用户而言显得更为灵活。仅当所有代码都写在ES6模块中并且因此导入可以被静态分析时,这才起作用。

–李·本森(Lee Benson)
16-11-30在10:46



我更新了答案@LeeBenson,我认为,如果考虑到浏览器引擎的本机支持,我们还无法比较。甚至在我们设置CommonJS模块之前,也可以使用Webpack来实现方便的三摇选项,因为对于大多数实际应用程序,我们知道应该使用哪些模块。

–prosti
16 Dec 5'在21:57

您的答案完全正确,但我认为我们正在比较两个不同的特征。所有导入/导出都将转换为需求,授予。但是在此步骤之前发生的事情可以被认为是“性能”增强。示例:如果用ES6编写lodash并从lodash导入{omit},则最终捆绑包将仅包含'omit'而不包含其他实用程序,而简单的require('lodash')将导入所有内容。这将增加捆绑包的大小,需要更长的下载时间,从而降低性能。当然,这仅在浏览器上下文中有效。

–李·本森(Lee Benson)
16 Dec 6'在7:42

#5 楼

使用ES6模块对于“摇树”很有用;即启用Webpack 2,汇总(或其他捆绑程序)以标识未使用/导入的代码路径,因此不要将其放入生成的捆绑程序中。这可以通过消除不需要的代码来显着减小其文件大小,但是由于Webpack等无法知道是否需要,所以默认情况下捆绑了CommonJS。

这是使用静态分析完成的的代码路径。例如,使用以下代码:

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


...给捆绑程序提示不需要package.anotherPart(如果未导入,则无法使用-对吗,所以它不会打扰它。

要为Webpack 2启用它,您需要确保未安装Transpiler吐出CommonJS模块。如果您将es2015插件与babel一起使用,则可以在.babelrc中将其禁用,如下所示:

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


Rollup和其他功能可能会有所不同-查看文档如果您有兴趣。

评论


也非常适合摇树2ality.com/2015/12/webpack-tree-shaking.html

–prosti
16年2月2日,0:32

#6 楼

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

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


或者如果要使用require()然后,

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


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

在路由方面也更好。这是当用户将特定网站连接到其特定组件时使网络日志下载必要部分的一件事。例如仪表板无法下载仪表板的所有组件之前的登录页面。因为当前需要的东西(即登录组件)只能下载。

export也是如此:ES6 export与CommonJS module.exports完全相同。

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

UPDATE-正如Dan Dascalescu所建议:自v8.5.0(2017年9月发布)以来,node --experimental-modules index.mjs允许您使用不带Babel的import。您还可以(并且应该)将npm软件包作为本机ESModule发布,并具有对旧require方式的向后兼容性。

请参阅此内容,了解在哪里使用异步导入的更多权限-https://www.youtube .com / watch?v = bb6RCrDaxhw

评论


那么需求将被同步并等待吗?

–巴克拉赞
18-10-29在13:58

可以说实话!

–见Zaveri
18-10-29在14:14

#7 楼

从现在开始,ES6导入,导出始终被编译为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


评论


实际上,在使用CommonJS require时,您实际上也可以使用对象分解。这样就可以拥有:const {hello1,hello2} = require(“ ./ hello”);它将有点类似于使用导入/导出。

– Petar
9月15日9:08

#8 楼

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

在2019年,有84%的浏览器都支持ES6模块。尽管Node.js将它们放在--experimental-modules标志的后面,但还有一个方便的节点程序包esm,它使集成顺畅。

您可能会在这两个程序之间遇到另一个问题模块系统是代码位置。 Node.js假定源代码保存在node_modules目录中,而大多数ES6模块则以平面目录结构进行部署。这些都不容易调和,但是可以通过使用安装前和安装后脚本破坏package.json文件来完成。这是一个同构模块示例,并解释了其工作原理。

#9 楼

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

import {foo, bar} from "dep";


文件名:dep.js

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


贷给Paul Shan。更多信息。

评论


选的好!您是否还将npm软件包作为本机ESModule发布,并且具有对旧的require方法的向后兼容性?

– Dan Dascalescu
18-09-26在3:37

您可以使用require做同样的事情!

–瑞士
18-09-28在11:21

const {a,b} = require('module.js');也可以工作...如果您导出a和b

– BananaAcid
19 Mar 6 '19 at 5:04

module.exports = {a:()= {},b:22}-@BananaAcid的第二部分响应

–塞斯·麦克莱恩(Seth McClaine)
19-10-30在16:26

#10 楼

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

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


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"



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


另一方面:

index.ts

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