前端组件开发规范和指北

754 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

前言

没有规矩,不成方圆,对于团队来说,有一套完整的开发规范可以减少团队协作成本和维护成本,让代码阅读起来更容易。

环境搭建

项目中所需要的依赖都会在package.json文件中展示。

安装依赖需要使用npm命令,可去npm官方文档使用及学习。

这里建议使用淘宝的cnpm源

生成环境依赖

生产环境需要的依赖安装在属性dependencies之下。

可以使用命令:

npm i -D xxxx

webpack配置

目前common使用的vue-cli-service的方式启动,后续根据情况切换到webpack的自建脚手架

vue-cli介绍

关于vue-cli

项目规范

本项目由vue.js技术框架搭建,配合webpack打包。

.less 样式文件单独抽离,在组件标签的最顶层设置唯一的wg-xxx样式标识,避免造成样式污染问题

组件开发基于两种方式:

For example:

image.png

index.js

import wgElSelect from './src/main';

/* istanbul ignore next */
wgElSelect.install = function(Vue) {
  Vue.component(wgElSelect.name, wgElSelect);
};

export default wgElSelect;

src/main.vue

<template>
  <div
    class="el-select wg-el-select"
    :class="[selectSize ? 'el-select--' + selectSize : '', {'isFixHeight': isFixHeight }]"
    @click.stop="toggleMenu"
    v-clickoutside="handleClose">
    [...]
  </div>
</template>
<scrip>
import {Select} from "element-ui"

export default {
  extends: Select,
  name: 'wgElSelect',
  props: {
    isFixHeight: {
      type: Boolean,
      default: false,
    }
  }
}
</scrip>
<style lang="less" scoped>
@import './select.less';
</style>

src/select.less

.wg-el-select{
	.isFixHeight .el-select__tags{
    	height: 26px;
    	overflow-y: auto;
	}
}
  • 从0到1自主维护的基础组件及业务组件

For example:

image.png

index.js

import title from './src/main';
/* istanbul ignore next */
title.install = function (Vue) {
    Vue.component(title.name, title);
};

export default title;

src/main.vue

<template>
  <div
    v-bind="$attrs"
    class="wg-title f16 title"
    :class="size"
    >
    <span class="wg-line" :class="noline ? 'wg-line-no' : ''"
    ></span>
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'wgTitle',
  props:{
    size: {
      type: String,
      default: 'default',
    },
    noline:{
      type: Boolean,
      default: false,
    }
  }
};
</script>

<style lang="less" scoped >
@import './main.less';
</style>

src/main.less

.wg-title{
    .large{
        padding: 0px;
        font-size:16px;
    }
    .default,.small {
        padding:0 0 0 8px;
        font-size:14px;
    }
    .wg-line-no{
        background-color: transparent
    }
}
  • 技术版本
vue:2.6
vue-cli:4.5
vuepress:1.8.2
  • vuepress用于做文档规范管理,后文文档规范做介绍
  • 所有基础组件需求登记在confluence->基础组件,并且实时更新工作状态和jira状态
  • 所有业务组件需求登记在confluence->业务组件,并且实时更新工作状态和jira状态
  • 组件开发之前先在confluence中编写组件需求文档
  • 组件开完成以后需要编写.md 组件使用文档,并发布到线上
  • 只有组件编码完成并自测通过,并且confluence需求文档和线上md使用文档全部编写完成,才算该组件开发完成
  • 如果是用extends的方式开发组件,首先要确保该组件设计和开发遵循高内聚、低耦合原则,其次要维持组件“白盒子”状态,即组件底层源码可维护可修改并可控。

For example:

  • 基于Element-ui的穿梭框源码和原文件结构:

image.png

main.vue:

image.png

transfer-panel.vue:

image.png

  • 穿梭框源码main.vue只导出了transfer组件给我们extends,其中transfer-panel组件对于我们来说是一个黑盒子,那么我们extends穿梭框组件时,也需要把transfer-panel组件的源码完全移植过来并改造成适应于我们需求的底层组件。
  • 根据源码修改以后的wg-transfer:

image.png

修改后的main.vue:

image.png

wg-transfer-panel.vue:

image.png

项目结构规范

项目结构的核心思想

  • 业务功能模块的相关代码都集中在一块,方便移动和删除
  • 实现关注点分离,方便开发、调试、维护、编写、查阅、理解代码

项目目录

assets      //静态资源
config      //webpack配置文件存放地址
docs        //wg组件说明文档,md文件
mixin       //全局mixin注入方法
packages    // 组件存放的地方
src
 -router    // 路由相关
 -store     // vuex相关
 -utils     // 全局函数 
 -views     // 测试页面
 -api
  -index.js  // http封装
 -assets    // 静态资源
 -mian.js   // 项目入口
 -app.vue   // 主布局页面
package.json
vue.config.js
.gitignore

资源命名

  • 能直观的感受当前目录文件的作用
  • 以小驼峰方式命名
  • 不允许出现大写开头的命名
│   ├── pages/                 
│   ├── components/
│   │   ├── uiTemp/

页面命名

  • 能直观的感受当前文件的作用
  • 以小驼峰方式命名
│   │   │   ├── login.vue       # 登录页面
│   │   │   ├── changePhone.vue # 用户中心-修改电话号码页面

组件命名

  • 能直观的感受当前组件的用途
  • 组件命名始终是多个单词的,避免跟html元素或标签冲突
  • 小写开头
// 反例
components/
|- MyButton.vue
|- VueTable.vue
|- Icon.vue

// 好例子
components/
|- baseButton.vue
|- baseTable.vue
|- baseIcon.vue
|- select.vue

编码规范

见我之前的分享# 端工程计划规范篇之确立前端JS编码规范

规范化提交

规范化 git commit 对于提高 git log 可读性、可控的版本控制和 changelog 生成都有着重要的作用。

前端所有工程目前只有一个dev分支在使用,新的版本的开发任务以及提测版本错误问题也全部在这个分支上进行开发,从而会导致很多问题出现,非常不利于版本的控制

下面用这张图来说明前端分支管理方法

  • dev:开发主要分支

  • release/1-1-MS:测试分支; 命名规则: release + / + 版本号

image.png

  1. 目前前端工程目前已经开启了eslint,并参照我们的前端代码规范进行相关的eslint规则配置,默认会在提交commit时进行校验
  1. 当然如果比较紧急可以采用下面的的方式避开校验,但是这只是在短期内如此,后期将打开服务端校验,所以在下面一个周期内,每个工程对应的前端负责人,务必清除掉全部的eslint的问题
git commit --no-verify -m "提交注释" //可以跳过代码检查

git常用命令

  • git clone

    git clone url  克隆远程版本库
    
  • git checkout -b dev

    git checkout -b dev   创建开发分支dev,并切换到该分支下
    
  • git add

    git add .   添加当前目录的所有文件到暂存区
    git add [dir]   添加指定目录到暂存区,包括子目录
    git add [file1] 添加指定文件到暂存区
    
  • git commit

    git commit -m [message] 提交暂存区到仓库区,message为说明信息
    git commit [file1] -m [message] 提交暂存区的指定文件到本地仓库
    git commit --amend -m [message] 使用一次新的commit,替代上一次提交
    
  • git log

    git log  查看提交历史
    git log --oneline 以精简模式显示查看提交历史
    git log -p <file> 查看指定文件的提交历史
    git blame <file> 一列表方式查看指定文件的提交历史
    
  • git diff

    git diff 显示暂存区和工作区的差异
    git diff filepath   filepath路径文件中,工作区与暂存区的比较差异
    git diff HEAD filepath 工作区与HEAD ( 当前工作分支)的比较差异
    git diff branchName filepath 当前分支的文件与branchName分支的文件的比较差异
    git diff commitId filepath 与某一次提交的比较差异
    
  • git status

    git status  查看当前工作区暂存区变动
    git status -s  查看当前工作区暂存区变动,概要信息
    git status  --show-stash 查询工作区中是否有stash(暂存的文件)
    
  • git submodule

    git submodule init git子模块初始化
    git submodule update git子模块更新
    
  • git pull/git fetch

    git pull  拉取远程仓库所有分支更新并合并到本地分支。
    git pull origin master 将远程master分支合并到当前本地master分支
    git pull origin master:master 将远程master分支合并到当前本地master分支,冒号后面表示本地分支
    git fetch --all  拉取所有远端的最新代码
    git fetch origin master 拉取远程最新master分支代码
    
  • git push

    git push origin master 将本地分支的更新全部推送到远程仓库master分支。
    git push origin -d <branchname>   删除远程branchname分支
    git push --tags 推送所有标签
    

代码规范化

代码规范化的重要性不言而喻,代码规范化涉及的工具有 editorconfig、eslint、prettier 等。

  • eslint配置
module.exports = {
  env: {
    browser: true,
    commonjs: true,
    es6: true,
  },
  extends: ['plugin:vue/essential', 'airbnb-base', 'plugin:prettier/recommended'],
  parserOptions: {
    parser: '@babel/eslint-parser',
    ecmaFeatures: {
      jsx: true,
    },
  },
  // vue 的关键配置
  plugins: ['html', 'vue'],
  globals: {
    wx: true,
    qq: true,
    'qq.maps': true,
    $: true,
    Vue: true,
    getApp: true,
  },
  rules: {
    'arrow-body-style': [0],
    'class-methods-use-this': [0],
    'consistent-return': [0],
    'generator-star-spacing': [0],
    'global-require': [0],
    'import/extensions': [0],
    'import/first': [0],
    'no-param-reassign': [0],
    'no-plusplus': [0],
    'import/no-relative-packages': [0],
    'import/no-extraneous-dependencies': [0],
    'import/prefer-default-export': [0],
    'import/no-unresolved': [0],
    'import/no-absolute-path': [0],
    'import/order': [0],
    'linebreak-style': [0],
    'no-bitwise': [0],
    'no-cond-assign': [0],
    'no-console': [0],
    'no-debugger': [0],
    'no-else-return': [0],
    'no-nested-ternary': [0],
    'no-restricted-syntax': [0],
    'no-trailing-spaces': [0],
    'no-use-before-define': [0],
    'no-useless-escape': [0],
    'no-return-await': [0],
    'no-unused-vars': [0],
    'prefer-template': [0],
    'no-underscore-dangle': [0],
    'require-yield': [1],
    'no-prototype-builtins': [0],
    'no-shadow': ['error', { builtinGlobals: false, hoist: 'functions', allow: ['e', 'state'] }],
    'no-unused-expressions': [
      0,
      {
        allowShortCircuit: true,
        allowTernary: true,
      },
    ],
    'max-len': [0],
    'vue/singleline-html-element-content-newline': [0],
    'vue/multiline-html-element-content-newline': [0],
    'vue/multi-word-component-names': [0],
    'vue/no-mutating-props': [0],
    'vue/require-valid-default-prop': [0],
    'vue/no-unused-vars': 'off',
    camelcase: ['warn', { ignoreDestructuring: true }],
    'prefer-destructuring': ['error', { object: true, array: false }],
    'vue/html-self-closing': [
      'error',
      {
        html: {
          void: 'never',
          normal: 'always',
          component: 'always',
        },
        svg: 'always',
        math: 'always',
      },
    ],
    'default-param-last': 0,
  },
};

  • prettier配置
{
  "printWidth": 120,
  "singleQuote": true,
  "semi": true,
  "trailingComma": "all",
  "proseWrap": "never",
  "endOfLine": "auto"
}

文档维护

组件库文档比较重要的是有可以交互的 Demo 演示,这里我们使用vuepressvuepress-plugin-demo-container我们可以很好地满足 Vue 组件库文档的搭建。

vuepress官方介绍

  • 安装依赖
npm install -D vuepress
  • 创建你的第一篇文档
mkdir docs && echo '# Hello VuePress' > docs/README.md
  • 在 package.json 中添加一些 scripts

    • 这一步骤是可选的,但我们推荐你完成它。项目中我们会默认这些 scripts 已经被添加。
"scripts": {
    "lint": "vue-cli-service lint",
    "lib": "vue-cli-service build --target lib src/main.js",
    "watch": "vue-cli-service build --watch --target lib src/main.js",
    "com": "vue-cli-service build --target wc --name wg-sider './packages/sider/src/main.vue'",
    "view": "vue-cli-service build --target wc --name page-mune './view/index.js'",
    "dev": "cross-env NODE_ENV=development vuepress dev docs ",
    "build": "vuepress build docs"
  },
  • 在本地启动服务器
npm run dev

预览

image.png

image.png

总结

除了以上的行动事项之外,我们还需要确定发布流程单元测试

其中

发布流程 因为每个业务团队根据自身的场景需求都有一些相应的变化和不同,这里不做过多的阐述

单元测试文档 一样,是保障程序最小单元质量的重要一环。诚然一个成熟的组件库是必然有单元测试的身影。

在react中我用的是jest、babel-jest、@types/jest 这些依赖,vue不熟悉,需要探索或者之后再来补充

以前用的enzyme,基于以上依赖做的单元测试框架

Enzyme 是用于 React 的 JavaScript 测试实用程序,可以更轻松地测试 React 组件的输出。
还可以根据给定的输出进行操作,遍历并以某种方式模拟运行时。