Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

前端项目的 js 文件不适合直接依赖

  我以前只喷 CommonJS,最近想试着对一个前端项目完全使用 AMD,结果发现同样存在问题。所以这回我不仅仅是喷 CommonJS,就算是 AMD 甚至 ES6 Modules 它们也成了我喷的目标。前端这种瓶颈在传输上层上的平台,也许真的不适合让 js 文件之间互相依赖。
  我们老说到 2020 年要全面建成小康社会,本着这个原则,我们现在假设浏览器已经全面支持 ES6 Modules 吧?那会是怎么样一种体验呢?
  在前端代码中肯定会有一堆 import export 之类的东西来处理模块吧,浏览器在加载入口文件时根据其所需的依赖树,一层层加载下去。直到所有依赖加载完成后开始执行。
  浏览器一层层地分析依赖就是个坑。假如依赖树的深度是 n,客户端到服务器的 ping 延迟是 m,不考虑 TCP 连接建立和传输本身的成本,在前端代码开始执行前就耗费了至少 n*m 的时间。早在 AMD 时代就被这个问题坑过,解决的方式就是预编译。由于 AMD 可以主动对模块命名,只要在预编译期分析出所有依赖,想办法让页面加载这些模块的 js 文件就行。比如最简单粗暴的方式就是打包工具直接 concat 到入口的 js 文件中,或者服务器直接使用一个像 tengine 的 concat module 那样的东西来搞定。于是上面纠结的深度 n 就被降到了 1。
  但是 AMD 的这个方案对在 ES6 Module 上是行不通的,因为 ES6 Module 和 CommonJS 一样,文件即模块。而且如果真需要预编译,那还叫全面小康么?其实这个问题也是可以解决的。既然我们说全面小康社会,那就可以假设 HTTP2 已经完美支持。于是使用 HTTP2 的 Server Push 机制,每当客户端加载一个 js 文件时服务器就分析依赖,并把这个 js 文件所依赖的资源 Push 给客户端。
  结果小康社会动用了各种重量级武器,绕了一大圈解决了这个 AMD 早就解决了的问题。而且再看看人家 CommonJS,同样是文件即模块,就从来没有遇到过这种问题。因为现在基于 CommonJS 的打包工具,诸如 webpack 之类的东西都是一股脑地把 js 打包在一起了。
  然而我要讨论的核心问题并不是如何分析依赖加载模块,因为这件事太简单了,上面说的 AMD、CommonJS、ES6 Modules 都没问题。接下来才是重点!也是我想把它们全喷一遍的契机。—— 当一个模块更新时怎么办?
  一个 Web 页面的常规做法是对入口的 html 文件设置较短甚至不设置 http cache,对里面所需的各种资源设置长期缓存,并加上版本号来更新。这样新版本发布可以快速同步给用户,而资源如果没有更新就可以使用原来的缓存。
  按照惯例,我们还是先来黑 CommonJS 吧。把所有东西打包在一起的结果就是,任何变更都会造成更新从而无法使用缓存。黑不用太多,一句刷掉。然后就是 AMD,它的思想是在运行时每个模块都知道自己依赖了什么,从而动态地加载。如果每个模块依然使用文件路径作为模块名称,那么如果被依赖的目标版本号有变化就会导致从这个变更点往后的依赖树都需要更新从而无法使用缓存。小康社会的 ES6 Modules,同 AMD 的问题差不多,一个模块依赖了哪些其它模块的哪个版本也是写在文件中,同样会因为一个底层模块的更新导致依赖树无法使用缓存。
  如果我们细看就会发现,核心问题其实是依赖方维护被依赖方的版本号会导致依赖版本更新时依赖树无法命中缓存
  解决这个问题就是让避免在依赖方管理版本,依赖方只要知道自己依赖了什么就可以了,至于版本应该交给更专业的东西统一管理。而 Web 这个平台上,html 才是专业版本管理好么,还有配套的 manifest 可以用。具体的解决方案也有很多,比如 AMD。别忘了 AMD 的模块是可命名的,只要模块之间的关系只使用模块名字,带版本的文件名交给 html 来维护,这样就可以解决。
  到了这个层面上,其实 CommonJS 和 ES6 Module 也可以,虽然它们是文件即模块,但文件名和带版本号的文件名是两回事。我们只是缺少个工具而已!造嘛,所有能用工具来解决的问题都不是问题。
网名:
3.80.55.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^