2020 吾辈在公司推动的前端技术演进
本文最后更新于:2021年7月9日 下午
上古时期
吾辈来的时候已然不多,但部分 mobile 嵌入的静态页面仍然是这种形式
后端模板(JSP/FreeMarker)+ 前端静态 HTML 页面。那是个前端开发非常卑微的时代,除了还原 UI 和兼容性之外,所有业务逻辑、路由跳转、状态管理、部署维护都由后端包办完成。前端能做的事情非常有限,UI 还原、CSS 样式以及浏览器兼容性(例如传统前端需要掌握的精确到 1px 的兼容到 IE6 的高保真切图技巧)是所需的关键技能点,或许还要负责 UI 相关的任务。但这其中,许多更偏向于「手艺人」的工作,而非工程师的技术价值体现,更重要的是,它无法被沉淀为技术人的长期价值。
参考: 前端为什么会越来越复杂?
CSS 不是正交的,所以很麻烦,参考: CSS 为什么这么难学?
问题
- 难以应对大规模的项目开发,几乎没有工程化可言
- 前后端耦合导致修改、发布困难(就我们公司有个 gw 项目就是反例)
- 前端能做的事情非常有限,几近是后端的附属(App 有趣的一点就是极大的增强了客户端的能力,许多独立 App 开发者的工具类产品可能对后端的依赖极少)
跨入现代前端: vue + webpack
- 框架: vue
- 项目组织: git 分支 + 目录
在吾辈进来的时候基本是这样的
无论如何,前后端分离都已然是大势所趋,公司的项目也不例外,也从 jq 升级为了 vue。vue 是一个及其新手友好的框架,官方文档在前端开源框架中无出其右,所以选择它对招聘确实有一定的帮助(准入门槛低)。
但这里确实存在一些问题,主要如下
- vue 和 ts 结合的不好,所有前端项目,不是已经用了 ts,就是在走向 ts 的路上(去年 vue3 选择使用 ts 重写 v3)
- 这是一个设计失误,因为 vue 作者更相信另一个 facebook 的静态类型脚本语言 flow.js,但它最终失败了
- vue 不是一个创新者,更像是一个现有技术的整合者,更新相比于 react v17/angular v11 慢得多(v3)
- vue 和 ant design 结合的不好,因为蚂蚁金服使用 react 实现,vue 版本并非官方实现(关于这个 UI 框架的选择其实令吾辈有些困惑)
- 项目组织方面使用 git 分支 + 目录的形式可能是模仿了后端,但由于目录之间并非模块的形式加上 git 没有规范可言导致没有利用本地模块的优势反而只有劣势
- 有些项目使用原生 webpack 进行配置打包,而这实际上非常难以维护,参考: webpack 为什么这么难用?
典型项目: 前端旧项目
静态类型: 引入 typescript
- 语言: typescript
- 项目组织: 分散的 git 项目
由于使用目录+分支管理的项目组织非常麻烦,所以后面的一些项目采用了每个业务创建一个 git 项目,然后最终将之打包整合到官网(其实就是不同的子路由),独立每个项目的开发和部署。而后希望通过 ts 的静态类型增强前端开发的效率及开发体验(主要是 IDE 在提示、重构和导航方面的支持),所以将之引入了进来,吾辈有自信可以解决任何遇到的 ts 问题。
但老实说,问题仍然没有完全解决
- ts 和 vue 2 结合的不好,甚至 vue 3 中 ts 也没能解决模板层面的问题,参考: web-types 讨论
- 分散的 git 项目之间如果存在相同代码,比较难以共享(但因为有 npm 私服所以还不太明显)
- 组件粒度较粗,比较小的组件难以抽取
典型项目: 新管理后台
替换框架为 react
事实上,引入 react 没有 ts 那么安全,因为这次吾辈不能说吾辈有自信可以解决任何遇到的 react 问题,因为吾辈也没有实际生产环境的经验。但吾辈仍然引入了,有以下几个考量
- 更好的结合 ts 使用
- 直接使用官方维护的 ant design for react 版本
- 相比于 vue 好得多的 IDE 支持
- 更好的开发大型项目(报告系统)
- 尝试变化,使用最新的技术
但这仍然并非终点,在报告系统项目中,单个前端项目的代码量首次达到了 2w+ 行(至今已到 4w+),分割模块势在必行,这里就提到了需要使用到 lerna 了。
典型项目: 公开图库
引入 lerna 以使用 monorepo
- 项目组织: monorepo
Java 后端的项目天然是 monorepo(因为 maven 的原因),而前端在很长时间内都没有类似的工具,直到吾辈遇到了 lerna。这点某位离职的同事有曾提及,但直到几个月后报告系统第一版基本结束时进行重构才真正实用。
使用它吾辈改进了以下几点问题
- 使用 monorepo 更好的组织模块,保证项目在超过 4w 行代码、20 个模块时仍然保持可维护性
- 更简单的共享和复用代码,只要抽离一个模块即可在所有模块直接引用它了
- 更加容易统一整个项目的技术栈,引入一个依赖的不同版本几乎不可能出现了
- 更加容易容易统一项目的配置,保证整个项目的代码风格都是一致的(例如 eslint/prettier)
典型项目: 旧版报告系统
引入 gh-pages 简化发布
事实上,生产环境的发布一直非常麻烦,即便建宏已经实现了两版的发布系统,但目前使用体验仍然不算好(gw 项目必须要用),但前后端分离的项目却不尽然。后来在一个偶然的情况下,吾辈发现了一种更高效的方式: 使用 gh-pages 发布前端项目,同时喜获冰淇淋一个。
当然,由于后来主要在开发 electron 客户端,web 项目在生产环境的发布变得较少,但这件事仍然证明只要将现有的工具整合起来,仍然可能极大的提高生产力
典型项目-大屏看板
rushstack: 标准化的前端 monorepo
- 项目组织: lerna monorepo => rushstack
虽然 lerna 可以分割模块,但它并没有所谓的最佳实践,事实上,吾辈在微软的 monorepo 工具 rush 的相关项目 rushstack 中找到了一种最佳实践,在形式上有许多参考价值(虽然由于大而全且包含许多问题的原因导致没有 lerna 使用广泛),最新的两个生产项目均已重构成这种形式的 monorepo 项目,稍晚一些会将它们合并,便于之后它们的整合。
在 rushstack 的介绍中有这么一段话非常有趣:
灵活性有其缺点。Node.js 工具因其令人困惑的选项而臭名昭著:选择您的编译器、linter 工具、打包工具、包管理器、任务引擎、单元测试工具、测试断言库等。一旦决定(下了赌注),整合所有这些组件就变成了自己的软件项目。随着规模的扩大,这些成本可能会迅速增加!
核心就是前端定制需求过多,要求工具链非常灵活,进而导致无法标准化(和现在的报告系统业务面临的问题多么相似。。。)
典型项目 miis
架构
统一和规范
- 定义统一的业务目录的结构,形成约定俗成
- 通过更高层的 cli 抽象强制统一
lib
的项目结构,例如入口文件一定是lib/src/index.ts
,出口一定是dist/index.js
,而打包出来的一定默认支持esm/commonjs
- 使用 prettier+git hooks 统一项目中的代码风格
- 通过 syncpack 统一多个模块之间的依赖版本
- 通过多个模块中的 npm script,例如启动 web 项目的开发环境是
start
,启动 electron 开发环境则是dev:win/mac
,打包项目是build
,而libs
的模块打包发布是pub
,打包 electron 项目是pkg:*
分层和解耦
- 通过 monorepo 分割 electron 项目不同的进程,将之作为单独的 nodejs 项目和 react 项目管理和发布
- 通过使用 api class 的形式,将与后端的连接封装在单独的逻辑层,定义参数、返回值的类型,业务层直接引用相关的单例对象调用方法即可
吾辈也好奇现代前端框架强绑定、重 UI 层的现在,如何更好的分离 UI 与业务逻辑
优化
- 使用 lerna 的
--include-dependencies --stream
参数尽可能按照依赖顺序并发运行命令,例如打包所有模块 - 为了减少安装依赖上的麻烦,尝试过
nrm/yrm
改源,npmrc
配置镜像,但最终还是要求所有前端必须能够使用 SSR+透明代理工具,否则无法启动项目 - 在公司 blog 上分享一些自己的心得和感触
- 通过使用 docsify 提升文档维护者的体验,使用 VSCode 编辑文档,然后使用 git 提交便自动部署了
- 引入 storybook 为通用组件编写交互式文档
其他
- 公司前端文档: 公司的前端项目在线文档,使用 git + markdown 增强开发者编辑体验,希望更多人在更好的维护它
- common-util: 前端工具库,近期使用 monorepo 将之彻底重构
至少优化了以下几项- 清理不需要的功能,减少了 60% 的代码
- 使用 monorepo 增强项目的可维护性
- 支持 nodejs 引入
- 使用 monorepo 分割打包减小在项目中的引入成本 – babel 的 567 三个版本转变的惨剧历历在目
- 找到对用户更友好的使用方式 class vs function – 面向对象 vs 函数式
- web-logger: 前端日志,已在报告系统及之后后所有的前端项目中实用,目前还未支持 nodejs