精读《js 模块化发展》

news/2024/2/28 2:08:29

1 引言

logo

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

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

2 内容概要

直接定义依赖 (1999): 由于当时 js 文件非常简单,模块化方式非常简单粗暴 —— 通过全局方法定义、引用模块。这种定义方式与现在的 commonjs 非常神似,区别是 commonjs 以文件作为模块,而这种方法可以在任何文件中定义模块,模块不与文件关联。

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

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

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

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

Sandbox 模式 (2009): 这种模块化方式很简单,暴力,将所有模块塞到一个 sandbox 变量中,硬伤是无法解决命名冲突问题,毕竟都塞到一个 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 精读

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

从 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 的小文件。后续若其中任意一个文件发生变化,可以保证其他的小 chunk 不需要重新下载。

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

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

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

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

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

补充阅读

  • JavaScript 模块化七日谈
  • JavaScript 模块化编程简史(2009-2016)

总结

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

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

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

未来 css、html 的模块化会自立门户,还是赋予 js 更强的能力,让两者的模块化依附于 js 的能力呢?目前 html 有自立门户的苗头(htmlModules),而 css 迟迟没有改变,社区出现的 styled-component 已经用 js 将 css 模块化得很好了,最新 css 规范也支持了与 js 的变量通信,难道希望依附于 js 吗?这里希望得到大家更广泛的讨论。

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

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

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


http://www.ppmy.cn/news/1350010.html

相关文章

Hadoop:认识MapReduce

MapReduce是一个用于处理大数据集的编程模型和算法框架。其优势在于能够处理大量的数据&#xff0c;通过并行化来加速计算过程。它适用于那些可以分解为多个独立子任务的计算密集型作业&#xff0c;如文本处理、数据分析和大规模数据集的聚合等。然而&#xff0c;MapReduce也有…

从零开始实现消息队列(二)

从零开始实现消息队列 .核心API交换机类型持久化网络通信Connection和Channel 消息应答模块划分 . 核心API 对于Broker来说,要实现以下核心API,通过这些API来实现消息队列的基本功能. 创建队列(queueDeclare)销毁队列(queueDelete)创建交换机(exchangeDeclare)销毁交换机(exc…

QT学习文件操作类 QFile

&#xff08;一&#xff09;QFile QFile 是 Qt 框架中用于文件处理的一个类。它提供了读取和写入文件的功能&#xff0c;支持文本和二进制文件。QFile 继承自 QIODevice &#xff0c;因此它可以像其他 IO 设备一样使用。 &#xff08;1&#xff09;主要功能 1. 文件读写…

学习记录691@spring面试之bean的作用域

Spring为Bean定义了5种作用域&#xff0c;分别为Singleton&#xff08;单例&#xff09;、Prototype&#xff08;原型&#xff09;、Request&#xff08;请求级别&#xff09;、Session&#xff08;会话级别&#xff09;和Global Session&#xff08;全局会话&#xff09;。 S…

mysql、mybatis中SORT

SORT排序 根据数据表sys_series中HOT&#xff08;int类型&#xff09;进行升序排列&#xff1a; 原来的数据库中存储&#xff1a; 排序 # 结果是HOT字段为null的所有数据都排在最前面&#xff0c;不为null的数据按升序排列 SELECT * FROM sys_series ORDER BY HOT;# 结果是H…

项目02《游戏-13-开发》Unity3D

基于 项目02《游戏-12-开发》Unity3D &#xff0c; 任务 &#xff1a;宠物系统 及 人物头像血条 首先在主面板MainPanel预制体中新建一个Panel&#xff0c; 命名为PlayerInfo 新建Image&#xff0c;作为头像 新建Slider&#xff0c;作为血条 对Panel组件添加一个水…

python从入门到精通(二十):python的exe程序打包制作

python的exe程序打包制作 python打包的概念python打包的模块导入模块安装验证基本语法命令参数文件夹模式单文件模式资源嵌入exe更改图标启动画面&#xff08;闪屏&#xff09;禁用异常提示 python打包的概念 将普通的*.py程序文件打包成exe文件。exe文件即可执行文件&#xf…

林浩然与杨凌云的Java世界奇遇记:垃圾回收大冒险

林浩然与杨凌云的Java世界奇遇记&#xff1a;垃圾回收大冒险 The Java Adventure Chronicles of Lin Haoran and Yang Lingyun: Garbage Collection Odyssey 在一个充满0和1代码森林的世界里&#xff0c;住着两位勇敢的程序员侠侣——林浩然和杨凌云。林浩然是个身怀Java绝技的…

[职场] 如何通过运营面试_1 #笔记#媒体#经验分享

如何通过运营面试 盈利是公司的事情&#xff0c;而用户就是你运营的事情。你需要彻底建立一个庞大而有效的用户群&#xff0c;这样才能让你们的公司想盈利就盈利&#xff0c;想战略就战略&#xff0c;想融资就融资。 一般从事运营的人有着强大的自信心&#xff0c;后台数据分析…

Ainx-V0.2-简单的连接封装与业务绑定

&#x1f4d5;作者简介&#xff1a; 过去日记&#xff0c;致力于Java、GoLang,Rust等多种编程语言&#xff0c;热爱技术&#xff0c;喜欢游戏的博主。 &#x1f4d7;本文收录于Ainx系列&#xff0c;大家有兴趣的可以看一看 &#x1f4d8;相关专栏Rust初阶教程、go语言基础系列…

爬爬今天爬小说————爬虫练习

爬不同的的小说&#xff0c;会有略微的改动。 我今天这个是从一章的提前到全部的提前。 在我们电脑里面了&#xff0c;想怎么看就怎么看。 代码代码&#xff1a; import re import requestsheaders {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x6…

Java图形化界面编程——弹球游戏 笔记

Java也可用于开发一些动画。所谓动画&#xff0c;就是间隔一定的时间(通常小于0 . 1秒 )重新绘制新的图像&#xff0c;两次绘制的图像之间差异较小&#xff0c;肉眼看起来就成了所谓的动画 。 ​ 为了实现间隔一定的时间就重新调用组件的 repaint()方法&#xff0c;可以借助于…

【每日一题】牛客网——链表分割

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…

设计模式-状态模式 State

状态模式 一、简介概述二、有限状态机1、分支法2、查表法3、状态模式 三、重点回顾 一、简介概述 状态模式并不是很常用&#xff0c;但是在能够用到的场景里&#xff0c;它可以发挥很大的作用。 状态设计模式是一种行为型设计模式&#xff0c;它允许对象在其内部状态发生变化…

【Kubernetes】kubectl top pod 异常?

目录 前言一、表象二、解决方法1、导入镜像包2、编辑yaml文件3、解决问题 三、优化改造1.修改配置文件2.检查api-server服务是否正常3.测试验证 总结 前言 各位老铁大家好&#xff0c;好久不见&#xff0c;卑微涛目前从事kubernetes相关容器工作&#xff0c;感兴趣的小伙伴相互…

Tdesign 常用知识

Mock数据中的常见随机数&#xff1a; mock 数据中&#xff0c; 开头的是 Mock.js 的语法。Mock.js 是一个用于生成随机数据的库&#xff0c;它提供了一些特殊的语法&#xff0c;可以方便地生成各种类型的随机数据。 在这个 mock 数据中&#xff0c;使用了以下语法&#xff1a…

blender怎么保存窗口布局,怎么设置默认输出文件夹

进行窗口布局大家都会&#xff0c;按照自己喜好来就行了&#xff0c;设置输出文件夹如图 这些其实都简单。关键问题在于&#xff0c;自己调好了窗口布局&#xff0c;或者设置好了输出文件夹之后&#xff0c;怎么能让blender下次启动的时候呈现出自己设置好的窗口布局&#xff…

【doghead】uv_loop_t的创建及线程执行

worker测试程序,类似mediasoup对uv的使用,是one loop per thread 。创建一个UVLoop 就可以创建一个uv_loop_t Transport 创建一个: 试验配置创建一个: UvLoop 封装了libuv的uv_loop_t ,作为共享指针提供 对uv_loop_t 创建并初始化

中科大计网学习记录笔记(九):DNS

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

【每日一题】LeetCode——反转链表

&#x1f4da;博客主页&#xff1a;爱敲代码的小杨. ✨专栏&#xff1a;《Java SE语法》 | 《数据结构与算法》 | 《C生万物》 ❤️感谢大家点赞&#x1f44d;&#x1f3fb;收藏⭐评论✍&#x1f3fb;&#xff0c;您的三连就是我持续更新的动力❤️ &#x1f64f;小杨水平有…
最新文章