仿 vue-cli ,搭建一个脚手架

92 阅读5分钟

初始化脚手架相关文件

  • 在根目录下初始化一个 package.json
npm init -y
  • 新建一个 bin 文件,bin 内新建一个 main 文件(不带任何后缀)
#! /usr/bin/env node
console.log('脚手架工具入口文件...')

main 是base脚本,需要在第一行通过 #! /usr/bin/env node 指定脚本的解释语言,我们使用的语言是node。

  • 在 package.json 中添加配置
"bin": {
    "junxu-cli": "bin/main"
 }
  • 在终端通过执行命令 junxu-cli 正常打印出 “脚手架工具入口文件...”

配置 options

我们需要用到一个插件 commander, 安装一下:

npm install commander
  • 在 main 文件中添加如下配置代码
const program = require('commander')
const version = require('../package.json').version

program
    .name('junxu-cli')
    .usage(`<command> [option]`)
    .version(version)

program.parse(process.argv);

image.png

增加提示

为了增加提示,我们需要引入插件chalk

npm install chalk

chalk是用来美化字体的插件,也就是改变字体, 背景颜色等等。

执行命令: junxu-cli --help

image.png

配置脚手架命令(command)

脚手架的核心是命令,例如vue create xxx,所以,我们也需要实现自己的脚手架命令(毕竟开发脚手架就是为了这个)。

我们现在明确这个脚手架的需求是:使用这个脚手架,可以选择拉取vue2或者vue3的模版代码(当然,以后你们的脚手架可能是拉取公司的基础框架代码或者某些模块)。

  • 添加命令模块

现在,我们需要有一个模块(比如create模块),来完成创建指令。

program
    .command('create <project-name>')
    .description('create a new project')
    .option('-f, --force', 'overwrite target directory if it exists')
    .action((projectName, options) => {
        console.log(projectName, options);
    })
  • program.command是定义一个命令,命令的格式是junxu-cli create xxx
  • description就是这个命令的描述,不多介绍了
  • option是命令后面可以携带的参数以及参数的相关描述
  • action后面是一个回调,回调的第一个参数就是上面的xxx,第二个参数就是--后面的

运行命令junxu-cli create test --force image.png

编写create模块

由于create模块可能有很多功能,比如校验目录重复,获取版本信息,拉取远程代码或模块等等功能。所以我们先创建一个类,根目录下新建一个文件夹lib,并增加一个文件create.js,代码如下:

class Creator {
    constructor(projectName, options){
        this.projectName = projectName;
        this.options = options
    }
    
    // 执行 create 方法
    async create(){
        ...
    }
}

module.exports = async function(projectName, options){
    const creator = new Creator(projectName, options);
    await creator.create()
}

处理工程文件名重复的情况

使用 junxu-cli create xxx 创建一个项目时, xxx 文件名可能存在,则需要在创建前进行校验。

  • 使用了参数 --force 则删除原有目录,然后创建上诉目录文件
  • 如果没有使用 --force 参数,则会询问是否覆盖上诉同名文件,如果选择覆盖则执行,如果不覆盖则终止执行

询问用户是否覆盖的时候需要使用 inquirer 插件,使用 fs-extra 模块来判断是否存在同名文件

class Creator { 
        
        constructor(projectName, options) { 
            this.projectName = projectName; 
            this.options = options; 
        }
        // 创建 
        async create() { 
            const isOverwrite = await this.handleDirectory(); 
            if(!isOverwrite) return; 
            console.log('todo....'); 
        } 

        // 处理是否有相同目录 
        async handleDirectory() { 
            const targetDirectory = path.join(cwd, this.projectName); 

            // 如果目录中存在了需要创建的目录 
            if (fs.existsSync(targetDirectory)) { 
                if (this.options.force) { 
                    await fs.remove(targetDirectory); 
                } else { 
                    let { isOverwrite } = await new Inquirer.prompt([ 
                            { 
                                name: 'isOverwrite', 
                                type: 'list', 
                                message: '是否强制覆盖已存在的同名目录?', 
                                choices: [ 
                                    { name: '覆盖', value: true }, 
                                    { name: '不覆盖', value: false } 
                                ] 
                            } 
                        ]); 
                    if (isOverwrite) { 
                            await fs.remove(targetDirectory); 
                       } else {
                            console.log(chalk.red.bold('不覆盖文件夹,创建终止')); 
                            return false; 
                        } 
                    } 
                };
            return true; 
        } 
    }

运行 junxu-cli create test 命令 image.png

增加调取模版API

从远程去获取需要拉取的模版列表拉取到本地,先封装一下API,创建一个api目录

    TODO ...

拉取远程数据需要时间,增加一个 loading 效果,需要安装 ora 库: npm install ora@5

create.js 中增加方法 getCollectRepo 代码如下:

    const ora = require('ora');
    const api = require('./api/interface');
        // ...
    async create() {
        // ...
        await this.getCollectRepo();
    }
    // ...
    // 获取可拉取的仓库列表
    async getCollectRepo() {
        const loading = ora('正在获取模版信息...');
        loading.start();
        // const {data: list} = await api.getRepoList({per_page: 100});
        const {data: list} = await api.getRepoList();
        loading.succeed();
        // list = [
        //     {
        //         topics: ['template'],
        //         name: 'vue2-template'
        //     },
        //     {
        //         topics: ['template'],
        //         name: 'vue3-template'
        //     }
        // ]
        const collectTemplateNameList = list.filter(item => item.topics.includes('template')).map(item => item.name);
        let { choiceTemplateName } = await new Inquirer.prompt([
            {
                name: 'choiceTemplateName',
                type: 'list',
                message: '请选择模版',
                choices: collectTemplateNameList
            }
        ]);
        console.log('选择了模版:' + choiceTemplateName);
    }

image.png

下载对应模版

根据用户选择的模版来定向拉取对应的模版到本地了,拉取的模版地址已经准备好了: ShiJunXu/test。 我们需要使用download-git-repo插件来把git上面的项目拉取到本地,由于这个插件不支持promise,所以又需要使用node自带的util工具来支持。

安装插件:

    npm install download-git-repo

create.js 中添加如下代码:

    const util = require('util');
    const downloadGitRepo = require('download-git-repo');

    // ...

    class Creator {
    // ...
    // 获取可拉取的仓库列表
    async getCollectRepo() {
        // ...
        this.downloadTemplate(choiceTemplateName);
    }
    
    // 下载模板仓库
    async downloadTemplate(choiceTemplateName) {
        this.downloadGitRepo = util.promisify(downloadGitRepo);
        const templateUrl = `ShiJunXu/${choiceTemplateName}`;
        const loading = ora('正在拉取模版...');
        loading.start();
        await this.downloadGitRepo(templateUrl, path.join(cwd, this.projectName));
        loading.succeed();
    }
}

运行 junxu-cli create test

image.png

当模板拉取完成,在根目录下会有 test 文件的模板代码

模版提示

模板拉取完成,需要告诉用户如何操作

create.js 中新增如下代码:


class Creator {
    // ...
    // 下载仓库
    async downloadTemplate(choiceTemplateName) {
        // ...
        this.showTemplateHelp();
    }
    // 模版使用提示
    showTemplateHelp() {
        console.log(`\r\nSuccessfully created project ${chalk.cyan(this.projectName)}`);
        console.log(`\r\n  cd ${chalk.cyan(this.projectName)}\r\n`);
        console.log("  npm install");
        console.log("  npm run dev\r\n");
    }
}

执行 junxu-cli create test 脚手架命令从模板中创建项目:

image.png

发布脚手架

npm 官网注册账号密码,终端执行命令:npm login

这个过程中可能会报错: 需要设置 npm 源镜像, 通过npm config get registry查看镜像,如果不为 https://registr.npmjs.org, 则需要按 顺序执行如下代码即可:

  • npm config get proxy
  • npm config get https-proxy
  • npm config set registry https://registry.npmjs.org

image.png

执行 npm publish 发布

这个过程可能会出现如下问题: image.png 这是由于package.json 文件的 name 属性名与 npm库中重名了,修改一下即可发布成功。

每次更新的时候,修改你的package.json中的version,再次npm publish即可。

image.png