在我正在合作的一个项目中,我们有两种选择可以使用哪个模块系统:
require
module.exports
exports.foo
import
export
使用一个而不是另一个有什么性能优势吗?如果我们要使用ES6模块而不是Node模块,还有什么我们应该知道的吗?
主要优点是句法:
您不太可能看到ES6模块的任何性能优势。即使浏览器完全支持ES6功能,您仍然需要一个额外的库来捆绑模块。
您可能需要考虑以下几种用法/功能:
要求:
ES6进口:
此外,需要模块系统不是基于标准的。现在ES6模块存在,它不太可能成为标准。未来将在各种实现中原生支持ES6模块,这在性能方面将是有利的。
自Node v12(2019年4月)以来,默认启用了对ES模块的支持,自Node v15(2020年10月)以来,它是稳定的(参见这里)。包括节点模块的文件必须以.mjs结尾,或者最近的package.json文件必须包含"type": "module"。节点留档有更多信息,也是关于Common JS和ES模块之间的互操作。
.mjs
package.json
"type": "module"
性能方面总是有可能新功能不如现有功能优化得好。然而,由于模块文件只评估一次,性能方面可能可以忽略。最终你必须运行基准测试来获得明确的答案。
ES模块可以通过import()函数动态加载。与require不同,这返回一个承诺。
import()
使用一个比另一个有什么性能优势吗?
请记住,目前还没有原生支持ES6模块的JavaScript引擎。你自己说你使用的是Babel。默认情况下,Babel会将import和export声明转换为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不是必需的(如果它没有导入,它就不能使用-对吗?),所以它不会打扰捆绑它。
package.anotherPart
要为Webpack 2启用此功能,您需要确保您的转译器不会吐出Common JS模块。如果您使用es2015插件和Babel,您可以在.babelrc中禁用它,如下所示:
es2015
.babelrc
{"presets": [["es2015", { modules: false }],]}
汇总和其他人的工作方式可能不同-如果你感兴趣,请查看文档。
目前的答案是否定的,因为目前的浏览器引擎都没有实现ES6标准中的import/export。
import/export
一些比较图表http://kangax.github.io/compat-table/es6/没有考虑到这一点,所以当你看到几乎所有的绿色Chrome时,要小心。
换句话说,包括V8在内的当前浏览器引擎无法通过任何JavaScript指令从主JavaScript文件导入新的JavaScript文件。
(在V8根据ES6规范实现之前,我们可能仍然只有几个虫子就行了或几年的时间。)
这文档是我们需要的,这文档是我们必须遵守的。
ES6标准说,在我们读取模块之前,模块依赖关系应该存在,就像在编程语言C中一样,我们有(头).h文件。
.h
这是一个很好且经过良好测试的结构,我相信创建ES6标准的专家们已经考虑到了这一点。
这使得Webpack或其他包捆绑器能够在某些特别情况下优化捆绑包,并减少捆绑包中一些不需要的依赖项。但在我们有完美依赖关系的情况下,这永远不会发生。
它需要一些时间,直到import/export原生支持上线,require关键字在很长一段时间内不会去任何地方。
什么是require?
这是加载模块的node.js方式。(https://github.com/nodejs/node)
node.js
Node使用系统级方法读取文件。当使用require时,你基本上依赖它。require将以uv_fs_open(取决于最终系统、Linux、Mac、Windows)等系统调用结束以加载JavaScript文件/模块。
uv_fs_open
要检查这是否正确,请尝试使用Babel.js,您将看到import关键字将转换为require。
我个人使用导入,因为我们可以使用导入导入所需的方法、成员。
import {foo, bar} from "dep";
文件名dep.js
export foo function(){};export const bar = 22
图片来源:保罗·山更多信息
当涉及到异步或延迟加载时,那么import ()要强大得多。看看当我们以异步方式需要组件时,然后我们以某种异步方式使用import,就像使用await的const变量一样。
import ()
await
const
const module = await import('./module.js');
或者如果你想使用require(),那么,
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方式。
node --experimental-modules index.mjs
有关在何处使用异步导入的更多信息,请参阅此处-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文件来完成。这是一个示例同构模和一个文章解释它是如何工作的。
node_modules
不知道为什么(可能是优化-延迟加载?)它是这样工作的,但我注意到如果不使用导入的模块,import可能不会解析代码。在某些情况下,这可能不是预期的行为。
以讨厌的Foo类作为我们的示例依赖项。
foo.ts
export default class Foo {}console.log('Foo loaded');
例如:
index.ts
import Foo from './foo'// prints nothing
const Foo = require('./foo').default;// prints "Foo loaded"
(async () => {const FooPack = await import('./foo');// prints "Foo loaded"})();
另一方面:
import Foo from './foo'typeof Foo; // any use case// prints "Foo loaded"
截至目前,ES6导入,导出是总是编译到Common JS,所以有没有好处使用一个或另一个。尽管建议使用ES6,因为当浏览器的本机支持发布时它应该是有利的。原因是,您可以从一个文件导入部分,而使用Common JS,您必须需要所有文件。
ES6→import, export default, export
import, export default, export
使用JSrequire, module.exports, exports.foo
require, 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
// 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是更安全的执行模式。
strict mode
在ESM中,一些重要的Common JS引用没有定义。这些包括require , exports , module.exports , __filename,和__dirname
require , exports , module.exports , __filename,
__dirname
我们可以使用标准的导入语法从ESM导入Common JS模块。但只有default exports有效:
default exports
import packageName from 'commonjs-package' // Worksimport { moduleName } from 'commonjs-package' // Errors
但是,无法从Common JS模块导入ES模块。
ESM不能直接将JSON文件作为模块导入,这是一个在Common JS中经常使用的功能。这就是为什么在reactjsfetch中使用api。
fetch
import data from './data.json' //fails