自己动手写个简单的脚手架工具

2,143 阅读3分钟

本文是从我们公司的需求为出发点,编写一个简单的脚手架工具。

首先,让我们分析需求:目前公司的项目比较多,分属于不同的客户,每个项目都是独立部署的,但是项目的前端框架基本分为三种:

  • 以 app 为载体的 内嵌 H5 页面: vue 全家桶 + vant ui
  • admin 后台管理系统:vue 全家桶 + element ui
  • admin 后台管理系统: react + ant design

本文不涉及具体前端项目框架的实现,只解决创建项目时,根据业务的需求,使用脚手架工具生成简单的项目模板。

创建项目

mkdir gt-cli
cd gt-cli
npm init

package.json

{
  "name": "gt-cli",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "bin": {
    "gt": "bin/gt.js"
  },
  "url": "",
  "email": "",
  "author": "Iris Mei",
  "repository": {
    "type": "git",
    "url": ""
  },
  "license": "ISC",
  "dependencies": {
    "chalk": "^4.1.0",
    "commander": "^7.0.0",
    "inquirer": "^7.3.3",
    "log-symbols": "^4.0.0"
  }
}

安装依赖

npm install

chalk: 处理命令行输出文字的样式(颜色、背景色)

commander:node命令行解决方案

inquirer:node 命令行交互(信息录入、选择、信息确认等......)

log-symbols: 打印日志图标,如: √,×

目录结构

┌─── gt-cli

│├── bin文件夹

│    ├─ gt.js 

│├── command文件夹

│    ├─ init.js 

│├── node_module文件夹

│├── .gitignore 

│├── package.json 

│├── README.md

│├── template.json 

gt.js: 入口文件

init.js: 初始化项目逻辑代码

template.json: 不同项目模板的描述和git仓库地址

开始coding

首先,在template.json 中 配置不同项目模板的名称、描述、仓库地址:

{    
    "vue-admin": {        
        "gitUrl": "git 仓库地址",        
        "name": "admin system(vue + element)"    
    },    
    "vue-web": {        
        "gitUrl": "git 仓库地址",        
        "name": "web H5 (vue + vint)"    
    },    
    "react-admin": {        
        "gitUrl": "git 仓库地址",        
        "name": "admin system(react + ant design)"    
    }
}

在gt.js 中,针对不同的命令执行不同的操作,除了查看版本号,使用帮助以外,现在仅有初始化项目操作,使用 gt init <projectName> 初始化项目模板。

#!/usr/bin/env node --harmony
'use strict'
// 终端图标
const symbols = require('log-symbols')
// 终端命令行美化
const chalk = require('chalk')
// node命令行解决方案
const program = require('commander')

// cli 版本
program
    .version(require('../package.json').version)

// 使用说明
program
    .usage('<command>')

// 初始化一个项目 命令:gt init <projectName> || gt i <projectName>
program
    .command('init')                        // 命令
    .description('init a new project')      // 描述
    .alias('i')                             // 命令别名
    .action(() => {                         // 命令执行的操作
         // 如果参数中没有项目名称,退出命令行
         if (program.args.length < 2) {
            console.log(symbols.error, chalk.red('Please input the project name, for example: gt init vue-app'))
            process.exit()
        }
        require('../command/init')(program.args[1])
    })

// 处理非法命令
program
    .arguments('<command>')
    .action(cmd => {
    // 输出帮助信息
    console.log(symbols.error, chalk.bgRed(`Unknown command: ${cmd}.`));
    program.help()
  });

// 命令行参数解析
program
    .parse(process.argv)

// 命令行没有参数时,执行help命令!
program.args.length && program.help()

init.js 用来处理初始化项目的逻辑:用户录入项目信息,选择模板,脚手架从git仓库拉取项目模板,然后安装依赖。完整代码:

'use strict'
// 执行复杂的命令行命令
const exec = require('child_process').exec
// 终端图标
const symbols = require('log-symbols')
// 响应用户命令行交互
const inquirer = require('inquirer')
// 终端命令行美化
const chalk = require('chalk')
// 模板类型
const projectTpl = require('../template.json')

module.exports = async (name) => {
        // 项目需要录入的信息
        const promptList = [{
            type: 'input',
            message: `project name(default: ${name}): `,
            name: 'projectName',
            default: name
        }, {
            type: 'input',
            message: `The project description: `,
            name: 'description',
        }, {
            type: 'list',
            message: 'please choose a project template: ',
            name: 'projectType',
            choices: Object.keys(projectTpl),
            filter: function (val) {
                return projectTpl[val];
            }
        }];
        let answers = await inquirer.prompt(promptList)
        // 确认项目信息
        let confirm = await inquirer.prompt([{
            type: 'confirm',
            message: `Please confirm the project infomation: \n project name: ${answers.projectName} \n project description: ${answers.description} \n project template: ${answers.projectType.name} \n are you sure? `,
            name: 'isSure',
            default: ''
        }])
        // 如果没有确认项目信息,退出命令行
        if (!confirm.isSure) {
            console.log(chalk.bgYellow('The process already exited.'))
            process.exit()
        }
        // 拉取项目模板,使用master分支
        const cmd = `git clone ${answers.projectType.gitUrl} ${answers.projectName} `  
      // 开始初始化项目
        console.log('The project start cloning......')

        exec(cmd, (error) => {
            if (error) {
                console.log(symbols.error, chalk.red(error))
                process.exit()
            }
            console.log(symbols.success, chalk.green('git clone is successed!'))

            console.log('\n npm install......')

            exec(`cd ${answers.projectName} && npm install`, (error) => {
                if (error) {
                    console.log(symbols.error, chalk.red(error))
                    console.log(symbols.error, chalk.red(`please npm install again or use cnpm install`))
                    process.exit()
                }
                console.log(symbols.success, chalk.green('Generation completed!'))
                process.exit()
            })
        })}

本地调试

在package.json 同级目录中执行 npm link ,创建本地npm模块的链接。

执行后显示下面的信息,说明link 成功:

audited 39 packages in 1.408s

6 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

C:\Users\你的用户名\AppData\Roaming\npm\gt -> C:\Users\你的用户名\AppData\Roaming\npm\node_modules\gt-cli\bin\gt.jsC:\Users\你的用户名\AppData\Roaming\npm\node_modules\gt-cli -> C:\Users\你的用户名\Desktop\workspace\gt-cli

现在可以试试用自己写的脚手架创建一个空项目模板了,在终端中执行:gt init <项目名称>

然后根据提示,输入项目信息:

? project name(default: demo):  (demo)

? The project description:  A test project

? please choose a project template:  (Use arrow keys)
> vue-admin
  vue-web
  react-admin

? Please confirm the project infomation:
 project name: demo
 project description: A test project
 project template: admin system(vue + vue router + vuex)
 are you sure?  (Y/n) Yes

The project start cloning......
 git clone is successed!

 npm install......
C:\Users\iris.mei\Desktop\test process.cwd
 Generation completed!

需要取消link时,在 C:\Users\你的用户名\AppData\Roaming\npm\node_modules 目录中执行npm unlink <模块名称> :

写在最后

一个简单的脚手架写完了,还有很多可以优化的点:

  • 方法封装
  • 项目目录验证
  • 目录名称验证
  • ......

我先抛个砖,以后有空再优化 ~~

最后,部分代码借鉴其他大佬的博客,向大佬学习~~

文中如有不妥之处,欢迎指正~~