痛点
- 项目多了之后,每个项目重复安装相同的依赖包,每个项目都很“重”;
- 组件、功能复用性,复制粘贴,一个改动,各个项目跟着都得修改调试,太难以维护;
- 成员多了之后,代码风格参差不齐,难以规范管理,项目产能也就因人而异了;
- 不同复杂度项目的
webpack
构建环境配置甚至不一样,需要一一去匹配,然后等你想升级的时候发现工作量巨大; webpack
更迭,导致项目中构建版本参差不齐,难以维护,甚至发现相同的代码,换个项目不能用了...;- 各个项目生产环境打包结构差异,难以控制结构,造成的运维负担;
- 欢迎大家补充更多
痛点
,一起解决;
解决思路
以上种种都将不同程度的影响团队效能,团队效能对于产品发展的重要性想必大家都很清楚,下面分享一下我的解决思路:
- 统一前端开发规范,目录结构、语法、命名等;
- 硬方式,构建工具加入对应语法监听(
tslint
/stylelint
/htmllint
/jslint
等),然后配置对应语法的描述文件; - 软方式,习惯养成计划,通过
codereview
方式周期性维护团队开发习惯; - 思路和好处灌输的必要性,代领团队共同成长;
- 硬方式,构建工具加入对应语法监听(
- 统一前端物料(公共组件、公共UI、工具类、sdk等);
- 公共组件库建立的必要性,统筹产品的复用业务组件,写一次,所有项目通用;
- 公共 UI,统一前端样式规范,这个需要 UED 配合来统一建设;
- 私有仓库建设的必要性,公开库隐私托管,比如阿里
Node.js 性能平台 > 模块仓库
,这个是免费的,目前用下来还不错;
- 统一构建流程;
webpack
构建流程统一,比如支持对vue
/react
/angular
的支持,这个视团队技术栈定论;- 开发团队
cli
工具,统一配置公共webpack.config.js
,统一的优势便是方便统一管理和升级,不过劣势也很明显,一荣俱荣,一损俱碎
,不过我觉得优势更甚,要对自己有信心;
- 统一前端 CI(持续集成) / CD(持续交付);
- 上面构建流程统一后,便可以统一前端
构建产物
的规则,再配合迭代版本号,构建产物
变有条不紊了; - 构建产物固定文件规则,方便运维认知和维护,避免不必要的运维成本;
- 结合
oss
做构建产物
的版本管理,这里就可以扩展cli
指令,去对接oss api
,完成对各个项目迭代版本的构建产物
的上传和管理,这里就可以轻松做到版本的切换自如了,好处可想而知;
- 上面构建流程统一后,便可以统一前端
上图便是
cli
解决方案核心模块功能图,可以解决上述众多痛点
,可以统一管理前端众多项目,并且解耦项目中构建相关依赖包,项目变“轻”,项目的各方面维护都只会更简单和便捷,下面分享一下实现的一些细节与功能核心;
欢迎大家提出更多思路,多多益善,帮助完善cli
;
cli
开发核心点
自定义脚本指令
- 首先需要了解
package.json
文件中的bin
属性,该属性专门用来定义用户的自定义脚本,安装完包之后,会自动在./node_modules/.bin/
中建立一个软连接(window下叫快捷方式);{ "name": "@iosecret/cli", "version": "0.0.2", "description": "iosecret cli", "bin": { "iosecret": "bin/iosecret", "iosecret-api": "bin/iosecret-api", "iosecret-dev": "bin/iosecret-dev", "iosecret-prod": "bin/iosecret-prod" } }
name
这里定义包名,即将来发布到npm 仓库
或者其他私有库提供外部安装下载的唯一标识;version
当前发布的版本号,每次npm publish
时版本号必须不同;bin
声明快捷命令指向,以上配置执行npm link
或者通过仓库安装后将会在本地生成一个软连接,映射到当前bin/iosecret
文件,即执行iosecret
指令,将直接执行bin/iosecret
脚本;
- 其实大家可能也注意到了我们常用的
webpack
/gulp
/rimraf
等等在./node_modules/.bin
下出现,然后在package.json
文件中的scripts
使用的脚本会默认到./node_modules/.bin
下进行查找;{ "scripts": { "rm": "rimraf dist" } }
- 这里若执行
npm run rm
则会默认之前scripts
下rm
指令,即rimraf dist
,而npm则会默认先去./node_modules/.bin
下寻找指令rimraf
,再去当前系统命令中寻找指令;
- 这里若执行
指令引导
- 这里需要用到 commander 用来定义指令描述和指令扩展;
// 解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件。
#!/usr/bin/env node
const program = require("commander");
program
.version(require("../package").version)
// 定义指令描述
.usage(
`
iosecret api -n [name]
iosecret dev -t [type]
iosecret prod -t [type] -i`
)
// 定义指令描述详情
.description(
`Params:
api:
-n [name] 模块名称
dev|prod:
-t [type] 语言类型,vue/react/angular
-i 是否打印详细信息`
)
// 定义指令备注
.command("api", "生成api文件")
.command("dev", "开发环境构建")
.command("prod", "生产环境构建");
// 解析参数
program.parse(process.argv);
- 执行指令
iosecret
,可以看到下图现象,这里指令的雏形已完成
单指令开发
- 获取指令参数
const program = require("commander");
program
.usage("[options] -i")
// 定义接收参数类型
.option("-t, --type [type]", "语言类型, vue/react/angular")
.option("-i, --info", "是否打印记录")
// 解析指令参数
.parse(process.argv);
// program.type ==> 获取语言类型参数
// program.info ==> 获取是否打印记录
- 开启子进程监听,执行其他系统命令的时候需要用到,比如
scp
/ps
等Linux命令,这里需要调用webpack-dev-server
去开启构建的开发模式,这里需要参考child_process api
const { join } = require("path");
// node 子进程调用方法
const { spawn } = require("child_process");
// 开启子进程监听, 用法参考
// 这里的思路是通过调用 webpack-dev-server 再去调用对应的 webpack.dev.js 配置文件,去开启项目开发模式监听
// 具体的配置文件即可对应团队项目技术栈去适配,理论上可以去支持各种技术栈选型的
const build = spawn(
"node",
[
`${join(
__dirname,
"../node_modules/webpack-dev-server/bin/webpack-dev-server.js"
)}`,
"--watch",
"--config",
`${join(__dirname, `../lib/iosecret-build/webpack.dev.js`)}`
],
{
stdio: "inherit",
shell: true,
cwd: process.cwd()
}
);
build.on("error", error => console.error(error));
build.on("exit", code => {
console.log("exit", code);
process.exitCode = code;
process.exit();
});
指令拓展
- 生产模式监听
#!/usr/bin/env node
const { red } = require("chalk");
const program = require("commander");
program
.usage("[options] -i")
.option("-t, --type [type]", "语言类型, vue/react/angular")
.option("-i, --info", "是否打印记录")
.parse(process.argv);
const { getApp } = require("../lib/utils/getConfig");
// 获取项目配置文件
const appConfig = getApp();
if (!appConfig) {
console.error(red("> 缺少 app 配置"));
return;
}
const webpack = require("webpack");
const isInfo = program.info;
webpackConfig = require(`../lib/iosecret-build/webpack.prod.js`)({
...appConfig
});
const buildStamp = Date.now();
// webpack 启动监听
webpack(webpackConfig, async (err, stats) => {
if (err || stats.hasErrors()) {
if (err) {
console.error(red(err.stack || err));
err.details && console.error(red(err.details));
return;
}
stats.hasErrors() &&
console.error(stats.toString({ colors: true, chunks: false }));
console.log("\n> 构建异常 \n");
} else {
isInfo && console.warn(stats.toString({ colors: true, chunks: false }));
console.log(`\n> 构建完成,耗时 ${Date.now() - buildStamp} ms`);
}
});
- 其他工具拓展,比如
api
代码生成,原理是基于swagger api json
去生成对应ts
代码,这个在多项目组迭代进程中能省不少事
#!/usr/bin/env node
const program = require("commander");
program
.usage("[options] -n")
.option("-n, --name [name]", "api模块别名")
.parse(process.argv);
if (typeof program.name !== "string" || !program.name) {
console.log(yellow("> 缺少api模块别名"));
console.log(yellow("> iosecret api --name api模块别名"));
return;
}
const { join } = require("path");
const { getApi } = require("../lib/utils/getConfig");
const ApiGenerator = require("../lib/iosecret-api/generate");
const config = getApi();
// 初始化实例
const generator = new ApiGenerator({
swaggerApi: config[program.name],
destPath: join(process.cwd(), config.destPath),
filename: program.name
});
// 生成
generator.start();
总结
以上简单列出了基本的实现思路,其实开发过程中遇到了挺多问题的,这里就不再赘述了,欢迎小伙伴们提出新的见解,一起探讨。