精读《js 模块化发展》

此次是前端精读期刊与你们第一次正式碰面,咱们每周会精读并分析若干篇精品好文,试图讨论出结论性观点。没错,咱们试图经过观点的碰撞,争作无主观精品好文的意见领袖。javascript

我是这一期的主持人 —— 黄子毅css

本期精读的文章是:evolutionOfJsModularityhtml

懒得看文章?不要紧,稍后会附上文章内容概述,同时,更但愿能经过阅读这一期的精读,穿插着深刻阅读原文。前端

1 引言

现在,Javascript 模块化规范很是方便、天然,但这个新规范仅执行了2年,就在 4 年前,js 的模块化还停留在运行时支持,10 年前,经过后端模版定义、注释定义模块依赖。对经历过来的人来讲,历史的模块化方式还停留在脑海中,反而新上手的同窗会更快接受现代的模块化规范。

但为何要了解 Javascript 模块化发展的历史呢?由于凡事都有两面性,了解 Javascript 模块化规范,有利于咱们思考出更好的模块化方案,纵观历史,从 1999 年开始,模块化方案最多维持两年,就出现了新的替代方案,比原有的模块化更清晰、强壮,咱们不能被现代模块化方式限制住思惟,由于如今的 ES2015 模块化方案距离发布也仅仅过了两年。vue

2 内容概要

直接定义依赖 (1999): 因为当时 js 文件很是简单,模块化方式很是简单粗暴 —— 经过全局方法定义、引用模块。这种定义方式与如今的 commonjs 很是神似,区别是 commonjs 以文件做为模块,而这种方法能够在任何文件中定义模块,模块不与文件关联。java

闭包模块化模式 (2003): 用闭包方式解决了变量污染问题,闭包内返回模块对象,只需对外暴露一个全局变量。node

模版依赖定义 (2006): 这时候开始流行后端模版语法,经过后端语法聚合 js 文件,从而实现依赖加载,说实话,如今 go 语言等模版语法也很流行这种方式,写后端代码的时候不以为,回头看看,仍是挂在可维护性上。react

注释依赖定义 (2006): 几乎和模版依赖定义同时出现,与 1999 年方案不一样的,不只仅是模块定义方式,而是终于以文件为单位定义模块了,经过 lazyjs 加载文件,同时读取文件注释,继续递归加载剩下的文件。webpack

外部依赖定义 (2007): 这种定义方式在 cocos2d-js 开发中广泛使用,其核心思想是将依赖抽出单独文件定义,这种方式不利于项目管理,毕竟依赖抽到代码以外,我是否是得两头找呢?因此才有经过 webpack 打包为一个文件的方式暴力替换为 commonjs 的方式出现。git

Sandbox模式 (2009): 这种模块化方式很简单,暴力,将全部模块塞到一个 sanbox 变量中,硬伤是没法解决明明冲突问题,毕竟都塞到一个 sandbox 对象里,而 Sandbox 对象也须要定义在全局,存在被覆盖的风险。模块化须要保证全局变量尽可能干净,目前为止的模块化方案都没有很好的作到这一点。

依赖注入 (2009): 就是你们熟知的 angular1.0,依赖注入的思想如今已普遍运用在 react、vue 等流行框架中。但依赖注入和解决模块化问题还差得远。

CommonJS (2009): 真正解决模块化问题,从 node 端逐渐发力到前端,前端须要使用构建工具模拟。

Amd (2009): 都是同一时期的产物,这个方案主要解决前端动态加载依赖,相比 commonJs,体积更小,按需加载。

Umd (2011): 兼容了 CommonJS 与 Amd,其核心思想是,若是在 commonjs 环境(存在 module.exports,不存在 define),将函数执行结果交给 module.exports 实现 Commonjs,不然用 Amd 环境的 define,实现 Amd。

Labeled Modules (2012): 和 Commonjs 很像了,没什么硬伤,但生不逢时,碰上 Commonjs 与 Amd,那只有被人遗忘的份了。

YModules (2013): 既然都出了 Commonjs Amd,文章还列出了此方案,必定有其独到之处。其核心思想在于使用 provide 取代 return,能够控制模块结束时机,处理异步结果;拿到第二个参数 module,修改其余模块的定义(虽然颇有拓展性,但用在项目里是个搅屎棍)。

ES2015 Modules (2015): 就是咱们如今的模块化方案,尚未被浏览器实现,大部分项目已经过 babeltypescript 提早体验。

3 精读

本次提出独到观点的同窗有:流形黄子毅苏里约camsong杨森淡苍留影,精读由此概括。

从语言层面到文件层面的模块化

从 1999 年开始,模块化探索都是基于语言层面的优化,真正的革命从 2009 年 CommonJS 的引入开始,前端开始大量使用预编译。

这篇文章所提供的模块化历史的方案都是逻辑模块化,从 CommonJS 方案开始前端把服务端的解决方案搬过来以后,算是看到标准物理与逻辑统一的模块化。但以后前端工程不得不引入模块化构建这一步。正是这一步给前端开发无疑带来了诸多的不便,尤为是如今咱们开发过程当中常常为了优化这个工具带了不少额外的成本。

从 CommonJS 以前其实都只是封装,并无一套模块化规范,这个就有些像类与包的概念。我在10年左右用的最多的仍是 YUI2,YUI2 是用 namespace 来作模块化的,但有不少问题没有解决,好比多版本共存,所以后来 YUI3 出来了。

YUI().use('node', 'event', function (Y) {
    // The Node and Event modules are loaded and ready to use.
    // Your code goes here!
});

YUI3 的 sandbox 像极了差很少同时出现的 AMD 规范,但早期 yahoo 在前端圈的影响力仍是很大的,而 requirejs 到 2011 年才诞生,所以圈子不是用着 YUI 要不就本身封装一套 sandbox,内部使用 jQuery。

为何模块化方案这么晚才成型,可能早期应用的复杂度都在后端,前端都是很是简单逻辑。后来 Ajax 火了以后,web app 概念的开始流行,前端的复杂度也呈指数级上涨,到今天几乎和后端接近一个量级。工程发展到必定阶段,要出现的必然会出现。
 

前端三剑客的模块化展望

从 js 模块化发展史,咱们还看到了 css html 模块化方面的严重落后,现在依赖编译工具的模块化加强在将来会被标准所替代。

原生支持的模块化,解决 html 与 css 模块化问题正是之后的方向。

再回到 JS 模块化这个主题,开头也说到是为了构建 scope,实则提供了业务规范标准的输入输出的方式。但文章中的 JS 的模块化还不等于前端工程的模块化,Web 界面是由 HTML、CSS 和 JS 三种语言实现,不管是 CommonJS 仍是 AMD 包括以后的方案都没法解决 CSS 与 HTML 模块化的问题。

对于 CSS 自己它就是 global scope,所以开发样式能够说是喜忧参半。近几年也涌现把 HTML、CSS 和 JS 合并做模块化的方案,其中 react/css-modules 和 vue 都为人熟知。固然,这一点仍是很是依赖于 webpack/rollup 等构建工具,让咱们意识到在 browser 端还有不少本质的问题须要推动。

对于 css 模块化,目前不依赖预编译的方式是 styled-component,经过 js 动态建立 class。而目前 css 也引入了与 js 通讯的机制 与 原生变量支持。将来 css 模块化也极可能是运行时的,因此目前比较看好 styled-component 的方向。

对于 html 模块化,小尤最近爆出与 chrome 小组调研 html Modules,若是 html 获得了浏览器,编辑器的模块化支持,将来可能会取代 jsx 成为最强大的模块化、模板语言。

对于 js 模块化,最近出现的 <script type="module"> 方式,虽然尚未获得浏览器原生支持,但也是我比较看好的将来趋势,这样就连 webpack 的拆包都不须要了,直接把源代码传到服务器,配合 http2.0 完美抛开预编译的枷锁。

上述三中方案都不依赖预编译,分别实现了 html、css、js 模块化,相信这就是将来。

模块化标准推动速度仍然缓慢

2015 年提出的标准,在 17 年依然没有获得实现,即使在 nodejs 端。

这几年 TC39 对语言终于重视起来了,慢慢有动做了,但针对模块标准制定的速度,与落实都很是缓慢,与 javascript 愈来愈流行的趋势逐渐脱节。nodejs 至今也没有实现 ES2015 模块化规范,全部 jser 都处在构建工具的阴影下。

Http 2.0 对 js 模块化的推进

js 模块化定义的再美好,浏览器端的支持粒度永远是瓶颈,http 2.0 正是考虑到了这个因素,大力支持了 ES 2015 模块化规范。

幸运的是,模块化构建未来可能再也不须要。随着 HTTP/2 流行起来,请求和响应能够并行,一次链接容许多个请求,对于前端来讲宣告再也不须要在开发和上线时再作编译这个动做。

几年前,模块化几乎是每一个流行库必造的轮子(YUI、Dojo、Angular),大牛们本身爽的同时其实形成了社区的分裂,很难积累。有了 ES2015 Modules 以后,JS 开发者终于能够像 Java 开始者十年前同样使用一致的方式愉快的互相引用模块。

不过 ES2015 Modules 也只是解决了开发的问题,因为浏览器的特殊性,仍是要通过繁琐打包的过程,等 Import,Export 和 HTTP 2.0 被主流浏览器支持,那时候才是完全的模块化。

Http 2.0 后就不须要构建工具了吗?

看到你们基本都提到了 HTTP/2,对这项技术解决前端模块化及资源打包等工程问题抱有很是大的期待。不少人也认为 HTTP/2 普及后,基本就没有 Webpack 什么事情了。

不过 Webpack 做者 @sokra 在他的文章 webpack & HTTP/2 里提到了一个新的 Webpack 插件 AggressiveSplittingPlugin。简单的说,这款插件就是为了充分利用 HTTP/2 的文件缓存能力,将你的业务代码自动拆分红若干个数十 KB 的小文件。后续若其中任意一个文件发生变化,能够保证其余的小 chunck 不须要从新下载。

可见,即便不断的有新技术出现,也依然须要配套的工具来将前端工程问题解决方案推向极致。

模块化是大型项目的银弹吗?

只要遵循了最新模块化规范,就可使项目具备最好的可维护性吗? Js 模块化的目的是支持前端日益上升的复杂度,但毫不是惟一的解决方案。

分析下 JavaScript 为何没有模块化,为何又须要模块化:这个 95 年被设计出来的时候,语言的开发者根本没有想到它会如此的大放异彩,也没有将它设计成一种模块化语言。按照文中的说法,99 年也就是 4 年后开始出现了模块化的需求。若是只有几行代码用模块化是扯,初始的 web 开发业务逻辑都写在 server 端,js 的做用小之又小。而如今 spa 都出现了,几乎全部的渲染逻辑都在前端,若是仍是没有模块化的组织,开发过程会愈来愈难,维护也是更痛苦。

文中已经详细说明了模块化的发展和优劣,这里不许备作过多的讨论。我想说的是,在模块化以后还有一个模块间耦合的问题,若是模块间耦合度大也会下降代码的可重用性或者说复用性。因此也出现了下降耦合的观察者模式或者发布/订阅模式。这对于提高代码重用,复用性和避免单点故障等都很重要。说到这里,还想顺便提一下最近流行起来的响应式编程(RxJS),响应式编程中有一个很核心的概念就是 observable,也就是 Rx 中的流(stream)。它能够被 subscribe,其实也就是观察者设计模式。

补充阅读

总结

将来前端复杂度不断增长已成定论,随着后端成熟,天然会将焦点转移到前端领域,并且服务化、用户体验愈来愈重要,前端体验早不是当初能看就行,任何网页的异常、视觉的差别,或文案的模糊,都会致使用户流失,支付中断。前端对公司营收的影响,渐渐与后端服务宕机同等严重,因此前端会愈来愈重,异常监控,性能检测,工具链,可视化等等都是这几年你们逐渐重视起来的。

咱们早已不能将 javascript 早期玩具性质的模块化方案用于现代愈来愈重要的系统中,前端界必然出现同等重量级的模块化管理方案,感谢 TC39 制定的 ES2015 模块化规范,咱们已经离不开它,哪怕全部人必须使用 babel。

话说回来,标准推动的太慢,咱们仍是把编译工具看成常态,抱着哪怕支持了 ES2015 全部特性,babel 依然还有用的心态,将预编译进行到底。一句话,模块化仍在路上。js 模块化的矛头已经对准了 css 与 html,这两位元老也该向前卫的 js 学习学习了。

将来 css、html 的模块化会自立门户,仍是赋予 js 更强的能力,让二者的模块化依附于 js 的能力呢?目前 html 有自立门户的苗头(htmlModules),而 css 迟迟没有改变,社区出现的 styled-component 已经用 js 将 css 模块化得很好了,最新 css 规范也支持了与 js 的变量通讯,难道但愿依附于 js 吗?这里但愿获得你们更普遍的讨论。

我也认同,毕竟压缩、混淆、md五、或者利用 nonce 属性对 script 标签加密,都离不开本地构建工具。

听说 http2 的优化中,有个最佳文件大小与数量的比例,那么仍是脱离不了构建工具,前端将来会愈来愈复杂,同时也愈来愈美好。

至此,对于 javascript 模块化讨论已接近尾声,对其优缺点也基本达成了一致。前端复杂度不断提升,促使着模块化的改进,代理(浏览器、node) 的支持程度,与前端特殊性(流量、缓存)可能前端永远也离不开构建工具,新的标准会让这些工做作的更好,同时取代、加强部分特征,前端的将来是更加美好的,复杂度也更高。

若是你想参与讨论,请点击这里,每周都有新的主题,每周五发布。

相关文章
相关标签/搜索