-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
前端模块化开发那点历史 #588
Comments
sea.js的执行时间是在require的时候执行么?之前只是module ready? |
感谢玉伯为我们带来的Seajs。 |
读完这篇文章,也让我唏嘘不已,标准之争终究还是要进行下去 |
执行时机在ES6草案的讨论中也有争论。我个人倾向于静态链接,所以肯定支持early执行。 |
很配服玉伯,不论代码,还是文字,都很了得 |
star! |
好文值得看,值得深思。 |
顶好文。。 看完最大的感觉就是, 可能lz是阳春白雪,而我们则是屌丝。。。 喜欢requirejs胜过 commonjs, 其实我最不喜欢的就是那个多余的exports了,要多烦有多烦。 可能因为是后台开发的背景而且做的是test driven development, 我很喜欢requirejs的一个特性,比方说 define(['a', 'b', 'c'], function(a,b,c) {
return new function() {
this.someFunc = function() {}
};
}); 我只想返回一个构造体,则可以 define(['a', 'b', 'c'], function(a,b,c) {
var HelloService = function() {
this.someFunc = function() {}
};
return HelloService;
}); 如果语法当中非得来句exports, 我都要恶心的吐了。 其次, requirejs在支持minify,shim等等上面,至少在当时算做得最好的了吧? 这些在自动build上面都是很重要的。 |
exports 不是必须的哦,return 的写法,CMD 也支持。 |
为什么不喜欢 exports ,理解不能。 |
@lifesinger 是啊,不过个人感觉这就有点像javascript也可以不写分号, 而groovy也支持分号一样。。不是推荐使用。 requirejs的大部分官方案例上用法都是用return啊。。 (当然我也没太多的去研究,有错请忽略) @afc163 define(
//The name of this module
"types/Manager",
//The array of dependencies
["types/Employee"],
//The function to execute when all dependencies have loaded. The
//arguments to this function are the array of dependencies mentioned
//above.
function (Employee) {
function Manager () {
this.reports = [];
}
//This will now work
Manager.prototype = new Employee();
//return the Manager constructor function so it can be used by
//other modules.
return Manager;
}
); 你可以看到Employee是首字母大写,我们用这个表示该对象引用是需要实例化的,可以用 new Employee()来构建一个。 而如果实现的是singleton,在define引用的时候用首字母小写表示,表示是一个可用的instance。 如果用的是exports的话, 可能用的时候就得 var empolyee = new EmployeeModule.Employee(); 当然具体如何用有很多不同的实践并且适合不同的场景。确实更多的是个人喜好的问题把。 话说 C、java出身的程序员喜好 带 { } () 的语法, 而 python 出身 (包括以前的Fortran)可能更喜好stylus, coffescript这种语法。 |
如果返回的是类的话,可以用 module.exports 来代替 exports
目前 Arale 里大多是这么调用的。https://github.com/aralejs/overlay/blob/master/src/overlay.js#L184 module.exports 而不是 return 的好处是,一个文件里可能会出现多个 return 。 |
@afc163 测试上除了我们多用了一个chai之外, mocha ,expect(我们用的是chai的expect)和sinon用的都一样。 |
除了 module.exports, CMD 里也直接支持 return 返回的。 2013/6/19 Jeff [email protected]
王保平 / 玉伯(射雕) |
@lifesinger 在CMD的体系里面用return, 我的感觉就像下面所说的那样。。
当然这只是个人不成熟的理解。。 : ) |
其实我也喜欢 return ,因为少写几个字,而“少写几个字”正是社会前进的动力啊 |
嗯,用 AMD 和 CMD ,表面上的区别是书写格式不同。 AMD 下,默认推荐的模块格式是 define(['a','b'], function(a, b) {
// do sth
}) CMD 里,默认推荐的是 define(function(require, exports, module) {
var a = require('a')
var b = require('b')
// do sth
...
}) 目前越来越意识到,默认推荐的模式书写格式表面上的不同,背后带来的差异越来越有意思。 其中一个核心差异是: 就近原则 AMD 中的依赖通过函数参数传入,带来的好处是一个模块的依赖直接在头部一目了然,非常清晰。带来的不足是,AMD 下其实默认推荐了 var 在一起的写法,比如 var a = 1, b = 2, long long ...
// do sth A
// do sth B CMD 书写风格下,导向的是就近原则(变量在需要它的地方之前才定义): var a = 1
// do sth A
var b = 2
// do sth B 还有一个核心差异也跟就近原则有关,是 懒懒原则 在 AMD 里 define(['a', 'b'], function(a, b) {
// 模块 a 和 b 在这里就都执行好并可用了
}) 在 CMD 里 define(function(require, exports) {
// ...
var a = require('a') // 模块 a 运行到此处才执行
// ...
if (false) {
var b = require('b') // 当某些条件为 false 时,模块 b 永远也不会执行
}
}) CMD 更懒。 个人觉得 AMD 和 CMD 的核心差异体现在 对自然的追求上。
这两种对自然的理解,表面上看好像仅是代码风格区别,但随着 Sea.js 在实际项目中的锤炼和演进,我个人越来越觉得 CMD 规范从功能上更具有优势。比如 对打包的影响。 假设模块 a 和模块 b,打包后 define('a', [...], ....)
define('b', [...], ....) 在 AMD 下,由于 define 时模块 a 及其依赖立刻就执行了,这意味着,如果 b 依赖 a,那么在打包时,需要将模块 a 放在模块 b 前面,否则就执行有问题了。 但在 CMD 里,由于一切都是懒执行的,define 时仅仅是 meta 信息的注册,这意味着在 CMD 规范下,打包时,模块的顺序是无关的。 而包规范(Packages 规范)中很重要的一条,就是合并模块时,模块的顺序应该无关。 CMD 可以使得构建时的复杂度降低。 这个问题还可以深挖。目前 Sea.js 拥有 plugin-combo 插件,模块的合并可以放在线上动态做。有些情况下(比较容易出现),动态 combo 的地址会很长:
当 url 地址很长时,超过 2083(好像是这个值),在 IE 以及一些服务器配置下,过长的 url 会出问题。这时经典的解决办法是将 url 拆分成多段:
拆分后,在 CMD 规范下,上面两个 url 可以并发同时请求,谁先返回都没问题。但在 AMD 下,上面的需求,就挂了,很难实现。 单个你会说 RequireJS 鼓励的是项目上线前,通过构建工具先构建好,不需要线上 combo,也就不会遇到上面的问题。 我想说的是,RequireJS 通过严格的项目开发流程的确可以解决问题。但 Sea.js 放得更宽泛,提前合并好,还是线上时才动态 combo,对 CMD 模块来说都可行。很多时候,combo 真心省事,也更自然。前端开发并非处处要像 Java 一样引入严格的构建过程。 除了对打包的影响,CMD 的懒执行策略,也更有利于页面性能。通过 CMD,可以做到页面首次可交互时间(TTI)达成前,不会执行其他脚本。CMD 为执行点提供了更多可控项。 以上理解可能有误,RequireJS 2.0 后,不少理念也在悄悄地发生着变化,现在好像也支持懒执行了,当初对 CommonJS Wrap 格式的支持,也是一种妥协。Sea.js 目前更纯粹、简单。 当然,上面都是我说的。我希望我都是错的,但期望看到有人能理性的证明我是错的。 |
其实我也是更偏向于seajs的lazy load策略,这样可以只加载需要的js,完全符合脚本的动态执行的特性。。。 我们目前的开发和build过程确实更像是后台开发模式, 这些都直接忽视了seajs的强项。。 (当然项目其他人都是老外,其实都不知道seajs) 比方说,开发过程中,我们使用embed jetty (有gradle 插件), 所有js和less 文件都是原始的, 开启 less.watch 后, 直接多台显示器就能检查多种样式效果。 js方面则通过测试驱动,基本上不大需要debug。 在build的时候,则自动运行所有相关的java和javascript的单元测试, 同时利用r.js合并生成单一js文件(一个app一个),同时minify/urglify 相关文件, compile 所有的 less文件到一个静态css。 带来的文件引用方面的变化则通过jsp内部的taglib来做(根据环境判断是生产还是业务环境),同时taglib还会在构建过程中使用自动生成新的cache version。 combo功能看起来很酷,不过不清楚combo需要服务端做怎样的设置,有没有相关的文档可供参考? |
combo 目前用的是 nginx 的模块:https://github.com/alibaba/nginx-http-concat 我个人的观察: RequireJS 比较适合独立 App 型项目,能够做统一构建的那种。但在国内环境下,经常并不这么简单。国内很多应用,是介于 Web Pages 和 Web App 之间。一个页面,很可能头部是一个团队C开发的,页面区域A是团队A开发的,区域B则是团队B开发的。统一构建并不太现实。这种项目场景下,用 RequireJS 会不是很合适,Sea.js 在这种场景下适配性则更强些。 对于独立的中小型项目,无论 RequireJS 还是 Sea.js 抑或 Component,都能搞定。但对于复杂度比较大、团队协作比较『混乱』的项目而言,Sea.js 会具备一些优势,无论是在开发模式,还是运行的性能上。 可能有些偏颇,却是我真心所想。欢迎反驳,要有力的反驳。 |
“很可能头部是一个团队C开发的,页面区域A是团队A开发的,区域B则是团队B开发的。统一构建并不太现实。” |
“历史不是过去,历史正在上演。随着 W3C 等规范、以及浏览器的飞速发展,前端的模块化开发会逐步成为基础设施。一切终究都会成为历史,未来会更好。”——引用玉伯原文最后一段话,我个人也非常赞同。既然谈到了“未来”,我个人认为:前端 js 模块如果继续发展,其模块格式很可能会成为未来 WEB 一种标准规范,产生多种实现方式。就好比 JSON 格式一样,最终成为标准、被浏览器原生实现。 谁更有能成为未来的异步模块标准?SeaJS 遵循 CMD 规范,RequireJS 遵循 AMD 规范,先从这两种不同的格式说起。 CMDCMD 模块依赖声明方式:
CMD 依赖是就近声明,通过内部 CMD 规范的弊端
规范之外的约定意味着更多的文档说明,除非它们也是规范中的一部分。
AMDAMD 模块依赖声明方式:
AMD 的依赖是提前声明。这种优势的好处就是依赖无需通过静态分析,无论是加载器还是自动化工具都可以很直接的获取到依赖,规范的定义可以更简单,意味着可能产生更强大的实现,这对加载器与自动化分析工具都是有利的。 AMD 规范的弊端
关于第二点的问题需要特别说明下。其实无论是 CMD 还是 AMD 的异步模块,都无法与同步模块规范保持一致(NodeJS 的 Modules),只有谁比谁更像同步模块而已。AMD 要转换为同步模块,除了去掉 总结从规范上来说,AMD 更加简单且严谨,适用性更广,而在 RequireJS 强力的推动下,在国外几乎成了事实上的异步模块标准,各大类库也相继支持 AMD 规范。 但从加载器实现、功能来说,我更倾向于 SeaJS,理由:1、相对自然的依赖声明风格 2、相对精简的代码实现 3、贴心的外围功能设计 4、更好的中文社区支持。 如果有可能,我希望看到 SeaJS 也支持 AMD,与前端社区大环境保持一致最终幸福的是广大开发者。 |
CMD 的 define("gallery/moment/2.0.0/moment",["./i18n/{locale}"],function(t){return t("./i18n/{locale}")}) 你看, |
理论上可以使用正则获取 |
感谢 @aui 参与讨论。 很认可你的分析。CMD 的弊端,核心就一个:要通过 define(['a', 'b'], function(require, exports, module) {
// ...
}) 这样就解决了。从书写格式上看,CMD 其实是 AMD 的子集,可以约等于 AMD 中的 Simplified CommonJS wrapping 写法。CMD 有的问题,AMD 其实都有,AMD 的 dependencies 也是可省略的,省略时也需要通过 话说 ATC 不错,赞。 |
good |
如今回来看看这篇文章及讨论也是获益良多啊 |
我对不知道哪里看来的一句“前端现在是黎明前的黑暗,在几年内一定会明晰起来”感触越来越深,现在处于种种的探路期,究竟往哪走,走成什么样都还不甚清楚,但是作为一个前端开发者,现在正处在这样的一个洪流中,希望能在这里面留下我的一点足迹,能够推动哪怕一点点的变革。 |
2020年了,我也来mark一下 |
一切新方案的出现都是在解决当时技术上的痛点,感慨,感谢~ |
被“我会回来的,带着更好的东西”破防了,感谢前辈大佬们为前端的规范付出的一切 |
以前看了没留下评论,时至今日,我想说是不是一开始,用define的方式就是错的,我一直在想,有没有一种不这么麻烦的声明方式 |
这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
|
这篇文章发布的时候我大三, 如今10年过去了, 我参加工作写前端也快10年了, 还记得当初大学时候老师教我们写网页, 那个时候大概是2011年左右, 当时网页三剑客, "div+css", 如今前端发展迅猛, 百花齐放, 我也从一个不谙世事的页面仔也成长为了能为社区略尽绵薄的青年人了(是的, 头发都掉得差不多了) |
这是来自QQ邮箱的假期自动回复邮件。你好,我最近正在休假中,无法亲自回复你的邮件。我将在假期结束后,尽快给你回复。
|
您好,我是黄丽,您的邮件我已收到,谢谢
|
2023了,轩与冒个泡,向 yubo 致敬 |
2023 冒个泡 向历史致敬 |
大一统是趋势了 |
最近不断有人问及,想起前些天跟 @dexteryy 等人的讨论:dexteryy/OzJS#10 当时有过简单总结,重新梳理如下。
写在前面
CommonJS 社区
大概 09 年 - 10 年期间,CommonJS 社区大牛云集。CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践。
09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端,于是将社区改名叫 CommonJS,同时激烈争论 Modules 的下一版规范。分歧和冲突由此诞生,逐步形成了三大流派:
AMD 与 RequireJS
再来说 AMD 规范。真正的 AMD 规范在这里:Modules/AsynchronousDefinition。AMD 规范一直没有被 CommonJS 社区认同,核心争议点如下:
执行时机有异议
看代码
Modules/1.0:
AMD:
AMD 里提前下载 a.js 是浏览器的限制,没办法做到同步下载,这个社区都认可。
但执行,AMD 里是 Early Executing,Modules/1.0 里是第一次 require 时才执行。这个差异很多人不能接受,包括持 Modules/2.0 观点的也不能接受。
这个差异,也导致实质上 Node 的模块与 AMD 模块是无法共享的,存在潜在冲突。
模块书写风格有争议
AMD 风格下,通过参数传入依赖模块,破坏了 就近声明 原则。比如:
还有就是 AMD 下 require 的用法,以及增加了全局变量 define 等细节,当时在社区被很多人不认可。
最后,AMD 从 CommonJS 社区独立了出去,单独成为了 AMD 社区。有阵子,CommonJS 社区还要求 RequireJS 的文档里,不能再打 CommonJS 的旗帜(这个 CommonJS 社区做得有点小气)。
脱离了 CommonJS 社区的 AMD 规范,实质上演化成了 RequireJS 的附属品。比如
AMD 的流行,很大程度上取决于 RequireJS 作者的推广,这有点像 less 因 Bootstrap 而火起来一样。但火起来的东西未必好,比如个人觉得 stylus 就比 less 更优雅好用。
关于 AMD 和 RequireJS,暂且按下不表。来看另一条暗流:Modules/2.0 流派。
Modules/2.0
BravoJS 的作者 Wes Garland 有很深厚的程序功底,在 CommonJS 社区也非常受人尊敬。但 BravoJS 本身非常学院派,是为了论证 Modules/2.0-draft 规范而写的一个项目。学院派的 BravoJS 在实用派的 RequireJS 面前不堪一击,现在基本上只留存了一些美好的回忆。
这时,Modules/2.0 阵营也有一个实战派:FlyScript。FlyScript 抛去了 Modules/2.0 中的学究气,提出了非常简洁的 Modules/Wrappings 规范:
这个简洁的规范考虑了浏览器的特殊性,同时也尽可能兼容了 Modules/1.0 规范。悲催的是,FlyScript 在推出正式版和官网之后,RequireJS 当时正直红火。期间 FlyScript 作者 khs4473 和 RequireJS 作者 James Burke 有过一些争论。再后来,FlyScript 作者做了自我阉割,将 GitHub 上的项目和官网都清空了,官网上当时留了一句话,模糊中记得是
这中间究竟发生了什么,不得而知。后来我有发邮件给 @khs4473 询问,khs 给了两点挺让我尊重的理由,大意是
这两句话对我影响很大。也是那之后,开始仔细研究 RequireJS,并通过邮件等方式给 RequireJS 提出过不少建议。
再后来,在实际使用 RequireJS 的过程中,遇到了很多坑。那时 RequireJS 虽然很火,但真不够完善。期间也在寻思着 FlyScript 离开时的那句话:“我会回来的,带着更好的东西”
我没 FlyScript 的作者那么伟大,在不断给 RequireJS 提建议,但不断不被采纳后,开始萌生了自己写一个 loader 的念头。
这就是 Sea.js。
Sea.js 借鉴了 RequireJS 的不少东西,比如将 FlyScript 中的
module.declare
改名为define
等。Sea.js 更多地来自 Modules/2.0 的观点,但尽可能去掉了学院派的东西,加入了不少实战派的理念。最后
写着写着,有点沧桑感,不想写了。
历史不是过去,历史正在上演。随着 W3C 等规范、以及浏览器的飞速发展,前端的模块化开发会逐步成为基础设施。一切终究都会成为历史,未来会更好。
The text was updated successfully, but these errors were encountered: