什么是脚手架?
当我们建造房子的时候,工地上会先搭个架子,然后工人们会在这个的基础上添砖加瓦,直到房屋成型。那么我们程序也是这样的道理,会在我们开发之前有一套相对成熟且适用的架子(配置项、技术栈),然后我们会在这基础上直接产出需求功能,而需求重复的造架子。脚手架在我们开发项目中也是极其重要,能够提高我们的开发。
那么我们开发中有用到哪些脚手架呢?有vue-cli
、create-react-app
、yeoman
等。
Yeoman
yeoman较真来讲更应该称为脚手架框架,因为它不能直接创建项目文件,而是提供了一套完整的开发API,可以通过这些API来灵活地来制造符合你需求的脚手架方案。
开始
我们先来创建一个yeoman
脚手架生成器,注意文件夹的名字得为generator-开头,如generator-vuecli、generator-createapp。
npm install --global yo
mkdir generator-testcli && cd generator-testcli
npm init
,生成package.json
需要主要几点:- name 属性必须带有前缀
generator-
- keywords 属性必须含有
yeoman-generator
;
- name 属性必须带有前缀
npm install --save yeoman-generator
yeoman-generator
:yeoman 会根据用户选择的 generator
下载的本地,其中 generator
指的是一套具有模板的项目。
在官方例子中generator-fountain-webapp
就是其中的一个generator
。当然你也可以选择你要的generator
,如输入命令行yo
,会有选项Install a generator
让你选择你想要安装的generator
。
提示:假如安装过程报Error: EACCES, permission denied
错误,请使用sudo npm
进行安装。
目录结构
yeoman
支持两种不同的目录结构:./ 和 ./generators。即:1
2
3
4
5
6
7
8
9
10
11
12|---- package.json
|---- generators/
|---- app/
|---- index.js
|---- router/
|---- index.js
############################################
|---- package.json
|---- app/
|---- index.js
|---- router/
|---- index.js
重写构造函数
某些生成器方法只能在构造函数内部调用。这些特殊方法可能会执行诸如设置重要状态控件之类的操作,并且可能无法在构造函数之外运行。1
2
3
4
5
6
7
8
9
10
11
12var Generator = require('yeoman-generator');
module.exports = class extends Generator {
// The name `constructor` is important here
constructor(args, opts) {
// Calling the super constructor is important so our generator is correctly set up
super(args, opts);
// Next, add your custom code
this.option('babel'); // This method adds support for a `--babel` flag
}
};
添加功能函数
1 | method1() { |
完整代码为:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var Generator = require('yeoman-generator');
module.exports = class extends Generator {
// The name `constructor` is important here
constructor(args, opts) {
// Calling the super constructor is important so our generator is correctly set up
super(args, opts);
// Next, add your custom code
this.option('babel'); // This method adds support for a `--babel` flag
}
method1() {
this.log('method 1 just ran');
}
method2() {
this.log('method 2 just ran');
}
};
npm绑定全局
使用 npm 创建一个node全局模块,并且连接到本地。在脚手架项目根目录下generator-name/
,输入npm link
。
执行
yo testcli
,会出现如下图信息。
到这里一个小小的官方demo已经执行完成了,更多的可以查看Writing Your Own Yeoman Generator | Yeoman
自定义前端脚手架
刚才在上问已经说了,yeoman
实际上更像是一个脚手架框架,可以通过它来打造各种利器(脚手架)。那我们现在就从头开始学习下怎么打造一个利器。
我们现在就来简单的做一个小栗子,执行init
时会拉取GitHub上面的代码。上面已经说过了,实际上就是相当于clone一个基础项目到本地。
起步
我们把这个项目称为messi吧。
mkdir messi && cd messi
;npm init
(此处的package.json
就没有yeoman
那样需要name跟key有前缀的要求了);npm install --save chalk co co-prompt commander
入口文件
我们在根目录创建/bin
文件夹,然后在该文件夹创建的文件messi.js
。即bin/messi.js
为我们这个项目的入口文件。
1 | // bin/messi.js |
挂载全局
其实这样就可以看到效果了。我们现在在package.json
添加以下配置,表示全局钩子messi
的加载文件是bin/messi.js
。
例如:1
2>>> /usr/local/bin/messi -> /usr/local/lib/node_modules/messi/bin/messi.js
>>> /usr/local/lib/node_modules/messi -> /Users/xxx/Documents/study/messi
package.json1
2
3"bin": {
"messi": "bin/messi.js"
}
然后在根目录执行npm link
或sudo npm link
。打开另一个terminal标签执行messi
命令。
添加init模版
我们在根目录创建command
文件夹,然后在其文件夹创建我们的主角init.js
。目的是想通过init
从远端仓库拉取一个项目模板。
我们需要在messi.js
添加了相对应的命令行。1
2
3
4
5
6
7
8// messi.js
const init = require('../init');
program
.command('init')
.description('创建新项目')
.alias('i')
.action(() => { init(); });
然后我们需要在init.js
添加逻辑代码。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// init.js
const co = require('co');
const chalk = require('chalk')
const prompt = require('co-prompt');
const exec = require('child_process').exec;
module.exports = () => {
console.log(chalk.white('init...'));
co(function *() {
let pull = yield prompt('你是否想要拉取scaffold-cli项目? y/n ');
pull = pull.toLowerCase();
if (pull === 'n') {
console.log(chalk.red('已经成功退出!'));
process.exit()
} else if (pull === 'y') {
const projectName = yield prompt('项目名称:');
const gitCmd = `git clone git@github.com:Lucius0/scaffold-cli.git ${projectName} && cd ${projectName} && git checkout master`;
console.log(chalk.white('开始拉取...'));
exec(gitCmd, (error, stdout, stderr) => {
if (error) {
console.log(error);
process.exit();
}
console.log(chalk.green('拉取成功!'));
process.exit()
});
}
});
};
到此刻为止,我们基本上的一个小demo已经完成了。关于exec
方法,可以阅览exec。
目录结构
1 | |__ bin |
emmm。是不是感觉有些缺少什么?是的,我们如何能做到跟vue-cli
一样优秀,可以自主选择自己想要的选择(有点拗口)。
inquirer
常用交互式命令行用户界面的集合。
具体的官方文档可以看 -> inquirer,我们来简单的展示一下使用方法。首先npm install --save inquirer
,然后在messi.js
添加我们跟init.js
功能一样的指令init2
以及在init2.js
添加相对应的逻辑。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
66
67
68
69
70// messi.js
const init2 = require('../command/init2');
program
.command('init2')
.description('创建新项目')
.alias('i2')
.action(() => {
init2();
});
// init2.js
const co = require('co');
const chalk = require('chalk')
const inquirer = require('inquirer');
const exec = require('child_process').exec;
module.exports = () => {
console.log(chalk.white('init...'));
co(function *() {
var promps = [];
promps.push({
type: 'list',
name: 'pullProject',
message: '你是否想要拉取scaffold-cli项目?',
choices: [{
name: 'Yes',
value: 'y'
}, {
name: 'No',
value: 'n'
}]
});
inquirer.prompt(promps).then((answers) => {
console.log(answers);
const wanna = answers['pullProject'].toLowerCase();
if (wanna === 'n') {
console.log(chalk.red('已经成功退出!'));
process.exit()
} else if (wanna === 'y') {
promps = [];
promps.push({
type: 'input',
name: 'nameProject',
message: '项目名称:'
});
inquirer.prompt(promps).then((answers) => {
const projectName = answers['nameProject'];
const gitCmd = `git clone git@github.com:Lucius0/scaffold-cli.git ${projectName} && cd ${projectName} && git checkout master`;
console.log(chalk.white('开始拉取...'));
exec(gitCmd, (error, stdout, stderr) => {
if (error) {
console.log(error);
process.exit();
}
console.log(chalk.green('拉取成功!'));
console.log(`cd ${projectName}`);
exec(`cd ${projectName}`, () => {
process.exit();
});
});
});
}
});
});
};
接下来一步就是构建项目npm link
,然后执行messi ini2
。
好了。。。。。。睡觉。。。。。。。