JS modules,又称为ES modules或ECMAScript modules,是从ES6开始引入的模块系统,由于历史原因,我们只能借助babel来提前用上这些特性;最近,所有主流的浏览器都开始支持JS modules,所以是时候深入了解一下JS modules了。

作为前端攻城狮,相信你或多或少都接触过JavaScript的模块系统,例如CommonJS,AMD,CMD等,它们都允许你导入导出,但是一直没有一个统一的规范。JS modules的目的就是统一模块系统,可以使用import/export来导入/导出(或者export default 默认导出):

跟传统的JS scripts, JS modules有以下的不同:

  1. 默认开启严格模式
  2. HTML风格的注释(<!--这是注释-->)不再支持
  3. JS modules会生成顶级的词法作用域。例如在传统的JS scripts中,声明全局变量 var a = 1,可以通过window.a来访问。但是在JS modules中就不会(解决了命名冲突的老大难问题~)
  4. import/export只能在JS modules中使用,在传统的JS scripts中不能使用

所以我们如何在浏览器中使用呢?可以这样:

有几个注意的事项:

  1. 我们只需要在script标签上加上type="modules",浏览器会自动识别JS modules,把它当做text/javascript类型的文件来处理
  2. 第二个script标签,加了一个nomodule属性,这是为了让不兼容JS modules的浏览器也能正常工作,而兼容JS modules的浏览器不会执行这段代码
  3. 同时相信你也注意到了上面"main.mjs"中的"mjs"后缀,以此来区分是否为JS modules( 最初提出了四个方案,简单的说分别是: a. 不做任何改动,根据文件内容区分,b. 通过package.json来指出 c. 在文件开头加一段注释来区分 d. 通过不同的文件后缀名来区分。 最终选择了第四个方案 )

那JS Modules在浏览器中的行为,跟传统的JS scripts有什么区别呢?

一 、 JS modules只执行一次,例如:

上面的"classic.js"会执行两次,而"module.mjs"只会执行一次。

Access-Control-Allow-Origin: *

三, 以前可以在script标签上添加async属性,可以使JS的下载不阻塞 HTML的解析(JS的执行会阻塞HTML解析),同时在内联的script标签上添加async将不会生效,但是在JS modules的内联标签上会生效,例如: <script async type="modules">// js code </script>.

四、JS modules目前只支持导入绝对路径或相对路径的形式的模块,举个栗子:

五、JS modules 默认具有defer属性。以前,通过这样来声明:<script defer src="./hello.js"></script>,具有defer属性的JS scripts会立即下载JS代码,这个过程不会阻塞HTML的解析,同时会在HTML解析完成之后开始执行JS代码;现在,JS modules默认具有defer属性的行为,所以你不需要这样做<script type="module" defer src="./hello.mjs"></script>。他们之间的区别可以看这张图:

除此之外,JS modules还具有一些其他的特性,例如支持动态导入:

在使用JS modules时,我们一定会非常的兴奋,因为觉得不会再需要Webpack,Rollup,Parcel等构建工具了,但是适用场景有限,通常是:

  1. 在本地开发时使用
  2. 在小型网站中不会有太多的模块

对于大多数网站,仍然需要借助构建工具,可以看下面这个图:

因为import/export是静态模块,借助构建工具,可以分析模块中包含的内容,使用类似于Tree Shaking的技术来消除无效的代码。同时 ,the DevTools Code Coverage工具帮助分析模块中有多少的代码执行了,有多少的代码代码从未执行。

一个好的建议是保持模块尽量的小,更细粒度的去拆分模块。举个栗子:

我们声明了一个模块,里面包含三个导出的函数,另外一个模块会导入这个模块中的某个函数:

虽然我们只在main.mjs中导入了drop函数,但是浏览器仍会下载整个lib.mjs,然后进行编译执行。所以对于这种情况,最后是将其拆分成不同的模块,需要的时候引入对应的模块,还能充分利用缓存。

另外,为了提升性能,我们可以预加载JS modules,就像传统的JS scripts一样:

同时,目前有很多的网站使用HTTP2,利用HTTP2的多路复用特性,浏览器能够同时加载多个资源。但是HTTP2的另一个特性server push,还不能很好的在高度模块化应用使用;web服务器和浏览器目前也没有针对这种情况进行优化。例如:很难仅推送用户尚未缓存的资源,并且通过将源的整个缓存状态传达给服务器来解决这个问题,因为会有隐私风险。

JS modules未来如何呢?

目前JS modules在所有网站中的使用率为0.08%,同时,一些新的特性支持,以及性能方面的提升即将到来,让我们拭目以待。




参考资源: