yeoman 快速上手

项目文件名和目录结构

需要按照约定的方式 generator-name 给项目文件夹命名,name 为你的 generator 的名字。项目目录结构一般如下:

1
2
3
4
5
6
7
generator-name
├───package.json
└───generators/
├───app/
│ └───index.js
└───router/
└───index.js

package.json 例子:

1
2
3
4
5
6
7
8
9
10
11
12
{
"name": "generator-name",
"version": "0.1.0",
"description": "",
"files": [
"generators"
],

"keywords": ["yeoman-generator"],
"dependencies": {
"yeoman-generator": "^1.0.0"
}

}

name 属性必须为 generator- 开头,keywords 属性中必须包含 “yeoman-generator”。

yo nameyo name:router,产生所需文件。

编写 index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict';
const Generators = require('yeoman-generator');

module.exports = class extends Generator {
constructor(args, opts) {
// 为了正确初始化,需要调用 super constructor
super(args, opts);

// 然后,添加自己的代码
this.option('babel'); // 这个方法添加了对 `--babel` flag 的支持
}

// 添加自己的方法
method1() {
console.log('method 1 just ran');
}

method2() {
console.log('method 2 just ran');
}
};

每个在 class 中添加的方法都会在 generator 被调用时被执行一次,而且一般是按顺序执行。不过,有些特殊的方法名会触法特殊的执行顺序。

运行 generator

在 generator-name/ 目录下的命令行中,输入:

1
(sudo) npm link

这将安装你的项目依赖,并将本地文件链接到全局模块中。 成功后,即可通过 yo name 指令来生产项目,可以验证你写的 generator。

运行循环(run loop)

run loop 是一个队列系统并支持优先级。优先级由特殊的 prototype 方法名决定。如果方法名未匹配优先级,则将放入 default 组。按运行顺序,可用的优先级名如下:

1
2
3
4
5
6
7
8
1. initializing : 初始化阶段
2. prompting : 接受用户输入阶段
3. configuring : 保存配置信息和文件,如.editorconfig
4. default : 非特定的功能函数名称,如上面说到的method1
5. writing : 生成项目目录结构阶段
6. conflicts : 统一处理冲突,如要生成的文件已经存在是否覆盖等处理
7. install : 安装依赖阶段,如通过npm、bower
8. end : 生成器即将结束

下面是一个例子:

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
'use strict';
const Generators = require('yeoman-generator');
const utils = require('../../utils/all');
const prompts = require('./prompts');
const path = require('path');
const fs = require('fs');
const packageInfo = require('../../package.json');


const baseRootPath = path.join(__dirname, 'react-webpack-multipage-template');

/**
* Base generator. Will copy all required files from react-webpack-multipage-template
*/

class AppGenerator extends Generators.Base {

constructor(args, options) {

super(args, options);

// Make options available
this.option('skip-welcome-message', {
desc: 'Skip the welcome message',
type: Boolean,
defaults: false
});
this.option('skip-install');

// Use our plain template as source
this.sourceRoot(baseRootPath);

this.config.save();
}

initializing() {

if(!this.options['skip-welcome-message']) {
this.log(require('yeoman-welcome'));
this.log('Out of the box I include Webpack and some default React components.\n');
}
}

prompting() {

return this.prompt(prompts).then((answers) => {

// Make sure to get the correct app name if it is not the default
if(answers.appName !== utils.yeoman.getAppName()) {
answers.appName = utils.yeoman.getAppName(answers.appName);
}

// Set needed global vars for yo
this.appName = answers.appName;
this.style = answers.style;
this.cssmodules = answers.cssmodules;
this.postcss = answers.postcss;
this.generatedWithVersion = parseInt(packageInfo.version.split('.').shift(), 10);

// Set needed keys into config
this.config.set('appName', this.appName);
this.config.set('appPath', this.appPath);
this.config.set('style', this.style);
this.config.set('cssmodules', this.cssmodules);
this.config.set('postcss', this.postcss);
this.config.set('generatedWithVersion', this.generatedWithVersion);
});
}

configuring() {

// Generate our package.json. Make sure to also include the required dependencies for styles
let defaultSettings = this.fs.readJSON(`${baseRootPath}/package.json`);
this.log('defaultSettings: ', defaultSettings);
this.log('baseRootPath: ', defaultSettings);
let packageSettings = {
name: this.appName,
private: true,
version: '0.0.1',
description: `${this.appName} - Generated by generator-react-multipage`,
main: 'src/index.js',
scripts: defaultSettings.scripts,
repository: '',
keywords: [],
author: 'Your name here',
devDependencies: defaultSettings.devDependencies,
dependencies: defaultSettings.dependencies
};

// Add needed loaders if we have special styles
let styleConfig = utils.config.getChoiceByKey('style', this.style);
if(styleConfig && styleConfig.packages) {

for(let dependency of styleConfig.packages) {
packageSettings.devDependencies[dependency.name] = dependency.version;
}
}

// Add postcss module if enabled
let postcssConfig = utils.config.getChoiceByKey('postcss', 'postcss');
if(this.postcss && postcssConfig && postcssConfig.packages) {

for(let dependency of postcssConfig.packages) {
packageSettings.devDependencies[dependency.name] = dependency.version;
}
}

// Add cssmodules if enabled
const cssmoduleConfig = utils.config.getChoiceByKey('cssmodules', 'cssmodules');
if(this.cssmodules && cssmoduleConfig && cssmoduleConfig.packages) {
for(let dependency of cssmoduleConfig.packages) {
packageSettings.dependencies[dependency.name] = dependency.version;
}
}

this.fs.writeJSON(this.destinationPath('package.json'), packageSettings);
}

writing() {

const excludeList = [
'LICENSE',
'README.md',
'CHANGELOG.md',
'node_modules',
'package.json',
'.istanbul.yml',
'.travis.yml'
];

// Get all files in our repo and copy the ones we should
fs.readdir(this.sourceRoot(), (err, items) => {

for(let item of items) {

// Skip the item if it is in our exclude list
if(excludeList.indexOf(item) !== -1) {
continue;
}

// Copy all items to our root
let fullPath = path.join(baseRootPath, item);
if(fs.lstatSync(fullPath).isDirectory()) {
this.bulkDirectory(item, item);
} else {
if (item === '.npmignore') {
this.copy(item, '.gitignore');
} else {
this.copy(item, item);
}
}
}
});
}

install() {

// Currently buggy!
if(this.postcss) {
const postcss = require('./postcss');
postcss.write(path.join(this.destinationRoot(), 'conf/webpack/Base.js'));
}

if(!this.options['skip-install']) {
this.installDependencies({ bower: false });
}
}
}

module.exports = AppGenerator;