作为这个系列的最后一篇文章,我决定写一下有实践意义的东西。这篇文章主要讲的是如何从零开始搭建一个前端项目。 包含项目应用上的架构,以及工程管理上的一些小建议两个部分。系列其他文章👇👇
- 原本打算上下两篇就写完这个系列的,但是实际写的过程中发现如果揉在一块,篇幅会十分长😅,所以还是决定拆成 3 篇。 上篇主要介绍当前及未来前端发展的趋势,高效团队协作的开发配置;中篇主要是职业规划和选厂建议;下篇专门讲讲怎么从零开始搭一个前端项目。
- 这篇文章主要是写给刚毕业,或者刚转行入坑前端的同学, 总结了一些经验和学习心得,也算是以自身经历为例子的职业入坑指引吧。
- 文章中涉及到的技术栈和框架不会太深入去讲,主要是介绍职业规划和个人成长规划。对文章中某个点感兴趣的同学可以私我。
前言
我先问大家一个问题: “假设你的团队 Leader 让你做一个全新的业务,你会怎么做?”
前端老兵可能不觉得是什么大挑战,但是对大部分新人来说,可能就懵了。每位同学既然能应聘上 “前端” 这一岗位,就肯定有一定的开发能力。没错,你会 clone
仓库,你会写业务代码,你会提交你会上线。但问题是,如果没有仓库呢?需要从零开始起项目的时候,又该怎么去折腾呢?只是装完脚手架然后往仓库一推就完事了么? 如果你懒得看文章,可以直接翻到最后面的结论。
在开始之前,我先强烈建议大家创建项目的时候,在根目录写上 README.md
或者其他描述文件。在文档中写清楚项目的安装、运行、调试、部署步骤,这是对自己也是对项目的负责。如果没有文档,别说交接给其他小伙伴,就算是你自己,过个一年半载你也未必记得你对这个项目做过什么。
应用架构
应用的架构是每个项目初始化时必须面对的问题。如果架构不合理是会造成极大的开发阻碍的,甚至无法进行开发。好在基本上所有的框架都有自己的配套脚手架,这能让开发者省心不少,不需要关注框架内部的组织架构,只需要关注自身的应用架构就行了。
由于不同的技术栈之间的架构存在一些差异,在这里我将会把重点放在项目的工程管理理念上,把这些通用概念抽象出来,而不是陈述技术实现细节。
目录与文件规范
一个项目的目录结构是非常重要的。可能有的开发者觉得: 这又不影响功能和性能,没必要花时间去规范他,还不如多学点技术。 这是一个非常片面的观念。技术固然重要,但也不能小瞧规范。规范了标准能事半功倍,而一个无约束的项目分分钟能让你崩溃。
合理的目录规范能让你更高效开发,更快速的定位问题,还能对项目结构有一个抽象的认知。比如对请求数据流的抽象理解:请求进来,到中间件,到路由,到某个控制器,到服务层,到数据层…
如果你对某个工程体系或者某个框架有非常深的理解,那么你可以尝试自己搭建脚手架,自己搭建最理想的目录配置。不过我相信大部分项目其实还不到要硬核的定制脚手架的程度。为了更快捷接入开发,也为了拥抱社区容易躲坑,我的建议是有成熟的官方或社区脚手架,就用脚手架。
现在大部分前端框架都有自己的官方脚手架,比如 @vue/cli
,比如 create-react-app
,建议直接用,别折腾。如果项目有一些定制需求,也完全可以在脚手架的基础上二次开发。而对于一些 node
层的应用,可以去 github 上找一些高星的社区脚手架。比如狼叔的 koa-generator
安利一个输出目录结构的工具,
homebrew install tree
可以直接安装。忽略node_modules
查看三层目录的例子:tree -L 3 -I "node_modules"
代码规范
相信很多新入坑刚开始用 git
的同学都经历过一个绝望时刻: 一个格式化操作引发的雪崩式 merge conflict。 就保存了一下,完蛋了成百上千行的冲突。这些冲突往往不是什么功能变更,而是删掉了一个分号,或者补上了一个空格之类的。这就是没有 eslint
做代码风格统一的结果。
eslint
是用来规范 js
或者类 js
语言的。主要有两个功能:
- 规范项目代码风格,保证无论开发者在什么系统上,用什么编辑器,格式化的风格都是统一的。
- 简单的静态提示,比如提示某个变量声明了但是并没用到。
eslint
的配置因技术栈的不同而不同。一般的脚手架都集成了 eslint
,这也是为什么我推荐脚手架的原因。此外 eslint
有很多配置规则,可以参考官方文档
eslint
一般和 prettier
配套使用。前者是做规范检查,后者是做格式化处理。在这里我写一个简单的基于 ts 的例子,如果你是手撸项目的,也可以参考参考。
## 安装 eslint 的依赖,包括核心,解析器和一个扩充插件
yarn add eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin --dev
## 安装 prettier 依赖
yarn add prettier eslint-config-prettier eslint-plugin-prettier --dev
// 然后在项目根目录新建 eslint 配置文件
// 可以用官方后缀,也可以用 js 或者 json。这里以 js 演示
// 根目录新建 .eslintrc.js
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'plugin:@typescript-eslint/recommended',
'prettier/@typescript-eslint',
'plugin:prettier/recommended',
],
parserOptions: {
ecmaVersion: 2018,
sourceType: 'module',
},
};
// 在项目根目录新建 prettier 配置文件
// 可以用官方后缀,也可以用 js 或者 json。这里以 js 演示
// 根目录新建 .prettierrc.js
module.exports = {
semi: true,
trailingComma: "all",
singleQuote: true,
printWidth: 120,
tabWidth: 4
};
最后修改 vscode
的配置,实现保存时自动格式化,command + p
调出面板,然后搜 setting.json
直接改就行。
{
"editor.formatOnSave": true, // 保存自动格式化
// eslint 相关配置
"editor.codeActionsOnSave": {
"source.fixAll": true,
"source.fixAll.eslint": true
},
}
除了
eslint
之外,还可以用stylelint
和htmllint
来规范css
和html
。这里就不展开说了。
线上监控与报警
一般来说,你的应用 DAU 越高,用户体量越大,那么线上的告警就显得越重要。代码上线之后,唯二能知道运行是否正常的途径有两个:一是你自己亲自去把所有功能体验一遍,二是线上错误监控。
就算你自己把所有功能回归一遍,但总会有异常状态是漏网之鱼,亦或者是受到什么攻击导致某个服务崩溃。所以无论出于什么理由, 线上的监控与错误告警都显得十分重要。
- 用户行为埋点
先从最基础的说起。用户的行为埋点与否,其实并不会影响 web 应用的功能。做这些指标监控最直接的原因就是 知道用户的操作流程与习惯,给开发者一个反馈, 以便后期需求的分析与迭代。所谓埋点其实就是当用户触发某个事件的时候,发起一个请求,告知服务器用户进行了什么操作。你可以选择用 axios
发起一个 post
,也可以选择用 img
标签携带一些参数发起一个 get
请求。可以硬编码在业务逻辑中,也可以选择无侵入式的监听。
举个例子,比如一个轮播图,每次用户点击的时候,除了跳转之外,再发起一个埋点请求,告诉服务器当前的时间,用户机型,用户id等。利用这些数据可以做用户画像分析,亦或者结合曝光的埋点,来计算出某个 banner 的点击率是多少
- 性能上报
性能上报说简单也不简单,说复杂也不复杂。这完全取决于项目规模和需要统计的数据维度。常见的 web 应用性能收集,主要包括网络性能和脚本执行性能两个部分,一些注重视图渲染的应用可能还需要收集帧率作为指标。一般来说,网络的传输速度和脚本的执行速度,就决定了这个应用的体验流畅性。
关于性能的监控,业界主流有两种方式。一种是通过 performance
API 来统计网络关键节点的耗时,以及脚本解析的耗时。包括 DNS
的解析,TCP
握手的耗时,TLS
的耗时,资源下载的耗时,首屏,白屏时间等。
另一种方式一般针对大型应用或者需要精确统计的应用,比如电商。这种性能收集是通过 Mutation Observer
的 API 去监控整个 DOM
树的构建和渲染过程,并且给不同的节点赋上一个权重,比如主要图片权重100,标题权重1000,一些细节权重10。最终通过一个加权平均,计算出一个能表示 “主要元素已经渲染完” 的值,用作性能监控。
👉这篇文章有写关于白屏与性能监控的知识,感兴趣可以看看。托管在 github page 的,可能需要科学上网。
- 错误告警
错误告警是非常重要的一环,但也是很多开发者不太重视的一环,觉得运维监控就好了,自己不用管。其实每个开发者都需要为自己的项目负责,那么第一步就是知道代码在线上是否运行正常。对大型项目而言, 强烈建议采用 sentry 或者类似的监控平台。
从自己造轮子的角度上说,捕获异常的方式有很多种,比如 window.onerror
,addEventListener("error", cb)
,try catch
,unhandledrejection
等。
其中 onerror
不能捕获资源加载的错误,比如图片挂了等。而 try catch
不能捕获 promise 的错误
比较好的处理方式是,用,addEventListener("unhandledrejection", cb)
来捕获异步错误。并且统一抛出 new Error
,然后统一监听 error
事件
对于 vue,react 之类的框架错误,需要用框架内的错误捕获方法抛出异常
window.addEventListener("unhandledrejection", e => {throw e.reason})
window.addEventListener("error", args => {console.log("handler your error", args)
return true
}, true)
// 针对 vue 框架级的错误
Vue.config.errorHandler = function(err, vm, info) => {console.log("vue error", err)
}
捕获到异常错误之后,可以利用 img
标签的 src
进行错误上报。不过需要注意的是,构建完的代码都是压缩后的代码,需要同时上报 sourceMap
文件。上报 sourceMap
的操作可以交给 webpack
的插件去完成。构建结束后自动上报即可 sourceMap
即可
后端拿到上报的数据后,还要根据 sourceMap
解析 errorStack
。可以利用 error-stack-parser
这个库来做反解析。然后可以通过 email
或者 webhook
的方式来给自己发告警。
比如 node 的
nodemailer
就能给自己发邮件。如果觉得没有看邮件的习惯,也可以接入钉钉,飞书的机器人webhook
,给自己发消息,甚至接一些网络电话库直接给自己打电话。
- 配置与环境控制
我刚开始写代码的时候,实习的公司也不重视规范,团队经验也不足。每次修改配置都要发版上线一次,很麻烦不说,还容易出错。后来逐渐意识到这个问题,大部分的配置都改成接口下发。到现在的公司更是有一套完善的配置管理系统。
可以根据开发环境的维度,将配置文件大致分成三种: 开发环境,测试环境,生产环境。 如果是一些重要的项目还可以加多一个线上预览环境,数据库也是和线上同步的。可以针对不同环境定制不一样的配置,比如最基础的连接不同的数据库,不同的样式主题,线上环境剔除 console.log
等
最常见的区分环境的方法就是用 cross-env
这个包。直接在 package.json
的脚本中加上 cross-env NODE_ENV=xxx
就能在应用中读到配置。
{
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config build/webpack.config.js"
}
}
// 在应用中可以读到环境变量,根据这个变量就能加载不同的配置
process.env.NODE_ENV === 'production'
而对于一些大型团队来说,这个环境配置的过程就可以交给服务发现来完成。根据当前的服务标示,判断处于什么环境,然后结合配置平台读取相应的配置。这是一个很繁琐也很庞大的工程,一般是运维工程师去管理。感兴趣的同学可以关注一下 consul
工程管理
如果说前面的应用架构是必选项,那么工程管理就是一个强有力的辅助项。有没有它其实对项目的功能都没有什么额外的影响。但它是质量与高效并存的保障。
git 规范
- Merge Requests
Merge Requests
简称 MR
,是合并分支的时候发起一个申请,有同事审核(后面质量管理会说到)过后才允许合并。我非常建议启用 MR
的规范。一方面它可以减少个人误操作发生的概率,另一方面为代码审核提供了基础。开启 MR
可以参考 Gitlab
的 👉 文档
- workflow
在 应届前端的逆袭(上) 中有讲到 git
的工作流,这里不再赘述。
- commitizen
commitizen
是用来规范 git
提交信息的。
常见的规范有几种:
feat
表示新开发的功能,fix
表示修复 bug,WIP
表示功能正在开发中,还有其他描述可以参考这位大佬的风格 👉 .cz-config.js
配置 commitizen
有两种方式,一种是项目级别的配置,另一种是全局配置,无论哪种都可以。
## 项目级别的配置
yarn add cz-customizable --dev
## 然后在根目录新建并配置 .cz-config.js 文件,可以参考上面的文档
## 然后在 package.json 中添加
{
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
}
},
}
## 全局级别配置
yarn global add commitizen cz-customizable
echo '{ "path": "cz-customizable" }' > ~/.czrc
## 然后在 ~/ 路径下创建并配置 .cz-config.js 文件,可以参考上面文档
配置好后,就可以用 git cz
命令来代替 git commit
命令了。它会让你选择当前提交属于哪种类型,比如 feat
类型。然后根据这个类型自动生成规范的注释文本。
为了方便使用,建议大家装一个叫做
Visual Studio Code Commitizen Support
的 vscode 插件,然后command + p
呼出面板后,选择commitizen
就能快速选用模板。截图如下
项目质量
- Code Review
Code Review
(简称CR)指的是每次合并代码前,指定一个审查者检查代码。CR
能减少开发失误,提高提测质量,减轻 QA 同学的负担。gitlab
开启 CR
的 👉 文档
- husky
如果你对代码规范有更进一步的要求,可以选择用 husky
来校验 commit message
。这个名为二哈的库就是在 commit 之前做校验的。
## 安装哈士奇
yarn add husky@next --dev
## 然后在 package.json 中进行配置
{
"husky": {
"hooks": {
"commit-msg": "commitlint -e $GIT_PARAMS"
}
},
}
- 单元测试
单元测试也是一个很重要的环节,也不是一两篇文章就能说清楚的事情。这里不展开介绍,可以参考 jest
的 👉 官网
- typescript
typescript
是 JS 的一种超集,它主要是用来规范 JS 的类型的,这极大弥补了 JS 作为弱类型语言的缺点。我建议无论项目中还是平时写 demo,能用 TS 就尽量用 TS,养成良好的编程习惯。👉 官网
其他
其实一个企业级应用的开发、构建、部署,远不止文中提到的这些概念,还有诸如 npm script
,CI / CD
,AB test
等许多方面需要我们去完善。但是往往这些工作都不是一个人或者一个小团队能完成的。除去应用之外,更多的我们要了解这些常见的概念,在脑子里埋下一个种子,等到必要的时候再去挖出来实现。
总结
这篇文章主要还是给大家一个思路,而不是手摸手教学系列。这里随便抽一个板块出来,都能单独说上一两天,也不是一两篇文章能讲完的事情。
事实上并没有什么完美的架构,完美的工程。不同发展时期,不同的受众群体,不同的资源背景,都有其合适的开发框架。比如面对几百 UV 的站点,除了必要的研发规范之外,其他配套也没必要做的太繁琐复杂。而百万 DAU 的产品可能又是另一套架构思路。
那么最后总结一下思路。如果你要从零开始新建一个项目,可以从以下方面考虑:
- 必选项
- 选择官方或社区高赞脚手架,直接用成熟的目录组织
- 开启
eslint
和prettier
- 线上错误日志捕获与上报,
webhook
或者email
告警 cross-env
或其他工具区分多环境
- 可选项
- MR + CR
- commitizen 规范 git commit,husky 校验提交
- 使用 typescript
- 性能监控
- 用户行为埋点
- 动态下发配置
- 单元测试
- CI / CD
- ......
至此,应届前端的逆袭这个系列就正式完结了~以后有时间再分享一些有意思的东西
2020.10.29