JavaScript modules上(译文)

149 阅读2分钟

本文介绍了如何使用JS Modules,如何部署它们,以及Chrome团队如何在未来使模块变得更好 。

什么是 JS modules?

JS modules (也被叫做“ES modules” 或者 “ECMAScript modules”) 是一个关键的新特性(2018),您可能在过去使用过一些JavaScript模块系统. 例如 CommonJS like in Node.js, 或者是 AMD等等. 所有这些模块系统都有一个共同点:它们允许您导入和导出内容。.

JavaScript现在已经有了标准化的语法。在模块中,您可以使用“export”关键字导出任何内容。您可以导出常量函数或任何其他变量绑定或声明。只需在变量语句或声明前加上“export”即可:

// 📁 lib.mjs
export const repeat = (string) => `${string} ${string}`;
export function shout(string) {
  return `${string.toUpperCase()}!`;
}

然后可以使用import关键字从另一个模块导入模块。在这里,我们将从lib模块导入repeat和away功能,并在主模块中使用它:

// 📁 main.mjs
import {repeat, shout} from './lib.mjs';
repeat('hello');
// → 'hello hello'
shout('Modules in action');
// → 'MODULES IN ACTION!'

你也可以导出一个默认模块

// 📁 lib.mjs
export default function(string) {
  return `${string.toUpperCase()}!`;
}

这个默认模块可以使用任意变量名接收

// 📁 main.mjs
import shout from './lib.mjs';
//     ^^^^^

使用JS Modules和典型的script有如下区别

  • JS Modules默认开启strict mode
  • 模块中不支持HTML样式的注释语法,尽管它在经典的scrip中有效
// Don’t use HTML-style comment syntax in JavaScript!
const x = 42; <!-- TODO: Rename x to y.
  • Modules拥有顶级的词法作用域,这意味着,例如,运行var foo=42;在一个模块中,不会创建一个名为foo的全局变量,但是可以通过浏览器中的window.foo创建,尽管在经典script中是这样的。
  • Modules中的this不指向顶级的this(window),但是可以通过globalThis访问
  • 新的静态import和export语法仅在Modules中可用-它在典型的Script中不起作用
  • Top-level await (在Modules的顶层可以不用async包裹,使用await方法)在模块中可用,但在经典脚本中不可用。与此相关,await不能在模块中的任何地方用作变量名,尽管经典脚本中的变量可以在异步函数之外命名为await。

由于这些差异,相同的JavaScript代码在作为模块与经典脚本处理时可能会有不同的行为。因此,JavaScript运行时需要知道哪些脚本是模块。

在浏览器中使用JS Modules

在web上,通过将type属性设置为module,可以告诉浏览器将<script>元素视为模块。

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

使用type="module"可以让浏览器将这个script识别为JS Modules形式引入,关于nomodule的介绍我没看懂,建议大家自己去看原文

默认情况下Modules是defer的

默认情况下,<scripts>会阻止HTML parse。可以通过添加defer属性来解决这个问题,这可以确保脚本下载与HTML解析并行进行。

image.png 默认情况下,Modules Js会被延迟(类似 默认defer)。因此,不需要将defer添加到 script type=“module”标记中!不仅主模块的下载与HTML解析并行进行,所有依赖模块也是如此!

Modules和classic script之间的浏览器特定差异

如您现在所知,Modules JS与classic script不同。除了我们上面概述的平台不可知的差异之外,还有一些特定于浏览器的差异。

例如,模块只评估一次,而经典脚本无论添加到DOM多少次都会执行。

<script src="classic.js"></script>
<script src="classic.js"></script>
<!-- classic.js 执行多次. -->

<script type="module" src="module.mjs"></script>
<script type="module" src="module.mjs"></script>
<script type="module">import './module.mjs';</script>
<!-- module.mjs 只执行一次. -->

此外,模块脚本及其依赖关系是通过CORS获取的。这意味着任何跨源模块脚本都必须使用适当的标头,例如Access Control Allow origin:*。对于经典脚本来说,情况并非如此。(意思是使用type=module标注的script存在跨域)

关于文件扩展名的说明

您可能已经注意到,我们对模块使用了.mjs文件扩展名。在Web上,只要文件使用JavaScript MIME类型text/JavaScript,文件扩展名其实并不重要。由于脚本元素上的type属性,浏览器知道它是一个模块。

尽管如此,我们还是建议对模块使用.mjs扩展,原因有两个:

在开发过程中,.mjs扩展名使您和任何查看项目的人都清楚地知道,文件是一个模块,而不是一个经典脚本。(仅仅看代码并不总是能够分辨出来。)如前所述,模块的处理方式与经典脚本不同,因此差异非常重要!

它确保您的文件被运行时(如Node.js和d8)和构建工具(如Babel)解析为模块。虽然这些环境和工具都有通过配置将具有其他扩展名的文件解释为模块的专有方式,但.mjs扩展名是确保文件被视为模块的交叉兼容方式。

Modules需要表明引入路径

// Not supported (yet):
import {shout} from 'jquery';
import {shout} from 'lib.mjs';
import {shout} from 'modules/lib.mjs';
// Supported:
import {shout} from './lib.mjs';
import {shout} from '../lib.mjs';
import {shout} from '/modules/lib.mjs';
import {shout} from 'https://simple.example/modules/lib.mjs';

目前,模块说明符必须是完整URL,或以/,./,或../。

原文地址: v8.dev/features/mo…