Nodejs学习笔记编程实践

了解ES6模块和NodeJS模块

2019-07-04  本文已影响1人  全栈顾问

一句话总结:模块是一个JavaScript文件,文件名就是模块名,export定义了调用方能用什么(变量,函数,类),调用方通过import引入要用到的内容。

翻译自Understanding ES6 Modules

本文探讨了ES6模块,展示了如何在转换器(transpiler)的帮助下使用它们。

几乎每种语言都有模块概念——将声明的功能包含在一个文件中的方法。通常,开发人员创建一个封装的代码库,负责处理相关任务。该库可以由应用程序或其他模块引用。

好处:

  1. 代码可以拆分为自包含功能的较小文件。
  2. 可以在任意数量的应用程序中共享相同的模块。
    3 .理想情况下,模块不需要由另一个开发人员进行检查,因为它们已被证明有效。
  3. 引用模块的代码理解它是一种依赖。如果更改或移动模块文件,问题立即显而易见。
  4. 模块代码(通常)有助于消除命名冲突。模块1中的函数x()不能与模块2中的函数x()发生冲突。使用命名空间等选项,将调用变为module1.x()和module2.x()。

JavaScript中的模块在哪里?

几年前开始进行Web开发的任何人都会惊讶地发现JavaScript中没有模块的概念。无法直接引用或包含一个JavaScript文件。因此,开发人员采用了替代方案。

多个HTML <script>标签

HTML可以使用多个<script>标记加载任意数量的JavaScript文件:

<script src="lib1.js"></script>
<script src="lib2.js"></script>
<script src="core.js"></script>
<script>
console.log('inline code');
</script>

2018年平均每个网页使用了25个单独的脚本,但它不是一个实用的解决方案:

脚本连接

解决多个<script>标签问题的一种方法是将所有JavaScript文件连接成一个大的文件。这解决了一些性能和依赖关系管理问题,但它可能会导致手动构建和测试步骤。

模块装载机

诸如RequireJSSystemJS之类的系统提供了一个库,用于在运行时加载和命名其他JavaScript库。需要时,使用Ajax方法加载模块。这些系统有所帮助,但对于较大的代码库或将标准<script>标签添加到组合中的站点而言可能会变得复杂。

模块捆绑器,预处理器和Transpilers

Bundlers引入了编译步骤,因此在构建时生成JavaScript代码。处理代码以包含依赖项并生成单个ES5跨浏览器兼容的连接文件。流行的选项包括BabelBrowserifywebpack和更多一般任务运行者,如GruntGulp

JavaScript构建过程需要一些投入,但也收获好处:

ES6模块

上述选项引入了各种竞争模块定义格式。广泛采用的语法包括:

ES6(ES2015)中提出了单一的本机模块标准。

默认情况下,ES6模块中的所有内容都是私有的,并且以严格模式运行(不需要'use strict')。使用export公开变量,函数和类。例如:

// lib.js
export const PI = 3.1415926;

export function sum(...args) {
  log('sum', args);
  return args.reduce((num, tot) => tot + num);
}

export function mult(...args) {
  log('mult', args);
  return args.reduce((num, tot) => tot * num);
}

// private function
function log(...msg) {
  console.log(...msg);
}

或者,用单条export语句的形式。例如:

// lib.js
const PI = 3.1415926;

function sum(...args) {
  log('sum', args);
  return args.reduce((num, tot) => tot + num);
}

function mult(...args) {
  log('mult', args);
  return args.reduce((num, tot) => tot * num);
}

// private function
function log(...msg) {
  console.log(...msg);
}

export { PI, sum, mult };

然后用import将项目从模块拉到另一个脚本或模块(要用花括号):

// main.js
import { sum } from './lib.js';

console.log( sum(1,2,3,4) ); // 10

这个例子中,lib.js与main.js在同一个文件夹。也可以使用绝对文件引用(以...开头/),相对文件引用(起始./或../)或完整URL。

一次可以导入多个项目:

import { sum, mult } from './lib.js';

console.log( sum(1,2,3,4) );  // 10
console.log( mult(1,2,3,4) ); // 24

导入时设置别名以解决命名冲突:

import { sum as addAll, mult as multiplyAll } from './lib.js';

console.log( addAll(1,2,3,4) );      // 10
console.log( multiplyAll(1,2,3,4) ); // 24

可以通过提供命名空间来导入所有公共项:

import * as lib from './lib.js';

console.log( lib.PI );            // 3.1415926
console.log( lib.add(1,2,3,4) );  // 10
console.log( lib.mult(1,2,3,4) ); // 24

在浏览器中使用ES6模块

在撰写本文时,基于Chromium的浏览器(v63 +),Safari 11+和Edge 16+ 支持ES6模块。Firefox支持将在版本60中支持(它about:config在v58 + 的标志后面)。

必须通过type="module"<script>标记中设置属性来加载使用模块的脚本。例如:

<script type="module" src="./main.js"></script>

或内联:

<script type="module">
  import { something } from './somewhere.js';
  // ...
</script>

无论它们在页面或其他模块中引用了多少次,模块只被解析一次。

服务器端注意事项

必须使用MIME类型提供模块application/javascript。大多数服务器会自动执行此操作,但要警惕动态生成的脚本或.mjs文件(请参阅下面的Node.js部分)。

常规<script>标记可以在其他域上获取脚本,但使用跨源资源共享(CORS)获取模块。因此,不同域上的模块必须设置适当的HTTP标头,例如Access-Control-Allow-Origin: *

最后,除非将crossorigin="use-credentials"属性添加到<script>标记并且响应包含标题,否则模块将不会发送cookie或其他标头凭据Access-Control-Allow-Credentials: true

模块执行推迟

<script defer>属性会延迟脚本执行,直到文档加载并解析为止。模块(包括内联脚本)默认延迟。例:

<!-- runs SECOND -->
<script type="module">
  // do something...
</script>

<!-- runs THIRD -->
<script defer src="c.js"></script>

<!-- runs FIRST -->
<script src="a.js"></script>

<!-- runs FOURTH -->
<script type="module" src="b.js"></script>

模块后备

不支持模块的浏览器不会运行type="module"脚本。可以通过浏览器的nomodule属性给模块提供后备脚本。例如:

<script type="module" src="runs-if-module-supported.js"></script>
<script nomodule src="runs-if-module-not-supported.js"></script>

现在应该在浏览器中使用模块吗?(文章是18年4月写的)

浏览器支持正在增长,但转换到ES6模块可能还为时过早。目前,使用模块捆绑器创建一个可在任何地方工作的脚本可能更好。

在Node.js中使用ES6模块

当Node.js在2009年发布时,不能提供模块是不可想象的。它采用了CommonJS,这意味着可以开发Node包管理器npm。从那时起,使用量呈指数级增长。

CommonJS模块的编码方式与ES2015模块类似。使用module.exports而不是export

// lib.js
const PI = 3.1415926;

function sum(...args) {
  log('sum', args);
  return args.reduce((num, tot) => tot + num);
}

function mult(...args) {
  log('mult', args);
  return args.reduce((num, tot) => tot * num);
}

// private function
function log(...msg) {
  console.log(...msg);
}

module.exports = { PI, sum, mult };

require(而不是import)将模块拉入另一个脚本或模块:

const { sum, mult } = require('./lib.js');

console.log( sum(1,2,3,4) );  // 10
console.log( mult(1,2,3,4) ); // 24

require还可以导入所有项目:

const lib = require('./lib.js');

console.log( lib.PI );            // 3.1415926
console.log( lib.add(1,2,3,4) );  // 10
console.log( lib.mult(1,2,3,4) ); // 24

ES6模块很容易在Node.js中实现吗?不。

ES6模块位于Node.js 9.8.0+中的标志后面,并且至少在版本10之前不会完全实现。虽然CommonJS和ES6模块具有相似的语法,但它们的工作方式基本不同:

它在上面的示例中没有任何区别,但请考虑以下ES2015模块代码:

// ES2015 modules

// ---------------------------------
// one.js
console.log('running one.js');
import { hello } from './two.js';
console.log(hello);

// ---------------------------------
// two.js
console.log('running two.js');
export const hello = 'Hello from two.js';

ES2015的输出:

running two.js
running one.js
hello from two.js

使用CommonJS编写的类似代码:

// CommonJS modules

// ---------------------------------
// one.js
console.log('running one.js');
const hello = require('./two.js');
console.log(hello);

// ---------------------------------
// two.js
console.log('running two.js');
module.exports = 'Hello from two.js';

CommonJS的输出:

running one.js
running two.js
hello from two.js

执行顺序在某些应用程序中可能至关重要,如果ES2015和CommonJS模块混合在同一个文件中会发生什么?要解决此问题,Node.js将仅允许具有扩展名.mjs文件中的ES6模块。带.js扩展名的文件默认为CommonJS。这是一个简单的选项,它消除了很多复杂性,应该有助于编码和编辑。

你应该在Node.js中使用ES6模块吗?

ES6模块仅适用于Node.js v10以后(2018年4月发布)。转换现有项目不太可能带来任何好处,并且会使应用程序与早期版本的Node.js不兼容。

对于新项目,ES6模块提供了CommonJS的替代方案。语法与客户端编码相同,可以提供更容易的同构JavaScript路径,可以在浏览器或服务器上运行。

模块混战

标准化的JavaScript模块系统需要很多年才能到达,甚至更长时间才能实现,但问题已得到纠正。所有主流浏览器和2018年中期的Node.js都支持ES6模块,尽管在每个人都升级时应该会出现切换延迟。

立即学习ES6模块,为明天的JavaScript开发做好准备。

参考

MDN JavaScript模块
MDN export
MDN import

上一篇下一篇

猜你喜欢

热点阅读