应届前端的逆袭(下)

700 阅读15分钟

作为这个系列的最后一篇文章,我决定写一下有实践意义的东西。这篇文章主要讲的是如何从零开始搭建一个前端项目。 包含项目应用上的架构,以及工程管理上的一些小建议两个部分。系列其他文章👇👇

  • 原本打算上下两篇就写完这个系列的,但是实际写的过程中发现如果揉在一块,篇幅会十分长😅,所以还是决定拆成 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 之外,还可以用 stylelinthtmllint 来规范 csshtml。这里就不展开说了。

线上监控与报警

一般来说,你的应用 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.onerroraddEventListener("error", cb)try catchunhandledrejection 等。

其中 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 scriptCI / CDAB test 等许多方面需要我们去完善。但是往往这些工作都不是一个人或者一个小团队能完成的。除去应用之外,更多的我们要了解这些常见的概念,在脑子里埋下一个种子,等到必要的时候再去挖出来实现。

总结

这篇文章主要还是给大家一个思路,而不是手摸手教学系列。这里随便抽一个板块出来,都能单独说上一两天,也不是一两篇文章能讲完的事情。

事实上并没有什么完美的架构,完美的工程。不同发展时期,不同的受众群体,不同的资源背景,都有其合适的开发框架。比如面对几百 UV 的站点,除了必要的研发规范之外,其他配套也没必要做的太繁琐复杂。而百万 DAU 的产品可能又是另一套架构思路。

那么最后总结一下思路。如果你要从零开始新建一个项目,可以从以下方面考虑:

  • 必选项
    • 选择官方或社区高赞脚手架,直接用成熟的目录组织
    • 开启 eslintprettier
    • 线上错误日志捕获与上报,webhook 或者 email 告警
    • cross-env 或其他工具区分多环境
  • 可选项
    • MR + CR
    • commitizen 规范 git commit,husky 校验提交
    • 使用 typescript
    • 性能监控
    • 用户行为埋点
    • 动态下发配置
    • 单元测试
    • CI / CD
    • ......

至此,应届前端的逆袭这个系列就正式完结了~以后有时间再分享一些有意思的东西

2020.10.29