基于 Webpack+Vue 的单页面开发过程中,准备全面尝试拥抱 ES2015 规范,尝试一些新的东西。在进行实践的过程中,感受比较深的就是 ES6 的模块机制。之前 Node 环境下实现了 CommonJS 规范的模块机制,使用 require/module.exports 进行模块的同步加载。ES6 的模块机制是什么,为什么在已有了 CommonJS 模块规范的情况下又冒出来一个 ES6 的模块机制,两者本质上以及在使用过程中有什么不同,下面针对上述问题针对对应的案例进行了详细的解释。

在开发中,由于使用了 webpack 模块打包工具,其对现有的 JS 模块方案进行统一整合,然后打包输出最后的 js 文件。由于代码中既可以使用 CommonJS 的模块方案,也可以使用 ES6 的模块方案,究竟两者在使用的过程中有什么区别。因此下文中针对这两种不同的模块机制进行了阐述。

ES6 模块的由来

在 Javascript 中前世今生中,曾经是没有模块这个概念的。在 ES6 之前社区制定了一些关于模块的加载方案,主要的有 AMD,CMD,CommonJS。前两者主要用于浏览器中,用于浏览器中模块的异步加载。后者主要用于 Node 服务端,用于模块的同步加载。ES6 模块系统是在 ECMAScript 6 中的新特性,其在语言标准层面实现了模块的功能。也就是说,ES6 模块的设计是要统一浏览器端和服务端的,这是要一统 JS 天下的节奏,其究竟能不能成为浏览器和服务器通用的解决方案呢?

ES6 模块的设计理念

理解 ES6 模块的关键在于理解其静态思想的设计,也就是在编译时,运行之前就可以确定模块之间的依赖关系。ES6 模块通过 import 和 export 这种声明式的模块依赖关系使得其设计理念不同于 CommonJS 模块方案。ES6 中通过 export 和 import 所导出和导入的并非是一个对象,而是一些声明式的代码集合。了解了这一点也就可以更好的理解 CommonJS 与 ES6 的不同。

CommonJS 模块与 ES6 模块的不同

  • CommonJS 的 require/module.exports

    CommonJS 中是通过 module.exports 以及 require 来导出和导入模块的。研究 CommonJS 模块的实现过程可以知道,其导出的为一个 module 对象,在导入过程中,是导入了这样的一个对象,然后去遍历其属性达到使用导入模块的目的。这样的过程是一个动态的,也就是只有在运行后才能知道导出后的是一个什么的对象。

  • ES6的 import/export

    ES6 中的 import 和 export 都是静态的,即其导出的并非是一个对象,通过 export 导出的是一些声明式的代码的集合,通过 import 可以建立模块外部与内部的链接关系。这样的过程在编译时也就是运行之前就已经确定了。

也就是说理解两者的不同,关键是理解两者在导入和导出这个过程中的不同。

工程实践中遇到的问题

  • 按需加载问题(条件加载,动态加载)

    工程中经常会遇到这样的问题,有些模块是需要按需加载的,比如在开发环境与生产环境中部分模块的加载。因为 ES6 模块编译时解析的特性,导致其在按需加载方面不是那么给力。还好现在已经有了关于使用 import() 做异步加载提案,相信在不久的将来这也将成为标准,大家就用不着 babel 去做处理了。先如今通过使用 webpack+babel-loader + babel-preset-stage-2 即可在代码中使用 import() 进行异步加载,其关键是 syntax-dynamic-import 插件,详情见webpack中文指南-代码分离。比如:

1
2
3
4
5
6
7
8
9
// 测试ES6模块的条件加载
if (true) {
import('./test.js').then((module)=>{
console.log(module);
module.default();
module.test1();
module.test2();
});
}
  • Tree Shaking

    webpack 利用 ES6 模块的静态结构特性,提供了 Tree Shaking 这个特性,可以在打包过程中删除 Javascript 上下文中无用的代码。因为当 webpack 配合 ES6 模块来使用时,可以提高 webpack 的打包效率。详情可见webpack中文指南-tree shaking

参考文章:

webpack中文指南

webpack 2中的Tree Shaking

ECMAScript 6 入门

论ES6模块系统的静态解析