概述
开发此工具的目的是为了统一公司前端项目脚手架,技术选型,集成CICD方案,集成内部依赖包等。
原理
跟常规的npm包一样,通过在package.json中配置bin(PATH环境变量软链)安装的时候可以在shell终端使用命令来执行我们的nodejs脚本,通过提问式获取最终配置项,来创建项目的脚手架。
程序设计图:
核心设计
提问式模式(Prompt Design)
- 将脚手架功能细化成程序运行平台、基础框架选型、脚手架能力、CICD集成方案等模块化能力,通过用户选择将其动态组合起来,最终构建出目标工程。具体代码实现:
js
const inquirer = require('inquirer')
// 获取提供给用户的选择项
const getPromptModules = () => {
return ['feature-x', 'feature-xx', 'feature-xxx', 'feature-xxxx'].map(file =>
require(`../promptModules/${file}`)
)
}
const promptAPI = new PromptModuleAPI(this)
getPromptModules().forEach(m => m(promptAPI))
// 展示选项在终端中,合并最终选择项目
const resolveFinalPrompts = () => {
this.injectedPrompts.forEach(prompt => {
const originalWhen = prompt.when || (() => true)
prompt.when = answers => {
return isManualMode(answers) && originalWhen(answers)
}
})
let prompts = ''
if (this.featurePrompt.choices.length > 0) {
prompts = [
this.presetPrompt,
this.featurePrompt,
...this.injectedPrompts,
...this.outroPrompts,
]
} else {
prompts = [this.presetPrompt, ...this.injectedPrompts, ...this.outroPrompts]
}
debug('paxc:prompts')(prompts)
return prompts
}
const answers = await inquirer.prompt(resolveFinalPrompts())
插件机制(Plugin System)
- 提供给前端用户选择的内容都通过插件式的方式实现,方便第三方继承和扩展该能力,丰富功能。
- 有利于将核心能力聚合,对外暴露接口,形成高内聚低耦合的设计方式
具体代码:
js
// 获取本地插件
async resolvePlugins(rawPlugins, preset) {
for (const plugin of rawPlugins) {
let apply = ''
if (plugin.isCore) {
apply = require(`./plugins/core/${plugin.id}`).apply
} else {
apply = require(`./plugins/${plugin.id}`).apply
}
plugin.apply = apply
if (!plugin.options) {
plugin.options = preset
}
}
}
// 对插件的执行优先级排序,初始化插件,应用插件
_sortPlugins() {
const plugins = this.plugins
for (let i = 0; i < plugins.length; i++) {
const plugin = plugins[i]
if (plugin.isCore && i === 0) break
if (plugin.isCore) {
plugins[i] = plugins[0]
plugins[0] = plugin
break
}
}
}
async initPlugins() {
const pluginIds = this.plugins.map(p => p.id)
// apply generators from plugins
for (const plugin of this.plugins) {
const { id, apply, options } = plugin
if (apply) {
const api = new GeneratorAPI(id, this, options, this.preset)
await apply(api, options, this.preset)
if (apply.hooks) {
await apply.hooks(api, options, this.preset, pluginIds)
}
}
}
}
// 生成脚手架文件
async generate({ sortPackageJson = true } = {}) {
// core plugin first
this._sortPlugins()
await this.initPlugins()
const initialFiles = Object.assign({}, this.files)
// wait for file resolve
await this.resolveFiles()
if (this.pkg) {
// set package.json
if (sortPackageJson) {
this.sortPkg()
}
this.files['package.json'] = JSON.stringify(this.pkg, null, 2) + '\n'
}
// write/update file tree to disk
await writeFileTree(this.context, this.files, initialFiles)
}