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

支付宝小程序构建重构我的一些个人思考 #19

Open
soda-x opened this issue Apr 10, 2019 · 2 comments
Open

支付宝小程序构建重构我的一些个人思考 #19

soda-x opened this issue Apr 10, 2019 · 2 comments

Comments

@soda-x
Copy link
Owner

soda-x commented Apr 10, 2019

在我的理解中支付宝小程序构建经历了两大过程

  1. webpack 化

  2. 去 webpack 化

看上去就好像走错了路,但话说回来任何的技术变更其实都没有清晰的界限来判定是对的还是错的,因为所有的决定都是基于当时的状态下决定的。

webpack 化

先来说一说为什么 webpack 化。
个人觉得 webpack 最大的魅力是有视万物为 module 的能力,它高阶的扩展能力,可以有极高的个性化能力;极其活跃的社区氛围或多或少可以让我们少走路,也少走弯路;另外不得不单独提一下 CRA,CRA 在开发体验优化这块上下的功夫非常深,目前很多基于 webpack 的上层封装,绝大多数都会有 CRA 的影子。
总结来说 webpack :

  • 优异的 add-on 设计
  • 成熟的社区配套
  • 活跃的社区氛围
  • cra 提供了优异的开发体验模块

这些对于新生业务来说有着致命的诱惑力。
另外基于对原先 ant tool 的维护,使我成为了 webpack 死忠粉。所以自然而然我们采用了 webpack 做为构建的内核,另外基于它的 add-on 能力做了足够多的个性化能力输出。

确实一切看上去没什么大问题,除了 webpack 内置的缓存优化方案让我们坑了一次外没有任何的意外。

然而凡是都有个但是,随着小程序的一步步铺开,自然而然开发者的个性化需求开始见涨。正如大家所料到的越来越多的同学希望能开放核心的构建能力,更多的参数配置,webpack.config.js 又被搬上了台面。如果业务取向稳定,可控,那么 webpack.config.js 它绝对是滋生恶魔的来源,关于这一块探讨我在差不多2年前写过一篇文章构建工具的发展和未来的选择,这里就不再展开了,根本性问题我是担忧这给后续带来的维护性、稳定性、安全性问题。
由于 webpack 内置支持 pollyfill nodejs buildin 的模块,另外社区大库也不难发现他们也会很自然而然使用一些 module,但通常很多他们都会提供一个 dist 目录是纯可以跑在浏览器端的文件,而作为开发者,从我看到上千的项目使用中,我能保障90%的开发者没有这个意识,拿来即用成为了一种习惯。

这边扯出来另外一个话题,很有趣,我应该曾经2次在对外分享中问大家一个问题,如下代码,涉及了几种模块规范

import a from 'a';

const hello = require('./hello');

const d = {
  x: 1,
};
module.exports.c = function c() {};
exports.b = {};
export { d };

让人惊讶的是,能回答上来的人寥寥无几。

不知道大家有没有想过元罪是什么?是 webpack 这一类 universal 方案么? 还是 npm 把前端模块也引入了进来了?这是一个开放的命题没有标准答案。

回到正题,因为 webpack 巨大的包容性,当然这也怪我们当初没限死,慢慢的我们看到了在小程序业务中,居然出现了一类 cheerio 的依赖,看到越来越多的案例把传统 pc,node 开发思路慢慢的衍生到了小程序之上。做为我个人的观点,这是我不想看到的。

另外在调试环节下,小程序研发流程中用户会设置编译模式(即只调试固定页),webpack 这种贪婪式的编译模式(全量构建)是否真的契合小程序的调试模式。

于此之外,随着 WEB-IDE 的盛行,我们也琢磨着把编译流程整合到浏览器端,webpack 的厚重让可能性显得比较渺茫。

不过最最最重要的原因还是,webpack 在效率上还是显得有些局促,特别是在对比友商后,不过在这里要给 webpack 抱不平的是,webpack 的能力远大于友商的,而这种能力,就像现在的手机,它的算力是过剩的,而这种过剩的算力导致了时间的上累。

调研之路,友商带来的启发

友商在安全的防护上做的还是相当到位的,我很难通过 hack 的方式来窥视整个流程。
探究的过程中留给我最大的印象是对于克制的理解,产品层是克制的,功能是克制的,开放度是克制的。要真正在浮躁的互联网中做到还是挺难的。

接下来我们单纯从技术层面来说一说,以下都是我个人的理解,未必对。

友商的技术选型,按我的理解应该遵循四点:轻,快,可管控,够用就好。

这些方面可以体现在他们他们的任何一方面,框架、DSL、构建服务等。框架层友商相比蚂蚁,做的还是很薄的,蚂蚁背靠 react 生态,但这很有可能是把双刃剑,小程序真的 “小” 吗?我们是否做好了开放所带来的的管控问题?我觉得这些都是棘手的问题。而友商的轻薄,虽然在管控性上做的比较极致,但是面对开发者天马行空的需求,或许他们的问题更多的是如何来支持,典型的有一个案例就是 npm 支持,在蚂蚁这套技术体系下,我们是纯天然就支持现有的前端研发链路,所以在开发习惯的延续性上基本没有任何问题,但友商最初的做法是,让开发者在小程序生态外自己创建一个 npm 使用流程,这也就是为什么在社区里面为什么会有很多这类方案的原因,而后续友商发布支持 npm,但仔细琢磨,其实友商 npm 更加偏向于是 component 而不是非标准前端意义上模块,比如不支持 nodejs module shim 就可以看出。反观我们的 npm 目前已被玩坏。
另外在梳理过程中发现,友商的比如在处理 *xml 文件和 *css 文件时,它是解耦的,贪婪的。稍微深入一点就可以看到TA有专门的二进制编译工具在负责此类文件的编译,利用编译工具可以批处理诸如 *xml 和 *css 文件。这和我们之前基于 webpack 有着巨大的差异的,我们的编译本质上是有上下文的,即比如 component 样式会有作用域提升,进而影响 page,另外这当中大量依赖了 webpack 的语法分析 和 loader 机制,同此同时我们还依赖了一个让不属于构建但却又能影响构建的外部过程,所以从根本上我们在现有的技术架构上很难从真正意义上超越友商。
另外友商在框架层应该就考虑了模块加载,但我们在这一层依赖 webpack 提供的 runtime,所以友商对于模块的加载模式优化或者内部模块间的管控,比如控制 exports 有着更加灵活的空间,正因为这种模式,如果友商想要往比如 web-ide 靠其实有着更高的可行性。对于这一层的认知主要是我接触到了 Stackblitz,以及之后慢慢了解到了 Systemjs。这部分内容我放到下节。

通过友商的学习,更多的是让我感受到了小而美的力量,没有厚重的堆积感,确实能称得上 “小程序”。

CodeSandbox、Stackblitz 带来的启发

对于我来说,我的命题就是尽可能的让开发者以最快的速度来完成开发环境的初始化。我做过非常多的基于 webpack 的尝试,熟人常知的 happypack,cache-loader,thread-loader,hardsource,以及如何尽可能的让缓存增加有效性,以及甚至基于 webpack 的 memory fs hack 了一套自己的逻辑等等等等,但效果并不让人满意,甚至很多优化反而会导致很奇怪的问题。

很神奇有一次无意看到了友商在 worker 端代码的加载过程,可以看到的是 Ta 并没有真正意义上的 bundle 过程,对所有的 worker 进行了全量的 http 请求,我的第一个反应是 http 同源策略为什么他们还敢这样做,难道并发量不会成为瓶颈吗,这种方式我见不到对于webpack 的优势到底是什么?!很长时间带着这样的困惑,但这时 Stackblitz 走入到了我的眼前,最初就是他那篇 turboCDN 文章。我基本上挖坟了所有有关 Stackblitz 的 Issue Twitter。实际上 CodeSandbox 在这一块上也用着类似的方案,但 CodeSandbox 的问题是他在这块的实现在那会儿和其业务实现耦合非常深,所以我更加倾向的投向了 systemjs,至少它是独立的,且有自己的生态。随着更多的深入了解渐渐的我对他如何在浏览器内实现伪 bundle 和 npm 如何跑在浏览器端有了一定的了解。当时我的最大想法就是,我可以基于 systemjs 来实现一套动态和按需的加载方案,在本地开发阶段省去所有 bundle 过程,文件只有在真正用到时再进行编译,甚至通过一些方式比如在浏览器端实现 fs 那么这套方案就可以被移植到 web-ide 上。基于这样的构想,我开始了很长时间的尝试,从 18 年 10 月初我写下了第一行代码,代号被取为 Gravity,更多 Gravity 设计的内容我会放在下一段 。但在浏览器端实现 fs 和 利用 web worker 来实现 compile 上我碰到了很多壁,因为浏览器端没有 nodejs 环境,我要解决所有的 pollyfill 的问题,以及 文件 resolve 的差异性。然而时间上并不允许我做过多的技术性探究,所以在第一阶段我退而求其次,把这部分内容架设在了本地,通过启动一个 koajs 实例来解决,即浏览器端发生资源请求时,通过中间件(所处 nodejs 环境)来实现真正的编译过程。但是仅仅只是这一步尝试我把原先在 mac 上需要花 40s+ 的应用降到了 8s 左右,说实话我自己都没法相信。

通过学习 CodeSandbox、Stackblitz 带来的启发是利用纯浏览器带来的架构上的变更或许是另外一种出路。

再来谈谈 bundler 历史

最近有一篇文章出现在社区,题目是:A Future Without Webpack。如文中所说在过去的几年中 JavaScript 打包过程,一个原先仅仅面向于生产环境优化到现在成为 web 应用开发必要的一个步骤。不管你是喜欢也好,厌恶也罢,都很难否定一个事实,那就是它增加了 web 应用开发大量新的复杂度。而 web 领域一直来它所引以为傲的的点是,view-source 和 easy-to-get-started。这或许现在成为了一种讽刺。
为什么我们需要 bundler?
把时间倒退到六年前,那会儿我们对于打包的概念应该还是在 grunt 或 gulp 流式的任务处理,对资源文件的处理也仅仅是压缩和拼接。而后前端界兴起了模块化浪潮,模块化后的代码放在哪儿,又如何被引入相信这个问题是那会儿前端们最为关注的事情,所以又要翻翻老账 - seajs 有自己的源服务器来承载模块化后的模块也是非常自然的事情。但大家也都知道,世界上最大的代码源服务是 NPM,但如上文中提到的起始时 NPM = “Node.js Package Manager”,它并不真正意义上服务于前端浏览器。但是开发者对这一块的诉求实在是太大了,但 Node.js 众所周知使用的是 CJS 模块规范,所以不经过打包根本不可能运行于浏览器中,而诸多的模块定义,也给了像 Browserify, Webpack 空间,特别是 Webpack universal 的概念非常棒的契合了大家的诉求。

当然作者观点更多是当下已然是 2019 了,我们应该往前看,因为在浏览器端已经支持了 ESM。对我而言我觉得这种想要跳出困局寻求突破的精神是更加值得学习的。另外也抛给我一个问题,撇开作者的提供的思路或实现,是否本地 bundle 基于现有的技术架构,能否有所破局。

来谈谈新技术方案 Gravity

在谈 Gravity 前,还要来回首下我在几年前写的一篇文章 - 支付宝前端构建工具的发展和未来的选择,那会儿我们最大的困扰是配置带来的不可控性。所以那会儿我提出了构建因子以及 preset 的想法。基于对配置的敬畏所以在 Gravity 中我把这一套想法完全实现了出来(其实在那篇文章几个月后就有一版实现,但不幸的是没有继续深入流产了,另外也因为我工作内容被被动调整了)。

另外对于我看到了市面上各个公司都想往小程序上走,大家在小程序架构上都是大同小异,是不是有一种可能性能做一套构建底层来适配所有的小程序业务。这是我做 Gravity 的第二个念想。

所以 Gravity 的最 base 的架构思路是让 Gravity 变成构建工具的工厂,让各种业务形态的小程序构建变成 Gravity 的一种上层实现。要实现如上这个想法也就意味着,Gravity 必须要有好的插件机制。这个时候 tapable 自然而然成为了我的最佳选择,对于 tapable 渊源还要从我解析 webpack watch 实现说起,当然这不是我们今天的重点。重点是 webpack 就是基于 tapable 实现出来的,它的灵活性健壮性毋庸置疑,另外我彻彻底底研究过 tapable,真的是喜欢到不行。还有非常重要的一点是,我用 tapable 来设计插件机制,可以对开发者非常友好,因为基本没有学习的过程。

另外还有一个好处是基于 tapable 我可以非常轻松去实现一种时序,比如说我现在要实现一个 css 文件的加载 loader,在上文中我大概说了因为在时间上的原因我并未在一期就尝试把所有流程都丢到浏览器内完成,而是把一部分工作丢给了 koa 的中间件,所以在文件处理上(webpack 中叫 loader),我实现了一套动态生成中间件的方案,原因在于实现一个 css 文件的加载可能需要经过多个加载器,比如 post-css,css,style,这其中就有时序的问题,所以借由 tapable 我可以很方便来根据描述文件(类 webpack rules 设计)动态创建一个时序转而变成一个中间件。

在设计 gravity loader 时,和 CodeSandbox 一样,我们把 API 尽可能的往 webpack 靠了。原因就在于我想要有复用 webpack loader 的可能性。另外对于上层实现,也会更加友好,因为基本可以做到和 webpack 长得一样,用的一样。

另外还有很多细节我这里就不在阐述了。

Gravity 会进一步维护,也会在合适的时候开源。它的目标也非常明确,成为真正意义上浏览器端方案,在上层实现层可以对接到更多的业务场景。最终通过一个 web-ide 把所有的事情都串联起来。

总结

抛弃偏见,我相信 Cloud IDE 一定是未来,而面向 web 的架构一定是当务之急。

@okoala
Copy link

okoala commented Apr 11, 2019

画几张图出来就舒服了~

@leviding
Copy link

leviding commented Jul 31, 2019

@pigcan 赞 👍

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

No branches or pull requests

3 participants