谈谈国内前端的三大怪啖
因为工作的原因,我和一些国外的工程师们有些交流。他们对于国内环境不了解,有时候会问出一些有趣的问题,大概是这些问题的启发,让我反复在思考一些更为深入的问题。
今天聊三个事情:
- 小程序
- 微前端
- 模块加载
小程序
每个行业都有一把银座,当坐上那把银座时,做什么便都是对的。
“ 我们为什么需要小程序?”
第一次被问到这个问题,是因为一个法国的同事。他被派去做一个移动端业务,刚好那个业务是采用小程序在做。于是一个法国小哥就在被痛苦的中文文档和黑盒逻辑中来回折磨着 。
于是,当我们在有一次交流中,他问出了我这个问题: 我们为什么需要小程序?
说实话,我试图解释了 19 年国内的现状,以及微信小程序推出时候所带来的便利和体验等等。总之,在我看来并不够深刻的见解。
即便到现在为止,每次当我使用小程序的时候,依旧会复现这个问题。在 ChatGPT 11 月份出来的时候,我也问了它这个很有国内特色的问题:
看起来它回答的还算不错,至少我想如果它来糊弄那些老外,应该会比我做的更好些。
但如果扪心自问,单从技术上来讲。以上这些事情,一定是有其他方案能解决的。
所以从某种程度上来看,这更像是一场截胡的商业案例:
应用市场
全世界的互联网人都知道应用市场是非常有价值的事情,可以说操作系统最值钱的部分就在于他们构建了自己的应用市场。
只要应用在这里注册发行,雁过拔毛,这家公司就是互联网世界里的统治阶级,规则的制定者。
反之则需要受制于人,APP 做的再大,也只是应用市场里的一个应用,做的好与坏还得让应用商店的评判。
另外,不得不承认的是,一个庞大如苹果谷歌这样的公司,他们的应用商店对于普通国内开发者来说,确实是有门槛的。
在国内海量的 APP 需求来临之前,能否提供一个更低成本的解决方案,来消化这些公司的投资?
毕竟不是所有的小企业都需要 APP,其实他们大部分需求 Web 就可以解决,但是 Web 没牌面啊,做 Web 还得砸搜索的钱才有流量。(某度搜索又做成那样…)
那做操作系统?太不容易,那么多人都溺死在水里了,这水太深。
那有没有一种办法可以既能构建生态,又有 APP 的心智,还能给入驻企业提供流量?
于是,在 19 年夏天,深圳滨海大厦下的软件展业基地里,每天都在轮番播放着,做 XX小程序,拥抱下一个风口…
全新体验心智
小程序用起来挺方便的。
你有没有想过,这些美妙感觉的具体都来自哪些?以及这些真的是 Web 技术本身无法提供的吗?
- 靠谱感,每个小程序都有约束和规范,于是你可以将他们整整齐齐的陈放在你的列表里,仿佛你已经拥有了这一个个精心雕琢的作品,相对于一条条记不住的网页地址和鱼龙混杂的网页内容来说,这让你觉得小程序更加的有分量和靠谱。
- 安全感,沉浸式的头部,没有一闪而过的加载条,这一切无打扰的设计,都让你觉得这是一个在你本地的 APP,而不是随时可能丢失网页。你不会因为网速白屏而感到焦虑,尽管网络差的时候,你的 KFC 依旧下不了单
- 沉浸感,我不知道是否打开网页时顶部黑黑的状态栏是故意留下的,还是不小心的… 这些限制都让用户非常强烈的意识到这是一个网页而不是 APP,而小程序中虽然上面也存在一个空间的空白,但是却可以被更加沉浸的主题色和氛围图替代。网页这个需求做不了?我不信。
H5 | 小程序 |
---|---|
- 顺滑感,得益于 Native 的容器实现,小程序在所有的视图切换时,都可以表现出于原生应用一样的顺滑感。其实这个问题才是在很多 Hybrid 应用中,主要想借助客户端来处理和解决的问题。类似容器预开、容器切换等技术是可以解决相关问题的,只是还没有一个标准。
我这里没有提性能,说实话我不认为性能在小程序中是有优势的(Native 调用除外,如地图等,不是一个讨论范畴)。作为普通用户,我们感受到的只是离线加载下带来的顺滑而已。
而上述提到的许多优势,这对于一个高品质的 Web 应用来说是可以做得到的,但注意这里是高品质的 Web 应用。而这种“高品质”在小程序这里,只是入驻门槛而已。
心智,这个词,听起来很黑话,但却很恰当。当小程序通过长期这样的筛选,所沉淀出来一批这样品质的应用时。就会让每个用户即便在还没打开一个新的小程序之前,也有不错体验的心理预期。这就是心智,一种感觉跟 Web 不一样,跟 APP 有点像的心智。
打造心智,这件事情好像就是国内互联网企业最擅长做的事情,总是能从一些细微的差别中开辟一条独立的领域,然后不断强化灌输本来就不大的差异,等流量起来再去捞钱变现。
我总是感觉现在的互利网充斥着如何赚钱的想法,好像永远赚不够。“赚钱”这个事情,在这些公司眼里就是圈人圈地抢资源,看看谁先占得先机,别人有的我也得有,这好像是最重要的事情。
很少有企业在思考如何创造些没有的市场,创造些真正对技术发展有贡献,对社会发展有推动作用的事情。所以在国内互联网圈也充斥着一种奇怪的价值观,有技术的不一定比赚过钱的受待见。
管你是 PHP 还是 GO,管你是在做游戏还是直播带货,只要赚到钱就是高人。并且端的是理所应当、理直气壮,有些老板甚至把拍摄满屋子的程序员为自己打工作为一种乐趣。什么情怀、什么优雅、什么愿景,人生就俩字:搞钱。
不是故意高雅,赚钱这件事情本身不寒碜,只是在已经赚到盆满钵满、一家独大的时候还在只是想着赚更多的钱,好像赚钱的目的就是为了赚钱一样,这就有点不合适。企业到一定程度是要有社会责任的,龙头企业每一个决定和举措,都有会影响接下来的几年里这个行业的价值观走向。
当然也不是完全没有好格局的企业,我非常珍惜每一个值得尊重的国内企业,来自一个蔚来车主。
小程序在商业上固然是成功的,但吃的红利可以说还是来自 网页 到 应用 的心智变革。将本来流向 APP 的红利,截在了小程序生态里。
但对于技术生态的发展却是带来了无数条新的岔路,小程序的玩法就决定了它必须生长在某个巨型应用里面,不论是用户数据获取、还是 API 的调用,其实都是取决于应用容器的标准规范。
不同公司和应用之间则必然会产生差异,并且这种差异是墒增式的差异,只会随着时间的推移越变越大。如果每个企业都只关注到自己业务的增长,无外部约束的话,企业必然会根据自己的业务发展和政策需要,选择成本较低的调整 API,甚至会故意制造一些壁垒来增加这种差异。
小程序,应该是 浏览器 与 操作系统 的融合,这本应该是推动这两项技术操刀解决的事情。
微前端
qiankun、wujie、single-spa 是近两年火遍前端的技术方案,同样一个问题:我们为什么需要微前端?
我不确定是否每个在使用这项技术的前端都想清楚了这个问题,但至少在我面试过的候选人中,我很少遇到对自己项目中已经在使用的微前端,有很深的思考和理解的人。
先说下我的看法:
- 微前端,重在解决项目管理而不在用户体验。
- 微前端,解决不了该优化和需要规范的问题。
- 微前端,在挽救没想清楚 MPA 的 SPA 项目。
没有万能银弹
银色子弹(英文:Silver Bullet),或者称“银弹”“银质子弹”,指由纯银质或镀银的子弹。在欧洲民间传说及19世纪以来哥特小说风潮的影响下,银色子弹往往被描绘成具有驱魔功效的武器,是针对狼人、吸血鬼等超自然怪物的特效武器。后来也被比喻为具有极端有效性的解决方法,作为杀手锏、最强杀招、王牌等的代称。
所有技术的发展都是建立在前一项技术的基础之上,但技术依赖的选择过程中一定需要保留第一性原理的意识。
当 React、Vue 兴起,当 打包技术(Webpack) 兴起,当 网页应用(SPA) 兴起,这些杰出的技术突破都在不同场景和领域中给行业提供了新的思路、新的方案。
不知从何时开始,前端除了 div 竟说不出其他的标签(还有说 View 的),项目中再也不会考虑给一个通用的 class 解决通用样式问题。
不知从何时开始,有没有权限需要等到 API 请求过后才知道,没有权限的话再把页面跳转过去申请。
不知从何时开始,大家的页面都放在了一个项目里,两个这样的巨石应用融合竟然变成了一件困难的事。
上面这些不合理的现状,都是在不同的场景下,不思考适不适合,单一信奉 “一招吃遍天” 下演化出的问题。
B 端应用,是否应该使用 SPA? 这其实是一个需要思考的问题。
微前端从某种程度上来讲,是认准 SPA 了必须是互联网下一代应用标准的产物,好像有了 SPA 以后,MPA 就变得一文不值。甭管页面是移动端的还是PC的;甭管页面是面对 C 端的还是 B 端的;甭管一个系统是有 20 个页面还是 200 个页面,一律行这套逻辑。
SPA 不是万能银弹,React 不是万能银弹,Tailwind 不是万能银弹。在新技术出现的时候,保持热情也要保持克制。
ps. 我也十分痛恨 React 带来的这种垄断式的生态,一个 React 组件将 HTML 和 Style 都吃进去,让你即使在做一个移动端的纯展示页面时,也需要背上这些称重的负担。
质疑 “墨守成规”,打开视野,深度把玩,理性消费。
分而治之
分治法,一个很基本的工程思维。
在我看来在一个正常商业迭代项目中的主要维护者,最好不要超过 3 个人,注意是主要维护者(Maintainer) 。
你应该让每个项目都有清晰的责任人,而不是某行代码,某个模块。责任人的理解是有归属感,有边界感的那种,不是口头意义上的责任人。(一些公司喜欢搞这种虚头巴脑的事情,什么连坐…)
我想大部分想引入微前端的需求都是类似 如何更好的划分项目边界,同时保留更好的团队协同。
比如 导航菜单 应该是独立收口独立管理的,并且当其更新时,应该同时应用于所有页面中。类似的还有 环境变量、时区、主题、监控及埋点。微前端将这些归纳在主应用中。
而具体的页面内容,则由对应的业务进行开发子应用,最后想办法将路由关系注册进主应用即可。
当然这样纯前端的应用切换,还会出现不同应用之间的全局变量差异、样式污染等问题,需要提供完善的沙箱容器、隔离环境、应用之间通信等一系列问题,这里不展开。
当微前端做到这一部分的时候,我不禁在想,这好像是在用 JavaScript 实现一个浏览器的运行容器。这种本应该浏览器本身该做的事情,难道 JS 可以做的更好?
只是做到更好的项目拆分,组织协同的话,引入后端服务,由后端管控路由表和页面规则,将页面直接做成 MPA,这个方案或许并不比引入微前端成本高多少。
体验差异
从 SPA 再回 MPA,说了半天不又回去了么。
所以不防想想:在 B端 业务中使用 SPA 的优势在哪里?
流畅的用户体验:
这个话题其实涵盖范围很广,对于 SPA 能带来的 “流畅体验”,对于大多数情况下是指: 导航菜单不变,内容变化 发生变化,页面切换中间不出现白屏。
但要做到这个点,其实对于 MPA 其实并没有那么困难,你只需要保证你的 FCP 在 500ms 以内就行。
以上的页面切换全部都是 MPA 的多页面切换,我们只是简单做了导航菜单的 拆分 和 SWR,并没有什么特殊的 preload、prefetch 处理,就得到了这样的效果。
因为浏览器本身在页面切换时会在 unload 之前先 hold 当前页面的视图不变,发起一下一个 document 的请求,当页面的视图渲染做到足够快和界面结构稳定就可以得到这样的效果。
这项浏览器的优化手段我找了很久,想找一篇关于它的博文介绍,但确实没找到相关内容,所以 500ms 也是我的一个大致测算,如果你知道相关的内容,可以在评论区补充,不胜感激。
所以从这个角度来看,浏览器本身就在尽最大的努力做这些优化,并且他们的优化会更底层、更有效的。
离线访问 (PWA)
SPA 确实会有更好的 PWA 组织能力,一个完整的 SPA 应用甚至可以只针对编译层做改动就可以支持 PWA 能力。
但如果看微前端下的 SPA 应用,需要支持 PWA 那就同样需要分析各子应用之间的元数据,定制 Service Worker。这种组织关系和定制 SW,对于元数据对于数据是来自前端还是后端,并不在意。
也就是说微前端模式下的 PWA,同样的投入成本,把页面都管理在后端服务中的 MPA 应用也是可以做到相同效果的。
项目协同、代码复用
有人说 SPA 项目下,项目中的组件、代码片段是可以相互之间复用的,在 MPA 下就相对麻烦。
这其实涉及到项目划分的领域,还是要看具体的需求也业务复杂度来定。如果说整个系统就是二三十个页面,这做成 SPA 使用前端接管路由高效简单,无可厚非。
但如果你本身在面对的是一个服务于复杂业务的 B 端系统,比如类似 阿里云、教务系统、ERP 系统或者一些大型内部系统,这种往往需要多团队合作开发。这种时候就需要跟完善的项目划分、组织协同和系统整合的方案。
这个时候 SPA 所体现出的优势在这样的诉求下就会相对较弱,在同等投入的情况下 MPA 的方案反而会有更少的执行成本。
也不是所有项目一开始就会想的那么清楚,或许一开始的时候就是个简单的 SPA 项目,但是随着项目的不断迭代,才变成了一个个复杂的巨石应用,现在如果再拆出来也会有许多迁移成本。引入微前端,则可以…
这大概是许多微前端项目启动的背景介绍,我想说的是: 对于屎山,我从来不信奉“四两拨千斤”。
如果没有想好当下的核心问题,就引入新的“银弹”解决问题,只会是屎山雕花。
项目协同,抽象和复用这些本身不是微前端该解决的问题,这是综合因素影响下的历史背景问题。也是需要一个个资深工程师来把控和解决的核心问题,就是需要面对不同的场景给出不同的治理方案。
这个道理跟防沙治沙一样,哪有那么多一蹴而就、立竿见影的好事。
模块加载
模块加载这件事情,从玉伯大佬的成名作 sea.js 开始就是一个非常值得探讨的问题。在当时 jQuery 的时代里,这是一个绝对超前的项目,我也在实际业务中体会过在无编译的环境下 sea.js 的便捷。
实际上,不论是微前端、低代码、会场搭建等热门话题离不开这项技术基础。
import * from *
我们每天都在用,但最终的产物往往是一个自运行的 JS Bundle,这来自于 Webpack、Vite 等编译技术的发展。让我们可以更好的组织项目结构,以构建更复杂的前端应用。
模块的概念用久了,就会自然而然的在遇到浏览器环境中,遇到动态模块加载的需求时,想到这种类似模块加载的能力。
比如在遇到会场千奇百怪的个性化营销需求时,能否将模块的 Props 开放出来,给到非技术人员,以更加灵活的方式让他们去做自由组合。
比如在低代码平台中,让开发者自定义扩展组件,动态的将开发好的组件注册进低代码平台中,以支持更加个性的需求。
在万物皆组件的思想影响下,把一整个完整页面都看做一个组件也不是不可以。于是在一些团队中,甚至提倡所有页面都可以搭建、搭建不了的就做一个大的页面组件。这样及可以减少维护运行时的成本,又可以统一管控和优化,岂不美哉。
当这样大一统的“天才方案”逐渐发展成为标准后,也一定会出现一些特殊场景无法使用,但没关系,这些天才设计者肯定会提供一种更加天才的扩展方案出来。比如插件,比如扩展,比如 IF ELSE。再后来,就会有性能优化了,而优化的 追赶对象 就是用原来那种简单直出的方案。
有没有发现,这好像是在轮回式的做着自己出的数学题,一道接一道,仿佛将 1 + 1的结论重新演化了一遍。
题外话,我曾经自己实现过一套通过 JSON Schema 描述 React 结构的 “库” ,用于一个低代码核心层的渲染。在我的实现过程中,我越发觉得我在做一件把 JSX 翻译成 JS 的事情,但 JSX 或者 HTML 本身不就是一种 DSL 么。为什么一定要把它翻译成 JSON 来传输呢?或者说这样的封装其本身有意义么?这不就是在做 PHP、ASP 直接返回带有数据的 HTML Ajax 一样的事情么。
传统的浏览器运行环境下要实现一个模块加载器,无非是在全局挂载一个注册器,通过 Script 插入一段新的 JS,该 JS 通过特殊的头尾协议,将运行时的代码声明成一个函数,注册进事先挂载好的注册器。
但实际的实现场景往往要比这复杂的多,也有一些问题是这种非原生方式无法攻克的问题。比如全局注册器的命名冲突;同模块不同版本的加载冲突;并发加载下的时序问题;多次模块加载的缓存问题 等等等等等…
到最后发现,这些事情好像又是在用 JS 做浏览器该做的事情。然而浏览器果然就做了, <script type="module"></script>
,Vite 就主要采用这种模式实现了它 1 年之内让各大知名项目切换到 Vite 的壮举。
“但我们用不了,有兼容性问题。”
哇哦,当我看着大家随意写出的 display: grid
样式定义,不禁再次感叹人们对未知的恐惧。
import.meta
的兼容性是另外一个版本,是需要 iOS12 以上,详情参考: https://caniuse.com/?search=import.meta
试想一下,现在的低代码、会场搭建等等各类场景的模块加载部分,如果都直接采用 ESM 的形式处理,这对于整个前端生态和开发体验来说会有多么大的提升。
模块加载,时至今日,本来就已经不再需要 loader。 正如 seajs 中写的: 前端模块化开发那点历史
历史不是过去,历史正在上演。随着 W3C 等规范、以及浏览器的飞速发展,前端的模块化开发会逐步成为基础设施。一切终究都会成为历史,未来会更好。
结语
文章的结尾,我想感叹另外一件事,国人为什么一定要有自己的操作系统?为什么一定需要参与到一些规范的制定中?
因为我们的智慧需要有开花的土壤,国内这千千万开发者的抱负需要有地方释放。
如果没有自己掌握核心技术,就是只能在问题出现的时候用另类的方式来解决。最后在一番折腾后,发现更底层的技术只要稍稍一改就可以实现的更好。这就像三体中提到的 “智子” 一样,不断在影响着我们前进的动力和方向。
不论是小程序、微前端还是模块加载。试想一下,如果我们有自己的互联网底蕴,能决定或者影响操作系统和浏览器的底层能力。这些 “怪啖” 要么不会出现,要么就是人类的科技创新。
希望未来技术人不要再去追逐 Write Once, Run Everywhere 的事情…