Jeffery

人无远虑、必有近忧

  • 主页
  • 随笔
  • 技术
  • 相册
  • 关于
所有文章 友链 关于我

Jeffery

人无远虑、必有近忧

  • 主页
  • 随笔
  • 技术
  • 相册
  • 关于

Nodejs开发简单的脚手架工具

阅读数:0次 2018-06-11

文章导航

× 文章目录
  1. 1. 脚手架的作用
  2. 2. node相关基础知识
  3. 3. 思路
  4. 4. 初始化项目
  5. 5. 安装依赖
  6. 6. 配置入口文件
  7. 7. 处理命令行
  8. 8. 下载模板
  9. 9. 交互命令行
  10. 10. 渲染package模板
  11. 11. 链接全局命令

脚手架,这个名词对于作为前端的我们来说,也许并不陌生吧,像vue-cli,react-native-cli等,全局安装后,只需要在命令行中敲入一个简单的命令,便可帮我们快速的生成一个初始项目,如vue init webpack projectName,即可生成一个初始的vue项目。
本文主要是介绍开发一个简单的脚手架,了解开发的基本流程、最终通过npm link链接到全局包。

脚手架的作用

1、减少时间,不必从零开始搭建初始项目,提高开发效率。
2、便于多人协作。
3、项目更新同步方便,只需要更新代码库中项目模板,即可下载最新的项目。

node相关基础知识

在开始项目前,先简单介绍下node相关的一些基础知识,通过npm init初始化一个node项目时,会生成一个package.json的配置文件,包括项目名称、版本、作者、依赖等相关信息,主要说一下其中的bin字段。
很多包都有一个或多个可执行的文件,希望放在PATH中,(实际上,就是这个功能让npm可执行的)。
当你要用这个功能时,需要给package.json中的bin字段添加一个命令名,并指向需要执行的文件(即后文的入口文件)。初始化的时候npm会将他链接到prefix/bin(全局初始化)或者./node_modules/.bin/(本地初始化)。
比如,npm有:

1
{ "bin" : { "npm" : "./npm-cli.js" } }

所以,当你初始化npm,它会创建一个符号链接到npm-cli.js脚本到/usr/local/bin/npm。

如果你只有一个可执行文件,并且名字和包名一样。那么你可以只用一个字符串,比如:

1
2
3
{ "name": "my-program"
, "version": "1.2.5"
, "bin": "./path/to/program" }

结果和这个一样:

1
2
3
{ "name": "my-program"
, "version": "1.2.5"
, "bin" : { "my-program" : "./path/to/program" } }

想要了解package.json更多的详细配置,请参考这篇文章

思路

要开发脚手架,首先要理清思路,脚手架是如何工作的?我们可以借鉴 vue-cli 的基本思路。vue-cli 是将项目模板放在 git 上,运行的时候再根据用户交互下载不同的模板,经过模板引擎渲染出来,生成项目。这样将模板和脚手架分离,就可以各自维护,即使模板有变动,只需要上传最新的模板即可,而不需要用户去更新脚手架就可以生成最新的项目。那么就可以按照这个思路来进行开发了。

初始化项目

新建一个文件夹,打开命令行工具,通过npm init 进行项目初始化,会在项目根目录下生成package.json文件。

1
npm init

安装依赖

1
npm install commander download-git-repo inquirer handlebars ora chalk log-symbols shelljs -S

除此之外,还使用了nodejs的几个内置模块:fs、path、child_process

commander.js:可以自动的解析命令和参数,用于处理用户输入的命令。

download-git-repo:下载并提取 git 仓库,用于下载项目模板。

Inquirer.js:通用的命令行用户界面集合,用于和用户进行交互。

handlebars.js:模板引擎,将用户提交的信息动态填充到文件中。

ora:下载过程久的话,可以用于显示下载中的动画效果。

chalk:可以给终端的字体加上颜色。

log-symbols:可以在终端上显示出 √ 或 × 等的图标。

fs:node内置的文件处理模块。

path:node内置的路径处理、解析模块。

child_process:node中创建子进程模块。

配置入口文件

1、在package.json文件中增加bin字段,暂且先命名为okcli吧。

1
2
3
"bin": {
"okcli": "./index.js"
}

2、在项目根目录新建index.js文件,在index.js文件顶部添加以下代码:

1
#!/usr/bin/env node

即可进行开发。
在这里,简单介绍下这行代码含义。


!/usr/bin/node是告诉操作系统执行这个脚本的时候,调用/usr/bin下的node解释器;
!/usr/bin/env node这种用法是为了防止操作系统用户没有将node装在默认的/usr/bin路径里。当系统看到这一行的时候,首先会到env设置里查找node的安装路径,再调用对应路径下的解释器程序完成操作。
!/usr/bin/node相当于写死了node路径;
!/usr/bin/env node会去环境设置寻找node目录,推荐这种写法

处理命令行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const program = require('commander');
const inquirer = require('inquirer');
const symbols = require('log-symbols');
const download = require('download-git-repo');
const handlebars = require('handlebars');
const chalk = require('chalk');
const ora = require('ora');
const shell = require('shelljs');
const child_process = require('child_process');
const fs = require('fs');
const path = require('path');
program.version('1.0.0', '-v, --version')
.command('init <name>')
.action((name) => {
console.log(name);
});
program.parse(process.argv);

调用program.version(‘1.0.0’, ‘-v, –version’)会将-v和–version添加到命令行中,调用时可通过带上该参数获取该脚手架的版本号(命令 -v/–version),调用comand(‘init ‘)定义初始化命令,name参数必传,作为项目的文件夹名,如vue init webpack parojectName
action是执行command命令时发生的回调,参数为命令行中输入的name,即init 中的name,项目生成过程便发生在回调函数中。
现在可以通过调用node index.js init test,可以看到控制台中已经打印了输入的项目名,也就是test。
其中:program.parse(process.argv)解析命令行中的参数,解析出name,并传入action回调。

下载模板

通过download-git-repo或者直接使用shelljs或者child_process直接运行命令进行下载模块(个人选择的是第三种)。
download-git-repo 支持从 Github、Gitlab 和 Bitbucket 下载仓库,各自的具体用法可以参考官方文档。

1
2
3
4
5
6
7
8
download('https://github.com/jefferyE/webpack-configuration-for-vue', name, {clone: true}, (err) => {
if(err){
// spinner.fail();
console.log(symbols.error, chalk.red(err));
}else{
// spinner.fail();
}
})

或者

1
2
3
4
5
6
if (shell.exec('git clone https://github.com/jefferyE/webpack-configuration-for-vue').code == 0) {
// spinner.succeed();
} else {
// spinner.fail();
console.log(symbols.error, chalk.red('模板下载失败'))
}

或者

1
2
3
4
5
6
7
8
child_process.exec('git clone https://github.com/jefferyE/webpack-configuration-for-vue', function(err, stdout, stderr) {
if (err) {
// spinner.fail();
console.log(symbols.error, chalk.red('模板下载失败'))
} else {
// spinner.succeed();
}
})

其中:download() 第一个参数就是仓库地址,但是有一点点不一样。实际的仓库地址是 https://github.com/jefferyE/webpack-configuration-for-vue#master ,可以看到端口号后面的 ‘/‘ 在参数中要写成 ‘:’,#master 代表的就是分支名,不同的模板可以放在不同的分支中,更改分支便可以实现下载不同的模板文件了。第二个参数是路径,上面我们直接在当前路径下创建一个 name 的文件夹存放模板,也可以使用二级目录比如 test/${name}

交互命令行

命令行交互功能可以在用户执行 init 命令后,向用户提出问题,接收用户的输入并作出相应的处理。这里使用 inquirer.js 来实现,也可以自己使用node内置的readline模块。
具体可以参考我的这篇文章

1
2
3
4
5
6
7
8
9
inquirer.prompt([
{
type: 'input',
name: 'author',
message: '请输入作者名称'
}
]).then((answers) => {
console.log(answers.author);
})

通过这里例子可以看出,问题就放在 prompt() 中,问题的类型为 input 就是输入类型,name 就是作为答案对象中的 key,message 就是问题了,用户输入的答案就在 answers 中,使用起来就是这么简单。更多的参数设置可以参考官方文档。

渲染package模板

这里用 handlebars 的语法对 HTML5/H5Template 仓库的模板中的 package.json 文件做一些修改。并在下载模板完成之后将用户输入的答案渲染到 package.json 中。

1
2
3
4
5
6
7
8
9
10
{
"name": "{{name}}",
"version": "1.0.0",
"description": "{{description}}",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "{{author}}",
"license": "ISC"
}

注意:由于我的项目没有更改package模板,因此通过在下载模板后,解析时进行动态修改的。
除此之外,通过ora、chalk模块也进行了一些视觉美化。具体请参考完整代码。
完整代码如下:

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
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env node

const program = require('commander');
const chalk = require('chalk');
const ora = require('ora');
const fs = require('fs');
const inquirer = require('inquirer');
const shell = require('shelljs');
const symbols = require('log-symbols');
const download = require('download-git-repo');
const child_process = require('child_process');
const handlebars = require('handlebars');
const path = require('path');

program.version('1.0.0', '-v, --version').
command('init <name>').
action(name => {
console.log(name);
if (!fs.existsSync(name)) {
console.log('正在创建项目...');
inquirer.prompt([
{
name: 'description',
message: '请输入项目描述'
},
{
name: 'author',
message: '请输入作者名称'
}
]).then(answers => {
console.log(answers)
const spinner = ora('正在向下载模板...\n');
spinner.start();
child_process.exec('git clone https://github.com/jefferyE/webpack-configuration-for-vue', function(err, stdout, stderr) {
if (err) {
spinner.fail();
console.log(symbols.error, chalk.red('模板下载失败'))
} else {
spinner.succeed();
shell.mv(__dirname + '/webpack-configuration-for-vue', __dirname + '/' + name)
const filename = `${name}/package.json`;
const meta = {
name,
description: answers.description,
author: answers.author
}
if (fs.existsSync(filename)) {
const content = fs.readFileSync(filename).toString();
let dt = JSON.parse(content);
dt.name = '{{name}}';
dt.description = '{{description}}'
const result = handlebars.compile(JSON.stringify(dt, null, 2))(meta);
fs.writeFileSync(filename, result);
console.log(symbols.success, chalk.green('项目初始化完成'));
} else {
console.log(symbols.error, chalk.red('package不存在'))
}
}
})
})
} else {
console.log(symbols.error, chalk.red('项目已存在'));
}
})
program.parse(process.argv);

其中:
1、在用户输入答案之后,开始下载模板,这时候使用 ora 来提示用户正在下载中。
2、然后通过 chalk 来为打印信息加上样式,比如成功信息为绿色,失败信息为红色,这样子会让用户更加容易分辨,同时也让终端的显示更加的好看。
3、除了给打印信息加上颜色之外,还可以使用 log-symbols 在信息前面加上 √ 或 × 等的图标。

完成之后,就可以把脚手架发布到 npm 上面,通过 -g 进行全局安装,就可以在自己本机上执行 okcli init [name] 来初始化项目,这样便完成了一个简单的脚手架工具了。

链接全局命令

在根目录运行 npm link命令,将包链接到全局环境。
npm link命令可以将一个任意位置的npm包链接到全局执行环境,从而在任意位置使用命令行都可以直接运行该npm包。
具体请参考这篇文章

感谢:
参考文章文章1
        文章2

赏

谢谢你请我吃糖果

  • cli
  • javascript
  • nodejs

扫一扫,分享到微信

微信分享二维码
vue分析之template模板解析AST
readline模块的使用
  1. 1. 脚手架的作用
  2. 2. node相关基础知识
  3. 3. 思路
  4. 4. 初始化项目
  5. 5. 安装依赖
  6. 6. 配置入口文件
  7. 7. 处理命令行
  8. 8. 下载模板
  9. 9. 交互命令行
  10. 10. 渲染package模板
  11. 11. 链接全局命令
© 2019 Jeffery
Hexo Theme Yilia by Litten
  • 所有文章
  • 友链
  • 关于我

tag:

  • cli
  • javascript
  • nodejs
  • design
  • 设计模式
  • readline
  • vue
  • path
  • template
  • AST
  • 源码
  • 插件
  • 轮播
  • 滚动
  • welcome
  • intoduction
  • web
  • web安全
  • vnode
  • patch
  • diff
  • 正则表达式
  • 量词
  • 贪婪模式
  • 惰性模式
  • 苏州
  • 旅行
  • 风景
  • segmentfault
很惭愧<br><br>只做了一点微小的工作<br>谢谢大家