【写个工作用的脚手架cli】用脚手架整合模板和配置

脚手架cli的解决的问题

随着公司各端的业务进行,前端方面会沉淀出一些通用的解决方案和模板。此时,统一维护和管理就非常有必要了。allen-cli就是基于这样的场景而诞生的。

这个项目脚手架,最终实现:整合各个模板,一键生成模板

使用示例

目前实现的功能为:

  1. 输入allen init命令选择一个脚手架模版进行下载,然后创建对应的app。
  2. 动态选择构建环境,适配移动端等不同情况。

allen-cli的具体流程

项目的整体结构:

1. 创建项目

npm init创建package.json, 主要加上bin命令

{

"bin": {

"allen": "bin/allen",

"allen-init": "bin/allen-init"

},

}

2. 解析参数

一个CLI需要通过命令行输入各种参数,可以直接用nodejs的process相关api进行解析,但是更推荐使用commander这个npm包可以大大简化解析的过程。

#!/usr/bin/env node

const program = require('commander')

console.log('version', require('../package').version)

program

.version(require('../package').version)

.usage('<command> [项目名称]')

.command('init', '创建新项目')

.parse(process.argv)

3. main主体流程

allen-init

// NODE moudle

// node.js 命令行解决方案

const program = require("commander");

// node.js path模块

const path = require("path");

// node.js fs模块

const fs = require("fs");

// 常见的交互式命令行用户接口的集合

const inquirer = require("inquirer");

// 使用shell模式匹配文件

const glob = require("glob");

// 活动最新的npm包

const latestVersion = require("latest-version");

// node.js 子进程

const spawn = require("child_process").spawn;

// node.js 命令行环境的 loading效果, 和显示各种状态的图标

const ora = require("ora");

// The UNIX command rm -rf for node.

const rm = require("rimraf").sync;

async function main() {

let projectRoot, templateName

try {

// 检测版本

let isUpate = await checkVersion();

// 更新版本

if (isUpate) await updateCli();

// 检测路径

projectRoot = await checkDir();

// 创建路径

makeDir(projectRoot)

// 选择模板

let { git } = await selectTemplate();

// 下载模板

templateName = await dowload(rootName, git);

// 本地配置

let customizePrompt = await getCustomizePrompt(templateName, CONST.CUSTOMIZE_PROMPT)

// 渲染本地配置

await render(projectRoot, templateName, customizePrompt);

// 删除无用文件

deleteCusomizePrompt(projectRoot)

// 构建结束

afterBuild();

} catch (err) {

log.error(`创建失败:${err.message}`)

afterError(projectRoot, templateName)

}

}

3.1 创建文件下载模板

创建文件和选择模板

// 创建路径

function makeDir (projectRoot) {

if (projectRoot !== ".") {

fs.mkdirSync(projectName);

}

}

/**

* 模板选择

*/

function selectTemplate() {

return new Promise((resolve, reject) => {

let choices = Object.values(templateConfig).map(item => {

return {

name: item.name,

value: item.value

};

});

let config = {

// type: 'checkbox',

type: "list",

message: "请选择创建项目类型",

name: "select",

choices: [new inquirer.Separator("模板类型"), ...choices]

};

inquirer.prompt(config).then(data => {

let { select } = data;

let { value, git } = templateConfig[select];

resolve({

git,

// templateValue: value

});

});

});

}

下载模板, 用的是download-git-repo

const download = require('download-git-repo')

const path = require('path')

const ora = require('ora')

const logSymbols = require("log-symbols");

const chalk = require("chalk");

const CONST = require('../conf/const')

module.exports = function (target, url) {

const spinner = ora(`正在下载项目模板,源地址:${url}`)

target = path.join(CONST.TEMPLATE_NAME)

spinner.start()

return new Promise((resolve,reject) => {

download(`direct:${url}`,

target, { clone: true }, (err) => {

if (err) {

spinner.fail()

console.log(logSymbols.fail, chalk.red("模板下载失败:("));

reject(err)

} else {

spinner.succeed()

console.log(logSymbols.success, chalk.green("模板下载完毕:)"));

resolve(target)

}

})

})

}

3.2 界面交互配置

采用的是inquirer的这个库

// 常见的交互式命令行用户接口的集合

const inquirer = require("inquirer");

3.3 本地配置

如果需要将一些配置放在本地文件,则可以创建一些本地配置

/**

*

* @param target 模板路径

* @param fileName 读取文件名

*/

function getCustomizePrompt (target, fileName) {

return new Promise ((resolve) => {

const filePath = path.join(process.cwd(), target, fileName)

if(fs.existsSync(filePath)) {

console.log('读取模板配置文件')

let file = require(filePath)

resolve(file)

} else {

console.log('该文件没有配置文件')

resolve([])

}

})

}

template.json

  {

type: "confirm",

name: "mobile",

message: "是否用于移动端?"

},

{

type: "confirm",

name: "flexible",

message: "是否使用移动端适配?",

when: function (answers) {

return answers.mobile

}

},

4. 涉及到的node.js操作

// NODE moudle

// node.js 命令行解决方案

const program = require("commander");

// node.js path模块

const path = require("path");

// node.js fs模块

const fs = require("fs");

// 常见的交互式命令行用户接口的集合

const inquirer = require("inquirer");

// 使用shell模式匹配文件

const glob = require("glob");

// 活动最新的npm包

const latestVersion = require("latest-version");

// node.js 子进程

const spawn = require("child_process").spawn;

// node.js 命令行环境的 loading效果, 和显示各种状态的图标

const ora = require("ora");

// The UNIX command rm -rf for node.

const rm = require("rimraf").sync;

5. 本地安装使用

在项目目录下运行npm i -g,注册全局命令allen-cli即可使用

C:\Users\XX\AppData\Roaming\npm目录下会生成相应的可执行文件:

6. npm包allen-cli

一个基本的脚手架CLI就完成了。

  • npm包传送门:https://www.npmjs.com/package/allen-cli

  • github传送门: 项目脚手架allen-cli

欢迎试用:npm i -g allen-cli

相关解析

!/usr/bin/env node

使用过Linux或者Unix的开发者,对于Shebang应该不陌生,它是一个符号的名称,#!。这个符号通常在Unix系统的基本中第一行开头中出现,用于指明这个脚本文件的解释程序。了解了Shebang之后就可以理解,增加这一行是为了指定用node执行脚本文件

当你输入一个命令的时候,npm是如何识别并执行对应的文件的呢?

具体的原理阮一峰大神已经在npm scripts 使用指南中介绍过。简单的理解:

就是输入命令后,会有在一个新建的shell中执行指定的脚本,在执行这个脚本的时候,我们需要来指定这个脚本的解释程序是node。
在一些情况下,即使你增加了这一行,但还是可能会碰到一下错误,这是为什么呢?

No such file or directory

为了解决这个问题,首先需要了解一下/usr/bin/env。我们已经知道,Shebang是为了指定脚本的解释程序,可是不同用户或者不同的脚本解释器有可能安装在不同的目录下,系统如何知道要去哪里找你的解释程序呢?

/usr/bin/env就是告诉系统可以在PATH目录中查找。

所以配置#!/usr/bin/env node, 就是解决了不同的用户node路径不同的问题,可以让系统动态的去查找node来执行你的脚本文件。
看到这里你应该理解,为什么会出现No such file or directory的错误?因为你的node安装路径没有添加到系统的PATH中。所以去进行node环境变量配置就可以了。

NPM 执行脚本的原理

npm 脚本的原理非常简单。每当执行npm run,就会自动新建一个 Shell,在这个 Shell 里面执行指定的脚本命令。因此,只要是 Shell(一般是 Bash)可以运行的命令,就可以写在 npm 脚本里面。

比较特别的是,npm run新建的这个 Shell,会将当前目录的node_modules/.bin子目录加入PATH变量,执行结束后,再将PATH变量恢复原样。

参考链接

  • 阮一峰的网络日志-npm scripts 使用指南
  • vue-cli
  • http://nodejs.cn/api/

以上是 【写个工作用的脚手架cli】用脚手架整合模板和配置 的全部内容, 来源链接: utcz.com/a/44394.html

回到顶部