Appearance
实战-搭建开发环境
现在我们就正式进入了我们的实战,也是小册子的核心部分:从 0 到 1 开发一个图表库。前一章对我们要开发的东西 Sparrow已经有了一个简单的介绍,这里就不多说了。
在正式开发之前,我们先来搭建开发环境:一个合理的开发环境,会大大提高我们写代码的效率,规范性和健壮程度。
我们将会从初始化环境开始,然后从检查(Lint) 、测试(Test) 、构建(Build) 、版本管理(Version Control) 和 持续化集成(Continuous integration) 这几方面来搭建我们的开发环境。
那么首先我们来初始化我们的环境。
初始化环境
首先在任意位置新建一个名叫 sparrow 的文件夹,然后安装下面对应版本的 node 和 npm:
- 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.js
和 src/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)
代码检测之后就是代码测试了,优秀的单元测试能保证我们的项目在迭代的过程中不出问题,这里我们将使用 Jest 和 Jest 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.js
和 utils.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
增加 test
和 test-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
就可以发现目录中多了三个 dist
、esm
和 lib
文件夹就是我们的打包结果。当然这里我们还需要安装一个小工具 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)。