在做 React 开发时,每次新建一个组件都要需要创建一个 Xxx 目录,然后在目录中创建两个文件 Xxx.jsx 和 Xxx.css,然后引入巴拉巴拉。。。然后。。。
Yeoman 是一个脚手架工具,可以帮助我们创建目录,创建文件,执行 Grunt/Gulp,安装 Npm/Bower 依赖等等。我们用它来创建一个 generator-react-new-component。
准备工具
在编写生成器之前,先安装 yo
和 generator-generator
。1
npm install -g yo generator-generator
了解工具
通常一个 yo
生成器的目录结果是1
2
3
4
5├───package.json
├───app/
│ └───index.js
└───router/
└───index.js
或者希望保持生成器根目录的整洁,也可以这样1
2
3
4
5
6├───package.json
└───generators/
├───app/
│ └───index.js
└───router/
└───index.js
我们可以通过 yo
提供的脚手架来生成基本的目录结构,运行1
yo generator
按照交互提示输入,最终会得到1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21? Your generator name generator-xxx
? Description xxx
? Project homepage url xxx
? Author's Name xxx
? Author's Email [email protected]
? Author's Homepage
? Package keywords (comma to split) xxx,xx
? Send coverage reports to coveralls Yes
? GitHub username or organization xxx
? Which license do you want to use? MIT
create package.json
create README.md
create .editorconfig
create .gitattributes
create .gitignore
create generators/app/index.js
create generators/app/templates/dummyfile.txt
create test/app.js
create .travis.yml
create gulpfile.js
create LICENSE
其中,我们主要关注 generators/app/index.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 ;
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
module.exports = yeoman.generators.Base.extend({
prompting: function () {
var done = this.async();
// Have Yeoman greet the user.
this.log(yosay(
'Welcome to the amazing ' + chalk.red('generator-xxx') + ' generator!'
));
var prompts = [{
type: 'confirm',
name: 'someOption',
message: 'Would you like to enable this option?',
default: true
}];
this.prompt(prompts, function (props) {
this.props = props;
// To access props later use this.props.someOption;
done();
}.bind(this));
},
writing: function () {
this.fs.copy(
this.templatePath('dummyfile.txt'),
this.destinationPath('dummyfile.txt')
);
},
install: function () {
this.installDependencies();
}
});
每一个生成器都是 yeoman.generators.Base.extend
的扩展,你可以扩展一些处理函数。其中 constructor
是构造函数,只会执行一次,有些 yeoman.generators
提供的函数只能在这里执行1
2
3
4
5
6
7
8
9
10module.exports = generators.Base.extend({
// The name `constructor` is important here
constructor: function () {
// Calling the super constructor is important so our generator is correctly set up
generators.Base.apply(this, arguments);
// Next, add your custom code
this.option('coffee'); // This method adds support for a `--coffee` flag
}
});
还有一些 yeoman.generators
预定义的函数,它们会按照优先级的顺序执行这些函数
initializing - Your initialization methods (checking current project state, getting configs, etc)
prompting - Where you prompt users for options (where you’d call this.prompt())
configuring - Saving configurations and configure the project (creating .editorconfig files and other metadata files)
default - If the method name doesn’t match a priority, it will be pushed to this group.
writing - Where you write the generator specific files (routes, controllers, etc)
conflicts - Where conflicts are handled (used internally)
install - Where installation are run (npm, bower)
end - Called last, cleanup, say good bye, etc
你也可以自定义一些其他名字的函数1
2
3
4
5
6
7
8module.exports = generators.Base.extend({
method: function () {
console.log('method 1 just ran');
},
_privateMethod: function () {
console.log('method 2 just ran');
}
});
如果函数是 _
开头,则为私有函数,不会自动执行,否则这些自定义的函数的优先级为 default
,会自动执行。
开始编写
在了解了 yeoman.generators
的基本功能之后,我们在回头看看我们的需求,其实可以把要做的工作分为几步
- 获取用户输入的 componont name
- 选择创建的 path
- 创建相应的 dir/files
开始编写处理函数
前面两把都是通过跟用户交互获取输入,放在
prompting
这层做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
34prompting: function () {
var done = this.async();
this.log(yosay(
'Welcome to the gnarly ' + chalk.red('generator-react-new-component') + ' generator!'
));
var prompts = [{
type: 'input',
name: 'componentName',
message: 'What\'s name new component?'
}, {
type: 'list',
name: 'componentPath',
message: 'Choices component path:',
choices: function() {
var dirs = [];
var reg = new RegExp('^(client|app|src)/(component|container)(s)?/([a-z]*/)*$');
var entries = walkSync.entries('.', {globs: ['client/**/*', 'app/**/*', 'src/**/*']})
entries.forEach(function(entry) {
if (entry.isDirectory() && reg.test(entry.relativePath)) {
dirs.push(entry.relativePath);
}
});
return dirs;
},
default: 'client/components/',
}];
this.prompt(prompts, function (props) {
this.props = props;
done();
}.bind(this));
},通过定义
prompts
(基于 Inquirer.js)来跟用户交互,获取用户的输入,然后保存到this.props
上,给后续的处理函数使用。创建目录文件,放在
writing
这层做1
2
3
4
5
6
7
8writing: function () {
var jsPath = this.props.componentPath + '/' + this.props.componentName;
this.fs.copy(
this.templatePath('jsTemplateFile'),
this.destinationPath(jsPath),
{ componentName: this.props.componentName }
);
}根据用户输入的
componentName
和componentPath
值,得到我们生成文件的路径
。指定渲染的模板jsTemplateFile
,模板一般放在同级目录templates
文件夹下,1
2
3
4
5
6
7
8
9
10
11
12
13import React, { Component } from 'react';
class <%= componentName %> extends Component {
render() {
return (
<div>
<h1> <%= componentName %> </h1>
</div>
);
}
}
export default <%= componentName %>;模板支持
ejs
的语法,可以传值渲染在完成了基本开发之后,在发布我们的生成器之前,我们可以先本地调试下,确保没问题了再发布到
Npm
上。在generator-xxx
目录下执行1
npm link
完成之后,再执行
1
yo xxx
就可以调用我们的生成器了。确保没问题之后再执行
npm publish
发布。
后记
完整的版本在这里 generator-react-new-component。增加了几个 prompt
用于选择文件名后缀、样式文件后缀等。
之前看过一句话,大概意思是 懒
是程序员的第一生产力,貌似很有道理。
另外,上面只是介绍了 yeoman.generators
的基本功能,还有很多有意思、很人性化的功能都没提到,感兴趣可以继续深入。
相关
http://yeoman.io/authoring/index.html
https://github.com/SBoudrias/Inquirer.js
https://github.com/booxood/generator-react-new-component