基于nodeJS搭建的脚手架

792 阅读10分钟

前言

从前我总觉得脚手架是个很高大上的东西,只有那种牛叉的大佬才写的出来,可望而不可即。其实并不是因为困难使我们放弃,而是因为放弃才显得困难。只要你肯花个一天半天的时间去研究摸索,也能写出属于你自己的脚手架。

脚手架这个词是相信大家都不陌生,或多或少都有了解或者使用过,诸如vuevue-cli,reat中的create-react-app等等。用脚手架来开发,给我们的感觉就是便捷,迅速。但是我们需要去了解脚手架内置的一些东西,否则的话修改起来是相对不灵活的。而且现成的脚手架是不一定能够满足我们日常的工作环境需求的,所以有时候我们不得不自己从头去搭建一个项目的框架。如果一个框架要给多个项目使用,这当中就免不了很多的copy,git clone ...等一系列的操作。脚手架就是用来帮我们省掉这些操作的,它本质也是从远程下载一个模板来进行一个新项目,可以把它认为是一种高级克隆,提供一些交互式的命令让我们去动态的修改我们的模板项目。

本篇文章主要是基于之前学习工作中自己搭建的框架,搭建的一个脚手架。这当中包含了所用到工具,以及详细的代码步骤。学习过程中也查阅了很多资料借鉴了很多大佬,写的详细方便以后自己进行review,以及一些想要搭建脚手架的伙伴一起学习。其实真正实现一个基础功能的脚手架,代码量是不多的。

用到的工具

开发脚手架所需要用到的一些工具,如果之前有使用过的话可以略过,如果没用过的话建议做下了解,也欢迎推荐更好用的工具哈。

commander 命令行开发工具

commander是一个提供用户命令行输入和参数解析的强大功能,是前端开发node cli 必备技能。这边着重介绍一些基础的用法,不做深入拓展,有需要的可以阅读相关的库文档。

  1. version 定义脚手架的版本

  2. option 定义commander的选项options,参数解析:

    2.1 自定义标志<必须>:分为长短标识,中间用逗号、竖线或者空格分割;标志后面可跟必须参数或可选参数,前者用 <> 包含,后者用[]包含

    2.2 选项描述<省略不报错>:在使用 --help 命令时显示标志描述

    2.3 默认值<可省略>

var program = require('commander');

program
    .version('0.0.1', '-v, --version')
    .option('-i --init <name>[option]', 'init a project', 'myFirstProject')
    .parse(process.argv);  // 解析命令行参数

  1. command 添加命令名称,参数解析:

    3.1 命令名称<必须>:命令后面可跟用 <>[]包含的参数;命令的最后一个参数可以是可变的,像实例中那样在数组后面加入 ... 标志;在命令后面传入的参数会被传入到 action 的回调函数以及 program.args 数组中

    3.2 命令描述<可省略>:如果存在,且没有显示调用action(fn),就会启动子命令程序,否则会报错

    3.3 配置选项<可省略>:可配置noHelp、isDefault等

  2. action 定义命令的回调函数

  3. alias 定义命令的别名

  4. description 定义命令的描述

var program = require('commander');

program
    .version('0.0.1', '-v, --version')
    .command('init <name>[option]')
    .description('初始化项目')
    .action((name)=>{
        console.log(name)
    })
    .parse(process.argv);  // 解析命令行参数

download-git-repo 下载模版仓库

这是用来下载远程模板的,支持 GitHub、 GitLab 和 Bitbucket 等。

  1. 安装 npm i download-git-repo
  2. 使用
const download = require('download-git-repo')
download(repository, destination, options, callback)
// repository 是远程仓库地址
// destination 是存放下载的文件路径,也可以直接写文件名,默认就是当前目录
//options 是一些选项,比如 { clone:boolean } 表示用 http download 还是 git clone 的形式下载
//callback 下载之后回调函数,接受一个参数

ora 命令行加载进度标识

这是一个好看的加载,就是你下载的时候会有个转圈圈的那种效果,用法如下:

const ora = require('ora')
let spinner = ora('downloading template ...')
// spinner.start()
// spinner.fail('项目模板下载失败');
// spinner.succeed('项目模板下载成功');
...

inquirer 命令行交互工具

这是个强大的交互式命令行工具,如果你想要和用户进行交互,那么你需要使用它,具体用法如下:

const inquirer = require('inquirer');
inquirer.prompt([
    // 一些交互式的问题
    {
        type:'input',  // 提问的类型
        name:'author', // 存储在返回对象的 key 
        message:'请输入作者', // 问题的描述;
        default:''  //默认值
        ...
    }
  ])
  .then(answers => {
    // 回调函数,answers 就是用户输入的内容,是个对象
  });

还有很多的配置项,和交互类型input, confirm, list, rawlist, expand, checkbox, password, editor等等,有兴趣的同学可以自行了解尝试.

chalk 命令行输出字符样式

这是用来修改控制台输出内容样式的,比如颜色,加粗,字体背景色等等,具体用法如下:

const chalk = require('chalk');
console.log(chalk.green('success'));
...

起步

了解了以上的知识之后我们就可以正式开始搭建脚手架了。

  1. 新建文件夹,npm init创建项目package.json文件
  2. 修改package.json文件的bin字段,设置脚手架的入口文件
"bin": {
    "me-cli": "./index.js"
  },
  1. 新建入口文件index.js,在入口文件顶部添加代码#! /usr/bin/env node,声明该命令行脚本是node.js写的
  2. 下载所需要的工具依赖包,因为是之前查阅资料决定使用的这些工具,这边不做解释,直接下载再说
npm i commander download-git-repo inquirer ora chalk

设计指令

这边我暂且吧脚手架命令定义为me-cli,其实叫啥名字问题不大哈!

me-cli -v / me-cli --version`   //查看脚手架的版本号
me-cli -h / me-cli --help`      //查看脚手架的配置项和具有的功能
me-cli init 模板名 项目名       //脚手架初始化模板项目
me-cli list                     //查看脚手架可用的模板列表
...

本次实现最基础的功能,后续可新增.

入口文件代码如下:

#! /usr/bin/env node

const program = require('commander');  //命令行开发工具

program 
    .version('0.0.1', '-v, --version')
    .command('list')
    .description('查看所有可用的项目模板')

program  
    .command('init <template> <project>')
    .description('初始化项目模板工程')

安装和调试

  1. 当我们已经上传到npm仓库的时候,我们可以通过 npm i -g me-cli进行安装
  2. 我们开发本地调试的时候可以在项目目录执行 npm link创建一个软连接,然后也可以正常使用指令了。但是别忘了最后要冲npm库下载之前需要执行npm unlink进行解绑,以避免指令冲突。
  3. npm link之后,我们可以执行me-cli -vme-cli -h进行查看,结果如下图:

具体指令编写

me-cli list

具体代码如下:

#! /usr/bin/env node

const program = require('commander');  //命令行开发工具

program 
    .version('0.0.1', '-v, --version')
    .command('list')
    .description('查看所有可用的项目模板')
    .action(()=>{
        console.log(`
            reactCli        react基础模板
            reactReduxCli   react+redux模板 
            reactMobxCli    react+mobx模板
            reactTsCli      react+typescript模板
        `)
    })
        

program  
    .command('init <template> <project>')
    .description('初始化项目模板工程')

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

然后我们可以运行看效果

me-cli init

这部分是我们主要部分了,实现从远端拉取项目模版功能。

  1. 首先我们需要定义一个模版对象
// 可用模板
const templates = {
    'reactCli': {
        url: 'https://github.com/1149308443/react',
        downloadUrl: '1149308443/react#master',
        description: 'me-cli脚手架react'
    },
    ...
}

注意这边有个坑,对象里面的downloadUrl才是我们真正用来下载的地址,**#**后面的代表分支,是不需要携带前缀的。至于其他两项是我们用来自己更直观的看的,没有什么特殊作用。

  1. 首先我们定义了 <template> <project> 模版名称和项目名称,这两个必传参数,在回调中打印,并判断模版名称必须在模版列表内部。
program  
    .command('init <template> <project>')
    .description('初始化项目模板工程')
    .action(( templateName, projectName)=>{
        console.log(templateName, projectName)
        if(!(templateName in templates)){
            console.log('下载模板出错,请输入正确的模板');
            return;
        }    
        let { downloadUrl } = templates[templateName];
        console.log(downloadUrl)
    })

此时做正确的输入就可以拿到我们模版的下载地址和项目名称。

  1. 引入download-git-repo进行模版的下载,配置如下:
const download = require('download-git-repo'); // 模板仓库下载工具
...
download(downloadUrl, projectName, err => {
    if(err){
        console.log('下载模板出错');
    }else{
        console.log('下载模板成功');
    }
})
...

到这边其实我们已经可以正常的下载一个模版了,这还并没有结束。

  1. 引入ora 增加一个下载的状态标识。
  2. 引入chalk 为命令行的输出增加样式。
  3. 引入inquirer,当模板下载成功之后,通过与用户的修改获取用户的输入,以此进行模板项目内容的修改。
  4. 引入fs模块根据用户的输入信息修改模板项目的package.json

至此,我们的me-cli init 模板名 项目名指令就完成了,配置如下:

program
    .command('init <template> <project>')
    .description('初始化项目模板工程')
    .action(( templateName, projectName)=>{
        const  spinner = ora('正在下载模板...').start();
        if(!(templateName in templates)){
            spinner.fail('下载模板出错,请输入正确的模板');
            return;
        }    
        let { downloadUrl } = templates[templateName]; 
        //第一个参数是github仓库地址,第二个参数是创建的项目目录名,第三个参数是clone(是clone还是正常下载,可省略) 
        download(downloadUrl, projectName, err => {
            console.log(downloadUrl, projectName,err);
            if(err){
                spinner.fail('下载模板出错');
            }else{
                spinner.succeed('下载模板成功');

                //命令行交互
                inquirer.prompt([
                    {
                        type:'input',
                        name:'name',
                        message: '请输入项目名称',
                        default: projectName
                    },
                    {
                        type: 'input',
                        name: 'description',
                        message: '请输入项目简介',
                        default: ''
                    },
                    {
                        type: 'input',
                        name: 'author',
                        message: '请输入作者名称',
                        default: ''
                    }
                ]).then((answers)=>{
                     //根据命令行答询结果修改package.json文件
                    fs.readFile(`${projectName}/package.json`, 'utf8', function(err, data){
                        if(err){
                            console.log(chalk.red('读取配置失败'));
                            return;
                        }
                        let package = JSON.parse(data)
                        Object.assign(package,answers);
                        package = JSON.stringify(package,null, 4);
                        fs.writeFile(`${projectName}/package.json`, package, 'utf8', (err) => {
                            if(err){
                                console.log(chalk.red('修改配置失败'));
                                return;
                            }
                            console.log(chalk.green('项目初始化成功'))
                        });
                    })
                })
            }
        })
    })

发布npm

我们的基础功能已经实现了,接下来就是上传到npm仓库了。

  1. 首先我们需要注册一个npm的账号
  2. 在根目录下新建 .npmignore 文件,效果类似于.gitignore,是我们忽略要发到npm上的内容。
  3. 然后回到我们的根目录,执行npm login登陆自己的npm账号,之后再执行npm publish进行发布。

这边需要注意,发布npm仓库的时候,仓库的源必须是npm源,如果不是要先设置回去,发布之后再重新设置回来。

npm config get registry // 执行此命令查看仓库源
npm config set registry=http://registry.npmjs.org   // 执行此命令设置仓库源
  1. 如果只是作为测试,想从npm仓库删除,可以执行npm unpublish --force从仓库中删除。好像只有在发包的24小时内才允许撤销发布的包,而且24小时内是不可以重复发包的...
  2. 发布完了我们可以直接从npm库拉取,全局安装,执行npm unlink解绑本地调试,执行npm i -g me-cli全局安装,然后就可以正常使用我们的脚手架命令了,这边注意在解绑的时候最好加上模块名字

结束语

经过实践,会发现实现一个基础功能的脚手架真的不难,难的是要把它做精细。还有很多的功能要慢慢去发掘,深究。才能进一步去完善我们的脚手架,这有受限于本人的技术水平,有待于后期的学习完善。

本篇学习笔记,希望各位大佬写的不好的地方可以指正,有什么好的建议可以在评论区提出,也希望各位小伙伴一起学习,一起进步。。。