(构建配置篇)开发一个行为上报SDK

564 阅读13分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战」。

前言

在实现一个行为上报的 SDK 的过程中踩了很多坑,也学到了很多知识点,因此以书面的形式总结一下自己的经验。

本文将讲述我的行为上报SDK的开发历程。

目标

  • 实现 umd 格式 (支持 AMD/CJS/ESM 模块引入或者 script 标签引入)
  • 配置 Babel 实现语法转化
  • 配置 ESLint、Prettier、commitlint 规范代码格式和提交格式
  • 实现业务逻辑
  • 配置单元测试 Jest 提高代码质量
  • 配置 CICD 减少人力
  • 发布到 npm

正文

项目结构

├── babel.config.js // babel 配置
├── .commitlintrc.js // git commit 配置
├── .eslintrc.js // eslint 配置
├── .gitignore // git 忽略文件
├── .npmignore // npm 忽略文件
├── README.md // 项目介绍
├── dist // 生产目录
│      └── bundle.js // 打包后的 js 文件
├── package-lock.json
├── package.json // 项目配置
├── core // 源文件目录
│      └── clue-report.js // 入口文件
└── webpack.config.js // webpack配置

构建配置

俗话说,工欲善其事,必先利其器,开发 sdk 也是一样的道理,有一个良好的构建配置将减少 sdk 的开发和维护成本。

配置 babel

babel 可以帮我们将js语法/api转化为兼容目标浏览器的语法,使得开发者在不需要关心浏览器兼容性的场景下使用新的语法。例如使用箭头函数,在IE10的场景下是不支持的,他可以帮助我们将箭头函数转化成 IE浏览器支持的写法。

对于 const、let 这一类的语法,我们可以通过配置 @babel/preset-env 进行转化。

对于 promise 这一类的API,我们可以通过 babel-polyfill@babel/core-js 或者 @babel/runtime-corejs3

1. 配置

共有三种配置 babel 的方式:

  • 在 package.json 中配置 babel 字段
  • .babelrc.json 文件,具有不同的扩展名(.babelrc、.js、.cjs、.mjs)
  • babel.config.json 文件,具有不同的扩展名(.js、.cjs、.mjs)

如果是项目级配置的话,建议使用第三种形式。

对于行为上报的 SDK 使用的是 babel.config.js。

2. 语法转化

@babel/preset-env: 可以帮助我们将新的 js 语法转化为目标浏览器支持的语法, 配置如下:

// babel.config.js 
module.exports = {   
    presets: [     
        [
            '@babel/preset-env'// es6转es5语法
        ],
    ]
};

这时候我们就可以使用新的语法在目标浏览器上啦。

Q: 如何查看编译后的结果?

A: 可以通过 @babel/cli 打包输出

yarn add @babel/cli @babel/preset-env @babel/core -D
  • 安装 @babel/cli - 便于在packages.json的script中使用了babel命令
  • 安装 @babel/preset-env - 讲新的 js 语法转化为浏览器支持的语法
  • 安装 @babel/core - babel核心库,必安装

在package.json中添加script命令

"scripts": { 
    "build""babel core -d dist", // 打包core目录下的js文件,输出到dist目录
},

创建一个 core 目录,用于存放行为上报 SDK 的源代码,新建一个js文件,clue-report.js 存放核心代码。如下图所示

image.png

// clue-report.js
class ClueReport{}
Promise.resolve();
Array.from();

执行 yarn build 我们可以发现

"use strict"; 
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = trueif ("value" in descriptor) descriptor.writable = trueObject.defineProperty(target, descriptor.key, descriptor); } } 
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor"prototype", { writablefalse }); return Constructor; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var ClueReport = /*#__PURE__*/_createClass(function ClueReport() { 
    _classCallCheck(thisClueReport);
});
Promise.resolve();
Array.from();

编译后的代码中,_classCallCheck 是一个辅助功能实现的工具函数。如果多个文件中都用到了 class,每一个文件编译后都生成一个工具函数,最后就会产生大量重复代码,平白增加文件体积。

plugin-transform-runtime 就是为了解决这个问题,这个插件会将这些工具函数转换成引入的形式。

因此,执行

yarn add @babel/plugin-transform-runtime -D

并在 babel.config.js 中配置

// babel.config.js
module.exports = {
    presets: [
        '@babel/preset-env',
    ], 
    plugins: [ 
        '@babel/plugin-transform-runtime'
    ]
}

编译后的结果:

"use strict"; 
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); 
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass")); 
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var ClueReport = /*#__PURE__*/(0, _createClass2["default"])(function ClueReport() { 
    (0, _classCallCheck2["default"])(thisClueReport);
});
Promise.resolve();
Array.from();

从编译的结果我们不难发现,通过 plugin-transform-runtime 会引入 @babel/runtime内的工具函数,所以我们需要安装 @babel/runtime 这个依赖包,否则在项目打包的时候会报错。

babel/runtime并不是开发依赖,而是项目生产依赖。编译时使用了plugin-transform-runtime,你的项目就会依赖于babel/runtime,所以这两个东西是一起使用的

因此,我们需要安装 @babel/runtime 避免报错

yarn add @babel/runtime -S

由编译后的代码,我们还能看出,@babel/preset-env 并不会对 Promise、Array 进行转化,我们需要通过 @babel/runtime-corejs3进行转化

3. API 转化

使用 @babel/plugin-transform-runtime + @babel/runtime-corejs3 + @babel/runtime 进行 API的转化

因为 @babel/plugin-transform-runtime + @babel/runtime 已经安装过了,此时安装 @babel/runtime-corejs3 即可

yarn add @babel/runtime-corejs3 -D

在babel.config.js 添加如下配置

module.exports = {
    presets: [ 
        '@babel/preset-env',
    ], 
    plugins: [
        ['@babel/plugin-transform-runtime', { corejs3}]
    ]
}

重新执行 yarn build,编译结果如下

"use strict";
var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault");
var _promise = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/promise"));
var _from = _interopRequireDefault(require("@babel/runtime-corejs3/core-js-stable/array/from")); var _createClass2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/createClass"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime-corejs3/helpers/classCallCheck"));
var ClueReport = /*#__PURE__*/(0, _createClass2["default"])(function ClueReport() {
    (0, _classCallCheck2["default"])(thisClueReport);
});
_promise["default"].resolve();
(0, _from["default"])();

通过编译结果,我们不难看出,@babel/runtime-corejs3 会新增一个变量,用于对 Promise、Array.from 的兼容转化,而且还不会污染原生的Array.from方法。

结论:

使用 @babel/preset-env 可以进行语法转化

对于API的转化,如果我们使用了 Array.from,但是我们的依赖库 B 也定义了这个函数,这时我们全局引入 polyfill 就会出问题:覆盖掉了依赖库 B 的 Array.from。因此用 runtime 就相对安全了,它会新增一个变量,在不污染全局Array.from的场景下进行兼容处理。

因此使用@babel/plugin-transform-runtime + @babel/runtime + @babel/runtime-corejs3 ,目的是为了不影响业务全局的变量或者被影响,适合类库开发

包名功能说明
@babel/cli命令行执行babel命令工具非必装开发依赖,packages.json的script中使用了babel命令则需安装
@babel/corebabel编译核心包必装的开发依赖
@babel/preset-*功能实现插件预设开发依赖,按照需要的功能安装,js语言新特性转换推荐使用preset-env
@babel/plugin-transform-runtime复用工具函数非必装开发依赖,和@babel/runtime同时存在
@babel/runtime工具函数库非必装生产依赖,和@babel/plugin-transform-runtime同时存在
@babel/runtime-corejs*不污染变量的低版本浏览器兼容库非必装生产依赖,plugin-transform-runtime设置开启后,可以不污染变量的引入polyfill@babel/polyfill低版本浏览器兼容库非必装生产依赖,已不推荐使用,推荐通过preset-env的useBuiltIns属性按需引入
core-js@*低版本浏览器兼容库非必装生产依赖,通过preset-env引入polyfill需安装此包,并通过corejs指定版本
babel-loaderwebpack中使用babel加载文件非必装开发依赖,webpack项目中使用

如果想要进一步了解babel的配置,可以查阅官方文档:

打包组件

我们知道 webpack 除了可以打包应用以外,还可以用来打包一些 js 库或者组件库

当然,单纯只是打包 js 库和组件库的话 使用 rollup 打包也是一个不错的选择。因为 rollup 可以极大程度地去减少代码体积,可以通过 tree-shaking 去抹除无用的代码。

但 rollup 无法支持代码分割( splitChunks )等比较高级的特性,基于 SDK 后续的发展考虑,还是选择了功能更加强大的 webpack 进行打包。

npm init -y // 1 初始化 npm 仓库
git init // 2 初始化 git 仓库
yarn add webpack webpack-cli -D // 3 安装webpack依赖

创建 .gitignore 用来存放不需要提交的文件

// .gitignore
dist/
node_modules/

创建 webpack.config.js

因为我们需要打包一个js库,支持以下功能:

  • 它支持 AMD/CJS/ESM 模块引入
  • 支持通过 script 脚本直接引入链接

Q:我们如何将库暴露出去呢?

A:我们需要通过 output.library 配置项将库暴露出去。

Q:如何支持 AMD/CJS/ESM 模块引入或者 script 脚本的形式引入?

A:只需要将  output.library.type 赋值成 umd 即可

因为每次构建都会生成一次js文件,因此我们通过 clean-webpack-plugin 清理上一次的构建产物

yarn add clean-webpack-plugin -D

根据这些知识储备,我们可以将 webpack.config.js 编写成如下所示:

// webpack.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
  mode'production'// 这样可以支持tree-shaking/scope-hosting等特性
  entry: {
    'clue-report': path.resolve(__dirname, './core/clue-report.js'), // 入口文件
  },
  output: {
    filename'[name].min.js', // 输出的文件
    library: {
      name'clueReport'// 暴露出去的库的名称 - library
      type'umd'// 支持库引入的方式 - libraryTarget
      export'default', // 不添加的话引用的时候需要 clueReport.default
    },
    path: path.resolve(__dirname, 'dist'), // 输出的路径
  },
  plugins: [new CleanWebpackPlugin()], // 清理构建目录
}

在package.json脚本下的script添加

"scripts": {
  "build""webpack"
},

当执行 yarn build,会对入口文件进行打包,我们可以使用 babel-loader 对引入的 js 文件通过 babel 进行转化

yarn add babel-loader -D // 安装babel-loader

修改webpack.config.js

// webpack.config.js
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin'); // webpack5 已经集成,无需安装
module.exports = {
  mode'production'// 这样可以支持tree-shaking/scope-hosting等特性
  entry: {
    'clue-report': path.resolve(__dirname, './core/clue-report.js'), // 入口文件
  },
  output: {
    filename'[name].min.js', // 输出的文件
    library: {
      name'clueReport'// 暴露出去的库的名称 - library
      type'umd'// 支持库引入的方式 - libraryTarget
      export'default', // 不添加的话引用的时候需要 clueReport.default
    },
    path: path.resolve(__dirname, 'dist'), // 输出的路径
  },
  plugins: [new CleanWebpackPlugin()], // 清理构建目录
  module: {
    rules: [
      {
        test/\.js$/,
        include: [
          path.resolve(__dirname, 'core'),
          // path.resolve(__dirname, "node_modules/regenerator-runtime"),
        ],
        use: [
          'babel-loader'// 用babel-loader对引入的js文件进行babel转化
        ],
      },
    ],
  }
}

执行 yarn build 我们可以发现在dist目录上生成了打包后的构建产物

image.png

// webpack.config.js - 完整版本
const path = require('path');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin'); // webpack5 已经集成,无需安装
module.exports = {
  mode'production'// 这样可以支持tree-shaking/scope-hosting等特性
  entry: {
    'clue-report': path.resolve(__dirname, './core/clue-report.js'), // 入口文件
  },
  output: {
    filename'[name].min.js', // 输出的文件
    library: {
      name'clueReport'// 暴露出去的库的名称 - library
      type'umd'// 支持库引入的方式 - libraryTarget
      export'default', // 不添加的话引用的时候需要 clueReport.default
    },
    path: path.resolve(__dirname, 'dist'), // 输出的路径
  },
  plugins: [new CleanWebpackPlugin()], // 清理构建目录
  module: {
    rules: [
      {
        test/\.js$/,
        include: [
          path.resolve(__dirname, 'core'),
          // path.resolve(__dirname, "node_modules/regenerator-runtime"),
        ],
        use: [
          'babel-loader'// 用babel-loader对引入的js文件进行babel转化
          'eslint-loader',
        ],
      },
    ],
  }, 
  optimization: {
    minimizetrue,
    minimizer: [
      new TerserPlugin({
        extractCommentsfalse// webpack5:不将注释提取到单独的文件中,这样的话就不会生成LICENSE.text文件了
      }),
    ],
  },
};

配置 ESLint 和 Prettier

1 Prettier

Prettier 是统一代码风格的工具,使用 Prettier 可以帮助我们很好地管理团队的代码风格

首先,我们需要安装Prettier

yarn add prettier -D

创建一个空的配置文件.prettierrc.json,让编辑器和其他工具知道您正在使用 Prettier,配置如下:

// .prettierrc.json
{
  "printWidth"120,
  "tabWidth"2,
  "semi"true,
  "singleQuote"true,
  "quoteProps""consistent",
  "trailingComma""all",
  "bracketSpacing"true,
  "arrowParens""avoid",
  "proseWrap""never",
  "endOfLine""lf"
}

接下来,创建一个 .prettierignore 文件,让 Prettier CLI 和编辑器知道哪些文件不能格式化。下面是一个例子:

// .prettierignore
yarn.lock
dist/
CNAME
LICENSE
netlify.toml
*.sh
*.snap
*.md
.gitignore
.npmignore
.prettierignore
.editorconfig
.eslintignore
**/*.yml

借助 husky 和 lint-staged 在提交代码时,自动格式化

yarn add  husky lint-staged --dev // 安装依赖
npx husky install
npm set-script prepare "husky install" // 创建 script 下的 prepare 内容为 "husky install" (这里需要 npm >= 7)
npx husky add .husky/pre-commit "npx lint-staged" // 注册钩子,提交前 prettier 格式化一下

将以下内容添加到您的 package.json

// package.json
{
  "lint-staged": {
    "**/*.js": [
      "prettier --write",
     "git add", // lint-staged 10 以上就不需要添加这行命令了 https://github.com/okonet/lint-staged/issues/775
    ]
  }
}

注意事项:

1 执行 npm set-script prepare "husky install" 需要 npm 的版本大于等于7

如是7以下的版本,也可以在package.json上添加上script,如下图所示:

image.png

2 当前配置基于husky v5版本,在 2021 年 1 月 27 日,husky 迎来了 v5 的大版本升级,关于husky v4版本与v5版本差异介绍,请看升级husky5实践 (opens new window)

效果如下图所示:

123.gif

除此之外,

我们可以创建 .vscode 工作区配置, 这样的话就无需开发者手动设置了,保存时就会对编写的文件进行格式化

首先我们安装 vscode 插件 Prettier - Code formatter

其次新建 .vscode 文件夹,在该文件夹内新建一个 settings.json

// settings.json
{
  "editor.tabSize"2,
  "editor.formatOnSave"true// 保存自动格式化
  // ===========================================
  // ================ Editor ===================
  // ===========================================
  // ===========================================
  "[javascript]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
}

效果如下:

1243.gif

2 ESLint

ESLint 是一种用于检查 ECMAScript/JavaScript 代码中错误的工具,其目标是使代码更加一致并避免错误。

首先安装依赖,创建 .eslintrc.js 作为配置文件

yarn add eslint --dev // 安装依赖
// .eslintrc.js
module.exports = {
  "extends": "eslint:recommended" // 配置 eslint 推荐的检验规范
}

eslint --fix file.js [file.js] [dir] 格式化/校验目标文件的语法规范

在 package.json 下的 lint-staged 添加 eslint --fix 用于在提交代码前做一次格式化/校验语法规范的操作


// package.json
"lint-staged": {
  "**/*.js": [
    "prettier --write",
    "eslint --fix"
  ]
}
3 兼容 ESLint 和 Prettier

eslint-plugin-prettier: 调用 prettier 对代码风格进行检查,将 Prettier 作为 ESLint 规则运行,并将差异报告为单个 ESLint 问题。

eslint-config-prettier: 删除所有不必要的或可能与 Prettier 冲突的规则。

为了兼容 ESLint 和 Prettier 我们使用 eslint-config-prettier 做兼容处理,

为了将 Prettier 作为 ESLint 规则运行,我们使用了 eslint-plugin-prettier,

eslint-plugin-prettier 与 eslint-config-prettier 也会有兼容问题,因此我们使用 eslint-plugin-prettier 插件附带了一个 plugin:prettier/recommended 配置,可以一次性设置插件和 eslint-config-prettier的兼容规则。

yarn add eslint-plugin-prettier eslint-config-prettier babel-eslint -D // 安装依赖

修改配置

// .eslintrc.js
/**
 * 0 - off - 关闭规则
 * 1 - warn - 规则视为一个警告
 * 2 - error - 错误
 */
module.exports = {
  roottrue,
  parser'babel-eslint',
  // 配置 eslint-config-prettier 如果同时使用了eslint和prettier发生冲突了,会关闭掉与prettier有冲突的规则,也就是使用prettier认为对的规则
  // eslint-plugin-prettier 插件附带了一个 plugin:prettier/recommended 配置,可以一次性设置插件和 eslint-config-prettier。
  extends: ['eslint:recommended''plugin:prettier/recommended'], // https://github.com/prettier/eslint-plugin-prettier#recommended-configuration
  env: {
    browsertrue,
    nodetrue,
    es6true,
  },
  parserOptions: {
    parser'babel-eslint'// 词法解析器使用babel-eslint,以更好的适配es6的新api
    sourceType'module'// 设置"script"(默认)或"module"如果你的代码是在ECMAScript中的模块。
  },
  rules: {
    'no-console''warn',
    'no-debugger''warn',
    'no-control-regex''off',
    'no-prototype-builtins''off',
    'quotes': ['error''single'],
    'comma-dangle': ['error''only-multiline'],
    'no-unused-vars''off',
    'indent''off',
    'prefer-const''error',
    'no-case-declarations''off',
    'no-irregular-whitespace': ['error', { skipCommentstrue }], // 允许注释存在空白格
    'prettier/prettier': [
      // "prettier/prettier": "error",表示被prettier标记的地方抛出错误信息。
      'error',
      {
        endOfLine'auto',
      },
    ],
  },
};

注意事项:

eslint-plugin-prettier (安装 3.1.3 版本, 避规 ESLint: Error while loading rule 'prettier/prettier': context.getPhysicalFilename is not a function Occurred ) 用来配合 ESLint 检测代码风格。

eslint-plugin-prettier需要在 .eslintrc.js 下 rules 配置 - "prettier/prettier": "error"

4 保存时 ESLint 自动格式化

需在vscode上安装eslint 插件,然后修改 .vscode 下的settings.json

{
  "editor.tabSize"2,
  "editor.formatOnSave"true// 保存自动格式化
  // ===========================================
  // ================ Editor ===================
  // ===========================================
  // ===========================================
  "[javascript]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[jsonc]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter""esbenp.prettier-vscode"
  },
  // "eslint.autoFixOnSave": true, 这个设置被废弃了,使用下面的editor.codeActionsOnSave的配置
  "editor.codeActionsOnSave": {
    "source.fixAll"true,
    "source.fixAll.eslint"true
  },
  "eslint.validate": ["javascript""typescript""reacttypescript""reactjavascript""vue"]
}
5 在 webpack 中使用 ESLint

我们需要引入 eslint-loader,在打包的过程中做eslint校验

配置如下:

// webpack.config.js
module: {
    rules: [
      {
        test/\.js$/,
        include: [path.resolve(__dirname, 'core')],
        use: [
          'babel-loader'// 用babel-loader对引入的js文件进行babel转化
          'eslint-loader',
        ],
      },
    ],
},

配置 commitlint

日常开发中由于缺少对于 commit message 的约束,导致填写内容随意、质量参差不齐,可读性低亦难以维护,而书写良好的 commit message 能大大提高代码维护的效率。

出于这个原因,我们可以通过 commitlint 约束 commit message。

1 通过 @commitlint/cli @commitlint/config-conventional 规范提交格式
yarn add @commitlint/cli @commitlint/config-conventional -D // 安装依赖
npx husky add .husky/commit-msg "npx commitlint --edit $1" // 注册钩子,目的是提交代码的时候,去检验提交格式是否符合标准
2 创建 commitlint.config.js 配置提交规范
// commitlint.config.js
module.exports = {
    extends: ['@commitlint/config-conventional'],
};

这里遵循 Angular 的代码提交规范:

  • feature:新功能
  • fix:修补某功能的bug
  • build: 修改项目构建系统(例如 webpack,cli 的配置等)的提交
  • refactor:重构某个功能
  • style:仅样式改动
  • docs:仅文档新增/改动
  • chore:构建过程或辅助工具的变动
  • ci: 主要目的是修改项目继续集成流程
  • perf: 性能, 体验优化
  • test: 测试某功能、新增测试用例、更新现有测试
// bad
git commit -m ": some message"
git commit -m "fix:"
git commit -m "fix:some message"
git commit -m "FIX: some message"
git commit -m "some message"

// good
git commit -m "fix: some message"

只有在遵循代码提交规范的场景下才能提交代码,否则将无法提交,效果如下图所示: 12443.gif

通过 standard-version 生成 changeLog
yarn add standard-version -D // 安装依赖

通过 .versionrc.js 配置 changeLog

// .versionrc.js
/**
 *
 * 参考文档
 * https://www.npmjs.com/package/standard-version
 *
 */
module.exports = {
  //   skip: {
  //     // bump: true, //缓存变化,并重置git状态至最近的tag节点 true-绕过 默认false
  //     // changelog: true, //自动产出changelog文档 true-绕过 默认false
  //     commit: true, //提交变动 true - 绕过
  //     tag: true, //在git中增加tag标识 true - 绕过
  //   },
  header'# SDK更新日志 \n\n',
  types: [
    { type'feat'section'✨ Features | 新功能' },
    { type'fix'section'🐛 Bug Fixes | Bug 修复' },
    { type'perf'section'⚡ Performance Improvements | 性能优化' },
    { type'revert'section'⏪ Reverts | 回退' },
    { type'chore'section'📦 Chores | 其他更新' },
    { type'docs'section'📝 Documentation | 文档' },
    { type'style'section'💄 Styles | 风格' },
    { type'refactor'section'♻️ Code Refactoring | 代码重构' },
    { type'test'section'✅ Tests | 测试' },
    { type'build'section'👷‍ Build System | 构建' },
    { type'ci'section'🔧 Continuous Integration | CI 配置' },
  ],
};

第一次生成 changeLog 如下命令,做初始化

npx standard-version --first-release // 第一次生成 changeLog 执行

也可以在package.json配置如下命令,执行命令不仅会生成 changeLog 还会修改版本号

changelog:major - 修改大版本,软件做了不兼容的变更

changelog:minor - 修改次版本,添加功能或者废弃功能,向下兼容

changelog:patch - 修改小版本,bug 修复,向下兼容

{
  "scripts": {
       "changelog:major""standard-version --release-as major",
       "changelog:minor""standard-version --release-as minor",
       "changelog:patch""standard-version --release-as patch",
  }
}

配置CICD

在 SDK 中配置 CICD 的主要目的是为了在代码提交的场景下,可以自动打包部署到服务器,减少人力成本,提高开发效率。

gitlab 中 CI/CD 的基本配置流程
  1. 1 注册一台runner机子,填入项目地址和令牌,就可以关联到对应的仓库
  2. 2 当你推送代码到远程仓库时,会检查项目下有没有.gitlab-ci.yml文件
  3. 3 如果存在,会触发hooks在你当前runner机所处的位置,执行yml文件中描述的任务
具体配置流程
1 注册 runner 机子

这里分开windows和linux两种版本,实际业务中都是放在linux服务器,windows版可以自己用来熟悉一下yml的一些命令和ci的代码测试。

  • windows版

image.png

1 根据64位或者32位的系统下载 runner,下载完之后,把那个 .exe 文件重命名为,gitlab-runner.exe 目的是方便后面跟着步骤操作。

2 注册流程 可以从下图中获取到 runner 的 URL 以及令牌 image.png

  • 执行命令 ./gitlab-runner.exe register
  • 填入复制的url和令牌
  • 填入描述(备注一下机器的用途就行)
  • 填入 runner 的 tags,后续执行 ci 操作的时候会根据这个匹配
  • 选择执行脚本的语言,这里选 shell,后续有些 shell 命令相关操作
  • 完成注册。这时候目录下会多一个 config.toml 文件。刷新 gitlab 后台会看到一台新的注册机子

image.png

3 启动runner

  • .\gitlab-runner.exe run,执行完后,刷新gitlab后台可以看到机器的小点变绿色了,代表机器在运行。

  • 这时候只要配置了正确的yml文件,后续推送代码的时候,就会触发ci

  • Linux 版

如果是Ubuntu系统dpkg -i gitlab-runner_<arch>.deb,如果是CentOS执行rpm -i gitlab-runner_<arch>.rpm

开始注册,sudo gitlab-runner register

后面的填信息的步骤和windows的是一致的

创建一个.gitlab-ci.yml文件

根据需要配置即可,没什么好说的,这里直接贴代码

stages:
  - deploy

cache:
  paths:
    - node_modules/

# 变量
variables:
  # 源路径
  ORIGIN_DIR: "xxxx" 
  # 目标路径
  TARGET_DIR: "xxxxxx"

deploy:
  # 执行安装依赖的任务
  stage: deploy
  # 这个对应的是刚刚注册的runner的名字,这个非常重要,决定了你是否能启用某个runner机子
  tags:
    - ts-tag
  only:
    # 这个是限制的分支,这里表示只有在develop推送时,才会触发cicd
    refs:
      - develop
    # 这里代表commit的备注中,存在cicd这几个关键词时,才会触发
    variables:
      - $CI_COMMIT_TITLE =~ /cicd/
  # 脚本
  script:
    - cd $ORIGIN_DIR
    - yarn deploy:dev

配置Jest

通过 Jest, 我们可以测试我们编写代码的质量。

首先,我们需要安装Jest

yarn add --dev jest

将下列配置内容添加到 package.json

{
    "scripts": {
        "test": "jest"
    }
}

执行 yarn test 执行测试时,会查找项目中以 .test.js 结尾的文件,不限制需要放在哪个文件夹中,但一般我们都会存放在test目录中,统一管理和维护。

我们可以通过 jest.config.js 进行配置

/*
  % stmts	语句覆盖率	是不是每个语句都执行了?
  % Branch	分支覆盖率	是不是每个 if 代码块都执行了?
  % Funcs	函数覆盖率	是不是每个函数都调用了?
  % Lines	行覆盖率	是不是每一行都执行了?

 * For a detailed explanation regarding each configuration property, visit:
 * https://jestjs.io/docs/configuration
 */

module.exports = {
  collectCoverage: false, // 是否显示覆盖率报告
  testEnvironment: 'jsdom', // 测试环境改成浏览器环境
  collectCoverageFrom: ['core/clue-report.js'], // 告诉 jest 哪些文件需要经过单元测试测试
};

这里通过一个简单的测试用例介绍一下 Jest, 有兴趣的小伙伴可以通过官方文档进一步学习

/**
  前端测试并不是所有项目都必须的,因为写测试代码是需要要花费一定时间的,当项目比较简单的时候,
  花时间写测试代码可能反而会影响开发效率,但是需要指出的是,前端开发过程中,编写测试代码,有以下这些好处:

  1 更快的发现bug,让绝大多数bug在开发阶段发现解决,提高产品质量
  2 比起写注释,单元测试可能是更好的选择,通过运行测试代码,观察输入和输出,有时会比注释更能让别人理解你的代码(当然,重要的注释还是要写的。。。)
  3 有利于重构,如果一个项目的测试代码写的比较完善,重构过程中改动时可以迅速的通过测试代码是否通过来检查重构是否正确,大大提高重构效率
  4 编写测试代码的过程,往往可以让我们深入思考业务流程,让我们的代码写的更完善和规范。
*/
test('变量a是否为null', () => {
    const a = null
    expect(a).toBeNull()
})

test('变量a是否为undefined', () => {
    const a = undefined
    expect(a).toBeUndefined()
})

test('变量a是否为defined', () => {
    const a = null
    expect(a).toBeDefined()
})

test('变量a是否为true', () => {
    const a = 1
    expect(a).toBeTruthy()
})

test('变量a是否为false', () => {
    const a = 0
    expect(a).toBeFalsy()
})

test('test not', () => {
  const temp = 10
  expect(temp).not.toBe(11)
  expect(temp).not.toBeFalsy()
  expect(temp).toBeTruthy()
})

expect代表期望函数执行的结果,也就是toBe里的那个值。

test - 测试用例

expect - 断言,期望的值

toBeUndefined - 判断返回的值是否是undefined

toBeNull - 判断返回的值是否是null

toBe - 判断基本类型数据

toEqual - 判断引用类型数据

describe - 测试用户分组

not - 取反

发布到npm

打开控制台,输入以下指令

  • 如果你是第一次发包,使用 npm adduser
  • 如果不是第一次发,使用npm login

登录成功后,在项目的根目录下,与 package.json 同级的目录,执行:

npm publish

如果你的包内容需要更新,可以使用这种语义化的更新

//更新补丁(最小版本),一般用于 bug 的修复 ~1.0.1
$ npm version patch

//更新新特性 ^1.1.0
$ npm version minor

//更新大版本,一般不能向后兼容或者变更较大的情况下更新 2.0.0
$ npm version major

//最后发布
$ npm publish

发布成功如下所示: image.png

总结

我们通过 Babel 针对目标浏览器进行语法转化、通过 ESLint、Prettier、commitlint 规范项目的代码规范以及提交规范、通过 Jest 配置单元测试使得代码更具健壮性、通过配置 CICD 减少人力成本,提高开发效率。

经过了一系列的配置,我们对SDK的基本构建就完成了。

接下来我们将开始编写一个SDK,有兴趣的小伙伴可以看看(设计篇)开发一个行为上报SDK(待更新)

参考文档

强烈建议看官方文档

官方文档

  1. prettier.io/docs/en/ins… - Prettier

  2. www.npmjs.com/package/sta… - standard-version

  3. github.com/prettier/es… - eslint-plugin-prettier

  4. github.com/prettier/es… - eslint-config-prettier

  5. jestjs.io/zh-Hans/doc… - Jest

不错的文章

  1. segmentfault.com/a/119000004… - 配置 Prettier 和 commitlint (建议用 yarn 安装 husky, npm 有坑) - github.com/typicode/hu…

  2. www.npmjs.com/package/sta… - 生成 changelog

  3. juejin.cn/post/684490… - 配置 ESLint

  4. juejin.cn/post/684490… - 配置 ESLint

  5. juejin.cn/post/703910… - 配置 Jest

  6. juejin.cn/post/684490… - 编写 Jest

  7. juejin.cn/post/684490… - JavaScript设计指南