什么是脚手架 脚手架就是个工具,方便我们新建项目用的工具 ,有了这个项目我们就能直接通过几行命令, 再通过配置就可以开发了
想想我们平时开发一个新项目, 基本上我们可以用 git clone url 来新建(复制)项目, 或者这个复制粘贴整个文件夹
首先要基本了解vue-cli(@vue/cli) 是怎么工作的
脚手架的本质也是从远程下载一个模板来进行新的项目, 所以, 有什么不同呢? 就高大上啊😳, 当然不止于此, 脚手架是高级版的克隆, 它提供了交互式的命令让我们可以动态的更改模板/其他配置项, 然后用命令就可以搞定了
前置知识 这是用来编写指令和处理命令行的,具体用法如下
1 2 3 4 5 6 7 8 9 10 const program = require ("commander" );program .version('0.0.1' ) .command('init' , 'Generate a new project from a template' ) .action(() => { }) program.parse(process.argv);
回忆一下,我们曾用过的 vue init/create 命令就是这样声明的。
强大的交互式命令行工具,具体用法如下
1 2 3 4 5 6 7 8 const inquirer = require ('inquirer' );inquirer .prompt([ ]) .then(answers => { });
想象一下我们用 vue init webpack project-name 之后是不是会有几个交互问题,问你文件名啊、作者啊、描述啊、要不要用 eslint 啊等等之类的,就是用这个来写的。
这是用来修改控制台输出内容样式的,比如颜色啊,具体用法如下:
1 2 3 const chalk = require ('chalk' );console .log(chalk.green('success' ));console .log(chalk.red('error' ));
这是一个好看的加载,就是你下载的时候会有个转圈圈的那种效果,用法如下:
1 2 3 const ora = require ('ora' )let spinner = ora('downloading template ...' )spinner.start()
看名字很明显了,这是用来下载远程模板的,支持 GitHub、 GitLab 和 Bitbucket 等,用法如下
1 2 const download = require ('download-git-repo' )download(repository, destination, options, callback)
其中 repository 是远程仓库地址;destination 是存放下载的文件路径,也可以直接写文件名,默认就是当前目录;options 是一些选项,比如 { clone:boolean } 表示用 http download 还是 git clone 的形式下载。
项目搭建 有了上面的知识储备后, 我们就正式开始撸代码了
首先我们创建一个文件夹, 取名为 demo-cli
在改目录下执行 npm init -y
命令, 在生成的 package.json 文件中写入以下依赖并执行 npm install
安装, 如下
1 2 3 4 5 6 7 "dependencies": { "chalk": "^2.4.2", "commander": "^2.19.0", "download-git-repo": "^1.1.0", "inquirer": "^6.2.2", "ora": "^3.2.0" }
新建一个 bin 文件夹, 在 bin 文件夹下新建一个无后缀名的 demo 文件, 并写上:
1 2 #!/usr/bin/env node console .log('hello' );
这个文件就是我们整个脚手架的入口文件, 我们用 node ./bin/demo
运行一下, 就可以看到在控制台打印出 hello
这里要注意开头的 #!/usr/bin/env node 这个语句必须加上,主要是为了让系统看到这一行的时候,会沿着该路径去查找 node 并执行,主要是为了兼容 Mac ,确保可执行。
bin 目录初始化 当前,bin 目录下就只有一个文件,就是入口文件 demo。所以现在我们先来编写这个文件,由于内容较少,我们直接看代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 #!/usr/bin/env node const program = require ('commander' );program .version(require ('../package' ).version, '-V, --version' ) .usage('<command> [options]' ) .command('add' , 'add a new template' ) .command('delete' , 'delete a template' ) .command('list' , 'list all the template' ) .command('init' , 'generate a new project from a template' ) program.parse(process.argv)
这个文件的主要作用就是定义指令,现在我们用 node ./bin/demo 运行一下,就能看到如下结果:
当然,你可能会觉得每次输入 node ./bin/demo 这个命令有点麻烦,没关系,我们可以在 package.json 里面写入已下内容:
1 2 3 4 "bin" : { "demo" : "bin/demo" }
然后在根目录下执行 npm link
(就是把命令挂载到全局的意思),这样我们每次只要输入 demo,就可以直接运行了,so cool
是不是好像有点样子了呢😁😁😁,那就让我们继续完善下 bin 目录吧!ok,让我们在 bin 目录下再新建四个文件,分别对应上面的四个指令,然后分别处理四个指令要做的事情
同样的,我们修改一下 package.json 里面的 bin 内容,如下:
1 2 3 4 5 6 7 "bin": { "demo": "bin/demo", "demo-add": "bin/demo-add", "demo-delete": "bin/demo-delete", "demo-list": "bin/demo-list", "demo-init": "bin/demo-init" }
然后执行 npm unlink 解绑全局命令,再执行 npm link 重新把命令绑定到全局
最后顺便在根目录下新建一个 template.json 文件,里面的内容就是一个 {}。
编写具体指令 好了,一切准备就绪,接下来就让我们来写下具体的四个指令吧。
demo-add 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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #!/usr/bin/env node const inquirer = require ('inquirer' )const chalk = require ('chalk' )const fs = require ('fs' )const tplObj = require (`${__dirname} /../template` )let question = [ { name: "name" , type: 'input' , message: "请输入模板名称" , validate (val) { if (val === '' ) { return 'Name is required!' } else if (tplObj[val]) { return 'Template has already existed!' } else { return true } } }, { name: "url" , type: 'input' , message: "请输入模板地址" , validate (val) { if (val === '' ) return 'The url is required!' return true } } ] inquirer .prompt(question).then(answers => { let { name, url } = answers; tplObj[name] = url.replace(/[\u0000-\u0019]/g , '' ) fs.writeFile(`${__dirname} /../template.json` , JSON .stringify(tplObj), 'utf-8' , err => { if (err) console .log(err) console .log('\n' ) console .log(chalk.green('Added successfully!\n' )) console .log(chalk.grey('The latest template list is: \n' )) console .log(tplObj) console .log('\n' ) }) })