对大多数一个前端团队来说,Yeoman(简称yo)是一个非常值得学习的工具,它为前端项目提供了一种行之有效的方法,开发、分发、使用项目手脚架,提高项目启动速度,复用项目结构。
本文以generator-iview-admin 为例,简单说明手脚架,即generator的开发过程。
准备
开发 Yeoman 模板,需预先安装yo
:
npm i -g yo
yo 细心的为我们准备了手脚架项目的手脚架generator-generator:
npm i -g generator-generator
安装后,进入开发目录,运行:
yo generator
项目结构
执行上述命令后,主要生成如下文件:
├── .yo-rc.json
├── package.json
├── generators
│ ├── app
│ ├── templates
│ ├── dummyfile.txt
│ ├── index.js
其中
-
.yo-rc.json
用于存储项目配置,一般不会用到,无需关注 -
package.json
npm 项目信息文件,主要关注 author、version 域即可 -
generators
目录即项目模板代码 -
generators/templates
用于存放项目模板文件 -
generators/app/index.js
定义项目手脚架的代码
开发
每个generator都会继承yeoman-generator
类,即上述的generators/app/index.js
文件。该类有几个重要的生命周期节点:
-
initializing
- 初始化方法,用于获取项目状态、配置等 -
prompting
- 调用inquire方法获取用户输入 -
configuring
- 保存配置,创建.editorconfig
等文件 -
writing
- 执行文件写操作,即项目文件写入文件系统中 -
install
- 执行安装操作,需调用this.installDependencies
方法 -
end
- 最后执行,可清楚临时文件等
上述方法均支持返回
Promise
方式实现异步操作,仅当返回的Promise
实例resolve
时才会执行下一步操作。
Step 1. 获取用户配置
首先,我们需要在 prompting
中询问用户配置(完整实例在此处):
prompting() {
// Have Yeoman greet the user.
this.log(yosay('Welcome to the divine ' + chalk.red('generator-iview-admin') + ' generator!'));
const prompts = [{
type: 'input',
name: 'name',
message: 'Your project name',
default: this.appname
}, {
type: 'confirm',
name: 'lint',
message: 'Use ESLint to lint your code?'
}, {
name: 'lintStyle',
type: 'list',
message: 'Pick an ESLint preset',
when(answers) {
return answers.lint;
},
choices: [{
name: 'Airbnb (https://github.com/airbnb/javascript)',
value: 'airbnb',
short: 'Airbnb'
}, {
name: 'Standard (https://github.com/feross/standard)',
value: 'standard',
short: 'Standard'
}]
}];
return this.prompt(prompts).then((props) => {
// To access props later use this.props.someAnswer;
this.props = props;
});
}
这里,将用户输入的结果配置到 this.props
对象中,方便后续访问。
Step 2. 定义模板
yo 的核心,本质上是按需修改模板文件,一般包含三种方法:
- 直接写入文件
对于简单对象,如JSON,可以使用 initPackage 方式。即:读入模板、按配置修改对象、写入文件 - 使用模板方式,将结果写入文件
对于复杂文件,可使用 yo 提供的模板(ejs)引擎进行编写。 - 通过AST树修改文件
通过解析语法树,深入理解模板内容,一般用于修改已存在的文件内容。建议使用 esprima、cheerio 解析AST。
以ejs方式为例,编写模板(完整实例在此处):
<% if(vueFile==='standalone'){ %>
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
<% } %>
...
/* eslint-disable no-new */
new Vue({
el: '#app',
router,<% if(vueFile==='runtime'){ %>
render: h => h(App)<% } else if(vueFile==='standalone'){ %>
template: '<App/>',
components: { App }<% } %>
});
Step 3. 按需写入
获取配置后,可以正式开始将模板写入硬盘中(完整实例在此处):
writing() {
this.initPackage();
this.renderTplFile();
}
initPackage() {
let pkg = this.fs.readJSON(this.templatePath('package.json'), {});
const {
props
} = this;
pkg = _.merge(pkg, {
name: props.name,
description: props.description
});
...
this.fs.writeJSON(this.destinationPath('package.json'), pkg);
}
renderTplFile() {
let target = [
...
'src/components/Hello.vue',
];
if (this.props.unitTest) {
target = target.concat([
...
'build/webpack.test.conf.js'
]);
}
...
_.forEach(target, (file) => {
this.fs.copyTpl(
this.templatePath(file),
this.destinationPath(file),
this.props
);
});
}
yo 提供mem-fs-editor实例接口,包含一系列fs工具:
-
this.fs.read
- 读取文件 -
this.fs.readJSON
- 以JSON方式读取文件 -
this.fs.write
- 写文件 -
this.fs.writeJson
- 以JSON 方式写文件 -
this.fs.append
- 将内容已追加方式写入文件 -
this.fs.extendJSON
- 扩展JSON文件内容 -
this.fs.delete
- 删除文件
此外,还有一系列路劲及模板接口:
-
this.fs.copyTpl
- 复制模板文件,并按参数解析模板内容,写入目标文件中 -
this.templatePath
- 返回模板文件路径,即上述generator/app/templates
中的文件路径 -
this.destinationPath
- 返回目标文件路径,即执行 yo 生成模板文件的路径 -
this.registerTransformStream
- 生命钩子接口,用于转化文件内容,兼容gulp
插件
至此,我们已了解开发一个yo模板所需要的所有接口。
添加子模板
yo允许添加任意数量的子模板,只需执行:
yo generator:subgenerator [name]
以yo generator:subgenerator test
为例,生成如下文件:
├── generators
│ ├── app
│ ├── test
│ ├── templates
│ ├── dummyfile.txt
│ ├── index.js
templates、 index.js 文件的作用与上述无异,可直接开发。
试运行
可以通过如下方式,将项目加入本地generator 库:
npm link
之后,就可以执行如下命令,生成手脚架:
yo [your template name]
发布
模板开发完毕后,如需发布可执行如下命令:
npm publish
注意:
- 如果npm尚未登录,可执行
npm adduser
操作进行登录- 发布npm包必须使用
https://registry.npmjs.org/
源,切记切换。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。