概要
7月,字节跳动 Web Infra 做过一次主题为《迈入现代 Web 开发(字节跳动的现代 Web 开发实践)》的分享,在分享中我们梳理了「传统前端技术栈」的典型组成部分,展示了其中每个部分都存在的瓶颈问题。也介绍了在这些问题的驱动下,业界正在发生从「传统 Web 开发范式」到「现代 Web 开发范式」的「范式转移」。在这个分享的最后预告了 Modern.js 开源项目。
10 月 27-28 日的稀土开发者大会上,字节跳动 Web Infra 正式发起 Modern.js 开源项目。
本文是宋振伟同学跟我一起准备的专场分享内容,也是第一次介绍 Modern.js。
第一部分先介绍了业界和字节内部的前端开发、Web 开发在发生哪些影响深远的变革,从这些变革的角度,展示了基于 Modern.js 的现代 Web 开发。
这些变革包括:
- 更多「前端开发者」成为「应用开发者 / 产品开发者」
先讨论了什么根本因素在驱动这种转变,"Frontend Focused" 的意义,指出服务器端开发门槛不断降低的长期趋势、原有基建的缺陷,用 Modern.js 演示了「一体化、无服务器化的全栈开发」、「以客户端为中心的 Web 开发」。
- 从「前后端分离」到「前后端一体化」
分析了「前后端分离」产生的两种前端项目,为什么其中一种是「假分离」,另一种「不完整」,用 Modern.js 演示了「前后端一体化」在哪些地方带来改变。
- Meta Framework 取代传统「前端三剑客」
分析了四代「前端三剑客」,以及每一代都被下一代的成员「吞并」的规律,结合字节内部的真实案例,讲解了 Meta Framework 的角色。
- 形成基于「前端技术」的成熟 GUI 软件研发体系
先明确了「前端技术」的定义, 结合 Modern.js 的功能和设计,讨论了如何实现「充分抽象」,才能解决 DX 和 UX 的矛盾。
- 智能化、平台化、低码化
接下来第二部分系统介绍 Modern.js 的六大要素,包括:
- 普及:现代 Web 开发范式
回顾了这种范式的 9 大主要特征。
- 核心:现代 Web 应用(MWA)
从 Universal App、一体化、应用架构、Runtime API 这四个角度来了解 MWA。
在应用架构部分介绍了 Modern.js 中 Model 的设计和背景。
- 内置:前端工程最佳实践
列举了几个典型的最佳实践,包括 Post-Webpack Era 的新工具趋势、Modern.js 的 Unbundled 开发,Modern.js 推荐的「CSS 三剑客」,Modern.js 微前端项目跟直接使用 Garfish 的微前端项目对比、模块工程方案和 Monorepo 工程方案中的最佳实践。
- 包含:Web 开发全流程
演示了 Modern.js 在「编码」环节的微生成器功能、在「调试环节」的微前端调试。
- 提供:工程标准体系
- 鼓励:定制工程方案
末尾介绍了除已经发布的开源项目,还有哪些对现代 Web 开发者有帮助的事情在发起和推进中。也介绍了 Modern.js 当前高优的社区计划。
分享实录
大家好,我是来自字节跳动 Web Infra 的宋振伟,在字节跳动,我们部门负责打造和发展「Web 技术中台」和「前端研发体系」。
今年7月,我们做过一次主题是《字节跳动的现代 Web 开发实践》的分享,在分享中我们梳理了「传统前端技术栈」的典型组成部分,展示了其中每个部分都存在的瓶颈问题。
也介绍了在这些问题的驱动下,业界正在发生从「传统 Web 开发范式」到「现代 Web 开发范式」的「范式转移」。
在这个分享的最后也预告了 Modern.js 开源项目。
昨天上午的主题演讲中,字节跳动正式发布了 Modern.js。今天的专场分享,我想结合字节内部的变革和实践,介绍基于 Modern.js 的现代 Web 开发,和所带来的实际效果。
议程
今天的分享可以分成三个部分。
昨天的主题演讲有说到,整个业界和字节内部的前端开发、Web 开发,都在发生着影响深远的变革,我们首先从这些变革的角度,看下基于 Modern.js 的现代 Web 开发是什么样子,有什么区别。
然后,我们整体看下 Modern.js 有哪些要素和收益。
最后再看下除了已经发布的开源项目,还有哪些对现代 Web 开发者有帮助的事情在发起和推进中。
我们先来看第一部分「现代 Web 开发」
一、基于 Modern.js 的现代 Web 开发
1.1 更多「前端开发者」成为「应用开发者 / 产品开发者」
可以从这五个方面的变革,来展示「现代 Web 开发」是什么样子。
这五个变革之间是承前启后的关系。首先最根本的推动力,不是来自技术侧,不是前端开发者一厢情愿的发展自己主观的技术偏好,而是在互联网和 IT 行业、市场需求、用户产品这一侧的大趋势,需要更多「前端开发者」成为「应用开发者」或「产品开发者」,鼓励和倒逼着技术领域,不断产生更有利于这种需求的技术形态和基础设施。
当传统技术范式遇到瓶颈,不再能进一步适应需求,就会发生「范式转移」,出现从一开始就针对这种需求重新设计的、新一代的技术范式。
这种转变推动前端技术领域出现了「从分离到一体化」、新一代「前端三剑客」的变革。
这种变革带来的新一代技术标准和基础设施,开始形成完全基于「前端技术」的成熟 GUI 软件研发体系,并且进一步朝着平台化、低码化的方向发展。
1.1.1 "Frontend Focused" 的意义
我们刚才一直在说「前端」,「前端」这个概念似乎一直是只有开发者才关心的技术细节,但最近几年,却变成了商业领域、投资机构也都很关心的事情,全球市场上涌现出越来越多的新一代云平台和研发工具产品,多数都涉及前端研发特有的需求和模式,其中还有很多像 Vercel 这样明确「专注于前端」的产品。
就像幻灯片上这张图,云计算和研发产品最初是从最接近机器的底层开始发展,从虚拟化,到容器编排,到基于容器技术的各种平台化、服务化的研发工具形态,这个阶段是后端技术主导的,整个趋势是越来越向上层发展,越来越接近市场和商业价值最终所在的地方——也就是面向用户的产品,因此必然会发展到前端技术主导的抽象层,让应用开发和产品开发能更专注于用户需求,而越来越不需要关心服务器端的复杂性和专业技术细节。
所以市场需求会趋向于推动应用开发方式往「专注于前端」的方向的发展,专注于前端就是专注于用户,而专注于用户是多数企业、产品的根本利益所在
1.1.2 最大的开发者群体
另一方面,从进入移动互联网时代开始就不断大幅增加的应用开发需求,现在不但没减弱,反而还在加强,比如幻灯片上 IDC 的预测,要满足这么庞大的应用开发需求,传统开发方式和人才储备是很不够的,需要让尽可能多的开发者能独立、完整的开发这些应用,而前端技术栈的开发者,正是最大的开发者群体和技术社区。
所以在用户、产品、市场这一侧,一直有趋势和压力,需要更多「前端开发者」成为「应用开发者」或「产品开发者」,鼓励和倒逼着技术领域,不断产生更有利于这种需求的技术形态和基础设施。
1.1.3 服务器端开发门槛不断降低
在这种客观趋势的推动下,基于 Web 技术的应用开发中,服务器端的占比和门槛一直在不断下降,国内大厂的中台建设,提供了大量不跟特定客户端捆绑、专注于数据需求和底层业务逻辑的 API,让产品开发更聚焦在上层的客户端业务逻辑。还有 BaaS 和基于云函数的后端云 Serverless,也进一步降低服务器端的门槛,让前端开发者能更独立的、端到端的完成产品开发。
但要进一步降低门槛,提高效率,这些基础设施的一个缺陷就暴露出来,就是他们都把应用依赖的 API,放在应用项目之外维护,跟前端研发是割裂的。
还有一个缺陷是它们不解决 API 之外的服务器端需求,比如路由、SSR 等。
有一个字节内部的典型例子,前端开发者在自己实现的 SSR 项目中,始终用 HTTP 方式请求外网域名的 API 来获取数据,导致 SSR 频繁超时,HTML 响应慢严重影响用户体验。可以给前端开发者做培训,让他们具备足够的服务器端开发思维和知识,知道要在 SSR 环节切换成内网的请求方式,还要考虑缓存机制等,但更根本的解决方法是屏蔽这种服务器端问题和实现细节,自动处理这些问题。
1.1.4 一体化、无服务器化的全栈开发
因此服务器端门槛不断降低的趋势,自然会发展到「一体化、无服务器化的做全栈开发」的阶段,让前端开发者直接开发 「接近纯前端的项目」而不是 「Node.js 框架项目」,感觉就像没有服务器一样。
幻灯片上是这次分享中第一个 Modern.js 的 demo, 左侧是一个 Modern.js 应用项目的完整目录结构,src/ 目录下的应用主体,可以像调用普通函数文件一样访问 api/ 目录下的 BFF 函数,不需要了解网络细节,Modern.js 会自动基于 BFF 函数的路径、参数等自动生成 REST API,在 CSR 过程中自动请求。
接下来我们在 package.json 的 modernConfig 配置里启用 SSR、「差异化分发」和「自动 Polyfill」,可以看到这些功能不用增加代码逻辑,只需要静态开关配置。
构建后,幻灯片左侧可以看到产物里的 HTML 和 JS 都有 es6 和 es5 两个版本,用户访问时应用的时候,Modern.js 的 Web Server 会根据浏览器 UA 选择分发 es6 版本还是 es5 版本,也就是「差异化分发」。
右边上面的图是现代浏览器的访问结果,不会返回任何 polyfill 代码, 下面的图是低版本浏览器的访问结果, Web Server 会自动提供这个 UA 需要的 polyfill 代码。
可以看到 Modern.js 不但支持一体化的开发 BFF,也满足 BFF 之外的服务器端需求,尽可能自动利用自带的 web server 去做性能优化和提供产品级的兼容性,同时开发体验仍然是无服务器化的
之前我们启用了 SSR, 左侧图上高亮的 HTML 片段,已经包含了通过 BFF 请求到的数据,会根据应用运行的方式自动选择最高效的请求方式。
这种自动优化不会阻碍开发者对技术细节的掌控,右边这张图展示了 BFF 函数也会生成标准的 REST API,可以手动调用。
1.1.5 以客户端为中心的 Web 开发
这种一体化、无服务器化的全栈开发进一步发展,自然会得到一种客户端为中心的 Web 开发方式。
比如在传统 Web 开发中,要实现常见的权限识别和重定向,除了前端页面的逻辑,还需要在服务器端的路由中,添加实现跳转的业务逻辑。比如图上,在访问 home 页面的时候根据 cookie 的值决定要不要重定向到登陆页面。
同样的需求,在以客户端为中心的 Web 开发方式中,可以一体化的在客户端代码里实现,比如前面已经启用 SSR 的 Modern.js 项目,只需要添加 Redirect 组件,就可以实现和刚才完全相同的权限识别和重定向效果,访问页面时会根据 cookie 决定要不要返回 302 状态码。整个实现过程是客户端思维的。
以客户端为中心,不代表不能掌控服务器端,不能直接写服务器端业务逻辑。
如果已经习惯 Node.js 框架的开发方式,可以 server 目录的钩子文件里,对框架自带的 Web Server 添加自定义逻辑,比如自由添加中间件,可以在这个局部,用自己熟悉的传统 Web 开发方式实现权限识别和重定向。
1.2 从「前后端分离」到「前后端一体化」
Web 项目的技术栈也在转变,相当于是先发展出「前后端分离」,然后又用新方式回归了 「前后端一体化」
1.2.1 「前后端分离」
以前的 Web 开发就像图上这个 Ruby on Rails 项目,图中粉色的前端代码,「寄居」在图中绿色的后端 Web 框架项目中的,前端和后端会互相干扰互相拖累,做工程建设也比较麻烦。
之后 Web 开发普遍转变到「前后端分离」的模式,分离后的前端项目和后端项目,都倾向特定的类型。
后端项目不倾向包含 Web 的功能,而这时的前端项目可以归纳为两种类型。
MERN 这种项目类型相当于又回到了分离前的状态,整个项目是基于 Node.js 框架的,前端被嵌在里面。这种结果其实反映出「前后端分离」实现的更多是分工上的分离,而不是技术架构上的分离,在技术架构上仍然没有摆脱以服务器端框架为中心的 Web 开发。
从 MERN 项目的结构可以看到,它不但是假分离,而且也不算一体化,React 代表的前端部分和 Node.js 框架代表的后端部分,在项目里是泾渭分明的,没有真正融合到一起去。
使用 Node.js 框架的项目,多数属于这个类型。
「前后端分离」模式中另一种前端项目类型,我们称作「老一代 JAMstack」,这种类型没有假分离问题,就是纯粹的前端项目。可以实现 SPA 和 MPA,也能基于编译工具实现 SSG(静态网站生成)。靠静态托管来运行,鼓励在 CSR 中调用 API 满足动态的应用需求。
「老一代 JAMstack」最大的问题是,虽然分离成了独立的项目,却不足以承担完整的应用开发,只能产出静态文件,依靠外部的 Web 服务器去运行,无法实现 SSR,三大组成部分里的 API,也需要在项目之外,用云函数、独立后端项目等方式来实现,不能跟着项目一起迭代。
用 CRA 或直接用 webpack 搭建的项目,多数属于这个类型。
1.2.2「前后端一体化」
在前面说的需要更多「前端开发者」成为「应用开发者」的背景下,新一代的 JAMstack 项目用「客户端为中心」的「前后端一体化」方式,解决了上面说的问题
新一代 JAMstack 的三大组成部分虽然没变,对应的内容却有很大变化,JS 部分更加函数化和组件化、以 JS 为中心,HTML 可以完全不在项目中出现,自动生成。BFF API 变成项目自包含。和之前简单的静态托管相比,基于前端 Serverless 平台可以实现 SSR、SPR 等动态能力,即使是静态页面,也可以获得很多好处,比如前面展示的 「差异化分发」。
图上是用 Modern.js 的 demo 来展示新一代 JAMstack 项目。在开发中只需要聚焦在 JS 代码上,不论是 SPA 还是 MPA,HTML 都是自动生成的。不论是 SSR 渲染的代码,还是 API 逻辑,构建之后按照规范输出到 dist 下的不同目录,构建产物规范是 Serverless 友好的,支持把 Web、SSR、BFF 等拆分成不同服务器。
前面提到 Modern.js 倾向于 JS 为中心、自动生成 HTML。但不阻碍开发者自己掌控 HTML。图上是 Modern.js 渲染 HTML 的默认模板。
一体化 BFF 的调用,在前面的例子演示过,这里可以看到 BFF 函数的文件路径是有约定的,可以实现任意设计的 REST API。
构建产物会针对 BFF、Web、SSR 分别生成独立可运行的 Server,这是对前端 Serverless 平台更友好的,Serverless 平台可以自主选择让 BFF、Web、SSR 用不同方式在独立进程中运行,不会互相干扰。比如在 SSR 环节遇到 app 代码的的内存泄露导致 SSR 超时,Web Server 不受影响,可以自动降级到 CSR 模式,返回静态的 HTML 给用户作为兜底,用户的 HTML 请求始终不会超时或挂掉。
对于 SSR,同样可以前后端一体化的开发,图里高亮的 useLoader 函数中的代码,同时适用于 SSR 和 CSR,如果这个 Loader 在 SSR 中已经预加载,CSR 就会自动跳过,否则会执行。
SPR 相当于有缓存机制的 SSR,在 Modern.js 里也可以一体化的开发,只要使用这个预渲染组件。
SSG 实际上就是在编译时运行的 SSR,在 Modern.js 里只要配置 SSG 路由,就会自动启用这种编译逻辑,给路由生成静态 HTML。CSR、SSR、SSG 都是用同一份代码。
1.3 新一代「前端三剑客」和 Meta Framework
除了在技术栈层面向「前后端一体化」转变,在工程层面,传统的「前端三剑客」也在转变成元框架这种新的工程基建。
1.3.1 传统「前端三剑客」
先来回顾下传统的「前端三剑客」,第 1 代和第 2 代如图上所示,也被大家熟知。而第 3 代「前端三剑客」由视图框架、Node.js 命令行、Node.js 框架三个方向组成。
其中 Node.js 命令行代表了工程化,其中最典型的是像 Webpack 这样的打包工具,以及 Babel、PostCSS 这样的编译工具。
视图框架和 Node.js 框架很好理解,就是之前讨论的 MERN 项目中的前端和后端部分
1.3.2 第 4 代「前端三剑客」
随着现代 Web 开发范式的发展,第4代「前端三剑客」的轮廓已经越来越明显,由元框架、前端 PaaS、低代码三个方向组成。其中低代码方向在昨天晚上稀土大会的低码专场已经介绍过,而 Modern.js 就属于元框架这个方向。
从这张图可以清楚的看到,每一代前端三剑客中,都有一个方向,把上一代前端三剑客完整包含在自己里面,变成不需要太关心的底层,让自己取代他们成为前端开发的新地基。
第 3 代中的视图框架,就扮演这样的角色,把第二代的 HTML、CSS、JS 封装在自己里面,而第 4 代中的元框架,又对视图框架、Node.js 框架、Node.js 命令行做了整合和抽象,成为前端开发和工程建设性起点,元框架扮演了过去 Webpack、React 扮演的角色
这张 JS 框架的 S 曲线图,也能体现这种转变。在左边这个时期,发展的前沿、开发的起点,都是 React、Vue、Svelte 这样的视图框架,新的视图框架项目也层出不穷。现在已进入右边这个时期,前沿收敛到基于 React 发展更上层的元框架。
Modern.js 作为现代 Web 工程体系,是由元框架组成的,提供三大工程类型,鼓励开发者基于工程类型建设自己的业务工程方案。
以字节内部的「火山引擎子应用工程方案」为例,初始化的目录结构没有什么变化,只在配置中默认加载了自己的框架插件,插件中通过 server 提供的 hook 修改渲染后的 HTML ,在原来的 HTML 上套了层壳,也就是右下角截图中火山引擎统一的顶栏和左侧导航栏。
这样建设出来的工程方案,既能满足垂直场景的需求或自己的偏好,又能保持跟三大工程类型的兼容,自动获得 Modern.js 的能力和收益
1.4 基于「前端技术」的成熟 GUI 软件研发体系
在「前端开发者」成为「应用开发者」的大背景下,技术栈、工程基建的发展,开始形成基于「前端技术」的成熟 GUI 软件研发体系。
1.4.1 什么是「前端技术」
先明确一下我们一直说的前端技术,不是指做 UI 的技术,而是由 Web 原生语言、Web Runtime、Web 技术生态组成的技术栈,不是只在浏览器里才有前端技术,而是有 Web Runtime、有 Web 语言的地方,就有前端技术
1.4.2 DX 和 UX 同样重要
传统前端开发不是成熟的软件研发体系,缺乏足够的抽象和基建,导致 DX 和 UX 始终存在矛盾,此消彼涨。以往的产品开发中,习惯更重视 UX,这有两方面的原因,一来是因为产品是由产品主导,因此更重视 UX, 不会过多关注开发者体验,再就是缺乏足够的抽象和基建,导致 DX 和 UX 之间必须牺牲一个。
在新一代更成熟的研发体系的支持下,已经可以实现 DX 和 UX 的同时最大化了,也从「更重视 UX」转变为「DX 优先」的方式
要实现 DX 和 UX 的同时最大化,需要充分的抽象。比如前面提到的 Modern.js 的这个例子,项目里只有三个文件,就具备全面的能力,包括自动 Polyfill、差异化分发、SSR 等,既具备产品级的 UX,有保持了 DX 的简单、开箱即用等
1.4.3 充分抽象
要实现充分抽象,需要让项目从基于「库、工具」发展成基于「框架」,这两者的区别在图上表现的比较好。蓝底白边部分是项目开发者自己写的代码。左边是传统前端项目,由开发者手写整个应用,把库和工具当做积木来组装,填补项目里的空白。而右边是 Modern.js 项目,整个应用是框架本身,开发者手写的代码,是按照框架的要求填充到框架预留的位置上
要实现充分抽象,也需要在尽可能多的环节实现最大化的抽象,图上体现了 Modern.js 除了像常规的框架一样,在运行时和编译时做抽象,也会在 IDE 编写代码的环节,和部署产物的环节,引入最大化的抽象
要实现充分抽象,也需要解决前端模板的问题,Modern.js 把各种研发场景、项目类型,收敛和标准化成了始终固定的三个工程类型,其中「应用」工程方案,也就是 MWA,支持所有需要部署和运行的项目,「模块」工程方案支持所有需要实现代码复用的项目
1.5 智能化、平台化、低码化
Modern.js 代表的现代 Web 开发,也在继续朝着智能化、平台化、低码化的方向发展
智能化方面,当前可用的功能,是用 Modern.js 的初始化工具创建的项目,会开箱即用的在 VSCode 里做好配置,启用几千条规则组成的 ESLint 全量规则集,加上按最佳实践内置在 ESLint 里的 Prettier,期望尽可能多自动修正问题,而不是仅仅提示问题。也追求尽可能多的让 IDE 负责生成真正的源码,让开发者手写的代码变成跟 IDE 沟通的语言
在平台化方面,Modern.js 的目标之一就是形成「工程标准」,让各种前端 PaaS 平台可以围绕标准实现高级能力,比如图上粉色部分列出的产品级 Web Server、差异化分发、SSR 兜底、ESR、微前端等,都需要结合代码层面的工程标准。
除了部署运行环节方面的平台,有了工程标准之后,研发环节也可以引入更多低码提效。
目前我们内部使用的研发平台,可以直接在图形界面上简单操作完成 项目的创建、开发、部署。图上右侧可以看到图形界面上展示了当前项目的的状态: 入口数量、项目配置等,蓝色框内添加应用的入口,一键从 「单入口」 转变为 「多入口」 。右侧在 Web IDE 中也能看到 src 目录结构下的变化。
低码化有两个方向,一个是刚才说的跟研发工具结合,另一个就是研发从某些工作中解脱出来的低码搭建,昨天的低代码专题中有介绍
总结
到目前为止,我们从这些变革的角度,展示了很多 Modern.js 的 demo 和效果
二、Modern.js 的六大要素
接下来我们系统的看一下 Modern.js 是什么,Modern.js 提供了什么。
2.1 普及:现代 Web 开发范式
可以用这六大要素来说明 Modern.js 。
首先这个项目是希望能推动现代 Web 开发范式的普及,发展完整的现代 Web 工程体系,突破应用开发效率的瓶颈。
前面讨论「现代 Web 开发」的时候,已经展示过这种范式的 9 大主要特征。
其中 Serverless 范式、平台化、低码化这三个特征,在当前版本的 Modern.js 里还没什么体现,需要后续会跟一些平台配合提供。
2.2 核心:现代 Web 应用(MWA)
继续看第二个要素。Modern.js 三大工程类型中最核心的就是 「现代 Web 应用」,简称 MWA,或直接叫「应用」 。
2.2.1 从 Universal JS 到 Universal App
前面提到过,「应用」工程方案支持所有需要部署和运行的项目,把这些项目收敛成用同一套框架、同一套约定、同一套模板、同一套架构、一套 API 来开发。
反过来,我们也可以从 Universal App、一体化、应用架构、Runtime API 这四个角度来了解 MWA。
Universal JS 指同一份 JS 代码,既能在浏览器端运行,也能在服务器端运行。Universal App 是它的进一步发展,同一份 App 代码可以在不同环节运行,也可以用不同的模式来运行。
首先是常见的 MPA 和 SPA 的需求,本质上是「服务器端路由」和「客户端路由」的需求。
在 Modern.js 里它们可以随意组合。
我们之前的例子都是单入口应用,只需要把 App.tsx 组件、pages 目录这样的入口标识放到 src 的子目录里,就能将单入口应用变成多入口的 MPA。会基于入口名称,自动生成服务器端路由,比如图上的 admin-app 和 landing-page 两个入口的 URL。
admin-app 和 landing-page 也分别都是 SPA,根据入口标识不同,一个使用基于组件的客户端路由,一个使用基于文件系统的客户端路由。
然后是 MWA 中的「动静一体」
之前演示过,一个静态的 CSR 项目如何直接开启 SSR、SPR、SSG 功能。
Modern.js 也支持 CSR 和 SSR/SSG 混用,比如图上右侧红色高亮部分会在服务端被渲染到 HTML 中,蓝色区域的日期时间,在 CSR 阶段动态展示在页面上。也就是整体 SSR + 局部 CSR。
整体 CSR + 局部 SSR的能力后续会加入。
在 BFF 支持方面,Modern.js 中还提供了类型友好的方式,可以通过 Type Schema 实现运行时自动校验接口的参数和返回值。比如右下角请求时,参数 text 类型为 number 时,response 中会自动提示相应的错误。
Modern.js 还支持不同类型应用的开发和运行。
Modern.js 原生支持微前端,底层解决方案是 Web Infra 之前开源的 Garfish 微前端解决方案。一个 MWA 可以随时变成微前端主应用,在配置中指定子应用列表的加载地址,Modern.js 就会自动在 Web Server 中预加载子应用数据,注入到运行时。在 Runtime API 的帮助下,可以像普通 React 组件一样使用子应用。
MWA 也可以分别作为独立的 Web 和微前端子应用的运行和部署。
MWA 在启用 Electron 支持之后,能作为桌面应用来运行,项目里会新增 electron 目录用于写主进程相关代码。 除了开箱即用的 Electron 构建等能力,也提供运行时 API 支持 Electron 的常见需求和最佳实践,进一步提升开发效率。
2.2.2 前后端一体化
第二个看待 MWA 的角度是 「前后端一体化」
之前已经演示过 BFF 函数,api/ 目录下每个文件就是 BFF 路由,当服务器端逻辑更重的时候,可以加入 Node.js 框架元素,目前支持了 4 种框架,还可以自己开发 Modern.js 插件支持更多框架。
前面提到过:以客户端为中心,不代表不能掌控服务器端,不能直接写服务器端业务逻辑。
比如之前演示过的火山引擎子应用,除了通过框架插件来实现,我们也可以在项目里创建 server 目录和钩子文件,添加修改 HTML 渲染结果的逻辑。
在只有 src 目录,或有 api 目录的情况下,MWA 类似 JAMstack 项目。
如果增加了 server 等钩子文件,MWA 就能像传统 Node.js App 一样直接写服务器端业务逻辑,使用 Node.js 框架插件、中间件等。
如果删掉 src 目录,MWA 就是一个纯 REST API 的项目。
我们把这三种模式之间随意迁移的能力,称作「三位一体」
2.2.3 应用架构
接下来,我们从「应用架构」的角度看看
传统 Web 开发中的应用架构,等同于服务器端应用架构,前端部分的架构要么缺失,要么需要项目开发者自己摸索、搭建,缺乏 API 支持和一致的抽象,难以跨项目复用业务逻辑 。
如上图,MWA 提供的开箱即用的、客户端为中心的应用架构,可以通过标准化的 Runtime API,轻易实现 React 开发中缺少的 Model 层和 Controller 层。Model 作为封装 UI 无关业务逻辑的积木,跟 UI 组件一样可以复用和组装。
之前 Web Infra 举办的 React 核心开发者在线访谈里,Redux 作者 Dan 提到,状态管理最重要的是理解状态的类型,根据需要处理的状态是什么种类,来选择对应的方案。
常见的状态管理方案,都有适合的状态类型和场景,很多时候需要混合使用,而不是一把锤子锤所有钉子,要么所有状态都放到全局应用状态里,要么所有状态都在局部状态里。
很多开发者不用 Redux,是因为 Redux 本身只能算底层 API,需要手动创建和维护 store,业务逻辑被 reducer,action 等分散在不同的地方,提高了维护成本。其实 Redux 社区一直有解决方案,比如 Ducks Modular 设计模式会把业务逻辑聚集在一起,Redux 官方支持的主流库 RTK 也为解决这样的问题而生。
Modern.js 的 Model 基于 Redux 进一步提高抽象程度,保留了 Redux 在不可变数据、数据流等方面的收益,对整个 Redux 生态兼容,让使用和不使用 Redux 的开发者都能受益。支持多种状态类型,也支持不同的 Model 写法
2.2.4 Runtime API 标准库
最后看下 MWA 的 Runtime API 标准库。
相当于「应用」层级的基础 API,不止能在 MWA 里使用,在 Modern.js 的模块工程方案里,同样可以使用这些 API,开发可复用的业务组件,支持独立调试和测试。
图中最上面蓝色方块是业务开发中常用的 API,比如 useLoader,useModel 等 API。中间绿色部分就是前面提到的定制 web server、BFF 函数需要用到的 API,最底层的插件 API,是整个框架的基础,框架里所有的包都是用插件 API 来实现的,也可以用插件 API 来扩展框架、定制工程方案。粉色部分包括很多重要的工具 API,比如 useLocalModel。
当应用中的组件需要拆分成独立的模块复用时,实现中用到的 Runtime API 还能正常调试、测试吗,答案是肯定的,这套 API 相当于「应用」领域的 API 标准库,不止能在 MWA 里使用,在 Modern.js 的「模块」工程方案里,同样可以使用这些 API,开发可复用的业务组件,支持独立调试和测试。上图右侧是模块工程的目录结构。左侧 TableList 组件中使用了 useLoader API,调试时只需要提供对应的 story 文件,模块工程方案支持我们在 Storybook 可视化测试以及单元测试中测试使用了 Runtime API 的组件。
2.3 内置:前端工程最佳实践
对于第三个要素,简单列举几个 Modernjs 内置的前端工程最佳实践
2.3.1 Post-Webpack Era
传统前端工程建设都是基于 Webpack 的配置封装, Webpack 配置复杂和编译缓慢的问题,大家应该都有比较深的感受, 但是从去年开始业界涌现很多新的工具,完全不涉及 Webpack,比如 Snowpack、 Vite、wmr 等,有人把它称为 JS 第三纪元。
从第三纪元开始 esbuild、swc 这种编译打包工具使用非 JS 的系统编程语言开发,显著提高编译速度。编译时间的缩减也意味着不打包,按需编译的 ESM 场景可以实现,
Snowpack、Vite 这样的工具,就是在 esbuild 的基础上实现的、开发者体验优先的、不打包的开发调试模式。在 Modern.js 中不打包的模式目前已经被用于公共库的构建、业务项目的开发调试等真实场景。
Modern.js 中也内置类似 Snowpack、Vite 的不打包开发调试模式,图中左侧启用该功能之后,运行效果就像图中右侧那样,开发服务器在秒级启动。
为什么可以做到速度这么快?主要是因为业务代码只有在请求时使用 esbuild 按需编译,第三方依赖自动从 Goofy PDN 加载已经预编译好的产物。
2.3.2 CSS 最佳实践
在 CSS 开发方面,Modern.js 默认推荐图上「CSS 三剑客」搭配使用,有需要也可以开启 LESS/SASS 等预处理器和 CSS Modules 支持。
2.3.3 默认零配置、样板文件最小化
和以前把功能作为样板文件塞到项目里相比,现代 Web 开发范式下的最佳实践是默认零配置的,同时样板文件尽可能简洁最小化。之前我们也通过例子看到 Modern.js 项目手动创建非常简单,只需要应用根组件和 package.json。
跟直接使用 Garfish 开发微前端主应用的项目做对比, 上图可以看到直接使用 Garifish 的项目,需要手动运行 Garfish 框架、处理公用模块、路由等逻辑。除了运行时,还需要编译环节自定义一大堆配置。直接使用还是有一定的成本。
之前的例子已经说过,在 Modern.js 中使用微前端,只需要在 web 应用的基础上启用微前端功能,提供子应用列表即可,每个子应用加载后就是组件,路由可以自己灵活组织。
2.3.4 构建产物规范
Modern.js 的模块工程方案,会并行编译出多种符合社区主流规范的构建产物
模块的编译也是不打包的,更容易引入速度更快的工具比如 esbuild、swc 等。
2.3.5 Monorepo 工程方案
应用和库如果分散在不同项目中开发的话,通过 npm link 调试也比较麻烦,业界的主流方案通过 Monorepo 管理多个子项目, Modern.js 本身就是基于 pnpm monorepo 开发的,同时也将这部分最佳实践收敛到 monorepo 工程方案,默认使用 pnpm 进行包管理, 左侧是它的目录结构, apps 对应的是 MWA 应用 、features/packages 对应是可复用的模块。
右侧内部模块指的是不会发布到 npm、仅在当前仓库下复用的库。它本身不需要构建,同仓库下的应用直接使用它的源码即可。 monorepo 我们也提供了 new 命令,可以选择创建应用或者模块。
2.3.6 更多
除了上面提到的一些最佳实践,Modern.js 还提供了单元测试、集成测试、Visual Testing 等、ESlint 全量规则集等最佳实践。这里不一一展开介绍。可以查阅 Modern.js 文档进一步了解。
2.4 包含:Web 开发全流程
Modern.js 不只是在上述运行时、构建、调试等方面提供了支持,它本身就覆盖了 Web 开发的全流程。
在编码环节,可以通过微生成器启用某个功能或者添加入口,从 SPA 迁移到 MPA。和前面提到的通过研发平台 「低码提效」类似,还可以像图上那样在项目目录下执行 new 命令选择要启用的功能。这个命令会自动重构我们的代码。
通过微生成器按需自动启用的方式,可以放心的将一些功能作为插件提供,也可以控制 Modern.js 初始化项目的体积。
在微前端子应用开发时,通常情况下主应用已经部署上线了,这时候开发子应用就需要结合线上的主应用一起调试,解决方式之一是通过全局代理子应用 JS 到本地,比较麻烦。
在 Modern.js 中,只需要主应用像图中右下角那样启用 DEBUG 模式,之后打开主应用线上链接,在 header 中设置需要开发的子应用信息,server 会自动替换注入到 html 中的子应用列表数据。这样也就可以让线上主应用加载本地子应用。
在运行环节,传统的 Web 开发模式,通常没有提供生产环境运行项目的方式,MWA 项目本身自带产品级的 Server,自己就能产品级的运行自己,比如图上的自动 Polyfill 服务。之前也提过,结合 serverless 平台,可以自动做一些优化,也可以在本地运行模拟生产环境的效果。
2.5 提供:工程标准体系
Modern.js 不只是一个现代 Web 应用开发框架,而是提供了整套的现代 Web 工程体系。
前面已经介绍过,我们将前端开发中涉及的场景收敛到 3 种:应用、模块和 monorepo。
不仅解决了业务模板数量爆炸的问题。融合后的工程类型,比如 MWA 不是多个场景简单叠加,导致工程变的大而全,通过抽象可以做到很轻量,也能更容易交付一些之前不好实现的功能。
2.6 鼓励:定制工程方案
Modern.js 鼓励业务结合自身场景定制垂直的工程方案。
就像前面提到的火山引擎例子一样,封装插件、微生成器、定制出自己的业务工程方案。
关于 Modern.js 六大要素的更多解释和例子,可以到 Modern.js 官网进一步查阅。
三、Modern.js 社区和现代 Web 研发体系
最后我们一起看下除了已经发布的开源项目,还有哪些对现代 Web 开发者有帮助的事情在发起和推进中。
Modern.js 开源项目现在是刚起步的状态,昨天上线的官网,以及最新发的 1.0 版,都是公测状态,还需要更多意见、测试和实践,希望大家多参与社区建设。
Modern.js 的起点是字节内部的现代 Web 工程体系项目,现在大部分代码已经完全转到 Github 上开发,工作流还在建设中。
双月计划、每周计划、缺陷管理等,也都会全面转到 Github 上公开推进。
当前版本还没有包含 Roadmap 上一些重要功能,计划以每周发版的节奏,把这些功能补上。
昨天的分享介绍了 「现代 Web 研发体系」中的其他部分,这些部分也都算是 Modern.js 的重要功能,后续会陆续对外开放,欢迎大家关注。
最后,屏幕上右下角二维码是 Modern.js 官网的地址,可以在官网上通过快速上手和实战教程了解更多 Modernjs 的细节使用部分。
谢谢大家。