在平时开发中一般用的 vue create projectName来创建项目。所以想着是否可以模仿着写一个简易版的脚手架。下面是开发一个简易脚手架的步骤。觉得好玩的话可以试着敲一下。

# 1、搭建项目环境

  • 初始化目录
├── bin
│ └── tj-cli // 全局命令执行的根文件
├── package.json
├── lib
│ ├── main.js // 入口文件
│ └── utils // 存放工具方法
│── .huskyrc // git hook
│── .eslintrc.json // 代码规范校验
1
2
3
4
5
6
7
8
  • 在目录创建 bin 文件夹,并添加一个脚手架文件
//bin/tj-cli 
//表示在node环境下运行
#! /usr/bin/env node
# 引入入口文件
require('../src/main.js');
1
2
3
4
5
  • package.json 中配置跟 scripts 同级的 bin字段
 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "bin": {
    "tj": "./bin/tj",
    "tkj-cli": "./bin/tj"
  },
1
2
3
4
5
6
7
  • npm link 建立软连接,映射到全局(默认放到node设置的全局安装目录) 此时你可以在命令行(cmd)或者 gitbash中输入 tj-cli或者 tj 都会运行 tj-cli 文件。(tj/tj-cli是你在package.json中配置的名称)。此时你也可以将tj/tj-cli添加到 scripts 中使用了

# 2、代码编写

在编写代码之前要先安装一下项目中用到的包。

  • commander——核心模块,主要用于参数解析
  • inquirer——交互式命令行工具,有他就可以实现命令行的选择功能
  • download-git-repo—— 在git中下载模板
  • chalk——帮我们在控制台中画出各种各样的颜色
  • metalsmith——读取所有文件,实现模板渲染
  • consolidate——统一模板引擎

# 3、使用commander

npm install commander
1

在路口文件 main.js开始编写我们的代码。可以参考commander的官方文档地址 https://github.com/tj/commander.js/blob/master/Readme_zh-CN.md

commander主要有以下 API

  • commander.version()
//动态获取版本信息
program
    .version(`tj-cli@${require('../package.json').version}`)
    .usage(`<command> [option]`)
1
2
3
4

# 4、编写help命令

program.on('--help', () => {
    console.log()//是help命令离其他命令远一点
    console.log(`Run ${chalk.cyan("tj-cli < command > --help")} show detail`)
})
1
2
3
4

# 5、配置拉取模板项目的代码

const program = require("commander");
const chalk = require('chalk');
const cleanArgs = (cmd) => {
    const args = {};
    cmd.options.forEach(o => {
        const key = o.long.slice(2);
        if (cmd[key]) {
            args[key] = cmd[key];
        }
    })
    return args;
}
program
    .command("create <app-name>")
    .description("crreat a new project")
    .option("-f, --force", "overwrite target directory if it exist")
    .action((name, cmd) => {
        require("../lib/create")(name, cleanArgs(cmd))
    })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 5、拉取模板

模板一般放在github上,所以在拉取模板时需要用到axios包。实现一些交互的包 inquirer, ora

//lib/create.js 在这里创建项目目录
const path = require('path');
const fs = require('fs-extra');
const Inquirer = require('inquirer');
const Creator = require('./Creator');
module.exports = async function (projectName, options) {
    // 创建项目 
    const cwd = process.cwd(); // 获取当前命令执行时的工作目录
    const targetDir = path.join(cwd, projectName); // 目标目录
    if (fs.existsSync(targetDir)) {
        if (options.force) { // 如果强制创建 ,删除已有的
            await fs.remove(targetDir);
        } else {
            // 提示用户是否确定要覆盖
            let { action } = await Inquirer.prompt([ // 配置询问的方式
                {
                    name: 'action',
                    type: 'list', // 类型非常丰富
                    message: `Target directory already exists Pick an action:`,
                    choices: [
                        { name: 'Overwrite', value: 'overwrite' },
                        { name: 'Cancel', value: false }
                    ]
                }
            ]);
            if (!action) {
                return
            } else if (action === 'overwrite') {
                await fs.remove(targetDir)
            }
        }
    }
    // 创建项目
    const creator = new Creator(projectName, targetDir);
    creator.create(); // 开始创建项目 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//lib/Creator
async create () {
        // 真实开始创建了
        // 1) 先去拉取当前组织下的模板
        let repo = await this.fetchRepo();
        // 2) 在通过模板找到版本号
        let tag = await this.fetchTag(repo);
        // 3) 下载
        await this.download(repo, tag);
    }
1
2
3
4
5
6
7
8
9
10
  • 获取模板
async fetchRepo () {
        // 失败重新拉取 
        let repos = await wrapLoading(fetchRepoList, 'waiting fetch template');
        if (!repos) return;
        repos = repos.map(item => item.name);
        let { repo } = await Inquirer.prompt({
            name: 'repo',
            type: 'list',
            choices: repos,
            message: 'please choose a template to create project'
        });
        return repo
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
//lib/request.js
//下载模板
async function fetchRepoList () {
    // 可以通过配置文件 拉取不同的仓库对应的用户下的文件
    return axios.get('https://api.github.com/orgs/zhu-cli/repos');
}
1
2
3
4
5
6
  • 根据tag标签获取指定版本
    async fetchTag (repo) {
        let tags = await wrapLoading(fetchTagList, 'waiting fetch tag', repo);
        if (!tags) return;
        tags = tags.map(item => item.name);
        let { tag } = await Inquirer.prompt({
            name: 'tag',
            type: 'list',
            choices: tags,
            message: 'please choose a tag to create project'
        });
        return tag;
    }
1
2
3
4
5
6
7
8
9
10
11
12
  • 下载模板
async download (repo, tag) {
        // 1.需要拼接处下载路径来 
    // zhu-cli/vue-template#1.0
    let requestUrl = `zhu-cli/${repo}${tag ? '#' + tag : ''}`
    // 2.把资源下载到某个路径上 (后续可以增加缓存功能, 应该下载到系统目录中,稍后可以在使用ejs handlerbar 去渲染模板 最后生成结果 在写入)

    // 放到系统文件中 -> 模板 和用户的其他选择 =》 生成结果 放到当前目录下
    await this.downloadGitRepo(requestUrl, path.resolve(process.cwd(), `${repo}@${tag}`));
    return this.target;
}
1
2
3
4
5
6
7
8
9
10

# 6、项目发布

将你自己的项目发布到npm

//nrm是一个包可以全局安装一下 npm i nrm -g
nrm use npm
//登录 输入你自己的用户名和密码
npm adduser 
//发布 
npm publish 
1
2
3
4
5
6
Last Updated: 9/14/2020, 12:20:49 PM