对于前端构建的一点思考

对于构建,相信对于每个前端都不是一件陌生的事情。

2021典型的web前端场景:前端在根据ts/jsx/esm这些规范写完代码之后,然后开始构建产生bundle,这些bundle里ts/jsx/esm都被转换成了兼容低版本浏览器的代码(表现和规范一样,但是实现完全不同), 然后把这份bundle上传到cdn。之后,用户根据对外域名,通过nginx转发访问到这些静态资源。

无论是开发时webpack让人感觉浪费生命的hmr,还是构建过程完全可以去喝一杯咖啡/上个厕所滑个水的时长,都让人不由产生一点疑惑:前端构建真的是必须的吗?我们的三剑客完全可以直接跑在浏览器上呀?

少年太久不思考人生就会变成油腻的中年人,程序员太久不思考现状就会变成用手指写代码的真.码农,所以对构建这件事,做一点思考,让自己少点油腻,远离机械劳动。

历史演进

历史从不重复,但他押韵。

由史可以知兴亡,要知道为什么前端构建为什么会是现在的样子,就不得不去探究更久远的年代发生了什么。

创世纪

1993年,NCSA Mosaic的问世开启了浏览器的时代。

那时候的页面非常简单,仅仅使用html/css/js去完成,没有任何构建的步骤,就像现在lua之于游戏引擎,在主要是展示的页面上,操作有限的浏览器对象去完成页面的有限交互。

对于很多前端原教旨主义者 (https://12ft.io/api/proxy?q=https://unixsheikh.com/articles/so-called-modern-web-developers-are-the-culprits.html)而言,这是最完美的形态:

  1. 无构建步骤,没有任何开发等待时间
  2. 所写即所运行,没有任何中间步骤,方便调试
  3. 基于静态文件,上手开发极快

我在写自己的博客网站(https://bert0324.github.io/)的过程中,一开始就是这么做的,非常的舒服,轻/简单/部署方便(配合github).

但是,随着文章数的变多,对博客功能的要求变多,我遇到了一些困扰:

  1. 难以复用代码。基于dom去制作组件,代码复用非常麻烦
  2. 新语法支持。比如optional chaining/decorator之类仍在proposal/stage的语法,难以马上使用
  3. 代码组织问题。基于html/js去组织代码,很容易让代码变得非常难维护

出埃及记

之后随着IE, Firefox, Opera,Safari,Chrome的问世,浏览器开始变得前所未有的普及。当然,因为浏览器变多,标准不统一,各个厂商的浏览器有细微的不同,让兼容性这个问题第一次摆在了网页制作者的面前。

2005年,jquery出现了,目的是为了简化js操作/兼容浏览器。

他让原来单纯的网页开发出现了很重要的变化:

  • 大部分的网页开发需要先声明一些依赖了

对于依赖的普遍使用,让原本单纯的网页开发变得有点复杂起来了。即使到今天,用标签的方式引入react/jquery/lodash仍然是一个非常常见的操作。

对于依赖的使用者而言比较简单,引入一个script标签即可。但是对于库的开发者来说,基于动态引入依赖方案的几个问题马上就摆在眼前:

  1. 动态引入的依赖体积越小越好,怎么让源码开发的js文件更小呢?

  2. 开发时可能会有多个js文件做模块拆分,最后怎么合并成一个mjs文件呢?

这时候有一些基于其他语言的工具链,比如c++/python,但是非常的小众,仅仅一部分的库开发者在有限的使用,但对于前端构建而言,已经是海中的那一条道了。

所罗门的圣殿

2009年对于js世界来说,发生了两件重要的事情:

  1. nodejs/npm诞生了

  2. AngularJS诞生了

nodejs的意义不仅在于后端开发,更重要的是让前端自己创建工具链的难度/要求大大的降低了,同时引入了npm这种中心化的包管理平台,让前端工程化有了物质基础。

AngularJS这种基于模版/vdom的框架的诞生,不单单让前端大型应用成为可能,也让打包构建成为前端必备的一步。

这时候,前端构建对于网页开发,已经是必备且重要的一步了。

随后babel, webpack的出现,让前端彻底进入了黄金时代,第一圣殿已经建成了。

巴比伦之囚

时间到现在,出现了越来越多的构建工具/方案:

  • vite:开发工具链,集成构建工具
  • rome:基于rust的开发工具链
  • esbuild:基于go的js打包工具
  • swc:基于rust的js打包工具

但是他们不像他们的前辈,马上就可以一鸣惊人,引领风潮,大部分都处在有讨论度,但是在生产环境少人问津的状态。

因为前辈们珠玉在前,功能完善齐备,使用历史悠久,该踩的坑都踩了,如果没有在某个方面百倍的提升,实在是难以说服群众脱离对于惯性的依赖。

就如同南北分裂后的巴比伦之囚们,在等待那一位弥赛亚。

未来趋势

我从不想未来,它来得太快。

虽然现在的前端构建百家争鸣,但是他们身上仍然有一些共性,可以管中窥豹,未来的一些趋势。

Bundleless

随着esm规范在浏览器的普及,bundleless的呼声/实践也越来越多,其中的代表是vite/esbuild。

不考虑浏览器兼容性,bundleless的好处可以在于:

  1. http2普及/设备性能提升的背景/前提下,跨页面缓存率的提升

  2. 开发阶段直接使用esm,仅更新修改文件带来的光速hmr

可以看到,应用bundleless在c端其实是有风险的,对于低端设备甚至会是体验负优化。这和http2这种技术普及是有本质性的区别的,如果没有降级方案,起码5年内我觉得是不太适合在生产上大规模使用的,而降级方案,又会带来额外的工程复杂度。

所以对于大公司应用bundleless在c端,个人认为是会很克制/滞后的。

但是对于未来开发工具链而言,个人觉得一个趋势是必然采用类似vite的bundleless开发+bundle生产构建的方式。

Rust-based & Unify Toolchain

可以很明显的看出,最近前端工具界对于用rust重写工具链是很有热情的. vercel的老哥甚至喊出了:

  • Rust Is The Future of JavaScript Infrastructure

可以看出来,前端是越来越卷了。除了创造新的就业机会,用rust重写带来的收益也是很明显的,理想情况下性能方面会有百倍的提升。

开发阶段的性能提升在大部分情况下其实蛮难说服大家更新工具的,甚至会感觉有点鸡肋,开发等待时间短了老子还怎么划水。

但是量变带来质变,构建速度提升会让js打包这一件事情从耗时/异步,变成快速/同步,这对于CICD/内部工具来说,有些操作会变成可能。

同时,随之出现的还有全工具链这个趋势。之前开发一个工程,大家会先eslint/prettier/stylelint/commitlint/tsconfig等等各种花活先整上,各个工具各司其职,让开发者自己去阻止起来。各大公司一般都有一套祖传开发框架,去做其中的一部分事情。

但是近期出现的vite/rome,明显比webpack的指责范围更广,他们不单单想负责构建这件事,还想做devServer/lint一条龙服务。

对于工具链而言,rust-based & unify会是一个靠谱的趋势,因为带来的提升还是比较明显的,也符合软件开发门槛越来越低的发展趋势,会缓慢但是稳定的发展下去。

Micro Frontend

微前端喊了好久, 但是相比于服务端的微服务化,无论是从普及,还是从效率的提升上,都是远远不及。现在前端微前端仍然是处于一个套个toolbar壳子的状态,可以去做工作台之类的集成,但是价值和场景也仅仅局限于此了。

前端真正的微服务化应该是怎么样的呢,我说一下我理想中的情况:

  1. 和其他前端应用有一个标准的通信机制

  2. 和其他应用有一个标准的依赖共享机制

  3. 框架无关性

现在除了第三点,1/2点都是差强人意的,1的话很好理解,如同rpc之于后端,是一个互相通信的方式,在前端甚至更好实现。2的话就是很独特的前端要解决的问题了,我个人觉得也是现在微服务一个最大的bottleneck,无法去共享依赖,让微服务很难真的去做到"微"。

如果是真的微,那么应该是去做到组件式的微服务,拼成一个前端页面就像搭建组件一样轻松自然。前端组件也像后端服务一样,严格定义io,对于团队分工/前端效率应该都会是一个很大的提升。

webpack5的Module Federation是一个很好的尝试,但是个人感觉方案不够成熟,难用且不好接入现有开发模式。

Web开发方式

有个蛮有意思的说法:

● 语言 < 库 < 框架 < 引擎

web前端其实和游戏客户端开发有点相似,只不过标准/生态更加的统一。游戏有商用的引擎,游戏开发/运营/美术都能用,还有资产中心,等同于搭建平台的开发/运营/交互,外加组件市场。

web企业级开发的终极形态,应该就是web开发引擎,组件开发可以在上面快速的开发/debug,运营配合蓝图搭建页面,交互在上面管理自己的美术资产。

End

随着操作系统和硬件的交互能力越来越完善(WebGPU/WebHID)和wasm的普及,个人感觉浏览器会越来越操作系统化,能力会越来越强,从长期来看非常看好浏览器使用场景的扩展。

随着前端场景的扩展,使用复杂度的提升,对于构建这件事的要求肯定会是越来越高的,模式/性能都会是未来发展的重点。

Reference