概要
7月,字节跳动 Web Infra 做过一次主题为《迈入现代 Web 开发(字节跳动的现代 Web 开发实践)》[1]的分享,在分享中我们梳理了「传统前端技术栈」的典型组成部分,展示了其中每个部分都存在的瓶颈问题。并介绍了在这些问题的驱动下,业x x x x界正在发生从「传统 Web 开发范式」到「现代 Web 开发范式」的「范式转移」。在这个分享的最后预告了 Modern.js 的开源项目。
10 月 27-28 日的稀土开发者大会[2]上,字节跳动 Web Infra 正式发起 Modern.js[3] 开源项目。在专场分享《介绍 Modern.js —— 现代 Web 工程体系》中,第一部分先介绍了业界和字节内部的前端开发、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 开发实践》[1]的分享,在分享中我们梳理了「传统前端技术栈」的典型组成部分,展示了其中每个部分都存在的瓶颈问题。
![]()
也介绍了在这些问题的驱动下,业界正在发生从「传统 Web 开发范式」到「现代 Web 开发范式」的「范式转移」。
![]()
在这个分享的最后也预告了 Modern.js 开源项目。
![]()
昨天上午的主题演讲[6]中,字节跳动正式发布了 Modern.js[7]。今天的专场分享,我想结合字节内部的变革和实践,介绍基于 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 内置:前端工程最佳实践
![]()
对于第三个要素,简单列举几个 Modern.js 内置的前端工程最佳实践
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 的重要功能,后续会陆续对外开放,欢迎大家关注。
![]()
最后,欢迎大家扫码入群交流,也可以在官网上通过快速上手和实战教程了解更多 Modernjs 的细节使用部分。\
谢谢大家。
官网:https://modernjs.dev/
Github: https://github.com/modern-js-dev/modern.js
Reference
[1]《迈入现代 Web 开发(字节跳动的现代 Web 开发实践)》:https://zhuanlan.zhihu.com/p/386607009
[2]稀土开发者大会: http://conf.juejin.cn/xdc2021
[3]Modern.js: https://modernjs.dev/
[6]昨天上午的主题演讲: https://conf.juejin.cn/xdc2021
[7]Modern.js: https://modernjs.dev/
点击直达官网,了解更多信息。