我想来简单地评述一下这篇文章,这篇文章我早前已经读过,现在我希望尽可能站在中立的角度来描述一下作者提及到的几个方面,然后从一个具备一定经验的 ember 开发者的角度来解读一番。
和作者一样,我不打算批判任何 ember 的“竞争者”,因为仅就普及和接纳的程度而言,ember 根本就不是 angular 或 react 的对手。但是从另外一个角度来讲,我所认识的 ember 开发者们对于其他框架的了解程度要远远大于反过来的情况。背后的原因是在我看来,ember 社区接受和学习其他框架的优点的愿望和能力是很强的,但却不会强到要改换门庭,因为 ember 有一些独特的价值是其他框架所不具备的。而对于那些外来的精萃,ember 社区在理解的同时也会很谨慎得来吸收并完善自己,而不是毫无主张漫无目的的全盘接纳。
对我来说这一点非常有趣,在很大程度上非常类似于今天的中国是如何在保持开放式发展的同时还力求塑造自己独特的文化。
事实上,我非常理解 TypeScript 的价值,前端开发的环境的不可控性是非常高的,TypeScript 这样的工具的确可以帮助很多开发者减少这些风险带来的影响。比方说很多人经常会写出“想当然”的代码,比如说假定入参是一个数字,于是不假思索的对其进行数学运算。大多数时候可能不会有问题,因为 JavaScript 有着很有趣的隐式类型转换,它可以在很多时候悄悄的完成你都没意识到的工作,可它并不能保证百分之百正确,更可怕的是有些时候所引起的逻辑错误并不是语法或是运算结果的错误,所以你可能需要很久才会发现这里有问题。
比如说很多人都没意识到如果把数字通过数据绑定关联到表单元素之上,等你把它拿回来的时候它们就已经不再是数字而是字符串了(即便你的表单元素是 Number
类型也不例外)。DEMO
TypeScript 的最基本的价值就在于迫使开发者考虑输入输出时的类型匹配问题,我认为如果你在做一个正儿八经的项目,同时参与的人又不见得能轻松驾驭 JavaScript 的各种特性的情况下,TypeScript 还是很管用的。而且它的价值还远不仅如此,Glimmer 2 是 ember 框架所采用的新一代渲染引擎,它就是用 TypeScript 开发完成的,core team 对其表现的反馈相当良好。
而 ember 社区很早就有了支援 TypeScript 的 addon,不过我自己没有用过就不多说了。我相信使用 TypeScript 来开发 ember 应用程序应该是很容易的事情。
至于 jsx,我同样非常理解 React 所作出的决定,因为它根本就不是模版而是函数体表达式的替代语法,和 ember 相比完全是两种不同的工程思维,所以并不好直接对比。不过在“结构描述可用的逻辑表达能力”这件事情上,一个是给予完全的自由(jsx),一个则是默认限制但允许扩展(通过 htmlbars helper),这种看起来两个极端的对比也的确很有意思。对于新手来说,或许会觉得 ember 在模板上的限制会让人束手束脚,可是这种决策背后的原因是需要你去理解的(当然如果你不能理解,React 欢迎你),这一点原文的作者已经描述过了。进一步来说,一旦你具备了一定的知识水准,你会发现其实 ember 也能写出兼具强大逻辑和易读语义的模板,区别仅仅在于是一下子就丢给你还是一步一步引导你罢了。
从这个意义上来看,反而是像 React 那样的理念对开发者水平的要求更高,因为它很容易失控。
至于说“ember 使用了 pure javascript,没有额外的语法”,well,在我看来这得看从什么角度来说了。
如果你只是 average javascript developer,ember 的确蛮“传统”的,语法上不会有太多的“惊讶”,这是好事。可是你不要以为 ember 永远会这样,因为这些传统中的一部分也在拖累着 ember,并且因为由来已久很难快速的根治,因此才拖延至今罢了。
比方说对于 ES2015 的语法的接纳,可能很少人知道这样的传统写法:
import Component from 'ember-component';
export default Component.extend({
init() {
this._super(...arguments);
}
});
已经可以写成这样了吧?
import Component from 'ember-component';
export default class extends Component {
constructor() {
super();
}
}
这倒的确是事实,但是我会劝你现在先别这么做,因为你很快就会注意到更换了语法之后很多东西就“坏”掉了。
原因是 Component
(已经它的其他同胞)并不是完完全全的 ES2015 Class,换句话说它们并不是传统意义上的构造函数(前提是你明白 class
只是传统的构造函数 + 原型继承的语法糖而已)。
在 ember 诞生并成型的时代 ES2015 还在娘胎里,ember 不能等它呱呱落地才开始自己的成长,所以它大量使用了 ES2015 之前的很多 magic trick 来实现现代 JavaScript 所实现的东西,其代价就是今天想要摇身一变就得需要时间。你可能会说 React 也经历过这种转变,但是它就很快啊?可是从框架复杂程度上来看 React 根本就不是一个级别的啊——如原文所说 React 都不算是一个框架。
当然这一类的变化也没有超出 pure javascript 的范畴,不过 impure 的语法也不是没有,比如说:
// Computed Property
incomplete: computed('[email protected]', function() {
let todos = this.get('todos');
return todos.filterBy('isDone', false);
}).readOnly()
即便从传统语法的角度来看,这种属性定义方式也是够离经叛道的了,如果换成现代的方式应该用一个 decorator 会更加优雅。事实上 ember 很早就有对应的 addon 实现了,它看起来是这样子的:
import computed from 'ember-computed-decorators';
@computed('[email protected]')
incomplete(todos) {
return todos.filterBy('isDone', false);
}
甚至更好:
import {filterBy} from 'ember-computed-decorators';
@filterBy('todos', 'isDone', false) incomplete
但是这样的东西一直都是以 addon 的身份存在着(作者和维护者就是 core team member),为什么 ember core 迟迟不接纳它成为正式成员?拿 decorator 来说,它就曾经被 ECMA 废止过,现在它经过重新完善再次进入标准委员会的待选名单,但是何时落地尚未有定论。对于 ember 来说,它不是不能用,但是要小心的,有选择性的,理解潜在风险的去尝试,所以到目前为止它只能是一个 addon。
我认为作为一个有责任感的团队做出这样谨慎的决策是非常理智和正确的,这也是长久以来 ember 可以保持平稳升级不给开发者造成太多麻烦的重要保障基础。在现实中有人称赞 ember 的坚实可靠,考量周全,当然也会有人批判 ember 略显保守,不思进取,两种声音都有道理,却也都不够全面。ember 团队的理念是(个人解读):最终我们要成为 web 开发的最好选择,无论是关注稳定还是关注特性的人都可以得到满意的结果,只不过 JavaScript 现今正处在不断变革的时代,所以我们要采取一步一步稳扎稳打的策略来朝着目标前进。
对于身处一线的开发者来说,任何一个框架都不可能满足他们全部的需求及渴望,同理任何一个框架也不应奢望能够干掉所有对手来独占所有的开发者用户,这和良性的市场竞争是同样的道理。有些工具并不是以获取更多的关注来作为自己的价值体现的,使用工具的人其实应该很容易理解这一点,否则的话大部分的开发者其实都可以回家种地了。
约定优于配置/强制性的最佳实践
Ruby 社区的人对这个概念是最熟悉不过的了。我自己对它的理解长期以来都存在一个问题:约定究竟是谁说了算?是不是也应该民主表决一下?
不是没有人对 ember 的约定表达过意见的,这种情形非常多。通常他们都会带着另外一个框架/工具的特性来问 ember team:为什么我们不能这样做/为什么我们没有这个?
最典型的例子就是 webpack,我已经见过很多次有人提出要用 webpack 替换 ember-cli 现在所使用的 broccoli 了,这里面有一些人甚至做出了自己的尝试,当然也有人态度非常恶劣就好像反对素食主义者的极端分子一样。问题是极少有人真正理解 webpack 和 broccoli 的区别是什么,甚至连 webpack 究竟是什么他们都未必一清二楚。
从功能性上来讲,broccoli 只做了 webpack 能做的一部分,而且这一部分它做得非常好(构建)。但是 ember-cli 可不是这么简单的东西,webpack 最重要的核心就是模块打包 (module bundler),由于众所周知的历史原因,JavaScript 的模版系统曾经极度混乱,webpack 是众多解决方案之中的佼佼者,后来居上干掉了所有的对手。它的做法本质上就是一个 adapter,定制了一套机制来兼容所有主流的模块规范,然后在公共接口上向真正的标准(ES 2015 Module System)靠拢(先是以 CommonJS 接口为准,然后逐渐过渡)。
然而在 webpack 被广泛接纳之前,ember-cli 早就成为 ember 系统里的事实标准了,broccoli 在其中只负责一些具体的流程工作,真正的核心还是 ember 自己的 module resolver。其实 ember-cli 也面临着和 webpack 一样的挑战:作为一个前端框架,ember 当然要拥抱占据主流的前端技术栈,然而另一方面作为一个强调约定的 opiniated framework,ember 还要给自己的开发者用户接近无感知的使用体验(意思是你不需要理解各种不同的模块是怎么工作在一起的,你只需要使用约定的接口去引入它们即可)。
谁敢说 webpack 的配置是“无感知”的?(客观地说,webpack v2 已经好很多了,但这不也是一步一步走出来的?)如果你非要让 webpack 成为 ember-cli 的基础,那么会有两种结果:
- 你必须花时间去学习和理解 ember 内部的模块解析机制,然而正确的配置 webpack,从而实现 ember 所提倡的约定——这么一来“约定优于配置”就成了笑话
- ember 花力气用 webpack 来适应自己的模块解析机制,把复杂的配置隐藏在 ember cli 的后面,用户并不需要知道 webpack 是怎么工作的,也就不需要配置——可是 webpack 的拥趸们喜欢的就是 webpack 近乎“无所不能”的可操控性
我想问问 webpack 的拥趸们:你们可以代表其他开发者吗?你们以为所有的人都喜欢折腾天书一般的 webpack configurations 吗?不用质疑我,在我去年打造 React SSR 全家桶的时候就已经把 webpack 玩得炉火纯青了,我非常理解他们的 G 点在哪里。可是反过来当我面对我的同事的时候,我宁可去教他们如何把 React 写得有条有理也不愿意教他们 webpack“真经”,因为这并不是什么愉快的体验。
这是一个例子,细心敏感的朋友大概能读出我在影射什么,不妨坦白说,就是所谓的“民主自由”。以下是我和另外一位同行对话时的节选:
我的观点是:这个世界终究是有序的,否则 webpack 的终极目标应该是推翻 ES2015 Module System 而不是顺应它。约定优于配置的精神意义就是,无论哪种行业,大部分人关注的是有序的创造而不是无序的折腾。且把后者留给那些生来就喜欢折腾的人吧(其实我自己就是),而他们也不应该把自己的偏好强加于人。
也有“光明”的一面,其实 ember 社区一直都走在民主自治的最前端。从基因上来说 ember 出自社区,它从来没有忘记自己的起源,也没有忘记自己的责任和义务。它是第一个推行 RFC 制度的前端框架,自施行以来所有涉及到 API 或是架构机制的增订/修改全部都经历了严格 RFC 流程,任何人都有权利参与这些改变的全过程,任何人也都有权利发表自己的见解。我们只需要明白两件事情:
- 你需要尊重整个社区的价值观,社区对你的提案接纳与否取决于社区其他人的态度而不是身份。举例来说,ember-engine 是有来自 Linkdin 的工程师主导开发的(但他还不是 ember core team member),它在架构上遵循了 ember-cli 的体系标准,因此可以顺利的搭载在任何一个 ember 应用程序上。等到它足够成熟(目前是以 addon 的身份存在,core team 协助开发)的时候就会成为 ember system 的正式组成部分(事实上已经公认了)。
- 如果你有想法,RFC,slack,community forum 全都对你开放,just play by the book。
关于缺点
如我之前所说,ember 社区的文化向来是广泛吸取各种建议和经验然后尝试为我所用的。有些时候你会觉得 ember 给出的方案貌似和“主流”不太一样,那是因为 ember 自身就很庞大和复杂,ember 社区的解决方案一贯都是以解决“自己人”的问题作为第一优先级,而不是为了提供“普适价值”。而一旦 ember 有了对整个 javascript 社群都有借鉴价值的产物,ember 社区也从来不会吝啬贡献自己的力量,比如说 ember-cli 的理念就影响了 react 和 angular 两大社区。
另外 ember 社区的多样性是非常广泛的,不仅仅局限于 javascript 一隅。比如说 Elixir 兴起之后,很多人体验了不同于其他 web 框架的 Phoenix Framework,其中 Ecto(强调一下,Ecto 不是 ORM!)里的 changeset 对于具有一半 FP 血统的 javascript 来说就很有参考价值。于是 ember 社区立刻就出现了相似的 ember-changeset,对于很多传统的 ember 开发者来说,这就是一个脑洞大开的尝试,而且实际效果也真的很不错。
实际上,有进步或是变革就代表着有缺点,而且在努力的改善着它们。而那些广受关注的正在改善过程中的缺点相信是更多人比较关心的,即使我此前已经在各种渠道说过很多遍,在这里我还是借这个机会再总结一遍,因为在内心深处我当然期待更多的开发者成为 ember 社区的一员,所以除了画饼之外我当然也应该把挑战摆在面前:
陡峭的学习路线
让我们面对现实吧,ember 不是那种用来做 demo 的工具,在你准备使用 ember 来开发你下一个 big idea 之前你至少应该摆弄过各种各样的 demo 很多次了。这不是一个给初出茅庐的毕业生准备的拟真游戏,所以如果你觉得 ember 很难学,那不是因为 ember 有意要给你难堪,而是它的目标受众群体不包含你在内。
其实 ember 已经拥有了最完善的指南和 API 文档,只不过它很大所以读懂它们需要时间。路线其实不陡峭,只不过爬坡的距离长了一些,如果你只是偶尔徒步健健身,你当然会觉得这座山峰不可逾越。实话讲,我“试图”征服这座山峰的过程并不顺利,前两次我都半途而废过,好在我很有“野心”所以又第三次契而不舍的去征服它。事实上,前两次的失败给我了很多宝贵的启示,我知道自己欠缺的基础知识是什么,然后就可以通过对于其他工具的实践来完善自己。
所以我觉得这就是个态度问题,你对你自己的态度,你觉得自己不能攀上高峰是山峰的问题还是自己的问题?同时没有人强迫你一定要上去,如果今天力有不逮,那就回去锻炼锻炼再来呗。除非你的志向不在于此,否则陡峭其实只是一个逃避认输的借口罢了。
稍慢的页面渲染
这是事实,作为客户端 SPA 框架,首页加载会慢是公认的事实,ember 在这方面的表现也不意外。不过认真细致的分析的话,实际上渲染很快(无论是首次还是后续),拖后腿的是静态资源的加载耗时以及 js 代码的执行消耗。
具体的分析我就不扯了,我只说应对方案。
解决静态资源加载耗时问题其实有两种不同的思路,第一种是从资源本身入手,削减它们的体积或者是有效的分离它们然后按需加载;另一种则是绕开加载耗时,先于资源加载呈现内容然后让资源在后面悄悄加载,对于用户则是无感知。
这两种方案其实是可以共同作用的,并且它们带来的收益也各不相同。ember-engines 是第一种方法的解决方案,它的原理是允许让开发者按照自己的需求将应用程序分割成若干部分,对应的静态资源也会随同一起分离。当然它能做到的不只这么简单,简要列举如下:
- 被分割的部分可以定义自己路由,因此它们的衔接就如同一体的 SPA 一样流畅,而中心化的 data store 也可以由它们共享;当然它们也可以是非路由的,那么就好像一个分离的组件一样随着自己的载体而渲染。
- 分离的静态资源按约定被输出到项目的物理空间之下,并且会形成一份统一的 assets manifest,因此它们是可以集中管控的。这意味着你可以自由决定何时何地加载它们,也可以让它们 by default 的随着所属的 engine 惰性加载。
- engine 本身也是可复用的,当它们 mount 到宿主应用的时候可以为它们分配各自的“命名空间”,对于用户来说就像不同的子域名/子目录,但实例并不需要重复。
- 你可以决定哪些 components / services 是共享的,engine 系统不仅仅是为了分离,同时也是为了共享。
因此,基本上 engine 就是自带路由体系和静态资源管控的高级 addon,事实上它真的就是 addon,所以你想用它的时候并不需要更改已有的架构,只需把你选定的部分按照 engine 约定重新安排一下就好了,除了 engine 独有的特性,其他的一切都遵循着 ember 的既有机制。简而言之,这就是大家经常说的:惰性按需加载。
现在你就可以打开 Linkedin,不声不响之间,Linkedin 全站已经 ember 化,而率先使用 ember-engine 就是它的最大亮点。之前我提到过,ember-engine 是由来自 Linkedin 的工程师主导开发的,再一次体现了 ember 社区的务实特性。
ember-fastboot 则是第二种方法的解决方案,从底层来看它是一个服务端渲染引擎,同时也是一套 isomorphic javascript 的解决方案(但是并不要求你一定要在服务端也是用 javascript,只是一个可选项)。最近也有一些讨论,大致的方向是客户端框架搞服务端渲染等于走回老路,纯属多此一举。从保有此种观点的人的角度来看,我个人能够理解,可是反过来我不觉得他们理解客户端框架的诉求,或者说他们日常工作的场景用不到所以也就不会在意这方面的诉求。
诚然追求快是所有人都认同的目标之一,但是对快的定义也不必追求的那么苛刻。就性能这个词来说,我作为程序员或作为产品人,扮演不同的角色的时候对它的考量角度也是不同。身为程序员我能把加载时间缩短 100ms 都会获得莫大的成就感,但是身为产品人我不禁要问我花费了多少资源在这上面?而我的用户会对这个变化产生多大的认同感?能对我的产品价值的提升带来多大的帮助?
如果我的产品平均加载时间超过 5s(假设),那么我缩短 1/5 会是一个了不起的进步,我的用户也可以直观的感受到这一变化;但如果是 1s,缩短 1/5 对于除我之外的人来说很难感觉到什么。可是让 5s 缩短 1/5 和让 1s 缩短 1/5 所付出的努力却是不相称的。如果在用户价值和商业价值上我没什么可追求的了,那么我愿意倾尽全力来让 1s 缩短 1/5 甚至更多,但显然这不是产品的核心价值,它只是影响用户体验的一个因素罢了。
现实则是非常多成功的客户端 SPA 应用并没有让用户觉得很慢,这并不是说在一些性能指标上客户端框架已经无可挑剔了,而是价值判定的标准并非如此单纯而已。
从另外一个角度说,如果传统的服务端渲染已经到了没必要改善的地步,那么像 turbolink 这种从客户端技术移植过来的东西又代表着什么呢?我们没有必要认为一种技术向另外一种技术取经就是多此一举,因为这不是在遮掩自身的不足,而是取长补短,在不影响自身价值的前提下更加全面的完善自己。
fastboot 就是这样一种完善自身的有益补充,从务实的角度来说 fastboot 不是必须要做的事情。一些 SPA 应用的天性就是允许一定程度上的妥协,因为对于它的用户来说,它带来的功能和体验足以让用户接受不是那么极速的首屏渲染时耗,也不依赖搜索引擎对其内容的索引。如果这些诉求对你的用户来说至关重要,那么你选择 ember 这样的客户端框架岂不是盲目之举?然后你再反过来抱怨“到头来我还是得用服务端渲染技术”,这能怨的谁来?
ember 的生态世界里有非常多的工具和特性值得探索,它们可以从不同的剖面来帮助你提升产品的质量和体验。作为一个从底层一点点实践过服务端渲染 SPA 应用的工程师,我的建议是最好把 fastboot 放在 todo list 靠后的位置去,除非你的技术实力够硬。
框架体积庞大
这是另外一个影响加载时耗的因素,它即消耗请求加载的时间,也消耗代码执行的时间,我的观点是在性能层面这应该是 ember 要去解决的最高优先级问题,那么为何直到现在这一点还在“罪恶黑名单”上高居榜首呢?
首先,我列举一些事实:
- 去年我完成 React SSR 全家桶之后拿最终的体积和 ember 做了一个对比,实际上 ember 的体积并没有大多少(大了 10% 左右),但是提供的功能要比我精挑细选的全家桶多多了。
- 一年以后最新的版本(v2.12.1)默认构建出的体积已经小于我自己做的全家桶了,当然这段时间以来我并没有更新 React 全家桶的依赖,所以这个结果不够客观。
- 在开发模式下,ember 构建出的 js 文件是 500k 出头,代码行数 70k+,而产品模式之后,体积骤减到 160k+,代码行数仅剩 30k+,这其中还有至少 1/3 是完整版的 jQuery。WHY?因为在开发模式下,源代码没有去除代码注释,这相当于集成了一份完整的 API 文档。
- 精简版的 jQuery 是可以直接替换进来的,我在 codepen.io 上做过实验没有任何问题。但是 ember-cli 没有提供选择精简版的 jQuery 选项,因为考虑到这些年来庞大的 addons 有很多都在依赖 jQuery 的各种功能。
- 事实上,除了为兼容第三方插件的考量之外,ember 框架本身对 jQuery 并没有强依赖,默认集成 jQuery 是历史包袱的结果。当然在
Component
之中还存在 this.$()
的公共 API(用于获取组件根元素的 jQuery 封装对象),可这是一个可选项,如果你较真的话你完全可以不用(想想操作 DOM 的时候你需不需要考虑利用 jQuery 强大的兼容性吧,在今时今日很多时候这已经是一个可选项了)
这并不是说 ember 框架自身已经足够好了,抛出上述经常被人忽略的因素之外,ember 框架内部的耦合性也是导致迟迟无法高效瘦身的重要原因,这是一个规模很大的问题,ember core team 并没有放过它,只是花费了很长的时间。
Glimmer 2 是独立于 ember core 的,意味着它可以独立使用。ember 的内部组成部分已经被逐渐拆解为各自独立的 npm package。自 ember-cli v2.11 之后,bower 已经成了可选项,ember-cli v2.12,默认已经取消了对 bower 的任何依赖,ember-cli v2.13 之后 bower 将从默认配置里被剔除。
在这些准备工作完成之后,下一步将会/正在执行两个重要的 RFC,它们的实现将大大缩减 ember 框架在应用中所占的体积。
第一个:JavaScript Module API
迄今为止,所有的 API 都通过一个庞大的树形架构寄生在顶级的 Ember
命名空间之下,这种古老的软件架构无法享受现代的模块处理机制,比如说自动削减未经使用的模块的技术(Tree Shaking)。不过 ember 框架的内部早已经逐步完成了各组成部分的模块化,而且早就通过 shims 的方式实现了语法上的模块导入/导出,现在到了最后一步:一旦 ember-cli 完成了新的模块管理机制,就可以实现真正的可剥离式的模块化框架架构了。也就是说,一俟完成,以后 ember-cli 就可以帮你把你没用到的模块自动剔除出去,在根源上削减最终的框架体积。
看看这份列表吧,之后你就会意识到一直以来 ember 框架提供多少丰富的工具库。一方面因为技术限制它们不能很好的按需使用,可另外一方面又有多少人确实有效的利用好了这个全功能的框架呢?如果你也摆弄过类似 React 全家桶之类的东西你就能明白做一个扎实丰富的 web 应用需要多少第三方资源的辅助,它们哪一个不占用应用最终的体积?ember 只不过是事先帮你都准备好罢了。体积和功能就像天平的两端,过去的声音一致集中在批判体积过大上面,却罕见有人来褒奖 ember 的周到和全面,我想这大概是 ember 框架最被误解和无视的一点了。
第二个:瘦身构建计划
这是一个相对简单的有益补充。为了保持框架在持续发展的同时不会影响旧有应用的运行,对于 API 的废弃 ember 从来都不会粗暴解决,而是使用一些内部机制来给出渐进式的提醒。然而随着时间增长,即使是这些善意的措施也会变成问题,越来越到的 deprecations 堆积在框架内部,也占用了很大的体积。这个问题其实早有讨论,在动手之前需要有一种妥当的机制来告知用户什么时候会做这些“破坏性”处理并且给他们足够的响应时间。
去年,ember core team 开启了 LTS 版本的支持,同时也预告了消除这些废弃代码的 deadline,现在正是时候。目前已经开始清理这些代码了,从开始有废弃机制开始一直到最近的 LTS 版本(v2.8)为止,所有上述的垃圾都将被一次性清除,因此称为瘦身构建计划。
好了,最后总结一下。上面我说的东西,一部分是历史,一部分是正在发生的变化,还有很多很多远的近的未来的计划。让我最爱 ember 社区的一点是:所有的计划都是摆在那里的,你不会担心被蒙在鼓里或是一觉醒来发现天变了。这其中有些计划会受到质疑被取消,有的会因为其他因素拖延进度,有的会保持开放性探讨直到确凿无疑,but anyway, developers are always under developing, it never stop in the past, the present, and the future. This is the reason I love about ember and its community.
BTW,Ember Conf 2017 马上就要开幕了,还有很多新鲜的信息正在路上。