演示仓库地址:https://github.com/wangpeng1994/builder-webpack
1. 构建配置包设计
将 webpack 配置包抽离成 npm 包,好处如下:
- 通用性:业务开发者无需关注构建配置,统一团队构建脚本
- 可维护性:构建配置合理拆分,README/CHANGLOG 文档等
- 质量:冒烟测试、单元测试、测试覆盖率、持续集成
可选方案:
- 通过多个配置文件管理不同环境的构建,webpack --config 参数进行控制
- 将配置设计成 npm 库,如 hjs-webpack、Neutrino(本文演示的方案)
- 抽成 CLI 脚手架工具,如 create-react-app
- 将所有配置放在一个文件,通过 --env 参数控制代码分支
不同环境采用不同的 webpack 配置:
- 基础配置:webpack.base.js
- 开发环境:webpack.dev.js
- 生产环境:webpack.prod.js
- SSR环境:webpack.ssr.js
通过 webpack-merge 工具更好的合并 webpack 配置项,如 dev 的最终配置就是合并自 base 配置:
const merge = require('webpack-merge');
const baseConfig = require('./webpack.base');
const devConfig = {
mode: 'development',
devServer: {
contentBase: './dist',
hot: true,
stats: 'errors-only',
},
devtool: 'cheap-source-map',
};
module.exports = merge(baseConfig, devConfig);
2. 功能模块设计和目录结构
image.png目录结构:
+ |- /test
+ |- /lib
+ |- webpack.dev.js
+ |- webpack.prod.js
+ |- webpack.ssr.js
+ |- webpack.base.js
+ |- README.md
+ |- CHANGELOG.md
+ |- .eslinrc.js
+ |- package.json
+ |- index.js
3. 使用 ESLint 规范构建脚本
// .eslintrc.js
module.exports = {
"parser": "babel-eslint",
"extends": "airbnb-base",
"env": {
"browser": true,
"node": true
}
};
// package.json
"scripts": {
"eslint": "eslint ./lib --fix", // eslint --fix 可以自动处理空格
// ...
},
4. 冒烟测试和实际运用
冒烟测试是指对提交测试的软件在进行详细深入的测试之前而进行的预测试,这种预测试的主要目的是暴露导致软件需重新发布的基本功能失效等严重问题。
- 判断构建是否成功
- 判断基本功能是否正常,编写 mocha 测试用例
- 是否输出了 JS、CSS 等静态资源文件
- 是否输出了 HTML 文件
目录结构:
template 目录里存放之前学习 webpack 时的演示项目,与 template 目录平级的是用于冒烟测试的脚本。
image.png// index.js
/**
* 测试构建是否成功
*/
const process = require('process');
const path = require('path');
const webpack = require('webpack');
const rimraf = require('rimraf');
const Mocha = require('mocha');
const mocha = new Mocha({
timeout: '10000ms'
});
// 变更当前 node 进程的工作目录为 template 目录
// 之后遇到的除了 require() 方法之外的相对目录,都是相对于工作目录
// require() 方法中的路径是指相对于当前文件的路径
process.chdir(path.join(__dirname, 'template'));
// 先删除输出目录
rimraf('./dist', () => {
const prodConfig = require('../../lib/webpack.prod.js');
// 直接引入 webpack 方法并传入配置文件后执行
webpack(prodConfig, (err, stats) => {
if (err) {
console.error(err);
process.exit(2);
}
// 这里观察是否有成功构建后的输出(是否冒烟)
console.log(stats.toString({
colors: true,
modules: false,
children: false
}));
console.log('Webpack build successfully, begin to run tests.')
// 下面继续测试基本功能是否正常
// 这里 __dirname 是指当前文件(index.js)所在目录的绝对路径,所以也可以不受当前进程工作路径的影响
mocha.addFile(path.join(__dirname, 'html-test.js'));
mocha.addFile(path.join(__dirname, 'css-js-test.js'));
mocha.run();
});
});
// css-js-test.js
/**
* 测试构建完成后的 build 目录是否有内容输出
*/
const glob = require('glob-all');
describe('Checking generated css js files', () => {
it('should generate css js files', (done) => {
const files = glob.sync([
'./dist/index_*.js',
'./dist/index_*.css',
'./dist/search_*.js',
'./dist/search_*.css'
]);
if (files.length > 0) {
done();
} else {
throw new Error('no css js files generated');
}
});
});
// html-test.js
/**
* 测试构建完成后的 build 目录是否有内容输出
*/
const glob = require('glob-all');
describe('Checking generated html files', () => {
it('should generate html files', (done) => {
const files = glob.sync([
'./dist/index.html',
'./dist/search.html'
]);
if (files.length > 0) {
done();
} else {
throw new Error('no html files generated');
}
});
});
5. 单元测试和测试覆盖率
image.pngimage.png
技术选型:Mocha + Chai(演示中虽然没有使用 Chai,但大同小异,使用了 assert,来自于 nodejs 的断言 API)
// index.js
/**
* 单元测试入口文件
*/
const path = require('path');
process.chdir(path.join(__dirname, 'smoke/template'));
describe('builder-webpack test case', () => {
require('./unit/webpack-base-test');
// 可以继续测试其他环境的 webpack 文件
});
// webpack-base-test.js
var assert = require('assert');
describe('webpack.base.js test case', () => {
const baseConfig = require('../../lib/webpack.base.js');
console.log(baseConfig);
it('entry', () => {
assert.equal(baseConfig.entry.index.includes('builder-webpack/test/smoke/template/src/index/index.js'), true);
assert.equal(baseConfig.entry.search.includes('builder-webpack/test/smoke/template/src/search/index.js'), true);
});
// 可以继续测试其他字段
});
istanbul 可以输出测试覆盖率,nyc (内部依赖于 istanbul)结合 package.json 中的 scripts 脚本使用起来更方便。
// package.json
"scripts": {
"test": "nyc mocha",
"test:smoke": "node ./test/smoke/index.js",
// ...
},
image.png
6. 持续集成和 Travis CI
当前项目构建地址:https://travis-ci.org/wangpeng1994/builder-webpack
- https://travis-ci.org/ 使用 GitHub 账号登录
- 在 https://travis-ci.org/account/repositories 为项目开启
- 项目根目录下新增 .travis.yml
language: node_js
sudo: false
cache:
apt: true
directories:
- node_modules
node_js: stable
install:
- npm install # 安装构建器依赖
- cd ./test/smoke/template
- npm install # 安装模板项目依赖
- cd ../../../
script:
- npm run test
- npm run test:smoke
7. 发布构建包到 npm
具体怎么发送,参考其他文章即可,保障网络通畅,使用原版 npm 而不是 cnpm,并且暂时切换回原版仓库地址(如果使用了 taobao 镜像的话),然后去 npm 注册个账号,npm adduser、npm login、npm publish,注意 package.json 中的包名 name 不能和仓库中现有的重名。
升级版本,可以使用 npm version xxx,会自定更新 package.json 中的版本号,并且打 tag:
- 升级补丁版本号:npm version patch
- 升级小版本号:npm version minor
- 升级大版本号:npm version major
使用方法见:https://www.npmjs.com/package/xiaofeng-builder-webpack
8. Git 规范和 CHANGELOG 生成
良好的 Git commit 规范优势:
- 根据 Git Commit 的元数据生成 Changelog
- 后续维护者可以知道 Feature 被修改的原因
技术方案:
- commitizen 用于生成规范的 commit 信息
- cz-conventional-changelog(来自 Angular 的提交规范)
- conventional-changelog-cli (用于生成 CHANGELOG,升级项目,发版本时才会用到)
以后用 git cz 命令代替 git commit 即可。
继续在 package.json 的 scripts 中和 npm version 结合,实现自动升版本之前运行测试用例,然后升级版本,并且自动打 tag 并自动更新 CHANGELOG.md 文件,之后自动推送 tag 和代码。
npm 会在执行 npm version 时,按顺序执行 preversion -> version -> postversion:
// package.json
// ...
"scripts": {
"test": "nyc mocha",
"test:smoke": "node ./test/smoke/index.js",
"eslint": "eslint ./lib --fix",
"commit": "git-cz",
"preversion": "npm test",
"version": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
"postversion": "git push origin --tags && git push"
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
// ...
所以现在的开发流程如下:
- 正常开发项目,之后暂存并运行 git cz 提交代码;
- 当哪次需要升级项目的版本号时,进一步运行 npm version [major|minor|patch]
- 需要的话,使用 npm publish 发布到 npm 上
了解一下提交格式要求(用了 commitizen,命令行中看着选菜单即可):
image.png image.png根据提交时的 feat、fix 等元信息生成的 CHANGELOG 形如:
image.png9. 语义化版本(Semantic Versioning)规范格式
- 软件的版本通常由三位组成,形如:X.Y.Z
- 版本是严格递增的,例如:16.9.0 -> 16.10.0 -> 16.10.1
- 在发布重要版本时,可以先发布alpha、beta、rc等先行版本
开源项目(React)版本信息案例:
image.png语义化版本(Semantic Versioning)规范格式:
- 次版本号:当你做了向下兼容的功能性新增;
- 主版本号:当你做了不兼容的 API 修改;
- 修订号:当你做了向下兼容的问题修正。
先行版本号:
先行版本号可以作为发布正式版之前的版本,格式是在修订版本号后面加上一个连接
号(-),再加上一连串以点(.)分割的标识符,标识符可以由英文、数字和连接号
([0-9A-Za-z-])组成。
- alpha:是内部测试版,一般不向外部发布,会有很多 Bug。一般只有测试人员使用;
- beta:也是测试版,这个阶段的版本会一直加入新的功能。在 Alpha 版之后推出;
- rc:Release Candidate) 系统平台上就是发行候选版本。RC 版不会再加入新的功能了,主要着重于除错。
网友评论