3 年前端所需要了解的工程化体系(一)

在谈前端工程这个概念之前,我们先回顾一下2000年以后前端技术的演进,主要分四个阶段:

  • 页面开发阶段(2000~2009) :在ECMAScript 2009发布之前,很多前端工作都是以单页面开发为主,需要重点解决兼容性问题,靠工具库提高效率,代表技术如:jQuery等。
  • 模块化开发阶段(2009~2015) :以模块化开发为主,要解决性能问题,靠构建工具和UI框架提高效率,特别是基于Node.js的各种前端工具,代表技术如:Angular、React、Less、Gulp等。
  • 应用开发阶段(2015~2022) :以应用开发为主,要解决工程化问题,靠自动化工具和跨平台提高效率,代表技术如:Webpack、React Native、Flutter等。
  • 智能辅助开发阶段(2022以后) :将前端工程化与 AI 结合,将重复冗余的流程通过智能化实现开发提效,实现智能代码生成、评审、智能编写单测、代码语言转化等。

什么是前端工程化?

前端工程化这个词,是国内前端圈子2018年前后才出现的,大概的意思是将(后端已经比较成熟的)许多软件工程概念、实践、工具引入前端开发,提升开发效率。

在前端开发和运维过程中,以降低成本、提高效率、保障质量为目的,通过一系列规范、工具、流程(分别对应软件工程中的方法、工具和过程)作为手段的实践体系。用一句话概括即前端领域内一站式解决方案。

从一个完整的项目流程来看前端工程化

一、技术选型

  1. 常规后台等项目采用什么技术?
  • SPA(单页面应用)
  • 比如React、Vue
  • 无虚拟 dom 的 Solidjs、Sveltejs
  1. 门户网站一般采用什么技术?
  • 考虑首屏加载速度和 SEO,可以使用 SSR,
  • SSR 的技术选型:Nextjs,Nuxtjs
  1. 小程序、原生、跨端
  • Taro 、 uni-app、Android、iOS、Flutter、ReactNative、Weex、H5或者Hybird 方案
  1. BFF的优势?
  • 复杂的前端展示,将数据获取和组装的负责逻辑在BFF端执行,提高页面响应率
  • 减少服务端资源消耗
  • 数据缓存、接口安全校验等

二、规范统一

1、eslint + prettier + stylelint + commitlint + husky

  1. ESLint:用于检测 JavaScript 代码中的潜在问题、错误和风格不一致,可以根据规则集对代码进行静态分析。ESLint 可以帮助团队确保代码的质量和规范性。
  2. Prettier:用于自动格式化代码,使其符合统一的代码风格。Prettier 可以消除团队成员之间关于代码格式的争论,确保代码风格的一致性。
  3. Stylelint:类似于 ESLint,但专门用于检测 CSS、Less、Sass 等样式表文件中的问题。Stylelint 可以帮助团队维持一致的样式表编码风格。
  4. Commitlint:用于检测 Git 提交信息的格式和内容,以确保提交信息遵循一致的规范。Commitlint 可以提高代码版本管理的可读性和维护性。
  5. Husky:Husky 是一个 Git hooks 工具,可以在 Git 钩子事件发生时触发相应的操作。结合上述工具,Husky 可以用于在代码提交前运行 ESLint、Prettier、Stylelint、Commitlint 等检查,从而确保代码质量和规范性。

2、TypeScript

  1. 提供类型检查,在开发阶段检测潜在的类型错误,提高代码
  2. TS 提供了代码补全、静态分析等
  3. 支持最新的 ECMAScript 标准,并可以编译成向下兼容的 JavaScript 版本
  4. 在大型项目中,使用 TS 可以更好的管理复杂性代码,减少代码错误

3. 项目结构规范

  1. 参照 antd-pro
arduino 复制代码
- .umirc.ts           // Umi 配置文件
- config              // 环境配置文件夹
- mock                // 数据 mock 文件夹
- public              // 公共资源文件夹
- src                 // 源代码文件夹
  - assets            // 静态资源,如图片、字体等
  - components        // 通用组件
  - layouts           // 布局组件
  - models            // 全局状态管理(dva)
  - services          // 服务,如请求后端API
  - utils             // 工具函数
  - locales           // 国际化文件
  - global.less       // 全局样式文件
  - global.ts         // 全局配置文件
  - App.tsx           // 应用入口
  - index.tsx         // 页面入口
- tests               // 测试文件夹
- .editorconfig       // 编辑器配置
- .eslintrc.js        // ESLint 配置
- .prettierrc.js      // Prettier 配置
- .stylelintrc.js     // Stylelint 配置
- package.json        // 项目依赖和配置文件
- proxy.config.js     // 开发环境代理配置
- README.md           // 项目说明

4. UI 样式统一

  1. 标准的 UI 设计规范 ,组件库的选取
  2. antd、vant、element-ui、TailwindCSS 等
  3. 自研前端组件库等

三、自动化测试

1. 单元测试

  1. 通俗的讲,在前端领域,单元测试就是 测试的单个模块,可以是一个函数,一个组件。
  • Jest 为例:
javascript 复制代码
// utils.js
export const add = (a, b) => {
  return a + b;
};

// utils.test.js
import { add } from './utils';

describe('add', () => {
  test('adds two numbers together', () => {
    const result = add(1, 2);
    expect(result).toBe(3);
  });

  test('returns a number', () => {
    const result = add(1, 2);
    expect(typeof result).toBe('number');
  });
});

在这个示例中,我们定义了一个名为 add 的工具函数,并使用 Jest 框架编写了两个测试用例,分别测试了函数的计算和返回值。

  • @testing-library/react
    • React 生态系统中的一个测试工具库,可以用测试组件等
    • testing-library/react 的设计理念是通过模拟用户行为来测试组件,而不是直接操作组件的状态和属性,以提高测试的质量和真实性。
    • 可以结合 jest 使用

CRA 中的 demo:

javascript 复制代码
import { render, screen } from '@testing-library/react';
import App from './App';

test('renders learn react link', () => {
  render(<App />);
  const linkElement = screen.getByText(/learn react/i);
  expect(linkElement).toBeInTheDocument();
});

APP.tsx中的源代码

npm run test 结果:

2. E2E 测试

  1. E2E 测试是端到端测试(End-to-End Testing)的缩写,是一种软件测试方法,旨在模拟真实用户在应用程序中的操作流程,从用户界面或用户体验的角度对整个应用程序进行测试。E2E 测试通过模拟用户行为和交互来验证应用程序在实际使用场景下的功能、性能和稳定性。

四、构建工具的选择

在探讨这个问题之前我们先思考如下几个问题

  1. 为什么前端需要构建工具?
  2. 浏览器认识我们的 .jsx .vue .less .scss 等这些文件中的代码么?
  3. 为什么现在 npm run start可以运行整个项目?按下保存之后页面会自动更新而数据不会丢失?
  4. start or build 之后发生了什么?

......

1、js 模块化的发展史和构建工具的变化

javascript 语言设计之初,只是作为一个简单的脚本语言用来丰富网站的功能,并不像其它语言一样有 module 的概念。发展到现在的模样也经历了相当长一段时间。

这段时间,可以简单归纳为:

  • 青铜时代 - no module;
  • 白银时代 - cjs、amd、cmd、umd、esm 相继出现;
  • 黄金时代 - 组件模块化;
1-1、青铜时代

没有 module 的概念,js 无法在语言层面实现模块之间的相互隔离、相互依赖,之恶能开发人员手动处理。而且很容易出现全局污染和依赖管理混乱的问题。

常见的方案有:

  • 通过对象、iife(或者闭包)的方式实现简单的模块隔离
  • 通过手动维护 script 的顺序来确定模块之间的依赖关系,常见的就是 jquery.js 要放在 script 的最上面
1-2、白银时代

chrome v8 引擎 和 node 的横空出世,给前端带来了无限的可能。

同时,javascript 的模块化标准也有了新的发展:

  1. commonjs 规范,适用于 node 环境开发。
  2. amd、cmd 规范,适用于浏览器环境。
  3. umd,兼容 amd、commonjs,代码可以同时运行在浏览器和 node 环境。
  4. ESM,即 ES6 module(这个时候还不是很成熟);

同时还出现了 less、sass、 es6、typescript 等新的东西, 前端角色也开始承担越来越重要的作用,慢慢的独立出来。

有了 node 提供的平台,大量的工具开始涌现:

  • less / sass 插件,可以将 less / sass 代码转化为 css 代码;
  • babel,可以将 es6 转化为 es5;
  • typescript,将 ts 编译为 js;
  • ...

这个时候,我们可以将上面的的这些操作配置成一个个任务,然后通过 Grunt / Gulp 自动执行任务。

1-3、黄金时代

基于 Angular、Vue、React 三大框架和 Webpack 的使用,组件模块化成为前端开发的主流模式。同时 ESM 规范也原来越成熟,被更多的浏览器支持。

以 react 和 webpack 为例,通常我们会将一个应用按照功能划分拆分成一个个的路由组件、页面组件、业务组件等。一个组件对应一个源文件,然后通过 webpack 将这些文件打包。在开发过程中还会通过Webpack 开启一个 local server,通过 socket 推送,实时查看代码的运行效果。

Webpack 是一个静态模块打包器,它会以 entry 指定的入口文件为起点,分析整个项目内各个源文件之间的依赖关系,构建一个模块依赖图 - module graph,然后将 module graph 分离为多个 bundle。在构建 module graph 的过程中,会使用 loader 处理源文件,将它们转化为浏览器可以是识别的 js、css、image、音视频等。

随着时间的发展, Webpack 的功能越来越来强大,也迎来诸多对手。

scss 复制代码
Webpack1
|
|
Rollup 出现(推崇 ESM 规范,可以实现 tree shaking, 打包出来的代码更干净)
|
|
Webpack2(也实现了 tree shaking, 但是配置还是太繁琐了)
|
|
Parcel (号称 0 配置)
|
|
Webpack4(通过 mode 确定 development 和 production 模式,各个模式有自己的默认配置)
|
|
Webpack5(持久化缓存、module federation)

Esbuild(采用 go 语言开发,比 Webpack 更快)

Vite(推崇 ESM 规范,开发模式采用 nobundle,更好的开发体验)

2、bundle vs unbundle

这些构建工具,根据是否会将源文件打包成一个 bundle,可以分为 bundle 类型构建工具和 unbundle 类型的构建工具。

bundle 模式

bundle 类型构建工具,典型的有 Webpack、Rollup、Parcel、esbuild。

unbundle 类型的构建工具,典型的有Snowpack、 Vite。

不过 Vite 当前也只是开发模式下采用 unbundle 模式,生产模式下依旧推荐使用 bundle 模式。

开发环境借助于浏览器对 EMS 的兼容和 esbuild,生产环境借助于 rollup,后序会迭代成 rolldown


bundle 模式,最显著的特点就是需要在打包构建时以入口文件为起点,分析各个源文件之间的依赖关系,构建一个模块依赖图,然后根据一定的策略将这个模块依赖图分解为多个 bundle,多个 bundle 之间也存在依赖关系。

从上面的了解中,可以看出,整个打包构建的过程中,构建模块依赖图和分离 bundle 是耗时大户 ,涉及到url 解析、文件读写、转换源文件、基于 AST 解析源文件来分析依赖关系、代码压缩等,这就导致项目规模越大,打包耗时越久。这一点在本地开发和发布上线过程中,影响尤为明显,一直备受开发、测试人员吐槽。

针对 bundle 类型的构建工具优化策略有:

  • 缓存:webpack5 的持久化缓存
  • 使用更高效的语言:esbuild 使用 Go 语言,swc使用 rust, swc可以替代 babel。
  • 缩小打包构建的范围;
  • 多线程;
unbundle 模式

unbundle 模式目前的应用场景主要是本地开发。它最大的特点就是快,dev server 启动往往可在几秒内完成,而且热更新速度也比 bunlde 类型的构建工具快。

核心机制是借助了浏览器对 ESM 规范的支持,源文件的依赖关系由浏览器解析,不再需要构建工具自己去处理。这就要求浏览器请求的文件必须符合 ESM 规范。在实际的项目开发中,我们的业务代码没有问题,可以完全遵循 ESM 规范来写,但是第三方库就无法保证了。如 react,因此浏览器在发起请求之前,必须对不符合 ESM 贵发的第三方库做预处理,将其转成符合 ESM 规范的代码。

这个过程,在 Vite 中称为预构建。过程如下:

  • 进行预构建,提前将项目的三方依赖格式化为 ESM 模块;(此处借助于middleware中 esbuild的 plugin 来完成)
  • 启动一个 node 服务;
  • 打开浏览器,去访问 index.html;
  • 基于浏览器已经支持原生的 ESM 模块, 逐步去加载入口文件以及入口文件的依赖模块;

1、 bundle 模式下转换过程是在 build 过程中完成,是打包构建耗时较久的罪魁祸首之一。

2、而 unbundle 模式下,转换过程是在浏览器发起请求以后,由 server 端借助 middleware 中的 plugin 完成的。

3、由于转换过程也需要消耗一定的时间,导致 unbundle 模式下,首屏和懒加载的性能会有一定的影响。

3、几种常见构建工具的对比

Rollup:

  • 特性: Rollup 推崇 ESM 模块标准开发,这个特点借助了浏览器对 ESM 的支持;
  • 优点: 生成代码量小、干净,是作为组件库开发的首选;(react、vue 都是 rollup 打包的)
  • 缺点: 不支持 HMR;拓展性比较弱;

Vite:

  • 特性:开发模式下借助浏览器对 ESM 的支持,采用 unbundle 的方式进行构建,能提供极致的开发体验;生产模式下借用 Rollup 进行构建;
  • 优点:开发模式下 dev server 启动和热更新都比较快, 开发体验好;
  • 缺点:目前生态还不如 Webpack;本地开发模式,首屏、懒加载响应速度对比 Webpack 会慢;二次预构建会对开发体验造成影响;

Webpack:

  • 优点: 大而全;生态丰富;配置多样、灵活;代码分割和静态资源导入有着"先天优势";
  • 缺点: 上手成本较高;随着项目规模的变大,构建速度越来越慢;不适合组件库的打包

Esbuild:

  • 特性: Go 语言开发,可以多线程打包,可充分利用多核 cpu 优势;
  • 优点:快;
  • 缺点: 无法修改 AST;不支持 HMR;不支持自定义代码拆分;插件之间兼容性问题较差;

4、一些新兴的构建工具

Rspack:

rspack是字节跳动出品,用于解决他们公司巨石应用时打包时速度慢的而开发出来的打包工具,也是用rust写的。其支持react/vue,并且兼容webpack的loader,但是并不能完全兼容- -。

Turbopack:

号称比 webpack 快 700 倍,由Vercel公司推出,基于 Rust 语言,并于 Nextjs 13 版本以上内置。

它将为闪电般快速的 HMR 提供动力,并天然支持 React Server Component,Typescript,JSX,CSS 等也一样。

但是很可惜,它是react/nextjs独享打包工具,vue开发者们就没这个福气使用了。

五、CI/CD

名词解释:

CI 是持续集成(Continuous Integration)

CD 是持续交付/持续部署(Continuous Delivery/Continuous Deployment)

常规公司的解决方案:

现在大部分的中小型公司部署前端代码都是比较简单的,主要步骤如下:

首先,通过脚手架提供的命令npm run build打包前端代码,生成dist文件夹;

最后,将dist文件夹丢给后台开发人员放在他们的工程里面,随后台一起部署;现在普遍是前后端分开部署,因此,利用nginx起一个web服务器,将dist文件夹放到指定的路径下,配置下nginx访问路径,对于请求接口使用proxy_pass进行转发,解决跨域的问题。

其实,前端部署不止这么简单。

这其实是一个非常严肃且复杂的问题,因为这关系到线上生产环境的稳定

更加高端一点的操作,是利用CI/CD + Docker + Kubernetes进行自动化部署,

并且CI 阶段支持代检测,自动化测试等。完整的集群化管理支持单个 pod 的重启,监控,日志输出,以及灰度发布、负载均衡、资源缓存等。

本次分享先不涉及自动化部署和容器集群化管理

gitlab-ci文件的优化

本期介绍的是从一个项目开始的第一步技术选型 开始,到规范统一测试工具构建工具 的选择,和 CICD 部署发版。到这为止整个项目就算结束了么?

并没有~

工程化的后续流程还会围绕着发版之后的 性能监控、错误上报、埋点上报 开始,以及拿到采集数据后的性能优化 。还有一些历史项目的重构微前端 等。

相关推荐
沉默璇年6 小时前
react中useMemo的使用场景
前端·react.js·前端框架
2401_882727576 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
前端李易安7 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js
红绿鲤鱼7 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
loey_ln9 小时前
webpack配置和打包性能优化
前端·webpack·性能优化
zhenryx10 小时前
前端-react(class组件和Hooks)
前端·react.js·前端框架
Thomas游戏开发11 小时前
Unity3D 逻辑服的Entity, ComponentData与System划分详解
前端框架·unity3d·游戏开发
Amd79412 小时前
Nuxt.js 应用中的 webpack:compile 事件钩子
webpack·自定义·编译·nuxt.js·构建·钩子·逻辑
三天不学习17 小时前
前端工程化-node/npm/babel/polyfill/webpack 一文速通
前端·webpack·npm
前端青山18 小时前
webpack进阶(一)
前端·javascript·webpack·前端框架·node.js