ES6 模块详细解读

559 阅读4分钟

直接使用 <script> 标签加载

浏览器加载ES6模块,也可以使用 <script> 标签,但要加入 type="module" 属性。

浏览器对于带有 type="module"<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等于打开了 <script> 标签的 defer 属性

如果网页有多个 <script type="module">,它们会按照在页面出现的顺序依次执行

<script> 标签的 async 属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染。

一旦使用了async属性,<script type="module"> 就不会按照在页面出现的顺序执行,而是只要该模块加载完成,就执行该模块。

静态import:

  • 通过import以静态的方式,导入另一个通过export导出的模块(MDN

示例

// ./utils.mjs
// 默认导出
export default () => {
   console.log('来自 default export!');
};

// 命名导出 “doStuff”
export const doStuff = () => {
	console.log('Doing stuff...');
};

静态引入和使用 ./utils.mjs 模块的方法

<script type="module">
	import * as module from './utils.mjs';
    module.default();
    // -> 输出 "来自 default export!"
    module.doStuff();
    // -> 输出 "Doing stuff...'
</script>

Note:

示例里使用 .mjs 扩展名来表示它是模块而非常规脚本。

在Web上,文件扩展名并不重要,只要文件在 Content-Type HTTP 标头中以正确的 MIME 类型(e.g. JS文件的 type/javascript)提供即可。

.mjs 扩展在其他平台很管用,比如 Node.js,因为它们没有 type="module" 来标识模块还是常规脚本。

所以这里使用同样的扩展来实现跨平台的一致性,并区分模块和常规脚本

这种用于导入模块的语法是一个静态声明:它只接受一个字符串文字作为模块说明符,并通过运行前(pre-runtime)"链接"过程将绑定引入本地作用域

静态import导入语法只能在文件的顶部(top-level of the file)使用

静态import可以实现重要的用例,比如静态分析(static analysis)、bundling tools 和 tree-shaking

用法:

import defaultExport from "module-name";    
import * as name from "module-name";    //导入整块模块的内容,name作为命名空间
import { export } from "module-name";     //导入单个导出
import { export as alias } from "module-name";    //导入时可以重命名导出
import { export1 , export2 } from "module-name";    //导入多个导出
import { foo , bar } from "module-name/path/to/specific/un-exported/file";  
import { export1 , export2 as alias2 , [...] } from "module-name";
import defaultExport, { export [ , [...] ] } from "module-name";
import defaultExport, * as name from "module-name";
import "module-name";    //模块仅为副作用而导入,不导入模块中的任何内容。这将运行模块中的全局代码,但实际上不导入任何值

动态import():

Motivation

既然已有上一节的静态 import 可以实现模块导入,还需要动态 import 做什么呢?

动态 import 可实现的功能:

  • 按需(或条件)导入模块
  • 运行时计算模块定位符(module specifier),(即 from 后面的 "module-name")
  • 常规脚本中引入一个模块

用法

动态 import(),引入了一种类似函数的新形式,可以在运行时动态加载 ES2015 模块:

    var promise = import("module-name");

import(moduleSpecifier) 返回所请求模块的 promise,该 promise 是在 fetching、instantiating、以及 evaluating 所有模块依赖以及模块本身之后创建的。

<script type="module">
    const moduleSpecifier = './utils.mjs';
    import(moduleSpecifier)
    	.then((module) => {
			module.default();
            // -> 输出 "来自 default export!"
            module.doStuff();
            // -> 输出 "Doing stuff...'
         });
</script>

这种使用方式也支持 await / async 关键字

let module = await import('/modules/my-module.js');
<script type="module">
    (async () => {
    	const moduleSpecifier = './utils.mjs';
    	const module = await import(moduleSpecifier)
		module.default();
        // -> 输出 "来自 default export!"
        module.doStuff();
        // -> 输出 "Doing stuff...'
     })();
</script>

Note:

如果从其他域名下(静态或动态)import scripts,则需要使用有效的 CORS 标头(比如 Access-Control-Allow-Origin:* )返回脚本。

因为与常规脚本不同,模块脚本(和其引入)是使用CORS获取的

推荐用法:

将静态导入用于初始绘制依赖,尤其是对 above-the-fold 内容

在其他情况下,考虑用动态 import() 按需加载依赖

webpack note:

调用 import() 之处,被作为分离的模块起点,意思是:被请求的模块和它引用的所有子模块,会分离到一个单独的chunk中

import规范不允许控制模块的名称或其他属性。但是在webpack可以通过注释接收特殊参数

//单个目标
import/* webpackChunkName:"my-chunk-name" */
  /* webpackMode:"lazy" */
  'module'
);

//多个可能目标
import/* webpacklnclude:/\.json$/ */
  /* webpackExc1ude:/\.noimport\.json$/ */
  /* webpackChunkName:"my-chunk-name" */
  /* webpackMode:"lazy" */
  `./locale/${language}`
);
  • webpackChunkName:
    • 新chunk的名称。
    • webpack 2.6.0开始,[index][request] 占位符,分别支持赋予一个递增的数字和实际解析的文件名
  • webpackMode:
    • 从webpack 2.6.0开始,可以指定以不同的模式解析动态导入。支持以下选项:
      • "lazy":(默认)
      • "lazy-once":
      • "eager"
      • "weak"
  • webpackInclude:
    • 在导入解析(import resolution)过程中,用户匹配的正则表达式。只有匹配到的模块才会被打包
  • webpackExclude:
    • 同上。所有匹配到的模块都不会被打包

webpackIncludewebpackExclude 选项不会影响到前缀,比如 ./locale

import 必须至少包含模块位于何处的路径信息。所以打包应当限制在一个指定目录或一组文件中

export()

默认导出整个模块,或具名导出模块(MDN)

//命名导出
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const
export function FunctionName() {...}
export class ClassName {...}
//默认导出。只能有一个默认的导出
export default expression;
export default function () { … } // also class, function*
export default function name1() { … } // also class, function*
export { name1 as default, … };

export * from …;   //不会导出已导入模块的默认导出
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;

nameN:导出的标识符(用来被其他脚本的import导入)

参考:

developers.google.com/web/updates…