Web 技术研究所

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

再黑一波 CommonJS

  要了解 CommonJS 如何工作就可以想想如何实现一个 CommonJS 加载器?其实就是把一堆代码包在一个函数作用域中,然后在这个作用域中提供 require、module、module.exports 之类的东西。这个匿名函数执行后将 module.exprots 储存下来作为接口对外提供。
  require 实际上是同步阻断式地加载并执行一个 JS,并获取目标 JS 文件对外提供的接口对象。CommonJS 最大的问题就是加载 JS 时是同步的。虽然这个问题在一般的 node 环境下暴露不出来,那是因为我们通常会忽略硬盘读写速度。如果磁盘是通过网络挂载的,读写操作很慢甚至还会失败,这种情况下 CommonJS 就很容易出问题。比如一个基于 http 模块的 Web 服务器,如果在服务过程中某一个业务模块加载由于磁盘原因缓慢,那就会导致当前进程上的整个 http 服务器都停止工作,请求会被全部挂起。如果此时内存扛不住这些挂起的请求服务器就要挂掉了。
  当然,我们也不会蠢到让生产机器上的代码使用外部挂载磁盘的资源,但是确实是个潜在的问题。其实这个问题的核心就是运行时同步加载资源。我觉得运行时做阻断操作是非常危险的,前端的 XHR 规范中也不推荐运行时同步加载资源(浏览器会抛出警告)。
  ES6 Modules 已经禁止运行时加载依赖,所有的依赖都必须是编译期确定的。import 语句必须放在 JS 文件的最外层作用域,而且其参数必须是字符串字面量,不能是引用。虽然 ES6 Modules 加了这些限制让一些 CommonJS 用户感到十分不爽,但我觉得 ES6 的做法是对的。它成功避免了运行时同步加载这个大坑。
  现在流行的许多前端工程化工具都是基于自动分析依赖实现的。而且好多基于 CommonJS 的依赖分析让我觉得不可思议。CommonJS 这种可以运行时加载资源的工作模式怎么可能自动分析依赖?比如我有一个叫 hehe 的目录,里面有一坨 js 文件 mkdir hehe for i in {0..99}; do echo "module.exports = $i;" > ./hehe/$i.js done   然后我的代码随机引用
var hehe = require('./hehe/' + (Math.random() * 100 | 0) + '.js'); console.log(hehe);   这是一个 CommonJS 合法的写法,请问依赖分析要如何做?其实神奇的 webpack 还真支持了这种写法,它把 hehe 目录下的所有模块都打包进来,最后再将没用到的模块删掉。但是这显然黑科技过头了。如果目录很大很大,全部打包进来可能就直接崩溃了。而且本地目录是可以扫描,如果路径支持 URL 怎么办?URL 的目录是无法扫描的。
  CommonJS 还有 require.ensure 这不伦不类的东西。它根本就是 AMD 嘛!如果认为 require.ensure 好,那就是在说 AMD 好。CommonJS 但同步加载让我非常讨厌它;ES6 Modules 只是将同步加载放到编译期而已,我可以接受,只是浏览器没有支持;而 AMD 当然也有自己的问题,虽然它解决了同步加载的问题,但是 require 是运行时决定的,导致了依赖分析无法完美实现。
  我的观点是,如果是直接跑在浏览器上的多页应用就适合使用 AMD,至少它比较擅长按需加载;在浏览器上跑的多页应用,希望资源打包在一起的可以考虑使用 ES6 Modules,因为它可以进行完美的依赖分析;写原生 node 的时候使用 CommonJS(其实并不完全是),或者说是无奈地选择 CommonJS,因为 node 自己是这么支持的。
网名:
54.144.24.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^