Skip to content
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

Closed
lifesinger opened this issue Mar 7, 2013 · 69 comments
Closed

前端模块化开发那点历史 #588

lifesinger opened this issue Mar 7, 2013 · 69 comments
Milestone

Comments

@lifesinger
Copy link
Member

最近不断有人问及,想起前些天跟 @dexteryy 等人的讨论:dexteryy/OzJS#10 当时有过简单总结,重新梳理如下。

写在前面

  1. 不谈什么:传统的模块化开发方式,比如文件拆分、全局变量、命名空间,以及 YUI3 式的模块化开发方式。有兴趣的可阅读:前端模块化开发的价值 #547
  2. 谈什么: 关于 CommonJS、AMD、Node.js、CMD 等相关的故事与未来趋势,很有意思。
  3. 不一定精准:本文是基于史实的扯淡,因此部分文字特别是时间都是模糊记忆,不一定精准。关于流派、趋势则是个人在社区的感受,不代表客观看法。(看法都是主观的,呵呵)

CommonJS 社区

大概 09 年 - 10 年期间,CommonJS 社区大牛云集。CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node.js 等环境下取得了很不错的实践。

09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端,于是将社区改名叫 CommonJS,同时激烈争论 Modules 的下一版规范。分歧和冲突由此诞生,逐步形成了三大流派:

  1. Modules/1.x 流派。这个观点觉得 1.x 规范已经够用,只要移植到浏览器端就好。要做的是新增 Modules/Transport 规范,即在浏览器上运行前,先通过转换工具将模块转换为符合 Transport 规范的代码。主流代表是服务端的开发人员。现在值得关注的有两个实现:越来越火的 component 和走在前沿的 es6 module transpiler
  2. Modules/Async 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范。这个观点下的典型代表是 AMD 规范及其实现 RequireJS。这个稍后再细说。
  3. Modules/2.0 流派。这个观点觉得浏览器有自身的特征,不应该直接用 Modules/1.x 规范,但应该尽可能与 Modules/1.x 规范保持一致。这个观点下的典型代表是 BravoJS 和 FlyScript 的作者。BravoJS 作者对 CommonJS 的社区的贡献很大,这份 Modules/2.0-draft 规范花了很多心思。FlyScript 的作者提出了 Modules/Wrappings 规范,这规范是 CMD 规范的前身。可惜的是 BravoJS 太学院派,FlyScript 后来做了自我阉割,将整个网站(flyscript.org)下线了。这个故事有点悲壮,下文细说。

AMD 与 RequireJS

再来说 AMD 规范。真正的 AMD 规范在这里:Modules/AsynchronousDefinition。AMD 规范一直没有被 CommonJS 社区认同,核心争议点如下:

执行时机有异议

看代码

Modules/1.0:

var a = require("./a") // 执行到此处时,a.js 才同步下载并执行

AMD:

define(["require"], function(require) {
  // 在这里,模块 a 已经下载并执行好
  // ...
  var a = require("./a") // 此处仅仅是取模块 a 的 exports

})

AMD 里提前下载 a.js 是浏览器的限制,没办法做到同步下载,这个社区都认可。

但执行,AMD 里是 Early Executing,Modules/1.0 里是第一次 require 时才执行。这个差异很多人不能接受,包括持 Modules/2.0 观点的也不能接受。

这个差异,也导致实质上 Node 的模块与 AMD 模块是无法共享的,存在潜在冲突。

模块书写风格有争议

AMD 风格下,通过参数传入依赖模块,破坏了 就近声明 原则。比如:

define(["a", "b", "c", "d", "e", "f"], function(a, b, c, d, e, f) {

    // 等于在最前面申明并初始化了要用到的所有模块

   if (false) {
       // 即便压根儿没用到某个模块 b,但 b 还是提前执行了
       b.foo()
   }

})

还有就是 AMD 下 require 的用法,以及增加了全局变量 define 等细节,当时在社区被很多人不认可。

最后,AMD 从 CommonJS 社区独立了出去,单独成为了 AMD 社区。有阵子,CommonJS 社区还要求 RequireJS 的文档里,不能再打 CommonJS 的旗帜(这个 CommonJS 社区做得有点小气)。

脱离了 CommonJS 社区的 AMD 规范,实质上演化成了 RequireJS 的附属品。比如

  1. AMD 规范里增加了对 Simplified CommonJS Wrapper 格式的支持。这个背后是因为 RequireJS 社区有很多人反馈想用 require 的方式,最后 RequireJS 作者妥协,才有了这个半残的 CJS 格式支持。(注意这个是伪支持,背后依旧是 AMD 的运行逻辑,比如提前执行。)
  2. AMD 规范的演进,离不开 RequireJS。这有点像 IE…… 可能是我的偏见。

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 规范:

module.declare(function(require, exports, module)
{
   var a = require("a"); 
   exports.foo = a.name; 
});

这个简洁的规范考虑了浏览器的特殊性,同时也尽可能兼容了 Modules/1.0 规范。悲催的是,FlyScript 在推出正式版和官网之后,RequireJS 当时正直红火。期间 FlyScript 作者 khs4473 和 RequireJS 作者 James Burke 有过一些争论。再后来,FlyScript 作者做了自我阉割,将 GitHub 上的项目和官网都清空了,官网上当时留了一句话,模糊中记得是

我会回来的,带着更好的东西。

这中间究竟发生了什么,不得而知。后来我有发邮件给 @khs4473 询问,khs 给了两点挺让我尊重的理由,大意是

  1. 我并非前端出身,RequireJS 的作者 James Burke 比我更懂浏览器。
  2. 我们应该协同起来推动一个社区的发展,即便它不是你喜欢的。

这两句话对我影响很大。也是那之后,开始仔细研究 RequireJS,并通过邮件等方式给 RequireJS 提出过不少建议。

再后来,在实际使用 RequireJS 的过程中,遇到了很多坑。那时 RequireJS 虽然很火,但真不够完善。期间也在寻思着 FlyScript 离开时的那句话:“我会回来的,带着更好的东西”

我没 FlyScript 的作者那么伟大,在不断给 RequireJS 提建议,但不断不被采纳后,开始萌生了自己写一个 loader 的念头。

这就是 Sea.js。

Sea.js 借鉴了 RequireJS 的不少东西,比如将 FlyScript 中的 module.declare 改名为 define 等。Sea.js 更多地来自 Modules/2.0 的观点,但尽可能去掉了学院派的东西,加入了不少实战派的理念。

最后

写着写着,有点沧桑感,不想写了。

历史不是过去,历史正在上演。随着 W3C 等规范、以及浏览器的飞速发展,前端的模块化开发会逐步成为基础设施。一切终究都会成为历史,未来会更好。

@lifesinger lifesinger mentioned this issue Mar 7, 2013
45 tasks
@hefangshi
Copy link

sea.js的执行时间是在require的时候执行么?之前只是module ready?

@lifesinger
Copy link
Member Author

@hefangshi

@mengzhiang
Copy link

感谢玉伯为我们带来的Seajs。

@norfish
Copy link

norfish commented Mar 7, 2013

读完这篇文章,也让我唏嘘不已,标准之争终究还是要进行下去

@hax
Copy link

hax commented Mar 7, 2013

执行时机在ES6草案的讨论中也有争论。我个人倾向于静态链接,所以肯定支持early执行。

@moxuanyuan
Copy link

很配服玉伯,不论代码,还是文字,都很了得

@weishai
Copy link

weishai commented Apr 19, 2013

star!

@anjorfu
Copy link

anjorfu commented Apr 23, 2013

好文值得看,值得深思。

@realdah
Copy link

realdah commented Jun 18, 2013

顶好文。。

看完最大的感觉就是, 可能lz是阳春白雪,而我们则是屌丝。。。
因为我们团队很多人喜欢less 胜过 SASS 和 sytlus, 喜欢原生javascript胜过 coffee script。

喜欢requirejs胜过 commonjs, 其实我最不喜欢的就是那个多余的exports了,要多烦有多烦。

可能因为是后台开发的背景而且做的是test driven development, 我很喜欢requirejs的一个特性,比方说
我想返回一个singleton,只需要

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上面都是很重要的。

@lifesinger
Copy link
Member Author

exports 不是必须的哦,return 的写法,CMD 也支持。

@afc163
Copy link
Member

afc163 commented Jun 19, 2013

为什么不喜欢 exports ,理解不能。

@realdah
Copy link

realdah commented Jun 19, 2013

@lifesinger 是啊,不过个人感觉这就有点像javascript也可以不写分号, 而groovy也支持分号一样。。不是推荐使用。

requirejs的大部分官方案例上用法都是用return啊。。 (当然我也没太多的去研究,有错请忽略)

@afc163
不喜欢exports是因为我偏好一个module就返回一个reference。而且在我们的代码过程中,比方requirejs的示例

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();
有点inner class的感觉。

当然具体如何用有很多不同的实践并且适合不同的场景。确实更多的是个人喜好的问题把。

话说 C、java出身的程序员喜好 带 { } () 的语法, 而 python 出身 (包括以前的Fortran)可能更喜好stylus, coffescript这种语法。

@afc163
Copy link
Member

afc163 commented Jun 19, 2013

如果返回的是类的话,可以用 module.exports 来代替 exports

define(function(require, exports, module) {

    function Manager () {
        this.reports = [];
    }

    module.exports = Manager;

})

目前 Arale 里大多是这么调用的。https://github.com/aralejs/overlay/blob/master/src/overlay.js#L184

module.exports 而不是 return 的好处是,一个文件里可能会出现多个 return 。

@realdah
Copy link

realdah commented Jun 19, 2013

@afc163
是啊,其实用法确实都差不多。。。 就是个人偏好的问题了 : -)
我们代码基本上看起来和Arale里面也差不多的,tests看起来也其实一样了。

测试上除了我们多用了一个chai之外, mocha ,expect(我们用的是chai的expect)和sinon用的都一样。
你们工程的那个makefile看起来比较怪,看来跟spm有关对吧。

@lifesinger
Copy link
Member Author

除了 module.exports, CMD 里也直接支持 return 返回的。

2013/6/19 Jeff notifications@github.com

@afc163 https://github.com/afc163
是啊,其实用法确实都差不多。。。 就是个人偏好的问题了 : -)
我们代码基本上看起来和Arale里面也差不多的,tests看起来也其实一样了。

测试上除了我们多用了一个chai之外, mocha ,expect(我们用的是chai的expect)和sinon用的都一样。
你们工程的那个makefile看起来比较怪,看来跟spm有关对吧。


Reply to this email directly or view it on GitHubhttps://github.com//issues/588#issuecomment-19678314
.

王保平 / 玉伯(射雕)
送人玫瑰手有余香

@realdah
Copy link

realdah commented Jun 19, 2013

@lifesinger
我之前就看到你的回复了啊, 我也写了我的理解,看引用部分

在CMD的体系里面用return, 我的感觉就像下面所说的那样。。

是啊,不过个人感觉这就有点像javascript也可以不写分号, 而groovy也支持分号一样。。不是推荐使用。
而requirejs的大部分官方案例上用法都是用return啊。。 (当然我也没太多的去研究,有错请忽略)

当然这只是个人不成熟的理解。。 : )

@edokeh
Copy link
Contributor

edokeh commented Jun 19, 2013

其实我也喜欢 return ,因为少写几个字,而“少写几个字”正是社会前进的动力啊

@lifesinger
Copy link
Member Author

嗯,用 return 还是 module.exports,纯个人习惯,没有优劣。

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 的核心差异体现在 对自然的追求上

  1. 何为自然?CMD 认为就近原则书写代码更自然,AMD 则觉得提前都写好更自然。
  2. 何为自然?CMD 认为需要时才执行更自然,AMD 觉得依赖就应该提前都执行好,这样才自然。

这两种对自然的理解,表面上看好像仅是代码风格区别,但随着 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 的地址会很长:

https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/long-url.js

当 url 地址很长时,超过 2083(好像是这个值),在 IE 以及一些服务器配置下,过长的 url 会出问题。这时经典的解决办法是将 url 拆分成多段:

https://a.alipaybojects.com/??path/to/a.js,path/to/b.js..................path/to/u.js
https://a.alipaybojects.com/??path/to/f.js,path/to/g.js..................path/to/long-url.js

拆分后,在 CMD 规范下,上面两个 url 可以并发同时请求,谁先返回都没问题。但在 AMD 下,上面的需求,就挂了,很难实现。

单个你会说 RequireJS 鼓励的是项目上线前,通过构建工具先构建好,不需要线上 combo,也就不会遇到上面的问题。

我想说的是,RequireJS 通过严格的项目开发流程的确可以解决问题。但 Sea.js 放得更宽泛,提前合并好,还是线上时才动态 combo,对 CMD 模块来说都可行。很多时候,combo 真心省事,也更自然。前端开发并非处处要像 Java 一样引入严格的构建过程。

除了对打包的影响,CMD 的懒执行策略,也更有利于页面性能。通过 CMD,可以做到页面首次可交互时间(TTI)达成前,不会执行其他脚本。CMD 为执行点提供了更多可控项。

以上理解可能有误,RequireJS 2.0 后,不少理念也在悄悄地发生着变化,现在好像也支持懒执行了,当初对 CommonJS Wrap 格式的支持,也是一种妥协。Sea.js 目前更纯粹、简单。

当然,上面都是我说的。我希望我都是错的,但期望看到有人能理性的证明我是错的。

@realdah
Copy link

realdah commented Jun 19, 2013

其实我也是更偏向于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。
所以一般来说, sourcemap基本上对我们来说没啥作用,

combo功能看起来很酷,不过不清楚combo需要服务端做怎样的设置,有没有相关的文档可供参考?

@lifesinger
Copy link
Member Author

combo 目前用的是 nginx 的模块:https://github.com/alibaba/nginx-http-concat
目前在阿里有成熟使用。国外,类似 YUI 的项目,也有大面积使用。Google 目前也有类似的方案。

我个人的观察:

RequireJS 比较适合独立 App 型项目,能够做统一构建的那种。但在国内环境下,经常并不这么简单。国内很多应用,是介于 Web Pages 和 Web App 之间。一个页面,很可能头部是一个团队C开发的,页面区域A是团队A开发的,区域B则是团队B开发的。统一构建并不太现实。这种项目场景下,用 RequireJS 会不是很合适,Sea.js 在这种场景下适配性则更强些。

对于独立的中小型项目,无论 RequireJS 还是 Sea.js 抑或 Component,都能搞定。但对于复杂度比较大、团队协作比较『混乱』的项目而言,Sea.js 会具备一些优势,无论是在开发模式,还是运行的性能上。

可能有些偏颇,却是我真心所想。欢迎反驳,要有力的反驳。

@HiZhaoxiaoyang
Copy link

“很可能头部是一个团队C开发的,页面区域A是团队A开发的,区域B则是团队B开发的。统一构建并不太现实。”
确实很有体会,尤其对那种积累了3~5年以上还要不断迭代开发的项目。

@aui
Copy link

aui commented Jun 20, 2013

“历史不是过去,历史正在上演。随着 W3C 等规范、以及浏览器的飞速发展,前端的模块化开发会逐步成为基础设施。一切终究都会成为历史,未来会更好。”——引用玉伯原文最后一段话,我个人也非常赞同。既然谈到了“未来”,我个人认为:前端 js 模块如果继续发展,其模块格式很可能会成为未来 WEB 一种标准规范,产生多种实现方式。就好比 JSON 格式一样,最终成为标准、被浏览器原生实现。

谁更有能成为未来的异步模块标准?SeaJS 遵循 CMD 规范,RequireJS 遵循 AMD 规范,先从这两种不同的格式说起。

CMD

CMD 模块依赖声明方式:

define(function (require) {
    var a = require('./a');
    var b = require('./b');
    // more code ..
})

CMD 依赖是就近声明,通过内部require方法进行声明。但是因为是异步模块,加载器需要提前加载这些模块,所以模块真正使用前需要提取模块里面所有的依赖。无论是加载器即时提取,还是通过自动化工具预先提取,CMD 的这种依赖声明格式只能通过静态分析方式实现,这也正是 CMD 的弊端所在。

CMD 规范的弊端

  1. 不能直接压缩:require是局部变量,意味着不能直接的通过压缩工具进行压缩,若require这个变量被替换,加载器与自动化工具将无法获取模块的依赖。
  2. 模块书写有额外约定:路径参数不能进行字符串运算,不能使用变量代替,否则加载器与自动化工具无法正确提取路径。

规范之外的约定意味着更多的文档说明,除非它们也是规范中的一部分。

注:SeaJS 静态分析实现是把模块包 toString() 后使用正则提取require部分得到依赖的模块路径。

AMD

AMD 模块依赖声明方式:

define(['./a', './b'], function (a, b) {
    // more code ..
})

AMD 的依赖是提前声明。这种优势的好处就是依赖无需通过静态分析,无论是加载器还是自动化工具都可以很直接的获取到依赖,规范的定义可以更简单,意味着可能产生更强大的实现,这对加载器与自动化分析工具都是有利的。

AMD 规范的弊端

  1. 依赖提前声明在代码书写上不是那么友好
  2. 模块内部与 NodeJS 的 Modules 有一定的差异

关于第二点的问题需要特别说明下。其实无论是 CMD 还是 AMD 的异步模块,都无法与同步模块规范保持一致(NodeJS 的 Modules),只有谁比谁更像同步模块而已。AMD 要转换为同步模块,除了去掉define函数的包裹外,只需要在头部使用require把依赖声明好,而 CMD 只需要去掉define函数的包裹即可。

总结

从规范上来说,AMD 更加简单且严谨,适用性更广,而在 RequireJS 强力的推动下,在国外几乎成了事实上的异步模块标准,各大类库也相继支持 AMD 规范。

但从加载器实现、功能来说,我更倾向于 SeaJS,理由:1、相对自然的依赖声明风格 2、相对精简的代码实现 3、贴心的外围功能设计 4、更好的中文社区支持。

如果有可能,我希望看到 SeaJS 也支持 AMD,与前端社区大环境保持一致最终幸福的是广大开发者。

@antife-yinyue
Copy link
Contributor

@aui

CMD 的 require 是可以被混淆的

define("gallery/moment/2.0.0/moment",["./i18n/{locale}"],function(t){return t("./i18n/{locale}")})

你看, require 已经变成了 t

@aui
Copy link

aui commented Jun 20, 2013

@jsw0528

理论上可以使用正则获取t的依赖,但是实际中压缩器可以让压缩后的代码产生很多类似t这样的短名称变量,这样导致静态分析依赖不准确。

@lifesinger
Copy link
Member Author

感谢 @aui 参与讨论。

很认可你的分析。CMD 的弊端,核心就一个:要通过 factory.toString() 和正则提取依赖。这个,其实可以快速解决:

define(['a', 'b'], function(require, exports, module) {
   // ...
})

这样就解决了。从书写格式上看,CMD 其实是 AMD 的子集,可以约等于 AMD 中的 Simplified CommonJS wrapping 写法。CMD 有的问题,AMD 其实都有,AMD 的 dependencies 也是可省略的,省略时也需要通过 factory.toString() 和正则去提取依赖。理论上 AMD 的 loader 都可以直接加载 CMD 模块,但反之不行。

话说 ATC 不错,赞。

@lifesinger
Copy link
Member Author

@aui @jsw0528 正则提取 require 是针对源码的,在压缩前完成。

@fxk01
Copy link

fxk01 commented Feb 6, 2018

good

@cunjieliu
Copy link

如今回来看看这篇文章及讨论也是获益良多啊

@kricsleo
Copy link

我对不知道哪里看来的一句“前端现在是黎明前的黑暗,在几年内一定会明晰起来”感触越来越深,现在处于种种的探路期,究竟往哪走,走成什么样都还不甚清楚,但是作为一个前端开发者,现在正处在这样的一个洪流中,希望能在这里面留下我的一点足迹,能够推动哪怕一点点的变革。

@asself
Copy link

asself commented Jun 30, 2020

2020年了,我也来mark一下

@fullyouth
Copy link

一切新方案的出现都是在解决当时技术上的痛点,感慨,感谢~

@moreorangess
Copy link

被“我会回来的,带着更好的东西”破防了,感谢前辈大佬们为前端的规范付出的一切

@lixianglong3210
Copy link

以前看了没留下评论,时至今日,我想说是不是一开始,用define的方式就是错的,我一直在想,有没有一种不这么麻烦的声明方式

@gt333
Copy link

gt333 commented Jan 26, 2022 via email

@SilentFlute
Copy link

这篇文章发布的时候我大三, 如今10年过去了, 我参加工作写前端也快10年了, 还记得当初大学时候老师教我们写网页, 那个时候大概是2011年左右, 当时网页三剑客, "div+css", 如今前端发展迅猛, 百花齐放, 我也从一个不谙世事的页面仔也成长为了能为社区略尽绵薄的青年人了(是的, 头发都掉得差不多了)
源于社区最后归于社区, 感谢前辈们无私的奉献, 我也希望能追随前辈们的步伐, 为社区的壮大, 为后辈的成长而尽自己的一份力, 就像我刚入行遇到的前辈们那样

@gt333
Copy link

gt333 commented Aug 17, 2023 via email

@summerhll
Copy link

summerhll commented Aug 17, 2023 via email

@semious
Copy link

semious commented Aug 17, 2023

2023了,轩与冒个泡,向 yubo 致敬

@sqlyr
Copy link

sqlyr commented Aug 31, 2023

2023 冒个泡 向历史致敬

@lwpersonal
Copy link

大一统是趋势了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests