前端进阶垫脚石-前端工程化
什么是前端工程化
前端工程化,就是降本提效的体现
广义上,前端工程化包含一切以降低成本、提高效率、保障质量为目的的手段
通过一系列的规范、流程、工具达到研发提效、自动化、保障质量、服务稳定、预警监控等
一个优秀的前端工程师,需要对所开发项目的效率、性能、质量等工程化维度,去制定和实施技术优化指标,只有具备这方面能力,才能应对和优化复杂项目,保证团队高效产出
为什么要进行前端工程化
项目发展过程中,随着项目成员增加,系统复杂度上升,会引发如下问题:
1.系统质量的下降
项目开发人员频繁变更,系统功能新增和迭代,都会导致缺陷的增加,需要通过系统上线前的质量监测来尽早发现问题
为降低系统复杂度,到了一定阶段,对系统进行局部或者整体的架构调整,引入新技术,但也可能引发新问题:
- 部分功能无法与新技术兼容
- 影响面广,可能有缺陷无法及时发现
2.开发效率的下降
系统上线之后,开发工作内容变得复杂,需要关注的事情更多,不停切换不同工作内容,容易遗漏工作步骤,导致流程出现问题,导致系统质量不稳定,技术债务更多,团队开发效率下降
前端工程化,就是要解决如上两大类问题,提升系统质量和开发效率,提升团队的人工投入产出比
如何进行前端工程化
系统质量
设计前端项目
设计项目时,需明确技术选型的影响因素(具体如下),再具体做对应决策
- 项目规模、功能交互
影响框架、工具选型,可考虑使用开源/现有框架,目前三大主流框架包含Angular、Vue、React
- 用户体系
影响兼容性的倾向,比如APP主要群体是老年人,需要考虑旧版本手机的兼容性
- 多人协作、团队规模:考虑完善规范,尽量使用工具保证规范进行* 使用一致的代码开发规范* 使用 Eslint 监测代码规范* 使用 Prettier 自动化格式代码* 使用 Git Commit Hooks 拒绝不符合规范的代码提交* 使用流水线检测出不规范代码,并拒绝合入主干分支,拒绝进入发布流程* 合入代码前,进行 Code Review,遇到分歧时,可通过投票* 制定合适的代码流程规范* 关联需求单/bug 单的分支命名* 检查代码是否符合规范、进行 Code Review* 自动化构建和测试代码* 灰度发布代码* 通过自动化工具确保流程正常进行,比如使用 CI/CD
- 团队技术栈* 考虑团队现有的技术栈、团队成员对框架/工具的熟悉程度,团队成员倾向的框架/工具* 使用团队成员比较熟悉的技术栈,可减少开发问题,提升开发效率* 针对新技术方案,则可通过成员讨论和投票
搭建前端监控体系
搭建前端监控体系是为了及时发现问题,快速定位并解决问题,监控页面整体访问情况包括常见的 PV、UV、用户操作行为,监控页面性能
前端监控需关注的数据
-
系统的生命周期数据:包括页面加载的关键时间点,比如页面打开、更新、关闭等* PerformanceTiming 属性可获取用于页面跳转、加载等数据* document.DOMContentLoader、document.readystatechange,可获取页面加载数据* Vue 的生命周期函数也可进行数据收集* MutationObserver 接口可结合 performance.now() 获取到具体的时间
-
HTTP 测速数据PerformanceTiming 属性也可获取 HTTP 请求相关数据,比如 HTTP 跳转开始/结束,域名查询开始/结束等时间戳,用于观察后端服务稳定情况、可优化空间
-
系统异常数据
包括逻辑错误、代码健壮性、网络错误、系统错误、页面内容异常等数据
前4种的异常数据,可用 window.onerror、document.addEvenetListener(error)、XMLHttpRequest status 等方法拦截,第5种异常可通过回归测试、UI 界面测试等方式获取
- 用户行为数据
包括页面浏览点击量、页面停留时间、访问入口、用户操作、用户链路数据,用于监控页面功能是否正常、分析用户行为,定位问题,以便更好地调整产品功能
可通过 DOM 元素的时间监听、页面加载情况
- 用户日志
使用用户日志定位系统异常信息
自动打印日志的方式:添加装饰器、对类方法进行劫持
存放系统输出日志:上报到服务器和本地存储两种方式配合使用
数据采集
- 数据埋点
- 数据上报* 定期/定量上报:收集数据到一定数量之后,再一次性打包,或者按照一定的时间间隔打包上传* 关键生命周期上报* 用户主动提交:引导用户主动上传异常和使用体验问题
搭建可视化管理端
- 日常监控:配置告警阈值,结合邮件、机器人推送,发现问题
- 版本发布和灰度:关注错误告警、全版本监控观察、分版本监控观察,出现数据异常,配合告警渠道通知负责人
性能优化
常见的性能优化方案
如何在项目中进行性能优化
- 确定优化的目标和预期
1.明确性能数据* 网络资源请求时间* Time To Start Render(TTSR):浏览器开始渲染的时间* Dom Ready:页面解析完成的时间* Time To Interact(TTI)):页面可交互时间* Total Blocking Time (TBT):总阻塞时间,代表页面处于不可交互状态的耗时* First Input Delay(FID):从用户首次交互,到浏览器响应的时间
2.对性能数据进行目标和预期的确定:比如对比原先数据优化到多少比例,分析竞品确定目标* 确定技术方案* 技术方案调研* 分析项目背景,挖掘项目痛点* 分析项目现状* 调研业界方案* 技术方案设计,方案选型/对比1.梳理项目现状:比如项目规模大,开发多2.梳理项目痛点:比如不同模块变更导致性能下降,问题往往在测试时才发现3.调研性能分析方案:通过 Performace 火焰图、Lighthouse、Chrome Devtools Protocol 等工具4.根据对比和分析确定最优方案,并跟领导解释
开发效率
主要分析项目在开发过程中的效率提升点,学会如何在未来的项目中选择和搭建最适合自己的开发工具
开发构建
热更新
当本地代码文件发生变化时,浏览器自动更新页面内容
webpack 热更新配置:
- 手动配置
- watch 模式,配置 watch:true,无需手动执行打包构建,但需要手动刷新浏览器
- Live Reload 模式,配置 contentBase:文件路径,open:true,浏览器内容自动更新,但页面状态无法保存
- Hot Module Replacement(HMR、 模块热替换),配置 hot: true
模块热替换插件实现热替换功能,主要依赖 module.hot 的属性
1.module.hot.accept(模块名称,回调方法):当依赖模块发生更新时,执行回调方法
2.module.hot.dispose(回调方法):当代码上下文的模块被移除时,执行回调方法
webpack 热更新原理:
1.监控源代码文件内容的变更
2.进行服务端和网页端的 socket 通信
3.进行模块解析和替换,依次执行匹配 module.hot 对应 moduleId 的回调函数,进行热替换,若未命中,则触发重新加载
sourceMap
在调试时将产物代码显示回源代码
sourceMap 的基本原理:在编译处理过程中,产物代码在被转换时,与源码生成了映射关系表,利用这映射表来实现调试时的显示与定位源代码功能
在开发环境中,通常关注构建速度快、质量高,以便于提升开发效率,不关注生成文件的大小和访问方式,建议使用 cheap-module-source-map,可以在浏览器下看到正确的错误提示
在生产环境为了防止源码泄露,建议使用 false,不使用 source-map
Mock 工具
模拟数据的工具,用于测试或者预先联调
选择 Mock 方案需注重:
- 仿真度:尽可能与后端实际提供接口的各方面保持一致
- 易用性:需要具备将接口文档自动转换为 Mock 接口的能力
- 灵活性:根据不同条件下返回值的差异做不同的交互处理
四种主流 Mock 工具:
- Mock.js:Mock 数据生成工具* 提供生成模拟数据的规范和方法* Ajax 请求拦截:拦截特定 url 的请求,直接将模拟数据作为响应值返回* 数据验证:验证指定数据和数据模板是否匹配* 模板导出:将 Mock.js 风格的数据模板转换为 JSONSchema
- Faker.js:Mock 数据生成工具* 提供指定类型的随机数据* 在 API 的使用方面较直观* 支持多种语言的本地化包
- YApi:开发、产品、测试人员共同使用的接口管理服务* 支持接口的定义、修改、运行、集合测试等* 提供 Mock 服务,以定义的接口可以通过服务直接获取 Mock 数据* 支持 Swagger 多种接口描述的数据导入与导出* 支持部署到内网服务以及自定义插件
- Apifox:桌面应用类的接口管理工具* 支持接口调试工具 Postman 的特色功能* 对同一个接口支持多种用例管理* 支持自定义期望数据,支持自定义占位符规则等* 支持生成自动业务代码和接口请求代码,支持自定义代码模板等
编码效率工具
使用预处理语言和预处理器:
- CSS 的主流预处理器:Less/Sass/Stylus* 使用人数从多到少排序:Sass > Less> Stylus* Less 缺少自定义函数的功能,Stylus 提供了超过 60 个内建函数* Sass 支持 .scss 与 .sass 两种文件格式,Less 的整体语法更接近 .scss,Stylus 则同时支持类似 .sass 的精简语法和普通 CSS 语法* sass-loader 和 stylus-loader 安装时都需要同时安装独立编译包 Sass / node-sass 和 Stylus,而 less-loader 则不强制要求
- HTML 模板的主流预处理器:Pug功能:简化标签书写、支持迭代、条件、拓展、包含、混合等逻辑功能
Vue 内置 pug-loader 作为预处理器,React 需要通过 babel 插件获得支持
使用代码生成工具:
- Snippet
开发过程中用户在 IDE 使用的可复用代码片段
可编写自身开发常用的个性预设片段,提升编码效率
可以在团队中共享和共同维护,提升团队效率
- Emmet
面向各类编辑器的 Web 开发插件,用于快速编写和编辑结构化的代码
支持功能:缩写代码块、CSS 缩写、自定义片段
云开发
将开发环境托管至云服务,开发服务器变更为云服务
优点:
- 集成开发环境所需基础设施,提升开发环境准备的效率
- 流程简化,易上手
- 统一流程规范、开发工具共享
- 提升资源利用率,降低硬件资产成本
注意点:
- 代码安全问题:设置访问权限,首选支持内部部署的云服务或者搭建自己维护的云服务
- 服务搭建与维护:考虑成本
- 服务降级与备份:考虑当系统异常时的策略
构建效率
主要分析影响 webpack 构建时间的关键因素,并分析对应的解决方案和工具
webpack 编译阶段提效
减少执行构建的模块
- ignorePlugin:剔除需要被排除的模块* 按需引入库模块:减少执行模块,在导入声明时只导入依赖包内的特定模块* 将项目依赖的框架等模块单独构建打包* DllPlugin 和 DllReferencePlugin:独立的配置文件,包含依赖包的独立构建流程,无须更改引用依赖包的子模块* Externals:配置简单,使用已传入 CDN 的依赖包,配置的依赖包需要单独指定依赖模块的加载方式,将引用依赖包的子模块打入项目包中
提升单个模块构建的速度
- include/exclude:include 只对符合条件的模块进行 loader 转换,而 exclude 相反,若出现冲突,优先 include
- noParse:省略使用 js 模块编译器进行编译的时间
- Source Map:本地调试时,显示回源代码的工具
- TypeScript 编译优化:ts-loader 配合 transpileOnly: true,babel-loader 配合 @babel/preset-typescript,两种形式都可配合使用 ForkTsCheckerWebpackPlugin将检查过程移至单独的进程,加快 TypeScript 的类型检查和 ESLint 插入的速度
并发构建(大中型项目的生产环境构建)
- HappyPack 和 thread-loader:以开启多进程的方式加速编译特定 loader
- parallel-webpack:实现不同配置的并行构建
webpack 打包阶段提效
提升当前任务工作效率
主要方式是压缩 Chunk 产物
- 面向 JS 的压缩工具* UglifyJSWebpackPlugin:早期压缩工具,基于 UglifyJS* TerserWebpackPlugin:基于 Terser,整体效率与质量略胜于 UglifyJSWebpackPlugin* Cache 选项:默认开启,使用缓存能够极大程度上提升再次构建时的工作效率* Parallel 选项:默认开启,并发选项在大多数情况下能够提升该插件的工作效率* terserOptions 选项:对具体压缩处理过程产生影响,compress 参数用于执行特定的压缩策略,mangle 参数用于对源代码中的变量与函数名称进行压缩
- 面向 CSS 的压缩工具* OptimizeCssAssetsPlugin:在 Create-React-App 中使用* OptimizeCssNanoPlugin:在 VUE-CLI 中使用* CSSMinimizerWebpackPlugin:2020 年 Webpack 社区新发布的 CSS 压缩插件,比起前两款压缩工具,它支持缓存和多进程,对项目构建效率影响明显,推荐使用
提升后续环节工作效率
- Split Chunks(分包):将 Chunks 根据一定的规则分离出子 Chunk,通过分包来抽离多个入口点引用的公共依赖* Tree Shaking:在构建打包过程中,移除那些引入但未被使用的无效代码,影响因素包含:* ES6 模块:只有 ES6 类型的模块才能进行Tree Shaking* 引入方式:以 default 方式引入的模块,无法被 Tree Shaking,而引入单个导出对象的方式则可以* sideEffects:只有 sideEffects 为 false 的依赖包,才能进行Tree Shaking* Babel:在 Babel 7 之前的 babel-preset-env ,modules 选项默认为 commonjs,无法被 Tree Shaking,在 Babel 7 之后的 @babel/preset-env,modules 选项默认为 auto,可以 Tree Shaking
缓存优化
基本原理
初次构建的压缩代码过程中,结果会写入缓存目录,再次构建进行到压缩代码阶段时,即可对比读取缓存
编译阶段的缓存优化
- Babel-loader* cacheDirectory:开启缓存* cacheIdentifier:计算缓存标识符* cacheCompression:将缓存内容压缩为 gz 包
- Cache-loader* 添加在 babel-loader 之前* 对比 babel-loader,构建时间更短,存储的 Buffer 形式的数据处理效率更高
打包阶段的缓存优化
- 生成 ChunksAsset 时的缓存优化:只有在 watch 模式下且配置开启 cache时才能执行缓存逻辑* 代码压缩时的缓存优化* JS 的压缩:TerserWebpackPlugin 和 UglifyJSPlugin 都支持缓存设置* CSS 的压缩:CSSMinimizerWebpackPlugin 默认开启缓存
- 减少缓存的失效* 缓存标识符发生变化导致的缓存失效:尽可能地减少标识符变化* 优化打包阶段的缓存失效:尽可能地把那些不变的处理成本高昂的模块打入单独的 Chunk 中,使用 splitChunks 优化缓存利用率
其他使用缓存的注意事项
- 为保持 CI/CD 中的持久化缓存,备份与还原.cache 文件
- 定期清理缓存区域
webpack 增量构建
webpack 只编译打包改动的文件,并将编译后的代码推送到浏览器端
影响因素
- watch 配置
使用 devServer 时,用于监控文件的变化
实现原理:构建完成并不自动退出,构建上下文的对象都可以保留在内存中,并在 rebuild 时重复使用
- cache配置
用于使用缓存
实现原理:在编译阶段添加模块时,若命中缓存 module,则跳过该模块的编译过程,在创建 chunk 产物代码阶段,若命中缓存 chunk,则跳过该 chunk 的产物代码生成过程
生产环境使用增量构建的阻碍
- 集成部署系统通常不可接受增量构建所需的保留进程和长时间占用内存
- 在生产环境下提升构建速度,需要将缓存写入到文件系统中,但 webpack4 的 cache 配置并不支持
- webpack 5 已正式支持基于文件系统的持久化缓存
部署效率
代码从构建到部署是前端能力的延申,学习如何提升自己前端项目的部署效率
部署工具介绍
如果所在企业需要选择一款 CI/CD 工具,需要考虑选择付费或免费系统,选择云服务还是自运维,是否需要对接上下游系统流程,使用配置是否便捷,对用户是否有学习成本,如下是四款工具简要介绍:
Jenkins
功能特点
- 提供各系统搭建方式,提供基于 Docker 的容器化搭建方式
- 免费
- 多类型 Job
- 插件系统丰富
- Job 配置灵活
- 提供 Restful 的 API 接口
胜在插件系统丰富且完全开源免费
缺点是缺少语言环境工作流的模板,使用成本高,服务器需要独立部署和运维
CircleCI
功能特点
- 基于云端的持续集成服务
- 免费版本受限制,收费提供更多功能
- 缓存依赖安装的数据
- 基于 SSH 访问构建容器
- 开箱即用,配置简化
- 提供 Restful 的 API 接口
胜在对接 Github 中的项目,支持 BitBucket、Heroku 等平台的对接
Github Actions
功能特点
- 多系统
- 矩阵运行
- 多语言
- 多容器测试
- 社区支持
- 公开仓库以及自运维执行器免费,私有仓库免费或收费
胜在是 Github 内置的 CI/CD 工具,使用成本最低,提供矩阵运行,多容器测试,多工作流模板等特色功能
Gitlab CI
功能特点
- 多功能
- 免费的社区版本和免费或收费的商用版本
- 内置 CI/CD 功能
企业比较受欢迎的版本管理工具,Gitlab 内置的 CI/CD 工具,使用 yml 的配置文件,但是要独立安装和配置执行器 Gitlab Runner
流程策略优化
项目部署流程包含获取代码、依赖安装、代码构建、产物打包、推送代码、重启服务,常见的流程策略优化主要是在依赖安装、代码构建、产物打包这三个阶段:
依赖安装阶段的提效
- 提升依赖安装速度* 依赖包下载使用国内镜像* 二进制下载源单独配置下载路径
- 多项目共用缓存,最大化命中缓存* 依赖工具相同的项目使用相同服务器* 技术栈相同的项目使用相同服务器
- 安装目录缓存1.缓存写入:package-lock.json 文件的 hash 值作为缓存目录的 key 值,压缩 node_modules 文件存储在缓存目录中2.缓存读取:判断 package-lock.json 文件的 hash 值是否命中缓存目录的 key 值,若命中则解压并使用缓存目录中的 node_modules 文件
- 检查 lock 文件项目开发人员需保存和维护 lock 文件,在 CI 流程中检测 lock 文件
代码构建阶段的提效
保持 CI 流程中的项目持久化缓存:
1.备份:在项目构建结束时,将 .cache 文件依据其相对项目根目录的路径生成备份目录名称
2.还原:再次重新构建时,查看备份空间是否存在对应项目的持久化缓存目录,若是则解析目录结构,将 .cache 文件还原到项目响应的目录中
产物打包阶段的提效
考虑提升产物压缩效率,使用 Gzip/Pigz/Zstd
- 压缩体积不大可使用 Gzip
- Pigz 和 Zstd 都使用并行处理,CPU 和内存占用都比 Gzip 高
- Pigz 产物与 Gzip 格式兼容,较广泛地作为 Gzip 的替代
- Zstd 产物与 Gzip 格式不兼容,Zstd 产物在解压时也需要 Zstd,适合压缩和解压流程闭环的场景
容器化部署
容器化基础概念
- 容器化:以 Docker 为代表,将操作系统内核虚拟化的技术
- Docker:运行在 Windows/linux/macOS 中开源的虚拟化引擎,用于管理容器
- 镜像:用于创建容器实例的虚拟化模板
- 容器:Docker 的核心功能单元,包含一个或多个应用程序以及运行它们所需要的环境依赖
- 数据挂载:挂载容器的宿主环境目录
- 数据卷:由 Docker 创建的宿主环境下的独立磁盘空间
容器化构建部署
把原先在部署服务器中执行的项目部署流程中的各个环节,改为使用容器化的技术来完成
- 镜像操作阶段:创建工作镜像,包含特定版本的 node 环境、git、项目构建所需要的依赖库
- 操作容器阶段:基于工作镜像,创建容器,操作各部署环节
容器化部署优势
- 环境隔离:每个项目在独立的容器内执行构建部署过程
- 多环节构建:可针对同一个项目生成多套不同的构建过程
- 便于调试:可使用浏览器访问容器环境,有问题则可第一时间进入容器环境调试
- 环境一致性与迁移效率:工作环境一致,可以一键迁移
容器化部署的挑战与建议
- 缓存问题* 依赖缓存:缓存目录不互通,无法持久化缓存,通过挂载宿主环境依赖缓存目录,使用安装目录缓存等方式解决* 构建缓存:缓存数据随着新容器部署而消失,通过在宿主环境中创建缓存目录并挂载到容器中,持久化数据的备份与还原等方式解决
- 性能问题* 容器资源限制:容器创建时,CPU 核心数和内存大小受限制,通过更改设置,分配更多资源的方式解决* 修改镜像层数据:如果涉及对镜像层数据的修改,比普通服务器消耗更多时间,所以尽可能避免将可变数据写入镜像中
总结
本文主要围绕什么是前端工程化、为什么要进行前端工程化、如何进行前端工程化这三个维度开展讨论前端工程化,涉及到很多知识点,仍需读者们进行不断地延申拓展
我们作为前端工程师,掌握前端工程化,即掌握前端项目降本提效的技能,是前端进阶垫脚石,为在裁员风声不断的当下大环境增添一份自信,大家一起加油吧
最后
整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。
有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:
文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取