Skip to content
On this page

实战-搭建开发环境


现在我们就正式进入了我们的实战,也是小册子的核心部分:从 0 到 1 开发一个图表库。前一章对我们要开发的东西 Sparrow已经有了一个简单的介绍,这里就不多说了。

在正式开发之前,我们先来搭建开发环境:一个合理的开发环境,会大大提高我们写代码的效率,规范性和健壮程度。

我们将会从初始化环境开始,然后从检查(Lint)测试(Test)构建(Build)版本管理(Version Control)持续化集成(Continuous integration) 这几方面来搭建我们的开发环境。

那么首先我们来初始化我们的环境。

初始化环境

首先在任意位置新建一个名叫 sparrow 的文件夹,然后安装下面对应版本的 nodenpm

  • node: 14.17.6
  • npm: 6.14.15

接下来运行 npm init \-y 来初始化环境,这之后 sparrow 文件夹里会出现一个 package.json 文件。又了它之后,我们就来搭建我们的开发环境!

代码编辑器推荐使用 VSCode

代码检查(Lint)

首先我们来看看代码检查,代码检查是一种静态的分析,常用于寻找有问题的模式或者代码,能帮助我们规范代码和加少出错的可能性。这里我们将使用 ESlint 来作为代码检查的工具。

$ npm install eslint --save-dev

我们可以通过下面的命令来初始化 ESlint 的配置,它会问你一些问题,根据你的选择去生成对应的配置文件。

$ npx eslint --init

这里把问题和答案都记录下来了。

  • How would you like to use ESLint? > To check syntax, find problems, and enforce code style
  • What type of modules does your project use? > JavaScript modules (import/export)

  • Which framework does your project use? > None of these

  • Does your project use TypeScript? > NO

  • Where does your code run? > Browser, Node

  • How would you like to define a style for your project > Use a popular style guide

  • Which style guide do you want to follow? > Airbnb: https://github.com/airbnb/javascript

  • What format do you want your config file to be in? > JavaScript

  • Would you like to install them now with npm? > Yes

最后对生成的配置文件进行简单修改后如下:

javascript
module.exports = {
  env: {
    browser: true,
    es2021: true,
    node: true,
    // 这里增加一行,用于支持后面的测试环境
    jest: true,
  },
  extends: ["airbnb-base"],
  parserOptions: {
    ecmaVersion: 13,
    sourceType: "module",
  },
  rules: {
    // 这里添加一行规则把这条规则隐藏
    "import/prefer-default-export": 0,
  },
};

因为我们需要使用 airbnb 的规则集合,所以需要额外安装下面的包:

$ npm install eslint-plugin-import eslint-config-airbnb-base@latest --save-dev

为了验证 eslint 已经正确使用了,我们在项目的根目录下增加:src/index.jssrc/drawRedRect.js,并且输入以下的代码:

javascript
// src/index.js

export { drawRedRect } from './drawRedRect';
javascript
// src/drawRedRect.js

export function drawRedRect(svg) {
  var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
  rect.setAttribute('x', 0)
  rect.setAttribute('y', 0)
  rect.setAttribute('fill', 'red')
  rect.setAttribute('width', 100)
  rect.setAttribute('height', 100)
  svg.appendChild(rect)
}

在控制台输入命令:npx eslint src/drawRedRect.js 会去检测 src/drawRedRect.js 文件存在的规则问题,如果加上 --fix 选项会修复在规则列表有黄色 🔧 标记的规则。这里我们输入 npx eslint src/drawRedRect.js \--fix,这个时候打开 src/drawRedRect,会发现 var 被修复成了 const,并且每一条语句末尾都被添加上了 ;

javascript
// src/drawRedRect.js

export function drawRedRect(svg) {
  const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
  rect.setAttribute('x', 0);
  rect.setAttribute('y', 0);
  rect.setAttribute('fill', 'red');
  rect.setAttribute('width', 100);
  rect.setAttribute('height', 100);
  svg.appendChild(rect);
}

这之后我们给 package.json 增加 lint 命令如下:

json
{
  "scripts": {
    "lint": "eslint --fix"
  }
}

为了更好的开发体验,如果使用 VSCode 话可以安装 eslint 插件(直接在应用商店搜索即可)。一方面插件会直接在文件中把问题高亮出来,另一方面我们可以通过命令来直接修复问题:cmd + p + > ESlint: Fix all auto-fixable Problems。更多 ESlint 相关的东西可以去官网学习。

代码测试(Test)

代码检测之后就是代码测试了,优秀的单元测试能保证我们的项目在迭代的过程中不出问题,这里我们将使用 JestJest Electron 来搭建我们的测试环境。

Jest 是一个流行的 JavaScript 的测试框架,一般都运行在 node 或者模拟出来的浏览器环境,但是因为我们开发的是一个图表库,真实的浏览器环境会更加易于我们调试,所以我们需要 Jest Electron。我们用如下命令安装,这里需要注意的是一定要安装下面版本的 Jest,否者会出问题。

$ npm i jest@26.0.1 jest-electron --save-dev

然后增加一个名叫 jest.config.js 并且增加内容如下:

javascript
module.exports = {
  testMatch: ['**/__tests__/**/*.spec.js'], // 只测试后缀为 .spec.js 的文件
  runner: 'jest-electron/runner', // 指定测试的 runner
  testEnvironment: 'jest-electron/environment', //  制定测试的环境
};

在真正开始测试之前我们还需要将代码编译成可以在 node 环境下面可以运行的代码,这个时候我们就需要 Babel 了。Babel 是一个 JavaScript 编译器,主要用于在当前和旧的浏览器或环境中,将 ECMAScript 2015+ 代码转换为 JavaScript 向后兼容版本的代码。

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env

然后增加一个名叫 .babelrc 的文件并且输入以下内容:

json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "entry",
        "corejs": "3.6.4"
      }
    ]
  ],
  "exclude": "node_modules/**"
}

这个时候我们在根目录下增加 __tests__ 文件夹,并且增加 index.spec.jsutils.js 文件:

javascript
// __tests__/index.spec.js

import { drawRedRect } from '../src';

describe('test', () => {
  test('drawRedRect()', () => {
    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
    svg.setAttribute('width', 400);
    svg.setAttribute('height', 400);
    svg.setAttribute('viewBox', [0, 0, 400, 400]);
    document.body.appendChild(svg);
    
    drawRedRect(svg);
    expect(svg.getElementsByTagName('rect').length).toBe(1);
  });

});
javascript
// __tests__/utils.js

export function createDiv() {
  const div = document.createElement('div');
  document.body.appendChild(div);
  return div;
}

如果一切顺利的话我们运行:npx jest 和普通的测试测试没有太多的区别,但是 DEBUG_MODE=1 npx jest 会额外打开一个基于 Electron 真实的浏览器,并且输出如下:

为了兼容 Windows 和 Mac 系统,我们需要额外安装一个 cross-env 这个库。

bash
$ npm install --save-dev cross-env

这样需要打开 Electron 测试的时候输入:cross-env DEBUG_MODE=1 npx jest

这个过程中 Jest 会首先读取 .babelrc 的内容,用 Babel 将代码编译之后再测试。之后我们给 package.json 增加 testtest-live 命令如下:

json
{
  "scripts": {
    "test": "jest --coverage",
    "test-live":"cross-env DEBUG_MODE=1 jest --coverage",
  }
}

代码构建(Build)

因为我们最后是完成一个库并且发布在 npm 上供大家使用,所以我们需要对我们的代码进行构建,这个时候我们需要对编译好的代码进行打包: 将小块代码编译成大块复杂的代码,然后在不同的环境下运行。这个地方我们选择 Rollup 来作为我们的打包器。

在打包之前我们需要 Rollup 用 Babel 对代码进行编译,所以我们还需要安装 rollup-plugin-babel 这个插件。

$ npm install --save-dev rollup rollup-plugin-babel

另一方面我们还需要 rollup-plugin-node-resolve 这个插件来保证打包过程能正确加载文件。

$ npm install --save-dev rollup-plugin-node-resolve

安装完成之后我们新建 rollup.config.js 文件,输入以下的内容:

javascript
import babel from 'rollup-plugin-babel';
import resolve from 'rollup-plugin-node-resolve';

export default {
  input: 'src/index.js', // 打包入口
  output: [
    {
      file: 'lib/sparrow.js', // 对于 Nodejs,打包成 commonjs
      format: 'cjs',
    },
    {
      file: 'esm/sparrow.js', // 对于浏览器,打包成 ES module
      format: 'es',
    },
    {
      file: 'dist/sparrow.min.js',
      name: 'sp',
      format: 'umd', // 对于 Nodejs 和浏览器,打包成混合模式
    },
  ],
  plugins: [
    resolve(),
    babel(), // 使用 babel 插件
  ],
};

这个时候我们运行 npx rollup \--config 就可以发现目录中多了三个 distesmlib 文件夹就是我们的打包结果。当然这里我们还需要安装一个小工具 rimraf,在每次打包之前把以往的打包代码删除。

$ npm i rimraf --save-dev

使用方法也很简单:npx rimraf \-rf ./dist ./lib ./esm 就可以把之前生成的文件删除了。之后我们给 package.json 增加 build 命令如下:

javascript
{
  "scripts": {
    "build": "rimraf -rf ./dist ./lib ./esm && rollup --config"
  }
}

版本管理(VC)

接下来看看我们的版本管理工具,这里当然使用 Git。我们首先运行 git init,然后添加一个如下的 .gitignore 文件来指定不加入版本管理的文件。

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Sys
.DS_Store

# Node
node_modules/

# Build
dist
lib
esm

# Test
coverage

往往在使用 Git 提交(Commit)代码之前,我们希望先对新加入暂存区代码进行一下检查,并且规范提交信息(Commit Message),这个时候我们需要三个工具:

  • husky: husky 能让你创建勾子(Hook),这些钩子会在指定时候执行。
  • commitlint:commitlint 会按照一定的规则对你的提交信息进行检查。

  • lint-staged:lint-staged 只会对你的新加入暂存区的文件进行指定的操作。

我们首先安装 husky:

$ npx husky-init && npm install

然后初始化两个钩子:pre-commit(在提交前执行后面的命令)和 commit-msg(在提交的时候执行后面的命令):

$ npx husky add .husky/pre-commit 'npx lint-staged'
$ npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

然后我们来安装上面提到的 lint-staged,并且修改 package.json 如下:

$ npx mrm@2 lint-staged --save-dev
javascript
{
  "lint-staged": {
    "*.js": ["eslint --fix"] // 在提交前用 eslint 修复所有 js 文件
  }
}

最后安装 commitlint 并且生成配置文件:

bash
# Mac 等系统
$ npm install --save-dev @commitlint/{cli,config-conventional}

# Windows 系统
$ npm install --save-dev @commitlint/config-conventional @commitlint/cli
$ echo "module.exports = { extends: ['@commitlint/config-conventional'] };" > commitlint.config.js

上面我们使用的 config-conventional 这个规则,具体可以参考这里

这之后我们来提交第一次提交代码:git add . && git commit \-m "chore: init local env",一切正常的话控制台输出图如下:

持续化集成(CI)

最后来到我们的持续化集成(CI):频繁地(一天多次)将代码集成到主干。持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。更多 CI 的概念可以参考阮一峰老师的这篇文章

所以我希望每次我们 push 代码,或者提交 PR(Pull Request)的时候都对代码进行检测(Lint),测试(Test)和构建(Build)。如果任何一个不成功就显示失败。

首先我们需要 npm-run-all 来顺序或者并行执行多个 npm 脚本命令:

$ npm install npm-run-all --save-dev

然后在 package.json 里面添加对应的 CI 命令,run-s 是指顺序执行:

json
{
   "scripts": {
      "ci": "run-s lint test build"
  }
}

然后我们使用 Github Action 来完成持续化集成。Action(动作)是指持续化集成的操作,比如上面提到的测试、构建和检测等等。Github 需要配置文件来知道 CI 的流程。

我们在项目的根目录新建一个 .github 的文件夹,然后在里面新建一个名叫 workflows 的文件夹,最后新建一个 ci.yml 的文件并且输入以下的内容。

yaml
name: ci

on: [push, pull_request]

jobs:
  build:
    runs-on: macOS-latest
    
    steps:
      - name: Checkout
        uses: actions/checkout@v2.3.4
        
      - name: Setup Node.js environment
        uses: actions/setup-node@v2.1.5
        with:
          node-version: "12"
          
      - name: Cache node modules
        uses: actions/cache@v2
        env:
          cache-name: cache-node-modules
        with:
          path: ./node_modules
          key: ${{ runner.os }}-build-cache-node-modules-${{ hashFiles('**/package.json') }}
          restore-keys: |
            cache-node-modules-

      - name: Run ci
        run: |
          npm install
          npm run ci

上面有几个地方很关键:首先是 on: [push, pull_request],说明是在 push 和 pull_request 的时候会触发当前的这个工作流(Workflow):持续集成一次运行的过程。然后每一个工作流包含多个步骤(Step),我们重点关注最后一个步骤:Run ci。可以发现这个步骤主要由两个动作构成:npm install 用来安装依赖,npm run ci 来真正执行我们的 CI 流程。

这个时候我们在 Github 上新建一个名叫 Sparrow 的仓库,并且和本地这个仓库关联起来。接下来提交代码并且推到远程仓库:

git commit \-m "chore: add ci" && git push

这个时候就会自动触发 CI 了:

CI 结束之后如下就说明没有问题了!

Github Action 的深入学习同样可以参考阮一峰老师的另一篇文章

总结

目前为止我们的开发环境就搭建完成了,此刻的代码可以在这个分支查看。总结一下一个比较完整的开发环境可以让我们有以下能力:

  • 代码检测:来保证代码的风格正确
  • 代码测试:保证代码的功能正确
  • 代码构建:保证我们的代码可以在不同环境里面运行
  • 版本管理:保证协同开发的统一性
  • 持续化集:成保证代码高质量快速迭代

下一章我们将开发用于 Sparrow 的数据渲染模块:渲染器(Renderer)。