首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES Module

ES Module

作者头像
ayqy贾杰
发布2019-06-12 12:38:24
8720
发布2019-06-12 12:38:24
举报
文章被收录于专栏:黯羽轻扬黯羽轻扬

零.7种模块化方式

1.分节注释

<!--html-->
<script>
   // module1 code
   // module2 code
</script>

手动添加注释来标明模块范围,类似于CSS里的分节注释:

/* -----------------
* TOOLTIPS
* ----------------- */

惟一作用是让浏览代码变得容易一些,迅速找到指定模块,根本原因是单文件内容太长,已经遇到了维护的麻烦,所以手动插入一些锚点供快速跳转

非常原始的模块化方案,没有实质性的好处(比如模块作用域,依赖处理,模块间错误隔离等等)

2.多script标签

<!--html-->
<script type="application/javascript" src="PATH/polyfill-vendor.js" ></script>
<script type="application/javascript" src="PATH/module1.js" ></script>
<script type="application/javascript" src="PATH/module2.js" ></script>
<script type="application/javascript" src="PATH/app.js" ></script>

把各个模块拆分成独立文件,有3个好处:

  • 通过控制资源加载顺序来处理模块依赖
  • 有模块间错误隔离(module1.js初始化执行异常不会阻断module2.jsapp.js的执行)
  • 各模块位于单独文件,切实提高了维护体验

但还存在2个问题:

  • 没有模块作用域
  • 资源请求数量与模块化粒度相关,需要寻找性能与模块化收益的平衡

3.IIFE

const myModule = (function (...deps){
  // JavaScript chunk
  return {hello : () => console.log('hello from myModule')};
})(dependencies);

可以作为补丁,配合其他方式使用,提供模块作用域

4.Asynchronous module definition (AMD)

RequireJS示例:

// polyfill-vendor.js
define(function () {
   // polyfills-vendor code
});// module1.js
define(function () {
   //...
   return module1;
});
// module2.js
define(function () {
   //...
   return module2;
});// app.js
define(['PATH/polyfill-vendor'] , function () {
   define(['PATH/module1', 'PATH/module2'] , function (module1, module2) {
       var APP = {};       if (isModule1Needed) {
           APP.module1 = module1({param: 1});
       }
       APP.module2 = new module2({a: 42});
   });
});

一套比较完善的模块定义方案,解决了模块依赖问题,提供了模块作用域,错误隔离/捕获等方案。但看起来稍微有些冗余

P.S.另外还有SeaJS(官网都没了,不做介绍)。社区实现的模块化补丁都只是过渡产物,目前看来,JS似乎终将迎来模块化特性

5.CommonJS

NodeJS示例:

// polyfill-vendor.js
   // polyfills-vendor code// module1.js
   // module1 code
   module.exports= module1;
// module2.js
module.exports= module2;// app.js
require('PATH/polyfill-vendor');const module1 = require('PATH/module1');
const module2 = require('PATH/module2');const APP = {};
if(isModule1Needed){
   APP.module1 = module1({param:1});
}
APP.module2 = new module2({a: 42});

NodeJS遵循CommonJS规范,文件即模块,同样是一套相对完善的方案,但不适用于浏览器环境

6.UMD (Universal Module Dependency)

UMD示例:

(function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
   typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, function () {
   // JavaScript chunk
   return {
      hello : () => console.log(‘hello from myModule’)
   }
});

同样是一个补丁,兼容AMD和CommonJS模块定义,实现了模块跨环境通用。出现UMD的根本原因是社区模块定义方式太多了,开源模块维护变得很麻烦(出现各种MD issue,只好换上UMD),所以迫切需要标准化,ES6肩负着这个使命

P.S.当然,开源模块的维护问题还在(为了迎合ES Module,又添上专门的ES6构建版本),但不会加剧,毕竟已经在标准化的路上了

7.ES6 Module

基本用法示例:

// myModule.js
export {fn1, fn2};function fn1() {
   console.log('fn1');
}
function fn2() {
   console.log('fn2');
}// app.js
import {fn1, fn2} from './myModule.js';
fn1();
fn2();// index.html
<script type="module" src="app.js"></script>

注意

  • script标签必须声明type="module"表明以ES Module方式解析内容,否则不会执行
  • import模块文件精确路径./)、文件后缀名.js)及对应的MIME类型必须要有,否则引入失败

目前各大主流浏览器都提供了ES Module实验性功能:

  • Safari 10.1.
  • Chrome Canary 60 – behind the Experimental Web Platform flag in chrome:flags.
  • Firefox 54 – behind the dom.moduleScripts.enabled setting in about:config.
  • Edge 15 – behind the Experimental JavaScript Features setting in about:flags.

等了2年的Demo终于能跑起来了:http://ayqy.net/temp/module/index.html

P.S.一般都叫ES Module,因为Module特性不存在多个版本,ES Module指的就是ES6引入的Module特性

一.语法

export

// 基本语法
export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var, function
export let name1 = …, name2 = …, …, nameN; // also var, const// 默认导出
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 …;

注意exportexport default的区别:

  • 每个模块(/文件)只能有一个export default,可以有多个export
  • export default后面可以接任意表达式,而export语法只有3种

例如:

// 不合法,语法错误
export {
   a: 1
};
// 而应该用export { name1, name2, …, nameN };
let a = 1;
export {
   a
};
// 或者export let name1 = …, name2 = …, …, nameN; // also var, const
export let a = 1;
默认导出

默认导出是一种特殊的导出形式,例如:

// module.js
export {fn1, fn2};
function fn1() {
   console.log('fn1');
}
function fn2() {
   console.log('fn2');
}
export default {
   a: 1
};
let b = 2;
export {
   b
};
export let c = 3;// app.js
import * as m from './module.js';
console.log(m);
// 输出结果
Module {
   b: 2,
   c: 3,
   default: {
       a: 1
   },
   fn1: ƒn1,
   fn2: ƒn2
}

默认导出被隔离在Module对象的default属性里,与其它export待遇不同

聚合导出

相当于import + export,但不会在当前模块作用域引入各个API变量(导入后直接导出,无法引用),仅起API聚合的中转作用,例如:

// lib.js
let util = {name: 'util'};
let dialog = {name: 'core'};
let modal = {name: 'modal'};export {
   util,
   dialog,
   modal
}// module.js
console.log(`before export from lib: ${typeof dialog}`);
export * from './lib.js';
console.log(`after export from lib: ${typeof dialog}`);

前后都是undefined,因为仅中转,不在当前模块作用域引入。而import + export会先引入,在当前模块可用

import

// 引入default export内容
import defaultMember from "module-name";
// 引入所有export内容,包括default,并打包到名为mame的对象
import * as name from "module-name";
// 按名引入指定export内容
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1, member2 } from "module-name";
import { member1, member2 as alias2 , [...] } from "module-name";
// 引入default export内容,同时按名引入指定export内容
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as name from "module-name";
// 不引入模块里暴露的东西,仅执行该模块代码
import "module-name";

最后一种比较有意思,被称为Import a module for its side effects only,仅执行模块代码,不引入任何新东西(只有影响外部状态的部分会生效,即副作用)

P.S.关于ES Module语法的更多信息,请查看module_ES6笔记13,或者参考资料部分的ES Module Spec

P.S.NodeJS也在考虑支持ES Module,但遇到了怎么区分CommonJS模块和ES Module的问题,还在讨论中,更多信息请查看ES Module Detection in Node

二.加载机制

也就是说:

  • type="module"的资源相当于自带defer效果(等到HTML文档解析完毕才执行)
  • async依然有效(资源加载完毕后立即执行,执行完继续解析HTML文档)
  • import资源加载是并行的

自带defer效果,与裸script默认行为(加载资源立即执行,并且阻塞HTML文档解析)不同。另外,虽然import加载同级资源是并行的,但寻找下一级依赖的过程不可避免是顺序串行的,这部分性能无法忽略,即便浏览器原生支持了ES Module,也不能肆无忌惮地import

类似于CSS中的@import规则,可能会发展出最佳实践,在模块化与加载性能之间寻求平衡

三.特点

1.静态机制

不能在iftry-catch语句,函数或者eval等地方使用import,只能出现在模块最外层

并且import提升(Hosting)特性,如同变量声明被提升到当前作用域顶部一样,模块里声明的import会被提升到模块顶部

P.S.静态模块机制有利于做解析/执行优化

2.新script类型

需要用新的script类型属性type="module"。因为解析器没有办法推测出内容是不是ES Module(比如没有import, export关键字,也遵循严格模式,那么算不算个模块?)

另外,根据内容猜测存在多次解析的性能损耗

3.模块作用域

每个模块有自己的作用域,模块下的变量声明不会暴露到全局

4.默认开启严格模式

this不指向global,而是undefined

5.支持Data URI和Blob URI

import grape from 'data:text/javascript,export default "grape"';// create an empty ES module
const scriptAsBlob = new Blob([''], {
   type: 'application/javascript'
});
const srcObjectURL = URL.createObjectURL(scriptAsBlob);
// insert the ES module and listen events on it
const script = document.createElement('script');
script.type = 'module';
document.head.appendChild(script);
// start loading the script
script.src = srcObjectURL;

6.受CORS限制

跨域的模块资源无法import引入,也无法通过script标签以模块方式加载

7.HTTPS资源无法importHTTP资源

类似于HTTPS页面加载HTTP资源,会被block掉

8.模块是单例

不同于普通script,引入的模块是单例(只执行一次),无论是import还是通过type="module"script标签引入

9.请求模块资源不带身份凭证(credentials)

与Fetch API脾气一样,默认不带身份证,需要给script标签添上crossorigin属性

四.问题

1.import报错

必须要给出精确的模块文件路径,否则不会执行模块内容,并且Chrome 60连报错都没有

P.S.import报错目前各浏览器还存在差异

2.模块间错误隔离仍然是个问题

资源加载错误:动态插入script加载模块,onerror监听加载异常

模块初始化错误:window.onerror全局捕获,尝试通过错误信息找出模块名,记下模块初始化失败

3.请求数量爆炸

比如lodash demo,需要加载600多个文件

上HTTP2能缓解碎文件的问题,但从根源看,需要一套适用于生产环境的最佳实践,规范模块化的粒度

4.动态import

目前还没有实现,import() API专门解决这个问题,规范还处于草案第3阶段,更多信息请查看Native ECMAScript modules: dynamic import()

5.模块环境检测

检查当前执行环境是不是模块:

const inModule = this === undefined;

看起来不很靠谱,但似乎只能这么干,因为document.currentScript在ES Module是null,没办法做type检查

五.降级方案

1.特性检测

过一遍特性检测,由环境检测util引入模块,比较费劲且亏性能,例如malyw/es-modules-utils

typeof行不通,因为import, export是关键字,可以插入type="module"script标签,加载空模块(可以用Blob URI或者Data URI),触发onload说明支持

另外还有一种取巧的方法

<script type="module">
   window.__browserHasModules = true;
</script>

引入这样的模块做特性检测,但因为ES Module自带defer效果,为了保证执行顺序,后续所有JS资源都要有defer属性(包括用于降级的正常版本)

2.nomodule

nomodule属性,作用类似于noscript标签,<script nomodule>console.log('仅在不支持ES Module的环境执行')</script>

但依赖浏览器支持,在不支持该属性但支持ES Module的环境就有问题了(两个都执行),已经添到了HTML规范,但目前兼容性还比较差

  • Firefox最新版支持
  • Edge不支持
  • Safari 10.1不支持,但有办法解决
  • Chrome 60支持

关于降级方案的更多信息,请查看Native ECMAScript modules: nomodule attribute for the migration

参考资料

  • Native ECMAScript modules – the first overview:ES Module系列4篇文章都很不错
  • WHY CHOOSE ES2015 MODULES, BASED ON THE STATE OF THE ART OF JAVASCRIPT MODULARIZATION
  • import()
  • ECMAScript modules in browsers
  • ES Module Spec
  • MDN | import
  • MDN | export
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-09-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端向后 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 零.7种模块化方式
    • 1.分节注释
      • 2.多script标签
        • 3.IIFE
          • 4.Asynchronous module definition (AMD)
            • 5.CommonJS
              • 6.UMD (Universal Module Dependency)
                • 7.ES6 Module
                • 一.语法
                  • export
                    • 默认导出
                    • 聚合导出
                  • import
                  • 二.加载机制
                  • 三.特点
                    • 1.静态机制
                      • 2.新script类型
                        • 3.模块作用域
                          • 4.默认开启严格模式
                            • 5.支持Data URI和Blob URI
                              • 6.受CORS限制
                                • 7.HTTPS资源无法importHTTP资源
                                  • 8.模块是单例
                                    • 9.请求模块资源不带身份凭证(credentials)
                                    • 四.问题
                                      • 1.import报错
                                        • 2.模块间错误隔离仍然是个问题
                                          • 3.请求数量爆炸
                                            • 4.动态import
                                              • 5.模块环境检测
                                              • 五.降级方案
                                                • 1.特性检测
                                                  • 2.nomodule
                                                    • 参考资料
                                                    领券
                                                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档