Modular Javascript

回顾历史

RequireJS

这是AngularJS (Angular 1.x) 时代带来的解决方案。通过define函数,require函数来做到模块化的效果,require的回调函数带入之前的lib作为相应参数。这种思路便是后端常见的Dependency Injection的思想。

典型的用例给的是mathlib这个远古库。


define(function() {
  return sum // 注意这里一样有“变量提升”和lamda的魔法
  function sum(...values) {
    return values.reduce((a, b) => a + b, 0)
  }
})require(['mathlib'], function(mathlib) {
  mathlib.sum(1, 2, 3)
  // <- 6
})

好处:初步的模块化编程思想,解决了长久一来复杂的包互相引用所带来的问题,也解决了原有思路(<script> + IIFE)引入了很难维护的函数列表的问题。

坏处:该模式是异步的,对于生产坏境来说,这样的性能开销不可接受。存在100个模块,就意味着100个请求同时并行。API同样也不是非常好理解,用来解决模块化的有着更好的语法,没必要一定包装在一个函数里面,看起来非常奇怪。

对于AngularJS v1来说,他引入了一种非常巧妙的模块解决方案,用一定的字符串来parse出依赖项,再使用函数的参数确定依赖项。然而,这种方式在使用UgilifyJS等后处理器的时候会出现很大的问题——使用字符串来引用,一旦变量名被修改,很难确定是否还能保持正确的引用。

当然,对于这样的一种过度包装的模块化方式,很多人是很难以接受的。大家更愿意是使用上面这种RequireJS来解决问题。事实上,这种使用array来包装的模块化注入有点脱裤子放屁的感觉。


module.factory('calculator', function(mathlib) {
  // …
})

module.factory('calculator', ['mathlib', function(mathlib) {
  // …
}])

CommonJS

来到了NodeJS的时代。CommonJS是NodeJS所带来的标准模块化实践。

Unlike RequireJS or AngularJS, CommonJS was rather strict. In RequireJS and AngularJS you could have many dynamically-defined modules per file, whereas CommonJS had a one-to-one mapping between files and modules. At the same time, RequireJS had several ways of declaring a module and AngularJS had several kinds of factories, services, providers and so on — besides the fact that its dependency injection mechanism was tightly coupled to the AngularJS framework itself. CommonJS, in contrast, had a single way of declaring modules. Any JavaScript file was a module, calling require would load dependencies, and anything assigned to module.exports was its interface. This enabled better tooling and code introspection — making it easier for tools to learn the hierarchy of a CommonJS component system. Eventually, Browserify was invented as a way of bridging the gap between CommonJS modules for Node.js servers and the browser. Using the browserify command-line interface program and providing it with the path to an entry-point module, one could combine an unthinkable amount of modules into a single browser-ready bundle. The killer feature of CommonJS, the npm package registry, was decisive in aiding its takeover of the module loading ecosystem.

ES6, import, Babel and Webpack

In Node.js v8.5.0, ESM support was introduced behind an --experimental-modules flag — provided that we use the .mjs file extension for our modules. Most evergreen browsers already support ESM without flags. Webpack is a successor to Browserify that largely took over in the role of universal module bundler thanks to a broader set of features. Just like in the case of Babel and ES6, Webpack has long supported ESM with both its static import and export statements as well as the dynamic import() function-like expression. It has made a particularly fruitful adoption of ESM, in no little part thanks to the introduction of a "code-splitting" mechanism[2] whereby it’s able to partition an application into different bundles to improve performance on first load experiences.