-
Notifications
You must be signed in to change notification settings - Fork 50
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
对 OzJS 的一些疑惑、建议与探讨 #10
Comments
thx,好像是之前改漏了,因为在实际使用中动态加载的模块也是打包过的,这个m.deps涉及的依赖会被ozma事先解决掉,所以一直没发现存在bug…
这里是故意用这样的称呼,不从『外表』/风格而是从用法/惯例上去分类
没理解这句话…oz.js里唯一用到串行加载的地方是那篇文档里的第5项: define('non_AMD_script_A', ['non_AMD_script_B'], "path/to/non_AMD_script_A.js"); 主要是为了保证非AMD文件之间的依赖关系,在实际使用中还是因为全都用了ozma,模块数量不会影响到页面加载过程。
这里我的看法是相反的,requirejs和seajs都用配置来承担了很多程序逻辑的实现,很难避免复杂度的膨胀和适应性的瓶颈,这方面做到极致的负面例子就是XML(比如Ant)。你举的这个例子就恰好让我觉的『承担了不应该承担的职责』。
『参数中的 require』是指 至于写在global作用域里的require和写在module的作用域里的require,抽象语义上同样是一致的,对应用开发者应该完全透明化,应用开发者只需要考虑我在什么场景下需要引入什么依赖,而具体实现机制、异步/同步、加载/合并、发布粒度等等都应该由另外的层级去处理。这里跟单一职责原则同样关系不大。 此外还有API简单一致的问题,oz.js实际上只提供两个声明式的抽象工具也就是require和define,同时尽最大可能避免配置,应对前端开发中各种环境各种项目的特殊需求的时候,能提供简单的解决方案,同时避免膨胀和蠕变。
我其实反对引入插件,所以这个也是实验性的…虽然在阿尔法城项目里用的很多…
模块ID固定跟上面提到的『公共模块/私有模块』有关。至于版本共存的问题,oz.js最初的版本就有 前端静态文件毕竟不是node_modules,原则上并不存在那么多黑盒,与其提供版本共存的方法,不如让代码更好的适应重构。此外如果是真正的『老页面』或『旧代码』,相关依赖经常是过时和不再维护的,不用考虑更新和并入上游的问题,源代码完全可以直接copy和修改(比如改成匿名)
能独立的我都尽可能独立发布了,比如eventmaster, DollarJS, URLKit这些以前都是属于mo的,mo和moui虽然是以library的形式来发布,内部仍然是是细粒度的模块(比如mo/lang/oop),使用的时候可以只依赖其中的小部分,不会像jquery和underscore那样捆绑一堆不用的东西。 生态圈方面,当然要尽可能消除不同体系之间的隔阂和协同成本,我期望中的开源JS module应该源自亲身实践、解决实际问题、实现特定方案、小而精、极简化、去中心化、针对单一层级、不独揽、不捆绑、利用和壮大现有资源、适应本地偏好、自由混搭,让后来者能轻松的站在更高层次思考问题、关注更有价值的事情、更高效的积累和交流。 |
感谢 @dexteryy 的回复,对 OzJS 背后的设计理念清楚了很多。几个问题继续探讨下: 私有模块的 id 问题
这个是想问下,对于 a.js
a.js 是一个类似上面的私有模块,没有写 id。这时,在 OzJS 的实现里,执行 define 时,是如何知道 a.js 的 id ?是否用路径做 id ?如果用路径做 id 的话,执行 define 时如何拿到 a.js 的路径? ozma 的使用场景在 SeaJS 里,build 工具是等到正式提交测试时才运行。在此之前,模块都是原生态,直接开发和调试。因为当开发时模块数量就很多时,比如超过 50 个,就会比较明显地影响到开发效率。 ozma 是在什么时候运行? 插件我也反对 RequireJS 的插件设计方式,使得 RequireJS 需要为插件多一些不必要的代码。但我觉得还是需要提供插件机制,毕竟很多时候需要对加载器进行扩展。OzJS 里,也有 var someUrl = ...
var relatedMod = seajs.cache[someUrl]
relatedMod.destroy() 通过暴露 除了暴露一些增加完备性的接口,SeaJS 还提供了一些事件接口,比如发送请求前的 request 事件,使得开发者可以拦截,不用 script element 去加载,而可以从 localStorage 中去读取。SeaJS 的 Node.js 版本,也是利用了自身的 request 事件,拦截下来,用 Node 原生的 配置这一块可能真的是理念差异,我很难忍受一个方法可以干多个事情。比如远程模块,个人觉得用配置来说明更简洁易懂。这个可能纯粹是喜好问题。就如 Grunt,其实不写 initConfig 也可以完成所有事情,但很明显,initConfig 带来了简洁和易理解,不适合放 config 的,再用代码去实现。 比如在 Node.js 里,require 中的路径解析,是相对当前模块的,但 fs.readFile 等接口,则是相对 cwd 的。虽然有很多用户会抱怨为什么 define(function(require, module, exports) {
var b = require('./a')
}) define(function() {
var b = require('./a')
}) 第一个写法是 ok 的,第二个写法是报错。第二个 require 是全局的,实际上是有区分,但却共用同一个名字,有点囧。 OzJS 这一点上好像比 RequireJS 清晰很多,但我觉得还是不妥。看起来是对使用者透明了,但也会造成迷惑性。 多版本共存这个理论上的确可以通过重构等方式去完美解决,但在阿里的现实场景下,经常行不通,比如
当新项目 B 往前开发时,由于各种因素,很难有时间有精力去修改老项目 A 的代码,这时项目 B 有两个选择:
目前支付宝是选择第二个方案,稳定、安全第一位,页面多加载一点文件,问题不是很大。 最后感谢 @dexteryy 参与讨论。越发觉得是不同的使用场景使得 RequireJS、OzJS、SeaJS 等工具的选择不一样。虽然基础层面的功能大同小异,但上层的实践让彼此的发展走向不同的分支。了解彼此的使用场景和最佳实践挺有帮助。 |
@lifesinger 嗯其实很多都是基本问题…
a.js里的私有模块在define时其实就是没有module ID,谈不上获取的问题,实际上这个顺序反了,oz.js的实现机制是在需要『用到』模块的时候(比如
前端开发者一直习惯于『动态语言』直接解析执行源代码的开发方式,但随着技术的发展和成熟,面向人类的源代码和面向解释器的目标代码之间终将避免不了编译/构建这个环节,无论JS、CSS、模板…都是如此,只存在如何由工具或运行环境来自动化和透明化的问题。OzJS推荐的最佳实践是在本地开发过程中就融入构建环节,调试跟线上环境完全一样的代码(只减去压缩环节),而ozma能尽可能保证零配置和透明化,至于它的运行时机则取决于开发者的偏好,比如每次修改源代码之后都执行、开始调试前执行、IDE手动触发执行…
new!如果是一个核心机制而不是扩展的话,就不需要引入插件,nodejs里的require.cache确实是合理的方法之一。至于hook、事件什么的,在nodejs运行环境里更有意义,浏览器端如果需要用到这些东西,一般都是有哪里搞错了…总之oz.js扮演的是一个很明确的职能角色,工作于单一层面,而不像以前的YUI/jquery的命名空间和构造函数那样,处于『中心』位置,需要用扩展机制去跟其他东西协调。
shim: {
// jQuery 的 shim 配置
'jquery': {
exports: function() { return jQuery; }
},
// jQuery 插件的 shim 配置
'jquery-plugins': {
match: /jquery\.[a-z].*\.js/, // 匹配所有 jquery 插件,自动化
deps: ['jquery'], // 动态指定依赖
exports: 'jQuery'
}
} define('jquery-src', 'lib/jquery.js');
['A', 'B', 'C', 'easing'].forEach(function(plugin){
define('lib/jquery-plugin/jquery.' + plugin, ['jquery-src']);
define('jquery.' + plugin, ['lib/jquery-plugin/jquery.' + plugin], function(){
return window.jQuery;
});
});
define('jquery', ['jquery.A', 'jquery.easing'], function(){
var $ = window.jQuery;
$.easing['jswing'] = $.easing['swing'];
$.extend($.easing, elib.functions);
return $;
}); 以上是配置和代码的对比,虽然后者多做了一些事… Grunt受过Ant的影响,但它是一个很好的将配置和代码平衡的例子。
嗯这种情况豆瓣主站也有很多,就像之前说的,我觉得移动和重命名旧版代码就行了 不过这种无人可以控制整个页面的最终输出的情况终归是不好的,而且很多都是受到了一些php/python旧框架的影响,之前在土豆网的时候对这种问题我自认为解决的较好,每个页面从JS数量、内容、监测到广告加载全都在前端的控制之下。
其实我觉得使用场景和问题需求应该是一样的,只是有没有切身体会的问题,我之前专注做阿尔法城的时候oz.js也相对狭隘些,无视了很多问题。 另外我觉得国内前端社区应该把重心放到解决实际问题和带来新价值的模块上,每个人都去做模块加载器或基础设施,有意无意的做差异化或重复建设,是有违这类项目的初衷的,也许能起到证明自身技术的作用,但只会被国外越甩越远。 |
这个规律不可靠的,最近声明的匿名模块未必是当前需要的模块,特别是在 IE6-9 下。用大写字母表述 define 的执行时机,小写字母表示 define 所在文件的 onload 时间,则
比如下面这种写法, a.js define(function(require, exports) { exports.name = 'a' }) b.js define(function(require, exports) { exports.name = 'b' }) c.js define(function(require, exports) { exports.name = 'c' }) main.js require(['a', 'b', 'c'], function(a, b, c) {
alert( a.name === 'a')
alert( b.name === 'b')
alert( c.name === 'c')
}) 我用 OzJS 试了下,在 IE 下会经常性报错。 这涉及下一个问题
@dexteryy 描述的在豆瓣的场景应该没问题,也因为如此使用,因此上面那个匿名模块的 url 获取问题也没问题(在 Chrome 或 Firefox 下开发,在 IE 下测试时已经经过 ozma 构建) SeaJS 当初也考虑过这种方式,让构建工具在开发时就运行,但在实际推广中遇到了比较大的困难,比如
这个不认可。loader 的事件或插件机制还是有必要的。比如加载 text 模板,这个需求很常见。还有支持服务器的动态 combo,以及方便的 nocache 插件。这些都是和加载相关的,通过插件扩展功能,感觉挺合适。否则另外实现起来不方便也不简洁好用。 插件仅限于扩展加载器本身的功能,比如加载前、加载中、加载后等。与加载无关的,不提供任何扩展。这和 YUI/jQuery 的扩展有很大不同,YUI/jQuery 那种扩展我也不喜欢。
这一段,有个很大的不一样,比如 shim: {
// jQuery 插件的 shim 配置
'jquery-plugins': {
match: /jquery\.[a-z].*\.js/, // 匹配所有 jquery 插件,自动化
deps: ['jquery'], // 动态指定依赖
exports: 'jQuery'
}
} 上面的 上面也能简单说明插件的好处。配置里也允许有逻辑,比如 shim 的 exports 可以是一个 function,还有一些配置也可以: seajs.config({
charset: function(url) {
if (url.indexOf('xxx') > 0) {
return 'gbk'
}
return 'utf-8'
}
}) 这类似 Grunt,通用配置 initConfig 搞定,个性化的,可以用脚本或 function 等方式来写。 配置不能太多,有合适的配置能省很多事。
想起最开始我想做的是,完全用 Node 的方式写模块代码 a.js var b = require('./b')
exports.name = 'a' 然后在浏览器端直接可加载使用。通过构建工具的方式可以很容易实现,并且 loader 可以在 100-200 内搞定,非常精简。 但上面这种方案,我实现过一个,也有配套的构建工具,但普通使用者始终难以接受,觉得 Geek。RequireJS 能流行起来,我觉得最大的一个特性是:不依赖任何服务、任何构建工具,AMD 模块就可以在浏览器运行。这种便利性让很多人开始使用并喜欢上 RequireJS。SeaJS 的发展历史也类似,每去掉一层依赖时,用户接受度就越高。
这个很赞同,曾想着 seajs 1.3 后再无更新。这次更新到 2.0,也是强烈希望 2.0 后 seajs 能“死掉”。更多的精力去开发模块,去探索具体场景下的最佳前端解决方案。这一块跟 @dexteryy 的想法一致。 |
先说下require重载的看法,我认为require(id:String)和require(id:Stirng, callback:Function)在重载上还是符合语义的,即都是使用模块,只是后者有回调,前者在factory里作用。 |
oz里面在factory的require出现作为异步很难接受,因为这有悖重载原则了,使用和异步加载使用还是有区别的,我其实是借鉴了require.async的实现 |
RequireJS 里,require 的重载是根据第一个参数来区分: require('a')
require(['a'])
require('a', callback)
require(['a'], callback) 上面让我很泪奔。SeaJS 里宁可增加 API:
|
我是将你的1和3合并了,分离出2,看来我们是进行了这玩意儿的排列组合,弄出所有可能性了…… |
在构建方面,想省略人工构建恐怕目前没有合理的方案。至于调试,将页面压缩后的文件重定位到开发机器上的单元文件,因为我像java那样遵循一对一原则,使用本地http服务器简单配置重定向代理,可以将所有文件代理到原子文件,比较简洁地解决了这个问题。 多文件构建这玩意儿取决于语言的本地io性能吧,多了总是不行。 |
@army8735 …你害得楼乱了
我知道这个执行顺序的问题呀,不记得是不是当年还在土豆网的时候测试的了。之所以不重视这个问题,除了『眼光向前看』,不为不值得的事情引入复杂度之外,更重要的是oz.js从最开始就一直不推荐匿名模块,更不用说动态加载的匿名模块,ozma跟oz.js在开发过程中是同等地位(一个负责静态处理一个负责runtime),会将包括匿名模块在内的各种风格的声明编译成统一的代码,所以到了线上运行的时候,动态加载的发布文件里的模块都不可能会没有module ID,而这种情况下是不需要那个『最近声明』机制的。简单来说提供这个机制只是为了方便新人上手、快速开发原型和调试,并不是给产品环境用的。
你列举的这些困难是最常见的,但都不是『比较大』的,调试方面我不太理解为什么『无法直接定位到具体文件名和真实行号』,无论自动生成注释和source map都可以快速定位,何况还有用本地的源代码文件直接覆盖打包文件中的模块声明的方法(
加载text模板正是OzJS在努力纠正的错误用法之一,相关项目见这里:grunt-furnace
…这处逻辑是我故意忽略的呀,因为oz.js目前并不考虑这种需求(实践中没有这样配置jquery插件的),假如要纳入的话,有什么能阻止oz.js提供更简洁更灵活的程序接口呢XD,这个例子想对比的是配置和代码的差别,就像你也承认配置中需要引入function一样,为了适应需求,配置『数据』终会发展到跟程序语言一样或植入程序语言的程度。
这个已经有现成的主流方案了,也就是TJ的component https://github.com/component/component/wiki/Components 但这种完全照搬commonjs的模块语法在真实世界的前端『应用代码』中却是存在缺陷的,最明显的就是动态加载。不过对多数JS开源项目来说无需考虑。 AMD和CJS风格的异步module、纯commonjs的component、乃至其他方案,在相当长一段时间里都要共存和协作,到这里又要再次推荐grunt-furnace项目了… |
@army8735 多文件或者说多页面的项目里,有时确实需要其他基础设施来补充纯前端的workflow,豆瓣在这方面也有很多好的实践,也在继续探索,不过这里有一点需要注意的是,同样是工具和基础设施,它们工作的层面却未必相同,后端web framework和构建脚本应该在前端的基础设施和workflow之上构建抽象层,混淆在一起讨论是很容易走歪路的… |
不需要构建自动用服务器构建,这对稳定性的风险很大。先前我很倾向这种,后来发现赌博得厉害。在单元测试未完善的情况下,底层子模块变动造成高层大范围修改很恐怖,而且前端有很多和浏览器交互挂钩,想单元测试更是难上加难。 |
@dexteryy 越来越理解你的设计理念和 Oz 背后的坚持了
如果是这样的话,不如把对匿名模块的支持去掉,更彻底地说明 ozjs 和 ozma 的协同关系。
这涉及调试问题,source map 目前依旧没解决调试时的映射问题,比如某个变量通过 source map 看到的是 abc,但要知道其值,还得知道这个 abc 压缩后的代码是什么,source map 是个温柔的谎言,对调试基本没用。
不是很认可。服务器端 combo 的价值,是处理那些不适合提前打包的,比如: seajs.use(["a", "b", "c"], callback) cdn 开启 combo 服务后,上面的三个模块可以合并为一个请求: 在支付宝 Arale 的实践里,任何模块,通过构建工具构建后,都会变成 define(id, deps, block) 其中 deps 包含了该模块的所有依赖(包括依赖的依赖),这样,任何一个模块,都只需两个请求就可以下载完毕:一个是该模块自身,另一个是该模块所有依赖的 combo 请求。 这使得性能默认优化,构建也变得简单一致。 我比较反对那种针对项目所有页面都合并成一个文件的构建方式,看起来对单页面有性能提升,但整体来看并不好。HTTP 链接数不是越少越好,而是需要找到合适的数量值,这往往跟业务逻辑相关。loader 和构建工具层不应该做太多事,性能优化方面的自主权应该更多地交给业务开发。 YUI3 后来有推出基于服务端的自动打包功能(比如请求 a.js,服务器会自动返回 a 和依赖的合并文件),这个我觉得也有点过火了。但纯文件合并的 combo 服务,我觉得还是很有价值的。
如果这样的话,我觉得 oz 有些自相矛盾。为何 js 不直接用 node 的写法? JS -> AMD,将 AMD 彻底从开发者视野中隔离掉,让 AMD 变成纯粹的 Modules/Transport 规范。这也是当初 CommonJS 社区里不少人的选择。 甚至可以直接用 es6 的 module 语法:https://github.com/square/es6-module-transpiler 这个 hax 也想做,国外又走了前面。 |
这次讨论,感觉核心差异点在:
最后一点是差异的根源。 |
配置的问题,依旧不清楚 @dexteryy 为何不喜欢。如果不喜欢配置的话,那么 oz 里的 aliases 配置也可以转换为 define: define('a', 'path/to/a.js') 这一块未能理会背后的原因究竟是啥 -.- |
tpl直接工具就行了吧,书写时就是个以.tpl结尾的普通类html文件之类的,构建变成一个模块,调试解析在重定向中做,逻辑和构建一样,自动将tpl转变为模块。js插件会增加js体积,用工具一个字母都不用。 |
tpl 的 js 插件仅在开发时用,不会增加 loader 自身的体积。上线时,通过构建工具转换成 js 文件。 |
抱歉这里我没表达清楚,不是说匿名模块彻底的不好,而是说通过鼓励匿名模块这样的语法将模块跟实体文件划等号,照搬nodejs或传统软件环境的方式是不好的,而『私有模块』就是匿名模块的好用法。ozma和oz都是模块机制的提供者,是将模块的依赖管理、加载、调用过程透明化所需的抽象层,这个层级之上的接口和语法都是为实际开发场景服务的,需要稳定、一致、灵活和具备充足的表达能力,所以匿名模块声明是需要的,而到了runtime环境里则是另一回事,ozma/oz就像浏览器里的JIT引擎一样可以不断改进来优化这些细节。
没理解,这个地方涉及的发布文件应该不包含压缩环节的罢…
这里也是不太了解seajs的用法,不过OzJS对应的做法是直接创建一个组合后的模块,内容可能会写成: //abc.js
require(['a', 'b', 'c'], function(){}); 然后在main.js里将三个子模块都映射到abc模块上: // main.js
define('a', 'abc.js');
define('b', 'abc.js');
define('c', 'abc.js');
require(['app'], function(){
}); 之后在需要引入依赖的时候仍然写: // app.js
define(['c', 'd', 'e'], function(c, d, e){
// do something
require(['a'], function(a){
// do something
});
if (...) {
require(['b'], function(a){
// do something
});
}
}); 对main.js执行ozma之后,会自动生成dist/main.js和dist/abc.js这样两个发布文件(也就是页面所需的全部JS文件),前者包含app.js和它依赖的c.js, d.js, e.js,后者包含a.js、b.js以及『依赖的依赖』,但不与dist/main.js里的模块重复。 也就是说引入a/b/c依赖的开发者在写代码时无需考虑这三个模块在发布文件里的组织方式和本地/下载的区别,也无需考虑最后有几个文件,只需要按抽象语义来使用require和define 将所有页面的代码完全合成一个文件我也是反对的,不过适度的合并是必须的,比如common.js、project.js和inline script这样的划分,总共应该控制在2~3个以内(动态加载的文件除外)。
这个问题在提到component的时候已经说过了,浏览器中运行的程序有自己的特殊架构特点,照搬commonjs或nodejs的方式是有局限的。 oz.js的定位其实就是偏向ES6 module的,也就是反复表达的『语法结构概念』而不是实体文件概念…
浏览器确实就是compiler和runtime,JS引擎是web的虚拟机(--Brendan Eich),不过这个部分的发展速度相对更慢,也不像传统软件开发一样是可以由开发者掌控和部署,另一方面,软件开发的历史主线就是抽象层的垒砌,浏览器的这层抽象之上,终归避免不了其他抽象,比如说现在有人不用scss/stylus/less么… 不过这个抽象的『度』也是一种要平衡的东西,比如coffescript我就是很不喜欢的。ozma/oz.js也只是绕开了旧浏览器的限制而已,只是给浏览器打补丁,还谈不上编译型模式… |
@lifesinger 顺便也问一个关于seajs的问题罢… 据我所知『CMD』这种称呼在国外似乎是不存在的,只有AMD和CJS之分,以下两种写法都会被视作AMD: define(['A', 'B'], function(a, b, require, exports, module){
return function(){};
}); define(function(require, exports, module){
var a = require('A');
var b = require('B');
module.exports = function(){};
}); 本质上也并无区别,经过静态环节的处理后会统一成同一种最容易处理的标准语法(也就是上面那种),即使在运行时里,这种天然的一致让oz.js只需极少的代码就可以同时兼容这两种写法。requirejs好像也同样支持罢。 而 我觉得以上这个约束是需要大家来共同维护、避免分裂的。不过在这个有约束的差异层之外seajs相对于requirejs提供了什么独特价值,我就有些疑惑了。另外我对spm的印象是『自建生态系统』,有些刻意模仿npm了,包括package的托管方式,这也是对整个JS社区不利的。 |
你们三位真的应该一起搞一个loader,提供不同场景下的完整解决方案(单页面APP、static只支持一个项目、static支持跨项目等)。 dexteryy:”另外我觉得国内前端社区应该把重心放到解决实际问题和带来新价值的模块上,每个人都去做模块加载器或基础设施,有意无意的做差异化或重复建设,是有违这类项目的初衷的,也许能起到证明自身技术的作用,但只会被国外越甩越远。“ 这个很赞。 |
支持楼上的楼上 @dexteryy 关于AMD的看法, 统一标准,避免差异化很重要。毕竟现在还是AMD的支持者多,无论是@dexteryy 还是 @lifesinger 都切记不要为了技术而技术, 为差异化而刻意差异化。 我是Seajs的支持者,在这之前没有接触过加载器,感谢Seajs。读了上面的一些话,也希望 @lifesinger 能考虑下未来发展吧。 据我所知您当时也是kissy的参与者,我真心觉得kissy有重复造轮子之嫌。 老弟对您有些粗浅建议就是Arale是加载器下一层的东西,感觉这个东西能流行起来,好好整整官网、文档,再做推广吧。到时国外开发者会因为Arale知道支付宝和您的哦。这个世界上最难做的技术工作,就是服务新手了。打个比喻jQuery是油箱,那么框架工具集就是工具箱,而市场上还没谁能把工具箱做好。 其他林林总总的库都解决了各自的不少问题, 技术理念短期难有大创新。 |
期待一起搞 Loader.. 期待有统一的中文社区和工具库.. |
这点我有异议。匿名模块的核心不是让模块跟实体文件划等号,而是鼓励 DRY 原则。SeaJS 也一直支持自定义 id 的用法: define(id, block) 比如直接写在页面中的内嵌模块,以及一个文件包含多个模块的场景。我觉得 id 的核心是模块的唯一标识符,通过 id 可以获取到对应的模块,和命名空间的本质是一样的。来看命名空间的演化史:
SeaJS 遵循的实践来自 Node.js 的处理方式,这背后是 约定大约配置,约定模块的 id 就是文件的访问路径,不需要用户自己去配置 id ,一旦鼓励用户去配置 id,就存在冲突的可能。 牺牲的是模块不再纯粹,与文件扯上了关系。但这个牺牲,在目前的浏览器架构下,我觉得是合适的。匿名模块可以让用户不用再担心命名冲突,能给用户做减法,我觉得值当。
这个再解释下:通过 source map,能拿到出错处的源码行,但无法打断点进行调试(可以停下来,但根本看不懂,很难拿到当前变量的值,堆栈信息也是压缩后的信息) @dexteryy 说的我明白,用非压缩版本替代,我觉得还是不够方便,不如单个源码文件清晰。调试不是关键,但是若能通过 loader plugin 为调试提供更好的体验,何乐而不为呢?
// main.js
define('a', 'abc.js');
define('b', 'abc.js');
define('c', 'abc.js'); 看到这里,回到了之前关注配置和远程模块的讨论。SeaJS 里,上面是 map 配置: seajs.config({
map: [
[ "a.js", "abc.js" ],
[ "b.js", "abc.js" ],
[ "c.js", "abc.js" ]
]
}) map 的含义是映射,告诉 loader 模块 a 的实际路径是 abc.js 文件。在实际使用时,多人协作的页面中经常会有很多入口: <script>
seajs.use(["a", "b"], function(a, b) { ... })
</script>
<script>
seajs.use(["d", "e"], function(d, e) { ... })
</script> 这些内嵌的代码很多不能提前预见。因此在 main.js 里通过 map 配置或 define 远程模块的方式,很很难默认做到性能最优(Speed by Default)。于是有了 combo 服务 + loader plugin 的方式来做:
这样,就能达成默认最优的效果,map 等配置都可以全部省去,除非需要人肉进行细节调优。 类似的还有 flush 插件,这里不再多说,详见文档: seajs/seajs#226 很核心的一点是,这些插件的机制与 RequireJS 很不一样,RequireJS 会侵入 require.js 的源码,SeaJS 的方式更多的是考虑自身的完备性,就如一个 UI 模块一样,会考虑在显示前等环节适当的暴露事件接口。这些事件接口不是为了特定插件存在,而是一种普适的可扩展机制。loader 本身不用关注插件的实现,loader 广播的只是自身的事件。
认可“软件开发的历史主线就是抽象层的垒砌”。不过现阶段,在阿里用 scss/stylus/less 的还是少数,用在正式项目中的几乎没有。核心原因跟国内社区的氛围有关,但还有一个不容忽视的原因是,stylus 等抽象层的价值还需要时间来证明,我接触过有不少人真实使用过,但后来还是喜欢直接用纯 css 搞定。 这和 @dexteryy 不喜欢 coffee 应该有类似的地方,呵呵。 |
一对一是利大于弊的,纵观其他语言的public class设计就能明白所有设计者们的苦心。小项目几个人互相之间口头约定下不会冲突,长远大项目人数一多,什么事情都会发生。 配置方面我认为是越少越好,配置一多就看得晕,多语义可能就会增加很多别人阅读上的成本。 至于重复造轮子,天下大事合久必分,竞争未必不是好事,真正需要的是标准统一。倘若chrome和firefox当时想的都是不要重复造轮子,都使用ie6,乔布斯不要发明ios继续用塞班,不知是什么情况。 |
@dexteryy 关于 AMD、Node/Modules、CMD 等,这个可以谈谈历史,很有意思。(主线 @dexteryy 应该都知道,下面纯当八卦轻松下就好) CommonJS 社区大概 09年 - 10年期间,CommonJS 社区大牛云集。CommonJS 原来叫 ServerJS,推出 Modules/1.0 规范后,在 Node 等环境下取得了很不错的实践。09年下半年这帮充满干劲的小伙子们想把 ServerJS 的成功经验进一步推广到浏览器端,于是将社区改名叫 CommonJS,同时激烈争论 Modules 的下一版本规范,这里主要有三个主流观点:
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 的附属品。比如
AMD 的流行,很大程度上取决于 RequireJS 作者的推广,这有点像 less 因 bootstrap 而火起来一样。但火起来的东西未必好,比如个人觉得 stylus 就比 less 更优雅好用。 关于 AMD 和 RequireJS,暂且按下不表。来看另一条暗流:Modules/2.0 流派。 Modules/2.0BravoJS 的作者有很深厚的程序功底,在 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 作者和 RequireJS 作者有过一些争论。再后来,FlyScript 作者做了自我阉割,将 GitHub 上的项目和官网都清空了,官网上当时留了一句话,大意是
这中间究竟发生了什么,不得而知。后来有发邮件给 FlyScript 作者询问,FlyScript 作者给了两点挺让我尊重的理由,大意是
这两句话对我影响很大。也是那之后,开始仔细研究 RequireJS,并通过邮件等方式给 RequireJS 提出过不少建议。 再后来,在实际使用 RequireJS 的过程中,遇到了很多坑。那时 RequireJS 虽然很火,但真不够完善。期间也在寻思着 FlyScript 离开时的那句话:“我会回来的,带着更好的东西” 我没 FlyScript 的作者那么伟大,在不断给 RequireJS 提建议,但不断不被采纳后,开始萌生了自己写一个 loader 的念头。 这就是 SeaJS。 SeaJS 借鉴了 RequireJS 的不少东西,比如将 FlyScript 中的 写着写着有点沧桑感,不写了。 |
OzJS同样推崇惯例优先原则,但你有没有注意到选择和声明文件路径本质上就是一种配置,匿名模块的滥用实际上是放任了这种配置能力,以致难以形成『惯例』,比如两个开源项目,同样都依赖mo/lang,一个将其放在../mod/mo/里,写作
对于Ozma的一次构建所涉的单个网页来说,不存在不可预见的代码(静态环境里就会模拟运行时),也能自动做到性能最优:不存在重复、连接数最小化、尽可能利用缓存、预加载/延后加载/按需加载任取所需。而你说的combo方式或性能优化插件增加了API的复杂度,把其他层面的逻辑和细节混入进来,降低普通开发者的思维层级,最终效果看上去也不容易达到最优。 P.S. 你看,继aliases, deps, shim, plugin之后,你的代码里又出现了叫map的配置……
RequireJS最大的局限不是AMD,而是源自它的出发点(文件加载器)。oz.js是2010年下半年开始『发现』和实践这种API(虽然那时候叫oz.def和oz.require),RequireJS也正是在那个时期的改版中变成AMD的样子,所以oz.js推荐这种API并顺水推舟沿用AMD这个名称并非因为这个规范那个规范或现在『谁更火』,而是因为它以Bottom-up的方式从一线实践中自然诞生,仿佛天生就能简单一致或者说『以不变应万变』的应对各种真实世界中的需求和问题,无需用新增API、配置参数、插件的形式不断打补丁。从这个角度来说RequireJS虽然有相似的API,却似乎不具备这种自然天成,究其根源:它的设计是在script loader的基础上打补丁打出来。 其实我觉得你太过于关注那些规范和争议了,开源社区是一种天然的去中心化组织结构,好的规范和好的设计,大部分都不是由委员会或少数牛人窝在irc、wiki或issues里,自顶向下的设计和争辩出来的,而是后于代码、后于实践,既各自独立同时又受环境影响,伴随着切身需求和解决实际问题的努力而产出的,『Worse is better』,把『更好的设计』本身作为初衷,反而不容易达到效果。 Early Executing正是我一直没动力去改的地方,在实践中这个问题真的重要吗?它带来的好处更多还是带来的问题更多?你说的『这个差异,也导致实质上 Node 的模块与 AMD 模块是无法共享的』,我很想看看实际例子是怎样的。 另外关于『JS社区』,这里有一个很普遍的陷阱是把所有写JS、开发JS项目的人都一概而论,实际上这里面有ruby社区资深成员,有习惯传统模式的java开发者,有脱离一线有因node而首次深入JS领域的开发者,这些人之间的差别有时已经不止是『偏好』了,而是理解和认识上的不同。 |
@lifesinger
现在 sourcemap 可以在源码上打断点,也能拿到当前变量的值,也可以看到正确的堆栈信息吧。 另外我觉得在 sourcemap 中提供源码文件比单文件要清晰,可以帮助开发者快速在本地代码中定位到有问题的地方。 |
匿名模块不能直接给外部调用,提供给外部用之前,要经过构建,即由私有模块变成公共模块。AMD 社区目前的 id 策略个人觉得不妥,比如 jQuery 的 id 就是 jquery,这不光导致 jquery 多版本共存困难,还使得存在命名空间抢注问题,比如有一个模块叫 base 了,就不能再有重名的。mo 里面加了一层命名空间,但我觉得依旧不妥。 id 规则,个人觉得已经不属于 loader 层面。目前 SeaJS 对 id 没有约定,id 仅仅是用来生成 uri,用来获取访问路径。id 的具体规则,属于 spm 或 CommonWeb 层面,目前在 Arale 里,采取的是 id 策略是:
比如 Arale 的 class 模块,最后提供给社区的 id 是:
之所以还存在 moduleName 层级,是因为一个包可对外提供多个模块。 目前这种 id 规则,可以做到信息完备,目前为止可以满足各种需求,并无隐患。
SeaJS 自带的配置并不多,总共 8 个:seajs/seajs#262 配置多我觉得不是问题,核心是每个配置的作用要清晰明确,SeaJS 目前每个配置背后都有清晰的语义和使用场景,这比代码简洁多了呀。 也许是个人喜好,目前为止没看到 @dexteryy 明确反对配置的真正原因,可以继续讨论下。
这个有点广告了,呵呵 举几个需求:
OzJS 给我的强烈感觉是需要用豆瓣的方式来搞定一切,一旦脱离豆瓣的实际场景,很多需求就会满足不了或比较别扭。从豆瓣的场景出发我觉得没有任何问题,也应该这样做。但以此就觉得可以『以不变应万变』,我觉得未必能应万变。 作为 loader,必须要满足实际项目中的实际需求。在此基础之上,我觉得有必要追求 loader 自身的完备性,力求站在 loader 的角度,做到功能增无可增,减无可减。
这个相当赞同。这也是我虽然在乎规范,但不愿意去 CommonJS 社区推广 CMD 规范的原因。CMD 规范是 SeaJS 的副产物,更多是一种梳理和总结。SeaJS 更在乎的是满足项目的实际需求,从淘宝到支付宝,一线实践是最关键的。SeaJS 里的不少功能点,都是为了满足项目的实际需求而增加的。
James Burke 当时力推 Early Executing 的背后,就如你说的一样,因为 RequireJS 骨子里是文件加载器。对文件加载器来说,Early Executing 是非常有必要的,否则文件加载了却没有执行,就乱套了。但对模块加载器来说,Early Executing 绝对没有必要。都还没有使用,干嘛要提前就执行好呢? 带来的问题,举例 a.js export.age = 14 b.js require('a').age = 22 main.js var a = require('a')
if (a.age < 18) {
require('b')
} 上面的代码在 Node.js 里一切正常,但用 AMD 写完后,main.js 会存在逻辑错误。 这种逻辑错误一旦出现了,问题定位起来要抓狂。在 2010 年时,我自己就遇到过不止一次。 提前执行还带来一个弊端是,对 TTI(Time to Interface) 不利。延迟执行更符合直觉,也更有利于页面性能。能懒则懒,As lazy as possible! OzJS、RequireJS 和 SeaJS 目前都是从一线实战出来的,各自面临的场景和需要解决的问题有不少差异,但也有很大重合度。我觉得最有价值的是把场景描述出来,然后看各自的解决方案是否最优,是否可互相借鉴。求同存异,开放互融。 |
@allenm 最新版 Chrome 的确可以了,不错。 |
说说规范之争我的看法。 |
如果要改为延迟执行的话,那么声明在define第二个参数的deps会怎么办?是否还要在factory里写require它们?还是写在deps里的不作为延迟,而factory里的require延迟? |
CMD 的好处是,使得提前声明和就近声明都可行,具体用什么风格,由使用者决定。 延迟执行对性能的影响,体现在一些与 DOM 操作相关的模块上,比如 define(function(require, exports) {
var iframe = document.createElement('iframe')
exports.init = ...
})
实现上可参考 https://github.com/seajs/seajs/blob/master/src/module.js#L259 |
按我的理解,ozjs的目标是语言层的,加载器只是实现这个目标的方式之一,也可以靠ozma的编译实现。 我想,实现这些功能的东西也是一个个amd模块。我想如果能继续想,甚至可以加载器本身也是amd的模块,可以自由搭配,可以结合不同的载入方式(比如:localStorage loader, ajax loader, etc...), 再通过ozma预编译出一个执行环境(相当于oz.js)。需要几个功能就require几个功能。 想了想,我觉得这有点像 Ender -- 人家是组装一个jquery。 补充, @dexteryy oz.config '_getScript', customGetScript 然后是否可以再实现这样的语法, define 'script', '/path/to/script', {useLocalstorage: true}
define 'script', '/path/to/script', {new: true}
define 'script', '/path/to/script', {chatset: 'utf8'}
define 'script', '/path/to/coffeescript', {coffeescript: true} 但这样会让define”身兼多职“了。 |
我越来越倾向于延迟了。只是感觉deps里有了,还要再写require一下,重复一遍让我难受。不过倘若借助工具的话,js解析依赖部分甚至可以取消掉。 |
localStorage的目的是什么?如果是为了缓存,那么文件本身是在静态服务器设置cache头来进行缓存的,放入localStorage有其它特殊需求? |
需求都有多种实现方式,想探讨用哪种方式最合适。比如 gbk 页面加载 utf8 文件,浏览器通过 charset 属性本身就支持,loader 应该要延续这种支持。 提 i18n 的例子,是考虑 loader 的完备性。对于模块的依赖,很容易想到两种:
对于同步依赖,我们在构建时就能够通过工具扫描分析出来,得到明确的依赖关系。 但除了以上两种依赖,还有一种动态依赖: 3、 动态依赖。在构建时,只能确定规律,不能确定具体路径。在执行时,才能得到具体路径,执行时是同步的。 引入动态依赖的概念后,loader 层的支持可以是: seajs.config({
// 声明变量
vars: {
locale: "zh-cn"
}
}) define(function(require, exports) {
// 通过 `{xxx}` 引入变量
var lang = require('./i18n/{locale}/lang.js')
}) 即动态依赖是路径中带变量的依赖,变量值需要在运行时才获取。 这样,不仅能很好的解决 i18n,还能解决换肤、个性化加载等等需求,这个抽象就是动态依赖。如果不用动态依赖,也有其他方案来解决 i18n 等问题。但探究需求背后的真正需求其实是:在不同的环境下加载不同的文件。作为加载器,从完备性上有职责从最底层去提供支持。 localStorage 的例子,是从可扩展性角度来讲。oz 里可以通过覆盖私有属性 _getScript 来提供,但总感觉不那么优雅。作为 loader,核心有几件事情:
从可扩展性角度讲,用户经常想干的一些事情是
以上都是可扩展点。在 SeaJS 里是通过事件的方式提供出来,loader 里核心考虑的是:
一旦有了这些可扩展点后,用户就可以做很多事情,loader 也就可以实现 @dexteryy 所说的“以不变应万变”,比如 localStorage 的需求可以通过:
loader 本身不需要关注 localStorage 的任何逻辑,只需在 request 请求文件时,提供可扩展事件就好。 SeaJS 2.0 的真实希望是把 SeaJS 永远停留在 2.0 版本,永远不再需要更新。这需要做到:
就如 Unix 中的 ls、copy 等命令一样,当把自己该完成的职责都完成后,应该就可以放心“死去”,再也不用更新。然后如 @dexteryy 所说,更多地精力放在提供功能的模块上,比如 mo、moui、arale 等。 |
@army8735 书写时用 localStorage 的需求来自移动端,尽量缓存,减少 http request,甚至做到纯离线。 |
如果移动项目缓存的话,那么css和img的缓存同为重要,在体积方面也远远大于js。移动上的浏览器的缓存性也应该和桌面一致。用localstorage来缓存js文件,不太恰当。 |
@lifesinger 最近项目很紧张,特别怕打断,所以没法及时回复了……先说说以下几个问题: 关于module ID的规则OzJS现在的作法是,一个模块在最初创建时,用最简的文件结构和最简的名称,比如event.js,代码写成匿名模块,可以在项目内以任意形式来使用。如果这个模块足够抽象,不含任何业务逻辑,足以跨项目使用或开源的话,则改成具名模块,这个名称要尽量避开直接表达抽象功能的词汇,或使用专有名词、复合词,比如eventmaster.js。 假如这个模块虽然足够通用却不足以独立发布,比如与其他一系列模块存在交叉依赖,常常组合使用,则作为一个library的子模块来发布,增加一个目录层级,这时用库的名称作为命名空间,比如 使用者只要依据『惯例』来建立本地项目的结构,比如把eventmaster.js和moui/放到『baseUrl』下面,就不需要任何额外配置就可以在代码里直接使用这些模块的默认名称。 接下来如果这个模块需要进一步拆分出子模块、建立内部结构或多版本共存,可以创建跟文件名相同的目录,形成类似这样的结构:
lang.js和2.0.js都是子模块的组合,所以用户可以直接导入整个package: 以上这种『惯例』的好处应该是比较明显的罢……有一点需要强调的是,它是一个『最简』而又能适应『变化』的模式,module可以从最简单最直白的形式开始,不做预先设计,没有繁文缛节,之后基于实践中的需求和检验,自然的向复杂结构发展,同时仍然保持单一成分的简单和用法的稳定。 即将发布的moui/gesture就是这种形式,用户可以导入自己需要的手势: 关于gbk编码等特殊需求和OzJS适用的场景你拿gbk来举例的原因我其实非常了解,土豆网跟淘宝在这方面非常像,都是曾经基于php和gbk编码的mysql,页面上需要加 但离开土豆之后我学到很多东西,其中之一是人在一个固定环境里呆久了,很容易看不清一件事物在更大范围内的重要程度和性价比,具体就不展开了,总之oz.js重视惯例和最佳实践、厌恶配置、追求简单一致的设计,但并不排斥需求,也不局限于特定场景,支持charset确实蛮有用,只是到现在为止还没人给我发issue而已… 关于Early Executing你误会了……我之前说的『我很想看看实际例子是怎样的』,是指你举得这个抽象例子所反映的问题在实际项目中是怎样体现的,因为我真心没有遇见过,就算遇见了一般也会归结为错误实践,问题本身我当然知道呀。 另外把执行延后在实现和设计上并不存在很大的障碍,如果只是基于兴趣和实验而不是实际需求的话,等我有空又不懒的时候可以改一个这种模式的oz.js版本来对比下。 |
module是个好东西,但是也是个麻烦东西。ES6的module从语法到语义改来改去,到现在还没个定案,以至于我的ES6 Loader/Module实现( http://github.com/hax/my.js )现在都扔在那里不高兴弄。我最近半年把精力都投入到了模板语言(http://github.com/hax/jedi )去了,暂时都没时间精力跟module方面的进展呢。但是这帖好,我得先占个坑,嘿嘿。 |
@lifesinger
我觉得用less, sass/stylus和coffeescript类比是不对的。 相对于css来说,javascript本身就是编程语言,什么语法结构其实都有了, coffeescript只是把语法精炼了下,提供了些sugar syntax, 当然也可以为不熟悉javascript的人生成"最佳"代码,我们就有组员有时候会用coffeescript写点代码然后生成javascript看怎样才是最“合适”的写法,最后就导致咱代码里面用了好多“self” -_-!! 如今有更多的高级语言比方说Haskell,都号称能编译成javascript之类的。 这类语言也许有更多的发展,但是目前争议也很大,毕竟大部分时间用什么语言写代码本质上没啥特别大的区别,但是一旦出了问题要在目标宿主(比如browser)里面调试,你总不能完全不知道javascript吧。。。 所以多一事不如少一事。 当然,这只是带有严重偏见的个人观点, 毕竟coffeescript确实带来可以很大的便利。 另外提一点: 当然阿里要支持IE6,7, 而IE8以上才支持less,sass/stylus,这可能是很多阿里人不愿意使用这些动态css的原因把。 最后汗汗的问个傻问题, 为啥会有在gbk编码的页面下去加载utf8的需求? |
@realdah 阿里系,因为历史原因,目前 90% 以上还是用的 GBK 编码。迁移成本很大,主要在数据库层面。 |
我从来没觉得改变编码有多少“迁移成本”。洗数据这种事情都经常做,改一个编码倒不肯。关键是后端本着多一事不如少一事的态度。前端对后端完全没有制约力。 |
源码中
forEach
的实现,以及m.deps.forEach
的用法,使得 OzJS 在 IE9 以下无法运行。是有意不支持 Old IE?如果不需要考虑 IE6-8,则 32 行的代码可以省略掉,在文档中说明就好。只支持高级浏览器的话,代码应该还可以简化。有个建议,文档上将 AMD 公共模块称呼为 AMD 具名模块,AMD 私有模块称呼为 AMD 匿名模块,这样可能更表意,呵呵。另外,对于 AMD 私有模块,模块 url 的获取,是通过串行加载来保证拿到的 url 是当前 define 的?没仔细看代码,想求证下。如果是串行的话,当开发时私有模块超过 20 多个时,豆瓣内部是怎么缩短模块加载时间的?(之前在淘宝遇到了这个问题,SeaJS 中改成并行才解决,但并行导致代码有些 hacky,不爽)
异步模块的支持挺有意思,但感觉有悖模块加载器的本职工作。个人觉得模块加载器不应该涉及异步等待逻辑。如果需要等待,可以调整依赖来解决。
远程模块的设计,个人觉得让 define 承担了不应该承担的职责。比如
上面两件事情,是否通过 config 来配置会更明确?比如
在 SeaJS 里,通过增加 shim 配置来实现,比如
shim 配置的方式,功能和 OzJS 的远程模块类似,但在批量处理上,感觉更方便些,比如上面 jquery-plugins 的声明方式,只要命名规则为 jquery.xxx.js 的插件,都自动添加好了依赖,使用上,直接 require 真实路径或 alias 就好。RequireJS 2.0 里,也是类似的处理方式。
从源码上看,OzJS 中的
require
也是身兼多职。这是我一直很难接受 RequireJS 的重要原因之一。为什么不职责单一一些?全局中的 require 跟 参数中的 require 应该不一样,就像 NodeJS 中的 require 一样,每个模块的 require 是私有的,是有上下文环境的,这样相对路径的解析也更合理。可能是个人喜好,如有冒犯,请忽略。new!
挺有意思,赞。mo 里面的模块挺小巧实用的,和 Arale 的理念异曲同工,呵呵。有个小疑问,mo 的模块 id 都是固定的,比如
mo/cookie
,这样,当 cookie 版本升级时,如果是一个老页面,有些功能点依赖 cookie 的老版本,但新功能点想依赖 cookie 的新版本,这种情况下,OzJS 里是如何处理的?Arale 里给每个模块都加了版本,比如arale/cookie/1.2.0/cookie
这种方式,这样两个不同版本的 cookie,可以认为是完全两个不同的模块,因此可以并存。想知道豆瓣这一块是如何处理。建议 mo 里的模块可以每个模块一个独立库,这样通过简单的 transport 工具,可以和 Arale 的组件互通起来。对于生态圈,也想听听 @dexteryy 的想法。
先说这些,希望能对 @dexteryy 有所帮助,祝 Oz 能越来越好。
玉伯 / Feb 20, 2013
The text was updated successfully, but these errors were encountered: