|
❝车同轨,书同文,行同伦-《礼记·中庸》转转在早期团队较小的时候,没有太多规范,遇到问题用自己最擅长的方式去解决就行。随着公司发展,团队越来越大,因为没有统一规范而造成的问题也越来越多,例如组件库重复开发,不同小组组件库各有长短,但整体质量有所欠缺不同业务之间,项目无法直接复用,需要大量的开发改造新人上手成本高,每个项目都要深度了解其代码与配置,而不是了解完业务就行一些自动化提效的工具,因为业务项目没有规范,而无法开展等等。。。基于这些问题,我们在去年上半年,制定了一个转转前端开发规范,经过这大半年的实践,达到了预期的效果。接下来我会具体介绍下转转的前端开发规范,希望能给大家带去一些帮助。之前我们也分享过转转的中后台规范建设,感兴趣的可以戳这规范包含哪些内容我们通过一张规范架构图来直观的了解一下如上图,我们把规范分为五大类开发规范监控规范协作规范流程规范开源规范其中每个大类下,又包含若干个小分类。本文中,我会详细的介绍下一些重点的规范以及其内容,对于非重点的,待后续我们把文档开源到Github上,大家可以自行查阅。开发规范本地存储规范主要包括localStorage的使用规范,避免使用存储时会遇到的问题基础库的使用使用localforage来实现本地存储,该库会优先使用IndexedDB以扩大本地存储空间,并采用优雅降级方案,处理了异常情况。存储溢出问题localStorage有存储限制,所以在存储空间用完的情况下,进行setItem操作就会报QuotaExceededError的错。事实上,在某些浏览器(如iOS11的Safari浏览器)的无痕模式下,localStorage也是无法读写的,进行setItem操作会报一样的QuotaExceededError错。为尽可能避免上述情况,或者在出现时优雅处理,请遵循以下规范:设置过期时间。按如下方式对存储的数据进行封装,并在合适的时机清理过期数据。const data = { data: Object, expiredTime: Date.now() + 7 * 24 * 60 * 60 * 1000}用try...catch包裹setItem操作try { localStorage.setItem('my_data', JSON.stringify(data))} catch (e) { // 错误处理}❝常见的错误处理方法有:清理过期数据并重试一次;转存至sessionStorage、自定义的全局对象myStorage、Vuex等,但记得在读取时也依次读取。存储的key覆盖问题key的命名需要符合如下规范业务线名_项目名_自定义名称如:const REFIX = 'platform_book_'try { localStorage.setItem(PREFIX + 'my_data', JSON.stringify(data))} catch (e) { // 错误处理}注释规范合理的注释,可以提高代码的可读性和可维护性。注释的原则如无必要,勿增注释,应当追求「代码自注释」// bad// 如果已经准备好数据,就渲染表格if (data.success & data.result.length > 0) { renderTable(data);}// goodconst isTableDataReady = data.success & data.result.length > 0;if (isTableDataReady) { renderTable(data);}如有必要,尽量详尽,需要注释的地方应该尽量详尽地去写注释的规范遵循统一的风格规范,如一定的空格、空行,以保证注释自身的可读性单行注释使用//,注释行的上方需要有一个空行;注释内容和注释符之间需要有一个空格function getType() { console.log('fetching type...'); // set the default type to 'no type' const type = this.type || 'no type'; return type;}多行注释使用/**...*//** * make() returns a new element * based on the passed-in tag name */function make(tag) { // ... return element;}使用特殊标记注释:如TODO、FIXME、HACK等// TODO: 这里还需要做什么// FIXME: 这里的实现有问题,以后需要优化// HACK: 这里的处理是为了兼容低端安卓机的 bug文档类注释,如函数、类、文件、事件等,使用jsdoc[1]规范/** * Book类,代表一个书本. * @constructor * @param {string} title - 书本的. * @param {string} author - 书本的作者. */function Book(title, author) { this.title = title; this.author = author;}避免情绪性注释:如抱怨、歧视、搞怪/** * 穷逼VIP(活动送的那种) * @param update * @return {boolean} */浏览器兼容规范团队应该根据的用户设备占比情况、应用类型、开发成本、浏览器市场统计数据等因素,来制定自己的浏览器兼容规范,转转的设备兼容规范如下总体的机型兼用要求安卓需要兼容安卓4.0以上,iOS需要兼容8.0以上# .browserlistrc> 1%last 3 versionsiOS >= 8Android >= 4Chrome >= 40CSS的兼容要求在CSS的兼容处理上,我们使用的是postcss的autoprefixer// postcss.config.jsmodule.exports = { plugins: { autoprefixer: {}, cssnano: { autoprefixer: false, zindex: false, discardComments: { removeAll: true }, reduceIdents: false } }}js的兼容要求在处理js的兼容性时,我们使用babel抹平浏览器差异。❝需要注意的是proxy是无法被降级处理的。需要慎重使用。业务项目中,通过配置@babel/preset-env的useBuiltIns:'entry',corejs:3,来根据.browserslistrc引入polyfill// babel.config.jsmodule.exports = { presets: [ ['@vue/app', { useBuiltIns: 'entry' }] ]}// main.jsimport 'core-js/stable'import 'regenerator-runtime/runtime'❝以Vue项目为例,@vue/babel-preset-app的配置会透传至@babel/preset-env,并默认使用core-js@3基础库中,通过配置@babel/preset-env的useBuiltIns:'usage',corejs:3来处理js兼容问题,减少项目体积// babel.config.jsmodule.exports = { presets: [ ['@babel/env', { useBuiltIns: 'usage', corejs: 3 }] ], plugins: [ ['@babel/transform-runtime'], ]}HTTP缓存规范转转的绝大多数项目都是单页应用,根据项目特点我们设置了如下的缓存规范静态资源静态资源域名s1.zhuanstatic.com,是通过cache-control来做强制缓存,缓存时间是30天cache-control: max-age=2592000html文件域名m.zhuanzhuan.com下的html文件,是通过Etag来做协商缓存etag: W/"5dfb384c-4cd"压缩所有的资源都已开启的gzip压缩通讯协议默认使用的通信协议是http2编辑器规范使用Editorconfig用来帮助你规范编辑器的的配置Editorconfig具体配置配置文件.editorconfigroot = true[*]indent_style = space # 输入的 tab 都用空格代替indent_size = 2 # 一个 tab 用 2 个空格代替end_of_line = lf # 换行符使用 unix 的换行符 \ncharset = utf-8 # 字符编码 utf-8trim_trailing_whitespace = true # 去掉每行末尾的空格insert_final_newline = true # 每个文件末尾都加一个空行[*.md]trim_trailing_whitespace = false # .md 文件不去掉每行末尾的空格Prettier规范Prettier是一个代码格式化工具。在转转的风格统一方案中,Prettier和ESlint配合使用,但是Prettier和ESlint的规则是有冲突的,我们做了很多的努力,尽量保持配置规则的一致性。但是还是有冲突的地方。业界有两种方案一种是以Prettier为主一种是以ESlint为主转转选择以ESlint为主,所以在修复错误的时候,是Prettier先格式化一遍,ESlint再修复一遍,最终检查以ESlint为准。所以如果遇到无法解决的错误,直接修改ESlint的配置就可以。Prettier具体配置文件// prettier.config.jsmodule.exports = { // 在ES5中有效的结尾逗号(对象,数组等) trailingComma: 'none', // 不使用缩进符,而使用空格 useTabs: false, // tab 用两个空格代替 tabWidth: 2, // 仅在语法可能出现错误的时候才会添加分号 semi: false, // 使用单引号 singleQuote: true, // 在Vue文件中缩进脚本和样式标签。 vueIndentScriptAndStyle: true, // 一行最多 100 字符 printWidth: 100, // 对象的 key 仅在必要时用引号 quoteProps: 'as-needed', // jsx 不使用单引号,而使用双引号 jsxSingleQuote: false, // 大括号内的首尾需要空格 bracketSpacing: true, // jsx 标签的反尖括号需要换行 jsxBracketSameLine: false, // 箭头函数,只有一个参数的时候,也需要括号 arrowParens: 'always', // 每个文件格式化的范围是文件的全部内容 rangeStart: 0, rangeEnd: Infinity, // 不需要写文件开头的 @prettier requirePragma: false, // 不需要自动在文件开头插入 @prettier insertPragma: false, // 使用默认的折行标准 proseWrap: 'preserve', // 根据显示样式决定 html 要不要折行 htmlWhitespaceSensitivity: 'css', // 换行符使用 lf endOfLine: 'lf'}git-commit规范在团队中代码提交gitcommit会有各种各样的风格,甚至有些人根本没有commit规范的概念,所以在我们回头去查找在哪个版本出现问题的时候,就会非常尴尬,很难快速定位到问题。为了项目的规范化,代码提交规范就显得尤为重要!我们通过提交规范插件vue-cli-plugin-commitlint进行代码的提交规范(该插件基于conventional-changelog-angular进行了修改/封装)。vue-cli-plugin-commitlint介绍vue-cli-plugin-commitlint是以vue插件的形式写的,可以执行vueaddcommitlint直接使用,如果不是vue的项目也可以根据下面的配置自行配置。结合commitizencommitlintconventional-changelog-clihuskyconventional-changelog-angular,进行封装,一键安装,开箱即用的代码提交规范。功能自动检测commit是否规范,不规范不允许提交自动提示commit填写格式。不怕忘记规范怎么写集成gitadd&gitcommit不需要执行两个命令自动生成changelog配置脚手架生成的项目默认包含,分为默认使用以及个性化配置如果是vue-cli3的项目直接使用即可vue add commitlint如果不是vue-cli3的项目npm i vue-cli-plugin-commitlint commitizen commitlint conventional-changelog-cli husky -D在package.json中添加{ "scripts": { "log": "conventional-changelog --config ./node_modules/vue-cli-plugin-commitlint/lib/log -i CHANGELOG.md -s -r 0", "cz": "npm run log & git add . & git cz" }, "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }, "config": { "commitizen": { "path": "./node_modules/vue-cli-plugin-commitlint/lib/cz" } }}增加commitlint.config.js文件module.exports = { extends: ['./node_modules/vue-cli-plugin-commitlint/lib/lint']};使用npm run cz # npm run log & git add . & git commit -m 'featxxx): xxx'npm run log # 生成 CHANGELOG代码提交npmrunczcommander选择一个类型会自动询问(非必填)本次提交的改变所影响的范围(必填)写一个简短的变化描述(非必填)提供更详细的变更描述(非必填)是否存在不兼容变更?(非必填)此次变更是否影响某些打开的issuepromptchangelog演示changelog规则规范名描述feat新增featurefix修复bugmerge合并分支docs仅仅修改了文档,比如README,CHANGELOG,CONTRIBUTE等等chore改变构建流程、或者增加依赖库、工具等perf优化相关,比如提升性能、体验refactor代码重构,没有加新功能或者修复bugrevert回滚到上一个版本style仅仅修改了空格、格式缩进、逗号等等,不改变代码逻辑test测试用例,包括单元测试、集成测试等Vue项目规范上面说了一些常规的开发规范,对于不同类型的项目Vue、VueSSR、React、小程序等等,都有着不同的规范,接下来我会以Vue项目为例来继续说说开发规范ESlint规范ESLint属于一种QA工具,是一个ECMAScript/JavaScript语法规则和代码风格的检查工具,可以用来保证写出语法正确、风格统一的代码。ESLint旨在完全可配置,它的目标是提供一个插件化的javascript代码检测工具。转转的ESlint配置基于Standard规则的基础上做了特定的修改。ESlint具体配置文件每个规则对应的0,1,2分别表示off,warning,error三个错误级别module.exports = { root: true, parserOptions: { ecmaVersion: 6, // 指定ECMAScript的版本为 6 parser: "@typescript-eslint/parser", // 解析 ts sourceType: "module" }, // 全局变量 globals: { window: true, document: true, wx: true }, // 兼容环境 env: { browser: true }, // 插件 extends: ["plugin:vue/essential", "@vue/standard"], // 规则 rules: { // 末尾不加分号,只有在有可能语法错误时才会加分号 semi: 0, // 箭头函数需要有括号 (a) => {} "arrow-parens": 0, // 两个空格缩进, switch 语句中的 case 为 1 个空格 indent: [ "error", 2, { SwitchCase: 1 } ], // 关闭不允许回调未定义的变量 "standard/no-callback-literal": 0, // 关闭副作用的 new "no-new": "off", // 关闭每行最大长度小于 80 "max-len": 0, // 函数括号前面不加空格 "space-before-function-paren": ["error", "never"], // 关闭要求 require() 出现在顶层模块作用域中 "global-require": 0, // 关闭关闭类方法中必须使用this "class-methods-use-this": 0, // 关闭禁止对原生对象或只读的全局对象进行赋值 "no-global-assign": 0, // 关闭禁止对关系运算符的左操作数使用否定操作符 "no-unsafe-negation": 0, // 关闭禁止使用 console "no-console": 0, // 关闭禁止末尾空行 "eol-last": 0, // 关闭强制在注释中 // 或 /* 使用一致的空格 "spaced-comment": 0, // 关闭禁止对 function 的参数进行重新赋值 "no-param-reassign": 0, // 强制使用一致的换行符风格 (linebreak-style) "linebreak-style": ["error", "unix"], // 关闭全等 === 校验 "eqeqeq": "off", // 禁止使用拖尾逗号 "comma-dangle": ["error", "never"], // 关闭强制使用骆驼拼写法命名约定 "camelcase": 0 }};ignore规范gitignore规范同步到远程仓库时予以忽略的文件及文件夹。基础配置如下,各项目也可以根据需求自行调整.DS_Storenode_modules/dist# local env files.env.local.env.*.local# Log filesnpm-debug.log*yarn-debug.log*yarn-error.log*# Editor directories and files.idea.vscode*.suo*.ntvs**.njsproj*.sln*.sw?❝注意:package-lock.json不建议忽略,参见相关讨论package-lock.json需要写进.gitignore吗?[2]https://www.zhihu.com/question/264560841/answer/286682428eslintignore规范eslint校验时予以忽略的文件及文件夹。基础配置如下,各项目也可以根据需求自行调整node_modulesdist/test/lib/es/Vue项目目录规范├── dist // [生成] 打包目录├── src // [必选] 开发目录│ ├── views // [必选] 页面组件,不允许有其他类型组件混入│ ├── components // [必选] 业务组件必须写在这里│ ├── libs // [可选] 公共库(一般用于对一些库的封装)│ ├── utils // [可选] 工具库(用于一些函数方法之类的库)│ ├── assets // [可选] 公共资源(被项目引用的经过webpack处理的资源)│ ├── store // [可选] 数据存储 vuex│ ├── route // [可选] 路由│ ├── style // [可选] 公共样式│ ├── App.vue // [必选] 根组件│ └── main.(js|ts) // [必选] 入口文件├── public // [必选] 不会被webpack编译的资源│ ├── index.html // [必选] 模板│ └── logo.png // [可选] 项目 logo├── config // [可选] 配置目录├── mock // [可选] mock 数据├── test // [可选] 测试代码├── docs // [可选] 文档│── .gitignore // [必选] git 忽略的文件│── .editorconfig // [必选] 编译器配置│── .npmignore // [可选] 如果是 npm 包是必选│── jsconfig.json // [可选] 用于 vscode 配置├── README.md // [必选] 导读├── package.json // [必选] 大家都懂└── ...... // [可选] 一些工具的配置,如果 babel.config.js 等components、views具体职能划分components只写公共组件,页面附带组件写在views内└── src ├── views │ └── home │ ├── index.vue │ ├── Banner.vue │ └── Card.vue ├── components │ ├── Swiper.vue │ └── Button.vue ├── store // 对于较大的项目,建议按业务模块拆分管理 │ ├── index.js │ ├── home.js │ └── mine.js ├── route // 对于较大的项目,建议按业务模块拆分管理 │ ├── index.js │ ├── home.js │ └── mine.js └── assets // 重复使用的公共资源放在顶层 assets 文件,避免重复定义监控规范监控主要包含性能监控、异常监控以及业务埋点,这里我主要介绍下性能和异常监控的规范性能监控规范转转所有的移动端面向C端用户的项目必须接入性能监控项目接入性能监控和埋点统一使用转转性能SDK,使用脚手架初始化的项目,默认会接入性能查看可以通过转转前端性能平台,查看你需要的性能数据性能预警规则预警的频率是一天一次预警线项目首屏时间低于1.5s或者1.5s开率低于60%页面首屏时间低于1.5s,计算的1天的平均值预警方式每天上午11点,通过企业微信发送白名单机制,过滤掉不需要报警的项目异常监控规范转转的前端错误监控体系基于Sentry建立,所有的项目必须接入异常监控。上报策略的调整使用Sentry过程中可能会遇到的一些问题,如下:收集信息混乱(所有错误信息混杂在一起);定位问题相对较慢;影响范围评估难;错误频率无法统计;部分监控缺失(不能全方位监控);小程序缺少监控;接口缺少监控;404请求缺少监控;预警邮件过于频繁(容易让开发人员接收疲劳);主动上报侵入项目,虽然前端实际工作中一直以对业务无侵入为研究方向。但在实际的业务中偶尔的侵入业务去做一些处理是很有必要的,给业务带来的收益也是可观的,我们能做的就是尽量少的侵入业务代码,导致污染。以下是我们对项目的改造策略:页面改造以上方案不止能有效捕获错误,区分错误级别,还能有效防止子组件错误影响整体页面渲染,导致白屏,简直一举两得。接口监控为什么要做接口监控?辅助后端错误监控及日志排查,提供更多有效信息;监控接口及服务异常状态,根据异常状态发现现有代码,服务器,及产品逻辑的漏洞;加强前端开发人员对于线上问题的重视,及对接口错误的重视,更好的融入业务;多维度标签&辅助查错信息&自定义错误分组规则优势:快速定位问题(1分钟内),迅速评估影响范围;更多分析问题需要的信息,辅助快速解决问题;规整错误列表,查看错误频率,优化代码,服务,及产品逻辑风险;流程规范包管理规范该部分内容主要包含公共包的开发与发布相关规范内部私服cnpm转转内部的包,使用内部搭建的cnpm,发布包时,registry源地址需要切换成转转的源地址npm包版本号规则主版本号.次版本号.修订号版本号递增规则如下:主版本号:当做了不兼容的API修改,次版本号:当做了向下兼容的功能性新增,修订号:当做了向下兼容的问题修正。先行版本号及版本编译信息可以加到“主版本号.次版本号.修订号”的后面,作为延伸。当主版本号升级后,次版本号和修订号需要重置为0,次版本号进行升级后,修订版本需要重置为0。关于测试版本后缀命名:alpha:初期内测版,可能比较不稳定,如:1.0.0-alpha.1。beta:中期内测,但会持续加入新的功能。rc:ReleaseCandidate,发行候选版本。RC版不会再加入新的功能了,主要着重于除错,处在beta版之后,正式版之前semver:semanticversion语义化版本固定版本:例如1.0.1、1.2.1-beta.0这样表示包的特定版本的字符串范围版本:是对满足特定规则的版本的一种表示,例如1.0.3-2.3.4、1.x、^0.2、>1.4语义化说明用到的语义化字符有:~、>、=、2.1' // 高于2.1版本的最新版本- '1.0.0 - 1.2.0' // 两个版本之间最新的版本(必须要有空格)- '*' // 最新的版本号- '3.x' // 对应x部分最新的版本号npminstall默认安装^x.x.x类型的版本,用于兼容大版本下最新的版本到这里,转转前端开发的核心主要规范就介绍完了,对于不同类型项目会有不用的规范,大家可以根据业务实际情况,制定属于自己的团队的开发规范~本月文章预告预告下,接下来我们会陆续发布转转在性能、多端SDK、移动端等基础架构和中台技术相关的实践与思考,欢迎大家关注公众号“大转转FE”,期望与大家多多交流参考资料[1]jsdoc:https://jsdoc.app/index.html[2]package-lock.json需要写进.gitignore吗?:https://www.zhihu.com/question/264560841/answer/286682428
|
|