浅析前端工程化

766 阅读20分钟

最近在整理一份前端开发所需知识体系,包含语法核心、软件设计、框架源码、性能优化等方面。感兴趣的朋友可以关注下,谢谢。

什么是前端工程化?

前端工程化是指使用软件工程的方法来规范前端项目。

前端工程化是一个概念,一个过程,一个系统,一系列约定和工具,一切能提升前端开发效率、提高前端应用质量的方法和工具也都可以是前端工程化。它的意义在于提高前端开发效率、减少重复工作、提高代码质量和可维护性、降低企业成本。

一般地前端工程化包含项目管理、编码、构建、测试、部署、运行和维护等方面,包括但不限于:软件开发方法、版本管理、模块化、组件化、规范化、自动化、性能优化、用户体验、安全、前端监控与错误追踪。

软件开发方法

软件开发方法(Software Development Methodology)是指一组用于指导软件开发活动的规则、准则和流程。软件开发方法包括需求分析、设计、编码、测试、部署和运行等阶段,每个阶段都有相应的活动和工具,这些活动和工具共同构成了一个完整的软件开发过程。

目前常用的两种软件开发模式:瀑布式开发敏捷开发

瀑布式开发是一种传统的软件开发模型,它得名于其过程像瀑布一样,从上游到下游依次流过各个阶段,每个阶段有明确的界限,不可逆向流动。此模式下,项目从需求分析、设计、实现、编码、测试到维护,每个环节按顺序进行,完成一个阶段后才可进入下一阶段。

敏捷开发以用户的需求进化为核心,采用迭代、循序渐进的方法进行软件开发。在敏捷开发中,软件项目在构建初期被切分成多个子项目,各个子项目的成果都经过测试,具备可视、可集成和可运行使用的特征。换言之,就是把一个大项目分为多个相互联系,但也可独立运行的小项目,并分别完成,在此过程中软件一直处于可使用状态。

版本管理

版本管理(Version Control)是指对软件开发过程中各种程序代码、文档等文件变更的管理。它主要作用是跟踪软件源代码的变更,可以查看文件在历史版本间的差异,恢复到指定的历史版本,实现代码协同开发。目前常用的版本管理工具有 Git、SVN 等。

模块化

首先我们要知道什么是模块?通常在模块化编程中,开发者将程序分解成离散功能块(discrete chunks of functionality),并称之为模块。每个模块具有比完整程序更小的接触面,使得校验、调试、测试轻而易举。精心编写的模块提供了可靠的抽象和封装界限,使得应用程序中每个模块都具有条理清楚的设计和明确的目的。

而模块化是指一种处理复杂系统分解成为更好的可管理模块的方式,它可以把系统代码划分为一系列职责单一,高度解耦且可替换的模块,系统中某一部分的变化将如何影响其它部分就会变得显而易见,系统的可维护性更加简单易得。

前端的模块化又分为 JS 模块化、css 模块化、资源模块化:

  • js 模块化:历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 出现后,在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
  • css 模块化:CSS 模块化的解决方案有很多,常见的三类:一类是彻底抛弃 CSS,使用 JS 或 JSON 来写样式,如 Radium,jsxstyle 等。优点是能给 CSS 提供 JS 同样强大的模块化能力;缺点是不能利用成熟的 CSS 预处理器(或后处理器) Sass/Less/PostCSS。另一类是依旧使用 CSS,但使用 JS 来管理样式依赖,代表是 CSS Modules。CSS Modules 则是通过 JS 来管理依赖,最大化的结合了 JS 模块化和 CSS 生态,API 简洁到几乎零学习成本。发布时依旧编译出单独的 JS 和 CSS。还有一类是 BEM,它旨在通过明确的命名约定和结构化的层级关系来提高 CSS 代码的可读性、可维护性和可扩展性。核心的三大概念:Block(块)、Element(元素)、Modifier(修饰符)。块是独立的实体,拥有自己的意义,可以独立于页面上的其他元素存在。在 BEM 中,块通常对应于页面上的一个组件,如按钮、导航菜单、表单等。元素是块的一部分,没有独立的意义,并且只能在块的上下文中使用。元素用于形成块的整体结构,例如按钮内部的文本或图标。修饰符用于表示块或元素的状态或变体,例如尺寸、颜色、禁用状态等。修饰符通过改变块或元素的外观或行为来提供额外的语义。
  • 资源模块化:任何资源都能以模块的形式进行加载,将项目中的字体文件、图片等可以直接通过 JS 做统一的依赖关系处理。

组件化

组件是描述了 UI 的一部分,例如按钮或复选框。它既可以提高可维护性,也允许代码重用。多个组件也可以组合成更大的组件。

而前端的组件化,其实是对项目进行自上而下的拆分,把通用的、可复用的功能中的模型(Model)、视图(View)和视图模型(ViewModel)以黑盒的形式封装到一个组件中,然后暴露一些开箱即用的函数和属性配置供外部组件调用,实现与业务逻辑的解耦,来达到代码间的高内聚、低耦合,实现功能模块的可配置、可复用、可扩展。除此之外,还可以再由这些组件组合更复杂的组件、页面。

组件化 ≠ 模块化。模块化是从文件层面上,对代码或资源进行拆分;而组件化是从设计层面上,对用户界面进行拆分。前端组件化更偏向 UI 层面,更多把逻辑放到页面中,使得 UI 元素复用性更高。

得益于技术的发展,目前很多框架在构建工具(例如 webpack、vite...)的配合下都可以很好的实现组件化。例如 Vue,使用 .vue 文件就可以把 template、script、style 写在一起,一个 .vue 文件就是一个组件。而如果不想使用任何框架和构建工具,亦可以通过 Web Components 实现组件化,它是浏览器原生支持的组件化标准。

规范化

规范化即是提前约定好的执行标准,用于构建健壮、易维护的程序。

一般地,在我们进行前端开发过程中由我们完成的应该包括需求评审、程序设计、编码、测试、部署、维护这几个流程,那么在前端工程化中就应该对这几部分做出规范化。但现实中如果每个流程都按照规范来走,没有工具辅助,那么可能每天我们得花费大量的时间在执行规范上面。所以大部分公司会做出一些取舍,但是大家都不会绕开编码规范、工程目录结构规范、版本管理规范、文件命名规范。而前端作为多语言项目,代码编写规范上,还分 HTML、JS、CSS 等规范。

编码规范

目前社区上比较流行的编码规范有:

目录结构规范与文件命名规范

目前比较流行的目录结构规范有:

版本管理规范

目前比较流行的版本管理规范有:

除此之外,还有 Git 提交信息规范、Git 钩子规范等,这些规范都是为了提高代码的可读性、可维护性和可扩展性。下面是一些常见的规范:

git-flow 规范

  • 分支规则:命名格式:以/(斜线)隔离字段,-(横线)连接字符,例如:feature/goods-modules/wjh
    • master:生产主分支,需保持稳定性,一般由发布分支合并进来,不直接修改代码
    • release/production:发布分支
    • release/test:预发布环境(UAT)提测分支
    • test:测试环境提测分支
    • feature:新功能分支,规则:feature/功能名/人名,例如:feature/- goods-modules/wjh(从 master 分支拉出来)
    • hotfix:线上 bug 修复分支,规则:hotfix/bug 名/人名(从 master 分支拉出来)
  • 工作流
    • 开发完成:从 feature 或 hotfix 合并至 test 提测
    • 测试通过: 从 feature 或 hotfix 合并至 release/test 进行预发布环境提测
    • 预发布测试通过:从 feature 或 hotfix 合并至 release/production 发布,当本次发布确认稳定后,合入 master 分支

注意:release/test、test 分支作为公共提测分支,永远只进不出,否则可能将测试中未通过的代码发布到线上!

如果是新项目重新创建新分支开发,请按照规范的格式(项目名/分支名),比如 dp/master,有利于统一规范,以及方便配置 gerrit 审核权限。

值得注意的是,区别于传统的 git-flow,该 git 工作流追求最大限度的新代码隔离,以避免多个功能提测时,部分功能未通过却不小心发布到了线上。

尽管如此,即使制定了合理的 git 工作流,我们仍不能完全杜绝问题,比如有人把 test 分支合到了自己的分支,比如在 code review 上没有严格把关,这些问题需要我们更多地去贯彻规范,避免生产事故。

commitlint

提交描述不清晰,会直接导致我们无法理解这个 commit 做了什么事情,以后出了问题,也会增加代码审查的成本,所以推荐的 commit 格式为:git commit -m "type(scope): subject"

可以通过 commitlint 校验 commit 是否符合规范。commitlint 是一个校验 commit message 的工具,通过配置文件可以定义自己的提交规则。

commitlint 配置文件 .commitlintrc.js 示例:

/**
 * commitlint
 * 所有项目通过此模板统一配置,如有变动也统一更新,不在项目中单独修改
 *
 * 格式:
 * git commit -m "type(scope): subject"
 *
 * type-enum:
 * build    编译相关的修改,例如发布版本、对项目构建或者依赖的改动
 * chore    其他修改, 比如改变构建流程、或者增加依赖库、工具等
 * ci   持续集成修改
 * docs 文档修改
 * feat 新特性、新功能
 * fix  修复bug
 * perf 优化相关,比如提升性能、体验
 * refactor 代码重构
 * revert   回滚到上一个版本
 * style    代码格式修改, 比如空格、符号,不是css修改
 * test 测试用例修改
 *
 * 使用示例:
 * git commit -m "feat(商品管理): 新增了虚拟商品功能"
 * git commit -m "fix(埋点): 修复了用户注册的上报字段错误"
 *
 * 详细配置请参考: https://github.com/conventional-changelog/commitlint
 */

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'scope-empty': [2, 'never'],
  },
};

自动化

自动化是指通过预先的配置,让机器代替人工完成一些重复性高、耗时长的任务,从而提高工作效率,减少人为出错的可能性。

前端工程化中,自动化主要体现在构建、测试和部署等方面。

构建

构建是指将源代码转换为可执行程序的过程。前端工程化中,构建通常包括:

  • 编译:将 ES6、TypeScript 等高级语言转换为浏览器能识别的 JavaScript 语言
  • 压缩:去除空格、注释等无用信息,减小文件体积
  • 打包:将多个文件合并为一个文件,减少网络请求次数
  • 预处理:将 CSS、HTML 等文件转换为 JS 模块,便于管理

常见构建工具:

  • webpack:是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。
  • vite:是一个基于任务的 JavaScript 构建工具,支持任务并行执行、文件监听、代码压缩等。
  • Rollup:是一个 JavaScript 模块打包器,可以将小块代码编译成大块复杂的代码,例如库、应用程序或浏览器扩展。
  • Parcel:是一个零配置的 Web 应用打包工具,支持 ES 模块、TypeScript、JSX 等。
  • Gulp:是一个基于流的自动化构建工具,支持任务并行执行、文件监听、代码压缩等。
  • Grunt:是一个基于任务的 JavaScript 构建工具,支持任务并行执行、文件监听、代码压缩等。

测试

测试是指通过自动化工具模拟用户操作,检查代码是否符合预期的行为和功能。前端工程化中,测试通常包括:

  • 单元测试:针对函数或模块进行测试,确保每个函数或模块都能按照预期工作
  • 集成测试:将多个组件或模块组合在一起进行测试,确保它们之间的交互正常
  • 端到端测试:模拟用户在真实场景下的行为,确保整个系统能够按预期运行

CI/CD

CI(Continuous Integration,持续集成)/CD(Continuous Delivery,持续交付/Continuous Deployment,持续部署)属于 DevOps 的概念,指将传统开发过程中的代码构建、测试、部署以及基础设施配置等一系列流程的人工干预转变为自动化。

持续集成(CI)是指自动且频繁地将代码更改集成到共享源代码存储库中的做法。CI 始终是指持续集成,这是一种面向开发人员的自动化流程,有助于更频繁地将代码更改合并回共享分支或“主干”。进行这些更新时,会触发测试步骤的自动执行,以确保合并代码更改的可靠性。

CI/CD 中的 CD 指的是持续部署或者持续交付、持续部署两者都有,这些相关概念有时会交叉使用。二者均与管道中的更多阶段的自动化相关,但有时会分开使用,以说明自动化的程度。选择持续交付还是持续部署取决于开发团队和运维团队的风险承受能力及具体需求。

持续交付不会自动部署到生产环境,持续部署则会自动将更新发布到生产环境。

CI/CD 通常由以下步骤组成:

  • 代码提交:将代码更改推送到共享存储库
  • 代码构建:将源代码转换为可执行程序
  • 代码测试:运行自动化测试,确保代码符合预期的行为和功能
  • 代码部署:将构建好的程序部署到生产环境或测试环境

常见 CI/CD 工具:

  • Jenkin:开源的 CI/CD 工具,支持代码托管、构建、测试和部署等环节;
  • GitHub Actions:GitHub 官方推出的 CI/CD 工具,支持代码托管、构建、测试和部署等环节;
  • Travic CI:开源的 CI/CD 工具,支持代码托管、构建、测试和部署等环节;
  • docker:开源的容器化工具,支持构建、测试和部署等环节;
  • 阿里云 DevOps:提供 CI/CD 解决方案,支持代码托管、构建、测试和部署等环节;
  • 腾讯云 TencentCloud:提供 CI/CD 解决方案,支持代码托管、构建、测试和部署等环节;
  • 华为云 DevCloud:提供 CI/CD 解决方案,支持代码托管、构建、测试和部署等环节。

性能优化

性能优化是指通过减少资源占用、提升页面加载速度等方式,提高用户体验。

一般的性能检查工具:

  • Lighthouse:Chrome 浏览器插件,用于评估网页的性能、可访问性、PWA 等。
  • PageSpeed Insights:Google 提供的网页性能分析工具,用于评估网页的加载速度、资源压缩等。
  • WebPageTest:一个开源的网页性能测试工具,用于评估网页的加载速度、资源压缩等。
  • Webhint:一个开源的网页性能分析工具,用于评估网页的加载速度、资源压缩等。

用户体验

用户体验是指用户在使用产品或服务时,对产品的感受和满意度。前端工程化中,用户体验通常包括:

  • 响应速度:页面加载速度是否快、是否卡顿
  • 稳定性:页面是否稳定运行、是否有崩溃等异常情况
  • 可访问性:是否符合 WCAG(Web Content Accessibility Guidelines)标准,是否支持屏幕阅读器等辅助设备
  • 交互体验:是否易于操作、是否有误导性提示等
  • 兼容性:是否支持不同浏览器、设备等
  • 国际化:是否支持不同语言、不同地区

安全

安全是指保护系统免受恶意攻击或破坏。前端工程化中,安全通常包括:

  • 代码安全:防止代码被篡改、注入恶意代码
  • 网络安全:防止网络攻击、数据泄露等
  • 服务器安全:防止服务器被入侵、数据丢失等

性能监控

性能监控是指通过实时收集和分析数据,发现并解决问题。前端工程化中,性能监控通常包括:

  • 错误监控:记录和统计代码中的异常、错误信息
  • 性能监控:监测页面加载速度、资源加载时间等指标
  • 用户行为监控:记录用户在页面上的操作行为

常见性能监控工具:

  • Sentry:一个开源的错误监控工具,支持 JavaScript、Node.js 等多种语言
  • New Relic:一个应用程序性能管理(APM)工具,提供性能监控、故障诊断等功能
  • DataDog:一个云原生应用性能监控(APM)工具,支持分布式追踪、性能分析、日志管理等功能
  • Lighthouse:一个开源的 Web 应用性能测量工具,用于评估网页的性能、可访问性、PWA 等
  • Google Analytics:Google 提供的网站流量分析工具,可以了解用户的行为、访问来源等

示例

下面我们通过一个vue示例看看前端工程化实现。

初始化

首先,我们通过vue提供的手脚架创建并初始化项目

npm create vue@latest

7350549827674079244-1.png

安装依赖并启动

npm i && npm run dev

好了,现在我们已经实现了前端工程化了。是的vue框架里面已经集成了一系列工程化工具,使得我们的项目支持模块化、组件化、规范化、自动化开发。

那么vue做了什么?又集成了什么呢?

ES6

通过设置package.json中type=“module”定义项目使用ESM规范,实现js模块化

7350549827674079244-2.png

css scoped、css modules

vue实现css模块化有两种方式,一种是使用组件作用域 CSS,当 style 标签带有 scoped attribute 的时候,它的 CSS 只会影响当前组件的元素,和 Shadow DOM 中的样式封装类似。它的实现方式是通过 PostCSS 给声明了scoped的样式中选择器命中的元素添加一个自定义属性,再通过属性选择器实现作用域隔离样式的效果。

<style>
.example[data-v-f3f3eg9] {
  color: red;
}
</style>

<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>

另一种则是CSS Modules,一个 <style module> 标签会被编译为 CSS Modules 并且将生成的 CSS class 作为 $style 对象暴露给组件:

<template>
  <p :class="$style.red">This should be red</p>
</template>

<style module>
.red {
  color: red;
}
</style>

vue默认使用scoped方式,而要启用css modules需要在插件vite-plugin-vue额外的配置。

资源模块化

vue使用vite作为构建、打包工具,使得静态资源可以像模块一样通过import引入,以此来管理项目对静态资源的依赖。

import imgUrl from './img.png' // imgUrl 在开发时会是 /img.png,在生产构建后会是 /assets/img.2d8efhg.png
document.getElementById('hero-img').src = imgUrl

组件化

Vue 实现了自己的组件模型,使我们可以在每个组件内封装自定义内容与逻辑。一般地,一个vue页面就是一个巨大的组件,然后由上自下拆分出一个个独立结构(dom)、独立表现(css)、独立行为(js)的组件。可以看成是层层嵌套的树状结构:

7350549827674079244-3.png

规范化

我们可以看到vue项目已经有了良好的目录结构了,我们可以遵循它。

7350549827674079244-4.png

从项目的初始化流程可以看到,我们可以集成eslint、prettier等。然后选择(或自己制定)我们的代码规范,通过eslint做代码质量检测、prettier做代码格式工具。

自动化

  1. 编译、打包:通过vite实现自动化编译与打包;
  2. 测试:通过vitest我们只要创建测试集即可实现自动化测试,还有cypress能够实现vue的视图表现测试,与vitest一样创建测试集即可实现自动化测试。

通过huskycommitlint做提交校验

安装husky

npm install --save-dev husky
npx husky init

husky是一个Git hooks工具,它提供了一系列git钩子,当初始化完成后,我们可以看到在项目根目录下生成了一个.husky目录,并在package.json添加了一个运行脚本配置。

7350549827674079244-5.png

现在我们修改.husky下面的pre-commit文件为npm run lint,然后提交一个更改

7350549827674079244-6.png

可以看到,变更在commit仓库之前触发了pre-commit钩子,执行了npm run lint。

安装commitlint,并搭配Angular的commit规范

npm i @commitlint/config-conventional @commitlint/cli -D

然后在.husky下添加commit-msg钩子文件,并写入:npx --no-install commitlint --edit "$1",添加配置文件.commitlintrc.json

{ "extends": ["@commitlint/config-conventional"] }

现在我们提交一个abc的变更,会提示我们commit不规范

7350549827674079244-7.png

改为chore: abc就可以。

CI/CD

CI(Continuous Integration,持续集成)/CD(Continuous Delivery,持续交付/Continuous Deployment,持续部署)属于DevOps的概念,指将传统开发过程中的代码构建、测试、部署以及基础设施配置等一系列流程的人工干预转变为自动化。

持续集成(CI)是指自动且频繁地将代码更改集成到共享源代码存储库中的做法。持续交付和/或持续部署(CD)是一个由两部分组成的过程,涉及代码更改的集成、测试和交付。持续交付不会自动部署到生产环境,持续部署则会自动将更新发布到生产环境。

CI始终是指持续集成,这是一种面向开发人员的自动化流程,有助于更频繁地将代码更改合并回共享分支或“主干”。进行这些更新时,会触发测试步骤的自动执行,以确保合并代码更改的可靠性。

CI/CD 中的“CD”指的是持续交付和/或持续部署,这些相关概念有时会交叉使用。二者均与管道中的更多阶段的自动化相关,但有时会分开使用,以说明自动化的程度。选择持续交付还是持续部署取决于开发团队和运维团队的风险承受能力及具体需求。

通过GitHub Actions实现CI/CD

我们通过Github Actions实现代码合并或推送到主分支,dependabot机器人升级依赖等动作,会自动触发测试和发布版本等一系列流程。

在项目根目录创建.github/workflows文件夹,然后在里面新建ci.yml文件和cd.yml文件

在ci.yml文件中写入:

name: CI

on:
  push:
    branches:
      - '**'
  pull_request:
    branches:
      - '**'
jobs:
  linter:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm ci
      - run: npm run lint
  tests:
    needs: linter
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v2
        with:
          node-version: 16
      - run: npm ci
      - run: npm run test:unit

上面配置大概意思就是,监听所有分支的push和pull_request动作,自动执行linter和tests任务。

现在我们将代码推送到GitHub上面,在Actions页签下面就可以看到我们的CI脚本运行了,正在对我们推送的变更做自动化校验与测试了。

7350549827674079244-8.png

在cd.yml文件写入:

name: CD

on:
  push:
    branches:
      - 'master'
  pull_request:
    branches:
      - 'master'
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Use Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 16
      - name: Strict Install dependencies
        run: npm ci --ignore-scripts
      - name: Run build task
        run: npm run build
      - name: deploy pipelines
        uses: cross-the-world/ssh-scp-ssh-pipelines@latest
        with:
          host: ${{ secrets.DC_HOST }}
          user: ${{ secrets.DC_USER }}
          pass: ${{ secrets.DC_PASS }}
          scp: |
            ./dist/* => /www/wwwroot/web/vue/test
          last_ssh: |
            nginx -t
            nginx -s reload

上面配置大概意思就是,监听所有主分支的push和pull_request动作,通过ssh-scp-ssh-pipelines将部署文件推送到服务器上,如果还需要重启服务等操作可以在last_ssh中配置上需要执行的指令。

此外DC_HOST、DC_USER、DC_PASS需要在GitHub上做配置。

7350549827674079244-9.png

结语

篇幅、文笔有限,许多的细节没有体现出来。前端工程化是一个庞大的架构,需要我们深入了解,不断实践,以此寻找最佳实现。文中有错漏之处,欢迎指出与交流。

参考 es6.ruanyifeng.com/#docs/modul… juejin.cn/post/697181… xiangzhihong.blog.csdn.net/article/det… segmentfault.com/a/119000003… www.redhat.com/zh/topics/d… docs.github.com/zh/actions