三年前,我写了一篇叫作“2015 年 JavaScript 发展状态”的文章。我在文章中呼吁,行业的快速变化要求我们使用“微型库”,也就是使用单一用途的小型库来取代单体框架。不过,现在我想说服你们重回单体框架。
首先,很少人愿意公开承认采用微型库架构存在的成本问题。他们需要编写胶水代码、做出决策、配置和承担风险,这些都会影响到他们的生产力。其次,这些成本不再是必要的:JS 生态系统已经放慢了发展的脚步,已经有很多很好的实践可供参考,并且 Web 应用程序变得越来越相似。当这些开发实践汇聚在一起时,框架就能对我们的项目发挥重要作用。
在你们读完这篇文章时,我希望能够说服你们,“一体”框架不仅仅是一个更好的选择,它们实际上是一种更加符合经济规律的解决方案。
2015 年的 JavaScript 发展状态
在我写上一篇文章时,谷歌刚刚发布 Angular 2,并声称他们重写了整个框架,而且不向后兼容。如果你是在 2013 年转向 NG,那么这等于说,在投入了一年的时间之后,突然就过时了。这真出乎人的意料!
我为此做了两个(非常谨慎的)预测:
第一,JavaScript 生态系统的变化速度是不可持续的,开发者会起来反对它。
第二,为了避免框架被淘汰给自己带来的影响,开发者转向微型库——具有简单 API 的小型单一用途库,使用这些库不需要太多框架“专有”知识。
我认为我的预测或多或少会发生。首先,webdev 社区开始出现了 JavaScript 用户”流式“,其次,他们开始采用 React,事实证明它就是一个小型库。
或许这听起来很奇怪,因为 React 的代码库其实是很大。但就像微型库一样,React 关注于解决好一个问题,就是把视图写成函数。它使用极其轻量的模板语言(JSX)来实现这一功能,JSX 引入尽可能少的模板语法。由于 React“几乎就是 JavaScript”,所以开发者不需要花费太多精力在库的概念上。就字节数而言,它可能不是“微型”的,但它肯定是一种低风险的投资,在转型时是可以考虑的。
几乎所有的 React 项目都遵循这种微型库架构原则:引入各种各样的插件和实用程序来帮助管理状态(Redux、Sagas)、路由(React-Router)、集合(Lodash)、不可变性(Immutable.js),还有其他很多已经记不清了。同样,这些项目的构建脚本由一系列 Webpack 和 Babel 插件提供支持,所有这些都与配置文件完美地结合在一起。
到目前为止,React 栈已经给我们带来了很多好处。随着时间推移,单个组件逐渐被替换——比如 Flux 的各种变种或 React-Router 的多次迭代——但不需要重写项目。同样,Webpack 允许我们通过添加 NPM 插件快速采用新技术,比如编译、Experimental JS、拆分捆绑和 Scoped CSS 等。
问题是,世界已经发生了变化,但我们的应用程序技术栈仍然停留在 2015 年,而这阻碍了我们的发展。
2018 年采用微型库的成本
在采用微型库架构时,我们做了一些折衷。
首先,大量的依赖关系意味着需要做出大量的依赖性决策。原先可能每个项目只需要做一次的任务,现在可能每周都要做。
选择一个 JavaScript 库很容易,但要做好这个选择却很难。大多数开发者把库的流行程度等同于质量。他们希望如果一个项目在 Github 上有一千个 Star,就永远不会被取代,永远不会引入重大变更,也不会给他们的项目引入漏洞。
不过,没有人能够对此作出保证,这个只要看看 left-pad 的案例就知道了。看看你自己的项目,看看它依赖了多少东西?它们有多少是经过安全审计的?有多少是做过性能测试的?有多少是由一帮稳定的人在维护?因为实在是太多了,你根本无暇顾及。
几周后,David Gilbertson 在 Hackernoon 上写了一篇非常好的文章:我正在从你的网站收集信用卡号和密码。文章写的是在 npm 软件包中注入木马的故事。你无法通过网络工具中发现它,因为它检测到的是 Chrome devtools。你也无法通过观察你的包来发现它,因为 HTTP 请求被混淆了。你甚至不能在 Github 上查看这个包,因为 npm 版本的代码与其他的有点不一样。你可能没有那么好的运气,而即使有人发现了,也为时已晚:开发者“安装 npm 软件包就像吃止疼药”一样,在发现木马警报时,恶意软件已经流入成千上万的下游网站中。
故事的寓意在于说明不要将第三方代码放在敏感的网页上,但我觉得它还有另外一个寓意。并不是说人类太马虎或太懒惰,而是基于我们的微型库技术栈系统是很难构建出健壮且安全的系统的。npm、Github 和 README 这样的组合扭曲了我们的决策行为,总是先让我们做出仓促的决策,鼓励我们先通过 npm install 安装代码库,然后再提问题。
这就是 2018 年 JavaScript 的真实状态:没有尽职调查和脆弱的依赖关系。这并不是因为开发者无能为力,而是因为微型库架构需要谨慎地做出决策,而这与大多数科技公司的发展步调不一致。
样板不是正确的解决方案
现在出现了一种创新,有可能是潜在的解决方案——在过去几年里,JavaScript 样板在崛起,但很奇怪的是,居然没有引起人们的注意。这些托管在 Github 上的项目通常会提供用于构建微型库架构 Web 项目的示例代码。它们把 React 栈和 Webpack/Babel 构建系统以及某种预生产服务器结合在一起。它们也非常受欢迎:最初的 React-Boilerplate 有近 17,000 个 Star。
但我从来都不喜欢这些,我希望能够了解应用程序技术栈的所有组件,这样可以避免很多风险。这一点对于构建脚本来说尤其重要:很多团队不知道他们的 Webpack 或 Gulp 是如何运行的,也不知道他们的代码是如何打包部署的。日子就这么过下去……直到生产环境发生了问题,让他们陷入噩梦之中。
不过它们的确反映出了开发者真正的需求:他们不希望项目开始的前几周都用在了选择微型库和开发胶水代码上。他们更愿意安装一个像 Create-React-App 这样的环境,它不仅提供了应用程序样板,还提供了一些工具用于生成项目脚手架、格式化代码,甚至是配置持续集成过程。
即使像 CRA 这样的框架功能很强大,但它们仍然无法真正用于管理依赖关系。如果 React-Router-5 推出全新的突破性变更,CRA 必须将这些变更推送给用户。如果一个依赖因为法律或知识产权纠纷(如 left-pad)而被撤回,CRA 最终也必须丢弃它。所以,样板库的作者仍然无法完全避免用户采用微型库的成本。
单体框架的好处
到目前为止,我主要阐述了基于单一用途微型库构建项目的成本,并没有解释单体框架可以为我们带来的好处。
总的来说,单体框架提供了一种统一的体验,这种体验与底层技术是分离的。它们因此能够创造特定于领域的语言和“框架魔法”,用透明度换来生产力的提升。
例如,在最初的 Angular 中,开发人员使用 Angular 特定的模板语言开发视图。这样就可以巧妙地将视图与后台的状态对象绑定在一起,也就是控制器:
<label>Click me: <input type="checkbox" ng-model="checked" ng-init="checked=true" /> </label> <br/> Show when checked: <span ng-if="checked"> This is removed when the checkbox is unchecked. </span>
神奇就神奇在这些 ng- 属性,虽然这些对于 HTML 来说是陌生的。我称它们为“魔术”,是因为虽然它们很强大,但却难以理解。Angular 究竟对这些属性做了什么?就像魔法咒语一样,我必须学会并记住它们,如果框架过时,学会的东西就没有用了。
将它与 JSX 进行比较:
onChange(e) { this.setState({checked: e.target.checked}); } render() { return (<div> <label>Click me: <input type="checkbox" onChange={this.onChange.bind(this)} checked={this.state.checked} /> </label> <br/> Show when checked: {this.state.checked && <span> This is removed when the checkbox is unchecked. </span> } </div> ); }
正如你所看到的,JSX 更加透明——它更接近于真正的 JavaScript,除了用于放置表达式的花括号外,它几乎没有特殊的 HTML 语法。不过,它是以牺牲生产力为代价的,因为它更加繁琐,你必须自己处理 JavaScript 函数绑定和原始 DOM 事件 API(如 e.target 等)。它不会假定组件之外的状态是如何进行全局管理的。
在技术发展还不稳定的时候,像 JSX 这样的技术是有一定意义的。因为如果框架可能在一年内过时,你肯定不会想去学习模板语法和特定于库的“咒语”。不过如果除去这些不说,那些简洁的抽象会让你感觉自己像个真正的程序员。
单体框架也有其他好处。它们有详细的文档,会对你的项目性质做出有用的假设。它们建立了一种标准技术,让程序员更容易更换工作,同样也更容易招到替代人员。
我会受到技术交替的影响吗?
不会的,尽管 Reddit 和 Hacker News 上不停有人抱怨,但 JavaScript 生态系统的发展步伐在过去两年实际上已经放缓了很多。
请看 2017 年 JavaScript 状态调查报告的这张幻灯片:
假设这个调查具有代表性,这些是目前最常用的前端 JavaScript 库。其中只有一个不到两岁:
- React,2015 年 3 月发布
- Angular 1,2010 年 10 月发布
- Angular 2,2015 年 12 月发布
- Vue 2015 年 10 月发布
- Backbone,2010 年 10 月发布
- Ember 于 2011 年 12 月发布
- Aurelia,2016 年 6 月发布
不仅技术出现了融合,开发范式也出现了融合。Web 开发人员的日常实践并没有多大改变。几乎所有的项目都通过 Webpack-Babel 转换机制来运行 ES6 代码。JSX 或 Vuefile 编译成为一种标准。
然而,这些项目都维护着大量相同的 Webpack 配置文件,将无数的微依赖关系串连在一起。工具赋予了我们这种奇妙的灵活性和令人眼花缭乱的选择,但是我们真的需要它们吗?
这里还有另一个令人不寒而栗的因素:Web 本身已经停滞不前。我们对 JavaScript 领域所有的创新都感到兴奋,以至于我们没有注意到同质化的 Web 应用程序会将变成什么样。
我的意思是,我们的网站类型并不是非常多样化。从我的职业生涯来看,我真正只构建了两种 JavaScript 应用程序:由广告支撑的内容网站和物理服务的前端 SPA。后者总是包含列表页面、可排序列的大表格和地图。如果我足够幸运,可以做一个带动画效果的甜甜圈图表。尽管这些应用程序是为不同行业的公司而构建的,但它们的相似程度还是相当惊人。
造成这种局面是有一些原因的。其中一个原因是,现在很难通过纯粹的 Web 内容获利——即文章、媒体或桌面应用替代品——因为谷歌和 Facebook 一直占据着广告收入(尽管这些收入并不多),它们几乎占据了 65%的数字广告支出。
另一个可能的原因是,浏览器供应商没有以一致的方式采用新的 Web API,最终阻碍了 JS 应用发挥本该发挥的作用。苹果公司尤为拒绝做出改变,部分原因是丰富的 Web 应用程序会降低 App Store 应用的开发热度。
最重要的不是原因,而是结果:在 2018 年,我们可能会为同一类型的公司构建相同类型的 Web 应用。经济学规律告诉我们,在这种环境下,我们将从“探索”——尝试新的实践、范式、技术——转向“开发”:以更快的速度、更低的成本构建相似的东西。
这就是单体框架体现其优势的地方。大型框架可以对我们正在构建的应用程序的类型做出假设,并为这些用例提供默认的选择。它们可以为这些常见的网站设计提供丰富的教程和示例代码。我们的项目现在需要的不是灵活性,而是能够减少 Web 开发成本的方法。
这将意味着什么?
在结束之前,我想花一点时间澄清一些问题。
这篇文章不是要倡导完全拒绝使用微型库。相反,如果你正在构建一个内容站点,需要在移动设备上进行秒级渲染,并且微型库可以让你处于 14kb SYN/ACK 窗口( https://tylercipriani.com/blog/2016/09/25/the-14kb-in-the-tcp-initial-window )中,那么就可以使用它们。这没有任何异议。
这篇文章也不是要攻击 React。纯组件和虚拟 DOM 是非常重要的想法。我只是认为,将来我们会使用比“普通 React”更加重量级的抽象。它们可能是通过路由和状态管理进行抽象的封装框架,或者它们可能更简单一些,更接近 Create-React-App。
我想说的是,在接下来的几年里,我们已经发展起来的微型库架构将逐渐被那些单体框架所取代。它们将采用一种约定配置(convention-over-configuration)的方式,用透明度换取生产力的提升。
当然,我有可能是错的。或者,我可能是对的,但我的理由却是错误,这更加让人感到沮丧。有些东西可能会彻底地“颠覆”Web 和我们构建它们的方式。或许,WebVR 可能会开启 HTML5 游戏的新时代,或者网站可能会开始在性能和渲染时间方面展开竞争,这意味着我们不会使用任何 JavaScript 库。
无论发生什么,我确信 Web 开发人员都不会离开。我们的社区在蓬勃发展,充满活力,人才济济。我们比以往任何时候都更有能力解决我们的挑战。无论我们在三年内使用 React 还是 Vue 或全新的东西,我都毫不怀疑我们会走出自己的路。
英文原文: http://www.breck-mckye.com/blog/2017/12/The-State-of-JavaScript-in-2018/
感谢覃云对本文的审校。
评论