当前,前端技术日新月异,公司的团队技术栈和业务场景不同,都会建立自己的前端工程体系。一个好的工程体系能够保证团队的研发流程规范,提高团队的研发效率,能够减少人员流动带来的项目交接和维护成本。2-3个项目还可维护,那20-30个项目?
在 easywebpack 工程体系的最初设计中,考虑前端工程的复杂性和扩展性问题,各种插件都是可插拔设计,可以灵活组装使用,也可以脱离 easywebpack 体系单独使用,从而保证能够基于 easywebpack 体系定制出符合自己的团队的工程体系方案。
easywebpack-cli
easywebpack-cli 主要包括 Command, Action, Config, Ask 四部分,默认集成了常用的功能服务,你可以很方便的集成已有功能和服务,同时通过覆写基类方法自定义实现相关功能。easywebpack-cli 命令注册是通过 commander 插件实现的,默认支持的命令有如下命令。
下面以实现 Egg + React 集成 CLI 为例,具备 easywebpack-cli 所有能力之外,同时根据 Egg + React 的框架形态进行定制,最终实现 res-cli 自定义 CLI 命令行工具。
Initialize
首先初始化一个 npm 代码库,可以通过 easy init 初始化一个简单的 npm package 代码仓库 res-cli, 然后添加 bin/cli.js
, lib/command.js
, lib/action.js
, lib/config.js
, lib/ask.js
等文件。同时在 package.json 中配置添加 bin 命令行入口配置接口初始化一个非常的简单的 res 命令行。
// ${root}/package.json
{
"bin": {
"res": "bin/cli.js"
}
}
// ${root}/bin/cli.js
#!/usr/bin/env node
const Command = require('../lib/command');
new Command().run();
接下来就是根据 easywebpack-cli 提供的 Command, Action, Config, Ask 实现自己的 CLI 定制部分。
Command
// ${root}/lib/command.js
'use strict';
const path = require('path');
const EasyCLI = require('@easy-team/easywebpack-cli');
// 自定义 Action, 见下面 Action 实现
const Action = require('./action');
module.exports = class ResCommand extends EasyCLI.Command {
constructor() {
super();
// 命令行库名称,日志输出显示
this.cli.name = 'res-cli';
// 命令行命令
this.cli.cmd = 'res';
// 当前 cli 库的根目录
this.context = path.resolve(__dirname, '..');
// 需要合并的 webpack.config.js 配置文件路径
this.program.filename = path.resolve(this.baseDir, 'config/res.config.js');
// 命令具体逻辑实现
this.action = new Action(this);
// 提供给 res init 骨架初始化的配置
this.boilerplate = require('./ask');
}
// 定义 res tsc 实现方法即可
tsc() {
this.program
.command('tsc')
.option('-p, --project [filename]', 'tsconfig.json file path', this.baseDir)
.description('typescript compile')
.action(options => {
this.action.tsc(options);
});
}
command() {
// 注册自定义命令 res tsc
this.register('tsc');
super.command();
}
};
Action
命令行命令逻辑自定义实现
// ${root}/lib/action.js
'use strict';
const EasyCLI = require('@easy-team/easywebpack-cli');
const Command = require('egg-bin');
const ScriptCommand = require('egg-scripts');
// 自定义 Config 实现,见下发 Config 实现
const Config = require('./config');
module.exports = class ResAction extends EasyCLI.Action {
// 提供 Cli 内置 Webpack 配置合并钩子, 需要集成到 cli 内部的 webpack 配置,通过复写该方法实现。
initCustomizeConfig(options) {
return Config.getResConfig(options);
}
// 覆写 dev 命令
dev() {
const cmd = ['dev', '--framework', '@easy-team/res'];
if (EasyCLI.utils.isEggTypeScriptProject(this.baseDir)) {
cmd.push('-r');
cmd.push('egg-ts-helper/register');
}
new Command(cmd).start();
}
// 覆写 debug 命令
debug() {
const cmd = ['debug', '--framework', '@easy-team/res'];
if (EasyCLI.utils.isEggTypeScriptProject(this.baseDir)) {
cmd.push('-r');
cmd.push('egg-ts-helper/register');
}
new Command(cmd).start();
}
// 覆写 start 命令
start() {
new ScriptCommand(['start', '--framework', '@easy-team/res']).start();
}
}
Config
res-cli 内置 Webpack 配置和合并项目自定义 Wepback 处理
// ${root}/lib/config.js
'use strict';
const path = require('path');
const fs = require('fs');
const merge = require('webpack-merge');
const easywebpack = require('@easy-team/easywebpack-react');
// 获取项目自定义 webpack 配置
exports.getResWebpackFileConfig = baseDir => {
const filepath = path.resolve(baseDir, 'config/res.config.js');
if (fs.existsSync(filepath)) {
return require(filepath);
}
return {};
};
// 获取 cli 内部默认集成的 webpack 配置
exports.getResConfig = (options = {}) => {
const { baseDir = process.cwd(), env } = options;
const baseConfig = {
baseDir,
framework: 'react', // 基于 easywebpack-react 扩展 webpack + react 配置方法
configured: true, // 表示项目自定义 webpack 配置在这里已经进行 merge 合并了,无需再次合并
output: {
path: path.join(baseDir, 'app/public')
},
module: {
rules: [
{
ts: true // res 默认开启 easywebpack 的 typescript 支持
},
{
scss: true // res 默认开启 easywebpack 的 sass 支持
},
{
stylus: true // res 默认开启 easywebpack 的 stylus 支持
},
{
less: true // res 默认开启 easywebpack 的 less 支持
}
]
}
};
const resConfig = exports.getResWebpackFileConfig(baseDir);
return merge(baseConfig, resConfig, options);
};
// 提供 webpack 最终配置的钩子方法,主要提供给项目自定义需要。
exports.getWebpackConfig = (options = {}) => {
const config = exports.getResConfig(options);
return easywebpack.getWebpackConfig(config);
};
Ask
easy init 骨架初始化交互式配置实现
- name:命令行显示文本说明
- value:唯一的标识
- pkgName: 下载代码模板的 npm 模块名称
- choices: 自定义选择,目前支持 name,description,npm(yarn/npm/cnpm安装模式), style (scss/sass/less/stylus样式)自定义配置。
// ${root}/ask.js
'use strict';
const chalk = require('chalk');
exports.boilerplateChoice = [
{
name: `Create ${chalk.green('React')} ${chalk.yellow('Server Side Render')} Web Application for Res`,
value: 'res-react-asset-boilerplate',
pkgName: 'res-react-asset-boilerplate',
choices: ['name', 'description', 'npm']
},
{
name: `Create ${chalk.green('React')} ${chalk.yellow('Client Side Render')} Web Application for Res`,
value: 'res-react-spa-boilerplate',
pkgName: 'res-react-spa-boilerplate',
choices: ['name', 'description', 'npm']
},
{
name: `Create ${chalk.green('React')} ${chalk.yellow('Nunjucks HTML Render')} Web Application for Res`,
value: 'res-react-html-boilerplate',
pkgName: 'res-react-html-boilerplate',
choices: ['name', 'description', 'npm']
},
{
name: `Create ${chalk.green('React')} ${chalk.yellow('Nunjucks Asset Render')} Web Application for Res`,
value: 'res-react-asset-boilerplate',
pkgName: 'res-react-asset-boilerplate',
choices: ['name', 'description', 'npm']
},
{
name: `Create ${chalk.green('React')} ${chalk.yellow('TypeScript')} Awesome Web Application for Res`,
value: 'res-awesome',
pkgName: 'res-awesome',
choices: ['name', 'description', 'npm']
},
];
项目结构
运行效果
验证 cli 可以通过 npm link 或者 npm publish 安装的方式验证 res 命令
- res --help
- res init
- res dev
- res build --size
实际案例
- Egg + React Node Framework Command Line Tool https://github.com/easy-team/res-cli
- Egg + Vue Node Framework Command Line Tool https://github.com/easy-team/ves-cli