学习总结篇,以是否造轮子来掂量学习效果。本篇次要介绍近期应用的一个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 nodeconst 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/