(閱讀筆記) 邁入現代 Web 開發(GMTC 2021 演講《字節跳動的現代 Web 開發實踐》全文)

Tags
Crypto
讀書會筆記
Date
Jan 23, 2022
 
notion image
節錄幾點讀完很有共鳴的點,以及自己的一些補充知識:
  • Processes 的無狀態特性,可以讓應用程式非常容易做水平擴展
notion image

邁入現代 Web 開發(GMTC 2021 演講《字節跳動的現代 Web 開發實踐》全文)

notion image
前言:
notion image
大家好,我是来自字节跳动 Web Infra 部门的杨扬。在开始分享前先解释下:
可以看到幻灯片上的标题,跟会议日程里的标题有些不一样,「现代 Web 开发」这几个字加上了引号,做这个修改是因为,原文很容易被断句成「现代的,Web 开发实践」,「现代」看上去只是一个普通的形容词,其实「现代 Web 开发」是作为一个整体的专有名词,来代指现在全球技术社区和全行业里,越来越重要的一个「大趋势」(Megatrend)、一场正在进行中的「范式转移」。
今天这场分享的主题,就是字节跳动如何把「现代 Web 开发」转化成具体的技术栈和研发工具体系,在内部广泛落地和从中获益。
这次分享的内容可以分成三个部分。
第一部分,先整体回顾「传统 Web 开发」范式中的「前端开发」技术和工程体系,有哪些瓶颈问题。
第二部分,对于在这些问题的背后、在这些问题的驱动下,正在发生的转变,做一下归纳和比较。
第三部分,介绍字节跳动在落地和推动这种转变中,发展和建设出的技术体系。对于字节这个「App 工厂」来说,这种发展相当于一场「引擎升级」的过程。
大家都知道字节跳动在业界有一个既含贬义也含褒义的外号,叫作「App 工厂」,如果我们从软件研发的角度来看待这个外号,那其实在字节内部,各种产品、工具、软件应用的开发,比大家从外部看到的更像「App 工厂」:无论数量还是多样性,形态和场景的丰富度,都是非常高、海量的。
而这些软件项目中,基于 Web 技术、前端技术的,占了大部分(这并不是因为字节有特殊的技术选型,而是行业的大背景和必然规律,我在19年一次关于「现代 Web 开发」的分享里有介绍过)。
由于字节有这种特点,所以前端技术和工程体系中的问题和瓶颈,在字节会体现的很全面、很典型,很多时候也会体现的更明显。
传统的前端技术体系,无论在字节内部,还是在全行业,都可以总结为图上这个样子。
图中从下到上,代表抽象层从底层到顶层。最右边三个方块,都从最下面延伸到了最上面,代表它们都是端到端的解决方案,跟左边的体系,以及彼此之间,都是割裂的,包含大量重复,这次分享因为时间关系,不讲这几个部分。
蓝色的方块都是代码层面的,绿色的方块都是平台层面的。
这套体系是字节曾经的主流,是从业务中自然发展出来的,但随着这种发展趋于停滞,这套技术栈正在同样自然的演变成历史遗留的「祖传技术栈」,其中每个部分都有比较大的瓶颈问题。
我们逐个看一下图里的每个问题域,首先是大家最熟悉的「前端脚手架」。
不管哪种形式的脚手架,本质都是复制粘贴一堆样板代码,组成新的项目。
虽然跟建筑行业使用的脚手架一样,都是在搭建过程中使用,用完就放到一边,只留下搭完的项目。但建筑脚手架拆掉之后留下的建筑,有一套不能动的钢筋混凝土骨架,而脚手架生成的前端项目,是混杂在一起的样板代码,虽然有文件结构,但可以随意修改,而且因为基建和示例代码混在一起,所以不仅是「可以改」,而且经常是「必须改」样板文件的内容和结构,才能完成真实项目的完整搭建。
假设一个脚手架包含 A、B、C 三块功能,用这个脚手架创建出的三个项目,最初都是一样的,也都包含 A、B、C 功能。
接下来,三个项目必然需要在脚手架的生成结果上,做各种增删改,可能是因为业务需求,也可能是因为技术沉淀和工程改进。时间长了之后,三个项目之间会差别非常大,如果要做统一的改进,或者把一个项目的沉淀和改进,应用到另一个项目里,都会很困难。有时甚至要推迟需求开发,先对这些项目做一轮统一的重构,但这也只能应付眼下,之后这些项目仍然会继续分裂和腐化。
另一方面,脚手架本身也在迭代改进,但因为脚手架是一次性的,一用即抛,这些改进不能对原先创建的项目带来好处,引入这些改进的成本,跟从其他项目里引入改进的成本差不多。
进一步看看脚手架中「项目模板」的问题。
脚手架的必然结果,是需要各种项目模板。不但脚手架建设者需要提供多种模板,覆盖不同的需求,使用者也经常需要复制原有模板,修改成新的模板,比如产品的技术形态是 SPA 还是 MPA,都会产生不同的模板。
图上每个方块,都代表一个真实存在的模板,可以看到这些模板中有大量重复、又不会完全相同的内容,升级维护模板、在模板之间同步技术沉淀,都有成本。很多模板会缺少更新,长期停滞,把成本留给搭建项目的人。
如果从项目场景的角度出发来设计和维护模板,也有相同的问题。图中的方块是一些最基础、最典型的场景,和场景中的技术需求,可以看到,不同场景之间的技术需求,重合度很高。
除了场景类型,一个项目还有很多类型维度,图中的每个方块,代表一种维度,比如按照图上的第三种维度,不同项目之间仅仅因为「组件库」和「设计系统」不同,就要设计和建设不同的模板。
这些维度之间的排列组合,要么会导致模板进一步分裂和数量爆炸,每种模板的维护成本更高,应用场景更小,ROI 因此变低,更加倾向于停滞;要么会导致模板对很多维度中的需求,不做考虑,只覆盖小部分需求,对项目开发的支持,局限在比较低的水平。
「传统技术栈」的第三个问题域,来自前端工程化建设中常见的对 Webpack 的「包装」。
为了避免每个前端开发者都成为「Webpack 工程师」, 很多脚手架、工程化建设,都会对 Webpack 做图上这样的包装,在最上层,提供围绕编译构建的两个命令 dev 和 build,搞出不同「规范」的配置文件和插件机制。
这种包装的第一个问题,是抽象程度很有限,配置 API 的设计也五花八门,体现不同的个人偏好和业务经验,这种配置虽然被称作「规范」,但在真实业务项目中存在感不高,业务项目还是要直接靠 Webpack 来解决很多问题,项目中包含很多 webpack 配置,脚手架模板也包含大量相关的样板代码,避免不了前面说的问题。
图中这段话来自 Redux 作者 Dan 写的一篇文章,讲 JS 工具的配置 API 的设计,这段话就是在讲这方面的抽象和设计能带来巨大的影响,有很高的门槛,需要非常严肃专业的对待,这种工作也需要高度的集中,而不是交给「Webpack 工程师」们搞各种各样的「规范」。
业务项目深度依赖 Webpack、包含很多 Webpack 配置,还带来另一个问题:
JS 开发工具从去年开始又出现新一轮更新换代的征兆,有人把这种趋势称作 「JS 的第三纪元」,新范式的项目涌现,开始进入到日常业务的开发实践,很多场景下已经没有 Webpack。
图中右侧的 esbuild 和 swc 这样的构建工具,把编译、构建、打包、压缩等在 Webpack 里属于不同环节的部分,合并在一起,加上非 JS 的系统编程语言的帮助,大幅提升构建速度。另一方面,也能支持 ESM 优先的、不需要打包的构建场景。
Snowpack、Vite 这样的工具,在此基础上实现了开发者体验(DX)优先的、不打包(Unbundled)的开发调试模式。
这些工具和模式跟 Webpack 的设计有一些本质矛盾,目前已经被用于公共库的构建、业务项目的开发调试等真实场景里。
基于 Webpack 包装的工程化建设,第三个问题是:对项目开发的工程支持只停留在比较低的水平,比如就像 dev 和 build 命令一样,局限于跟编译构建有关的环节。
而随着行业和业务的发展,随着前端技术的发展,一个前端工程的完整需求,就像图中一样,会包含蓝色方块代表的整个研发链路的每个环节,以及每个环节下面,绿色方块代表的工程需求。这些都超出了 Webpack 的能力范围。
刚才说的这个问题,其实也是传统前端工程化建设本身的问题。
传统的工程化建设,就像图中文字说的,只能实现「代码层面」的基础建设,在创建项目的时候,做的事情大部分都属于「代码初始化」。
现代的 web 工程和前端工程,越来越多的包含「代码层面」之外的「平台层面」。
图中绿色方块代表的,是靠「代码层面」来实现工程需求,橙色方块代表的,是要靠「平台层面」才能更好实现的需求。
第五个问题域是,很多前端开发场景,比如像字节内部的前端开发,都在统一收敛到 React 技术栈,但 React 本身也是有局限的。
在 Web Infra 部门建立之前,字节内部的业务方向,比如今日头条和抖音,就已经在推进统一到 React 技术栈,目前字节的现代 Web 研发体系建设,也有意收敛和围绕着 React 来进行。
选择 React 的原因可以归纳为图上这四个,其中,符合技术趋势,设计演进更快,走在最前面,这两点让 React 在基础建设中,在有基础建设团队支持的业务场景中,都具备很大的优势。
图上高亮的这句话,很好的表达了 React 引领业界和社区发展的特点。
刚才在选择 React 的原因里,还提到 React 庞大的生态红利或验证规模。图中可以看到,在比较贴近实际使用情况的依赖下载数据里,React 在绝对数量遥遥领先的情况下,增长势头也是更快的,React 生态下的 CRA、Next.js,单独拿出来都达到或接近其他非 React 技术的使用量。
这种生态和规模上的差距,在国内环境中也回避不了,国内 JS 开发者的数量差不多是全球的十分之一,所以单靠国内开发者,影响不了整个行业和技术社区的生态和基建,导致在业务支持和工程建设中,React 目前都无法取代。
但在工程体系中只靠 React 自己是远远不够的,React 本身只解决视图层的问题,距离一个 Web 框架还缺很多东西,在框架层面上,React 是无偏见的,比如没有限制路由实现、组件类型、SSR 解决方案等,也没提供默认的配置和工具体系,跟一个真正框架的必备要素,是完全相反的。
由于 Webpack、React 都不解决全链路的问题,缺乏框架级别的解决方案,很多前端项目会把目光转向发展时间更长、已经形成框架级别基建的服务器端框架领域,但这方面的 Node.js 框架,也有瓶颈问题。
业界主流的 Node.js 框架 NestJS 的作者,在文档首页的设计理念部分,就指出 React、Vue、各种开发工具,都没有解决「应用架构」的问题,而 NestJS 就是要提供开箱即用的应用架构。
传统的前端开发局限于视图层,跟完整的软件开发、产品开发相比,最缺的就是「应用架构」。
但是 Node.js 框架能提供的,只是「服务器端应用架构」,是以服务器端开发为中心的。
有些情况下,比如活动页面,客户端部分本来就很薄,谈不上「客户端应用架构」,但这种情况下的业务关注点仍然在客户端部分,如果基于有完整服务器端应用架构的 Node.js 框架去开发这种项目,对前端开发者来说有些偏离重点。
在另一些情况下,客户端部分比较厚,这种情况下需要的「客户端应用架构」,就像图中右边的蓝色部分,跟左边代表「服务器端应用架构」的橙色部分,是完全不同的,如果使用 Node.js 框架,蓝色部分仍然需要自己摸索和搭建。
不管什么软件架构,核心都是「分层」和「关注点分离」,完善的现代 Web 工程里,「服务器端应用架构」和「客户端应用架构」不会割裂,而是混为一体的。
除了架构,Node.js 框架也解决不了前面说的其他问题,同时还引入了图中右边的新问题:
业务逻辑既分散割裂,同时又重复,导致项目变得「低内聚高耦合」。
在「前后端分离」之后从「后端项目」里独立出来的「前端项目」,使用 Node.js 框架之后,又混入了很重的后端业务逻辑和研发需求。而 Node.js 框架随着发展,本质也越来越清楚:还是服务于专业服务器端开发的,降低不了前端开发者的服务器端开发门槛,这是一个最大的瓶颈。
从这张图可以看到,包含服务器端部分的「前端项目」,并不是真正的「全栈」,只是包含了服务器端最上面的、很薄的一层,解决 Web 和 BFF 需求(注意这里说的 Web 是指处理 HTML 请求,而 BFF 这个词在国内有很多误用,实际上是专指面向特定 UI 的 API 服务)
对于前端项目里的这些服务器端需求,用 Node.js 框架来开发,很多时候带来的问题比解决的问题更多。
最后,看下最底层的 IaaS 和 PaaS 部分。
传统云计算中的 IaaS 和 PaaS,就像图中的绿色部分,从最底层到最上层,有这样几层,最上面这层,有不同的平台和服务形态,传统上,前端项目跟后端项目一样,直接部署这一层上,比如字节内部后端技术栈的项目,都部署在被称作 TCE 的 PaaS 上,而前端技术栈的项目,以前也只能这样部署。
但是在这种 PaaS 上或直接在 IaaS 上做部署和运维,对前端开发者来说,不但复杂低效,而且在很多前端部署需求上,没有获得任何支持。
比如图中粉色部分中这些前端部署方面的通用需求。由于 IaaS 和 PaaS 都源自后端开发场景,是后端研发技术的平台化沉淀,设计和演进都天然的受到局限,对前端研发场景缺乏理解,就算理解,也无法在相同的基础设施里去兼顾这些前端部署需求。
也就是说,多数前端项目和底层的 IaaS 和 PaaS 之间,存在一大片空白,也就是图中粉色的区域。
在真实业务中,为了避免每个前端项目都直接跟 IaaS 和 PaaS 打交道,很多业务都会有意无意的做一些集中建设,填补这片空白。
比如字节的一些 web app 项目,会由后端负责的 go server 来运行。
很多有 SSR 需求的业务,会搞一个统一的 SSR server 项目,在开发各种前端仓库的时候,需要本地有 SSR server 的仓库,才能运行和调试这些前端项目。
像这样的集中建设,要么把前端项目中的正常组成部分,放到了前端开发者无法掌控的地方;要么反复重复的建设出被业务需求扭曲的部署方案,难以沉淀和演进,最大化的提效。
所以图中粉色这片区域的空缺,该由什么来填补,是传统前端技术栈里的一个大问题。
回顾了「祖传技术栈」的这些问题,接下来我们看下在这些问题的驱动下,业界和技术社区里已经形成的趋势,这些趋势带来的发展和优势,也反过来,让前面说的这些问题变得更明显,更急需解决。
这种趋势可以归纳为「传统 Web 开发」范式到「现代 Web 开发」范式的转变。我在图中这两次技术活动上,分别介绍过「现代 Web 开发」范式的起源、背景和发展状态,这里不再重复了。
这种转变,可以称得上是一场「范式转移」,原有的理解和习以为常的假设被打破,在原有的东西上小修小补解决不了问题,需要建一套新的东西,做出根本性的变化。
图中左边蓝色部分是最能代表「传统 Web 开发」的一些技术栈和规范,右边橙色部分是在「现代 Web 开发」趋势下出现的技术栈和技术形态,接下来把它们展开看看包含哪些要素,就会看到左边的东西跟右边的东西,差别非常大。
Ruby on Rails 是「传统 Web 开发」范式的一个典型代表, 本质上是以服务器端开发为中心的、MVC 架构的「服务器端 Web 框架」,就像图上右边这样,产出网页和 API。
网页部分需要的前端代码和工程化,一般包含在同一个仓库里,框架本身也会内置一些前端相关的工程化,经常会需要被专业全面的前端工程化替代掉。
「十二要素应用宣言」本质上是一种工程标准,是进入云计算时代之后,服务器端应用和工程项目中形成的规范,这套标准可以保证应用能很好的运行在「后端 PaaS」或其他的传统云计算平台上。
可以看到,在 Node.js 帮助下形成的前端工程规范,不管有没有服务器端代码,也都受到了这个规范很大影响。
MERN 技术栈,是前端代码和工程,从其他技术栈的服务器端框架中「分离」出来之后,又融合相同技术栈的服务器端框架,形成的「全栈」开发方案。
组成 MERN 的 4 个首字母,能体现这套技术栈的本质,也就是图上右边的 4 个组成要素,整体上还是以服务器端的研发方式、应用架构、平台基建为中心的。
在「现代 Web 开发」趋势下,在国外被称作「JAMstack」的技术栈,变得越来越清晰和主流。它还有另一种命名建议是图中第二行的「SHAMstack」。
这两个缩写的首字母组合虽然不同,但就像图中的推导结果,本质是一样的,都是由 4 个要素组成。
再展开看这 4 个要素。
跟刚才说的 MERN 技术栈比较,Serverless 模式和基建取代了 M 代表的传统后端基建;前端应用的构建产物 、 服务器端渲染、静态网站生成、前端程序中的 BFF、等等,取代了 E 代表的服务器端 Web 框架和服务器端代码。
这种技术栈的项目仍然是「全栈」的 Web 项目,但变成以客户端研发方式、客户端应用架构为中心,看上去很像是纯静态的、纯前端的项目。
还有人归纳了这种叫「STAR」的技术栈,本质上是图中右边这4个要素组成。
同一个现代 Web 项目,可以同时符合 JAMstack 和 STAR,JAMstack 强调的是前后端架构和开发范式,STAR 强调的是专业的编程方式。
特别要注意的是,STAR 其实体现了一个现代 Web 项目中的「前端工程化」,除了编译环节,也需要 Runtime 环节和代码编写环节的支持,除了视图层,也需要完整的应用架构和 Model 层的支持。
在「现代 Web 开发」趋势下,国外技术社区里越来越多的提到 meta-framework,本质上是把 JAMstack 和 STAR 强调的部分加起来,用以客户端为中心的、包含更上层抽象的、通用的 Web 框架的形式,整体系统的满足这些需求,抽象、简化这里面的各种模式。
在「现代 Web 开发」里,这种 meta-framework 是更合适的基础抽象层,取代了以脚手架和构建工具为代表的更原始的基础。
「S 型曲线」是一种事物「发展成长规律」的表示方法,这张 JS 框架的 S 型曲线图,很好的表达了 meta-framework 在 JS 技术发展中的意义。
在图中左侧的早期阶段,各种 UI 框架在行业和技术社区里不断涌现,包含各种各样的尝试和破坏性创新,随着发展,UI 框架不再是快速发展的前沿了,选择也不再那么多,技术发展逐渐稳定和收敛成了基于 React 做上层的整合和框架建设,这种框架变成了新的发展前沿,也产生了大量选择,同时作为更成熟的技术,以更快的速度被采纳使用,之后又逐渐稳定和收敛,形成主流技术。
比较了「传统 Web 开发」和「现代 Web 开发」中的不同现象,最后我们来归纳和定义一下这两种范式的本质特征和组成要素。
从框架和 UI 的角度来看,「传统 Web 开发」是以服务器端框架为中心来做全栈开发,客户端是围绕 HTML 和对象树的编程范式。
「现代 Web 开发」是以新一代客户端框架为中心来做全栈开发,客户端同样围绕专业、通用的软件开发语言和编程范式。
从架构的角度来看,「传统 Web 开发」中存在两套比较独立和欠缺融合的程序,「前后端分离」改善了分工协作,但对于分离出来的「前端技术栈的 Web 项目」,在架构和开发范式上没有真正实现「分离」,前端开发者需要跟服务器代码打交道。
「现代 Web 开发」实现了在同一套程序里一体化开发,在开发、调试、运行、部署等环节都能做到「无服务器化」,让前端技术栈的开发者更容易成为真正的「产品开发者」。
从抽象的角度来看,「传统 Web 开发」中前端部分的基础抽象是原始、初级的,比如脚手架、样板代码、基本的 Webpack 包装和 CLI 工具、React 和 JS 生态中的海量选择,很有「前端特色」,跟成熟的软件开发有显著差距。图中的 DX 是指开发者自身的效率,UX 是指开发出的应用的能力和质量,传统范式中的抽象太原始,导致 DX 和 UX 没法同时提升,提升其中一个,就会损害另一个。
「现代 Web 开发」中有了一体化的、更成熟的、框架级别的基础抽象,能同时追求和确保 DX 和 UX 的最大化。
从部署的角度来看,「传统 Web 开发」的项目,要么纯静态托管,要么作为服务器端应用来部署和运维。不同部署方式,会导致整个工程都不同,需要不同的脚手架模板。
「现代 Web 开发」的项目有更多样强大的运行和部署方式,但在 meta-framework 的支持下,项目模板的变化变少了,申请有机会完全融合、收敛成一个模板,成为「Universal App」。
最后,从平台角度来看,「传统 Web 开发」基于后端的云基础设施,由于代码层面缺乏抽象,客户端代码的复杂性容易停留在很基础的程度,有些时候会干脆直接改成 JSON 之类的配置。
「现代 Web 开发」基于新一代 Serverless 平台。在 Serverless 的支持下,能实现以前难以实用化的云研发。由于代码层面有统一的、上层的抽象,在开发中能引入更多低代码模式。
针对这些问题和趋势背景,字节在起步比较晚的 Web Infra 建设中,主动发展和建设了整套「现代 Web 技术栈」的研发体系,建立了「MWA」 —— 也就是「现代 Web 应用」—— 这种工程标准,形成了新一代「研发引擎」,支持了大量、多样的真实业务项目。
之前第一部分里我们讲了前端的「传统技术栈」是图上这样子。
可以看到「现代 Web 技术栈」的这张图,发生了非常大的变化,所以说这种转变是一场「范式转移」。
有几个需要注意的变化是:
图中绿色代表的平台化基建,占比超过蓝色代表的纯代码层面的基建。
没有割裂,中间的研发平台建立在「前端云」之上,工程框架基于研发平台支持研发全链路,右边的工程方案跨越上中下三层,是指可以通过工程方案,定制左边这三层的需求。
这套技术栈在字节内部,是一套被称作「Goofy」的研发体系,可以看到图中有多个名字里 Goofy 开头的产品,既可以整体使用获得更多高级能力,也可以单独使用,其中最早推出、应用最广泛的,就是最下层的「前端部署平台」,提供了 Serverless 的「前端云」模式,明天下午有一场分享是专门介绍这个部分的。
这次分享以图中蓝色部分代表的代码层为主,这个部分的解决方案,形成了一套被称作 Jupiter 的研发框架。
先通过视频演示直观的看一下。
屏幕上的这个应用,是 Goofy 前端研发工作台的 SaaS 版。
先在团队里创建一个工程项目。
默认会推荐用这套基于 Jupiter 框架的、标准化的工程方案体系,这里选择了 符合前面提到的 MWA 工程标准的「MWA 工程方案」。
后面会介绍这个工程方案非常强大,能力很多,但创建过程中需要做的配置很少,这里我们什么都不需要改就可以继续创建。
可以看到除了初始化代码,也会一站式的完成各种底层平台的初始化和配置。
刚创建出来的项目,已经是一个完备的工程,可以马上执行一次上线发布。
可以看到发布成功了,点击自动生成的域名,可以看到 Jupiter 框架的初始运行效果。
接下来看看开发环节。
在一个开发任务里,进入「代码」面板,可以看到项目代码的可视化展示和低码编辑界面,这个界面显示之前,会先初始化任务专用的 Web IDE 研发环境。
接下来直接进入 Web IDE。
可以看到一个 MWA 工程的代码,是极简、轻量的,只要在 src 下默认导出一个 React 组件,就能得到完善的 web 应用,项目默认是零配置的,看不到 webpack.config.js 之类的工程配置和样板代码。
接下来直接执行 dev esm 命令,可以看到瞬间就启动了调试命令,几乎没有编译耗时,就生成了开发环境的访问地址。
接下来快速做一段前后端一体化的开发,可以看到我们在 api 目录下准备了一个 BFF 函数文件。
可以在 web app 代码中直接导入这个函数文件,像普通函数一样调用。
可以看到输入代码的过程中,我完全不关心代码风格问题,保存的时候,会自动生成规范的、符合最佳实践的代码。
这里我写错了 API,会自动提示错误。
这个 useLoader 是 Jupiter 框架的运行时 API,能支持一体化的 SSR。
查看刚才打开的调试页面,可以看到修改的效果,这段文本实际上是通过 BFF API 获取到的,这个视频忘记录相关的演示。
接下来回到工作台的低码编辑界面,可以看到 MWA 工程的配置状态。
把服务器端渲染功能开启。
回到 Web IDE,可以看到 package.json 里已经自动增加了相关的配置。
打开调试页面,从 HTML 文件可以看到整个 UI,包括通过 BFF API 获取到的内容,都已经在提前渲染好了。
可以看到这个 MWA 工程的代码虽然极简,但能力已经比很多代码复杂的传统前端工程都要更强。
刚才演示中的工程项目,在传统前端开发里,是一个模糊、虚的概念,不止包含代码,还包括研发环境和各种平台上对应的注册和配置。
在字节前端研发体系里,「工程」被实体化了,在研发平台上,「工程」是「看得见摸得着」的对象,可以创建和管理,在里面做所有研发相关工作,每个工程都包含全链路每个环节,和一体化的工作流。
「工程」可以通过「工程方案」来一键创建,「工程方案」相当于「工程」的「模板」,取代了传统前端开发中的脚手架,而「工程方案」都有一个「工程类型」,这种类型相当于「模板的标准」。
建立和维护工程标准,推进围绕这些标准的基建和技术生态,是 Web Infra 部门的工作重点之一。
目前的建设已经基本完成了「工程类型」的收敛,形成了少量的、数量固定的「默认工程类型」,能支持体系中各平台的更多功能。Jupiter 框架实现了这些「工程类型」在代码层面的要求。
每种「工程类型」,除了直接提供「默认工程方案」,也支持开发者创建属于这种类型的、自己的「业务工程方案」,能跟标准保持兼容和同步。
以 MWA 为例,这是默认工程类型中唯一的 Web 项目类型,满足所有 Web 项目的开发需求,传统前端开发中的各种 Web 项目的模板,都被收敛成同一个「模板」。
MWA 之所以能承担唯一 Web 项目类型的责任,是因为有 MWA 框架的支持,MWA 框架不但是前面说过的 meta-framework,而且在 JAMstack 技术栈的基础上继续发展,把微前端、传统 Web 开发范式中的 Node.js Web 框架、等等,抽象融合到了一起。
这种融合不但不是简单的叠加,导致工程变得大而全、更重。反而通过抽象变得更轻量更简单,所以是 1+1<1,另一方面因为能一体化的考虑多种需求,能交付原本单个项目类型不好实现的功能,所以也是 1+1>2。
目前收敛得到的默认工程类型,有图上这些,每种类型都覆盖一个在研发方式和工程需求上有本质差别的场景领域,解决这个场景下的工程标准问题。
虽然工程类型要收敛,但业务工程方案的数量不做任何限制,比如图中是字节GIP 部门做的叫作 GHome 的内部研发门户,其中包含大量子平台,这些子平台之间有共通的业务逻辑和额外的工程要求。
在这种情况下,可以基于 MWA 标准,定制出 GHome 子平台工程方案。
图上右边还列举了其他一些业务工程方案。
进一步介绍下 Jupiter 和 MWA 框架。
一个最简单的 MWA 项目,可以只包含图上左边这样的两个文件,这样的MWA 已经有完善的工程能力,能马上做产品级的部署。图上右边列出了一部分能力。
我们接下来对这个项目做些小调整,就能实现传统上需要很多配置和样板代码,或是需要不同脚手架才能实现的效果。
这个项目当前是图中左上角这样的「单入口」模式,改成左下角这样的「多入口」结构,就自动变成了 MPA,自动得到了服务器端路由和多个 HTML。
如果像右上角这样,把 landing-page 的 App.tsx 入口组件,换成 pages 目录,就可以启用约定式路由功能,基于文件路径自动得到客户端路由。
如果像右下角这样增加 index.ts 和 head.html,可以启用高级的自定义能力,定制或掌控原本由框架自动处理的环节。
之前的演示里,我们通过低码界面启用了 SSR 功能,其实也可以在代码里增加 Jupiter 配置文件,用代码来控制配置。
一个 MWA 项目自带产品级的 web server,称作 Jupiter Server,项目自己就可以运行自己。
第一方 Serverless 平台使用相同的 web server,但还能基于 MWA 标准来理解项目的代码,自动生成更复杂的多进程的集群化的系统。比如 SSR 部分会独立部署,业务开发者写出来的 app 代码,容易在 SSR 中出现异常报错或内存泄露,这时前面的 web server 会让应用自动降级,变成兜底的纯 CSR 模式,保证用户请求不会出错不会超时。
要实现这种自动兜底,不但平台和框架的协同,也需要 MWA 提供的一体化 SSR 开发方式,用同一套 app 代码开发前后端业务逻辑,让业务逻辑始终支持 CSR,否则兜底就无效了。
MWA 支持用一体化的方式,开发同一套 app 代码,在图上这三个环节中以不同模式运行,也支持根据业务需求,让 app 代码中的不同部分,在不同环节以不同模式运行。
这种收敛后的开发方式,符合业界的一个发展趋势,就是让代码的运行尽可能「前置」,优先在编译时运行,剩余的才在服务器端 SSR 环节运行,最后剩下的才在浏览器里运行。
比如可以像图上左边这样,app 整体做 SSR 或 SSG,把局部留到 CSR。
也可以像图上中间这样,反过来,整体默认 CSR,允许对局部做 SSR 和 SSG。还可以进一步实现右边这样更复杂的效果。
要实现前面说的 1+1 < 1,除了合适的抽象,MWA 框架也通过插件和微生成器等方式,把核心功能拆分出来,可以按需启用。可以像图上这样运行 new 命令,选择启用这两个功能,微生成器会自动重构项目代码,增加必要的依赖和配置。
也可以在工作台的界面上,查看项目可选功能的开关状态,做修改调整。
之前的演示里,我们提前开启过 Unbundled 开发模式功能,可以用 dev esm 命令取代默认的 dev 命令,耗时可以从几秒减少到1秒不到。
这么快的编译速度,是因为背后已经完全没有 webpack,也不做任何打包,只用 esbuild 编译 ESM 模块,用自研的类似 Snowpack 和 Vite 的方案运行项目,也会自动从 Goofy PDN 平台上加载预编译的依赖包,进一步优化性能。
之前的演示里,还开启了一体化 BFF 功能,可以在项目里写「BFF 函数」文件,这种函数文件可以实现任意的 REST API,同时自动生成一体化风格的客户端 SDK,通过 SDK 调用,就像调用普通文件里的函数。
用这种一体化 API 调用 BFF,不但更直观、安全,也因为多了框架的抽象,框架可以自动做更多事情,比如在 SSR 中运行的时候,自动把 BFF 请求切换成内网方式。
Serverless 平台也会自动对 BFF 做优化,做独立部署,BFF 和 Web 之间不会互相干扰。
MWA 实现了图上这种「三位一体」应用,图上右边三种开发方式之间,可以无缝切换。
比如可以从现代 Web 开发范式,切换成传统的 Node.js Web 应用。也可以把 MWA 项目中的 BFF 拆分成独立的纯 API 服务项目。一个纯 API 服务,也可以随时切换成包含 web 的 MWA 项目。
这种能力有利于业务项目从传统 Web 开发范式向现代 Web 开发范式的转换。
前面说的这种「三位一体」,实际上是 MWA 最重要的「Universal App」功能的一部分。
「Universal JS」是指同一份 JS 代码既支持在浏览器端运行,也支持在服务器端运行。而「Universal App」是进一步发展,让同一份 app 代码,可以用图上中间这一排的方框代表的任意方式来运行,也支持同时部署多个不同运行方式的版本。
比如一个静态网站切换成 SSR,或启用 SPR 的时候,不需要调研技术选型和考量成本收益,几乎什么都不用改。也可以随时退回原来的方式。
比如为中后台项目同时提供 SaaS 版、桌面安装版和私有化部署版。
比如让微前端子应用,既能在主应用里访问,也能独立访问。
前面提到过 Node.js Web 框架只能提供「服务器端应用架构」,MWA 提供开箱即用的、符合现代 Web 开发范式、以客户端应用为中心、前后端一体化的应用架构,类似图上这样。
可以用 createModel、useModel 这样的 API,轻易实现 React 开发中欠缺的 Model 层和 Controller 层。可以利用 Redux 的 FP 编程优势和生态红利,同时不需要关心创建和配置 store,如何组织和组装 reducer、action 等。
前面说过,在抽象不足的情况下,前端开发中一直存在 DX 和 UX 不可兼得的问题,比如如果降低了开发成本,产品功能上可能会不灵活,性能不够好,反之把业务需求和性能做到位,开发体验就会很差。
MWA 框架在尽可能多的环节,实现最大化的抽象,类似图上这样,帮助前端开发者脱离「软件开发的石器时代」,获得更成熟的软件开发范式。
Jupiter 和 MWA 框架在字节内部,经历了半年的内测,和一年的正式使用,在内部已经进入推广普及阶段。
这个项目也很快会开源出来,帮助行业和社区里的更多项目,落地「现代 Web 开发」范式。
这个开源项目叫作 Modern.js,目前我们已经创建了 Github 项目,上线了开源内测主页,和一个现代 Web 开发者问卷调查,希望大家帮忙填写和转发下这个问卷,调查报告之后会公开出来。
(由于 modernjs.dev 这个域名还没备案,没办法部署在我们自己的 Serverless 平台上,目前是用 Github Pages 部署,似乎有被墙的问题,正在解决)

Loading Comments...