如何基于 Yeoman 创建项目脚手架
如何基于 Yeoman 创建项目脚手架
编写自己的 YEOMAN 生成器
创建一个generator文档 (opens in a new tab)
首先,创建一个文件夹,在其中写入生成器。
此文件夹必须命名为生generator-name(其中name是生成器的名称)。
这很重要,因为Yeoman依靠文件系统来找到可用的生成器
{
"name": "generator-name",
"version": "0.1.0",
"description": "",
"files": [
"generators"
],
"keywords": ["yeoman-generator"],
"dependencies": {
"yeoman-generator": "^1.0.0"
}
}name属性必须以generator-为前缀。
keywords属性必须包含"yeoman-generator"回购协议必须有一个描述才能由我们的生成器页面 (opens in a new tab)索引
您应该确保将最新版本的yeoman-generator设置为依赖项。您可以通过运行:npm install --save yeoman-generator做到这一点。
files属性必须是生成器使用的文件和目录数组
好几个要注意的设置
| 属性名 | 含义 |
|---|---|
| name | 必须 generator- 前缀 因为Yeoman依靠文件系统来找到可用的生成器 |
| keywords | 必须包含"yeoman-generator"回购协议必须有一个描述才能由我们的生成器页面 (opens in a new tab)索引 |
| files | 属性必须是生成器使用的文件和目录数 |
generator 文件夹树
Yeoman的功能取决于您如何构建目录树。每个子生成器都包含在自己的文件夹中。
当您调用yo name时使用的默认生成器是app生成器。这必须包含在app/目录中。
当您调用yo name:subcommand时使用的子生成器存储在与子命令完全相同的文件夹中
扩展生成器
一旦您安装了这个结构,是时候编写实际的生成器了。
Yeoman提供了一个基本生成器,您可以扩展该生成器来实现自己的行为。这个基本生成器将添加您期望的大多数功能来缓解您的任务。
在生成器的index.js文件中,以下是扩展基本生成器的方法
var Generator = require('yeoman-generator');
module.exports = class extends Generator {};我们将扩展生成器分配给module.exports,以将其提供给生态系统。这就是我们在Node.js中导出模块 (opens in a new tab)的方式
覆盖构造函数
一些生成器方法只能在constructor函数内调用。这些特殊方法可能会进行设置重要状态控制等操作,并且可能无法在构造函数之外运行。
要覆盖生成器构造函数,请添加这样的构造函数方法
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
}
};添加您自己的功能
添加到原型中的每个方法都会在调用生成器后运行,并且通常按顺序运行。
但是,正如我们将在下一节中看到的那样,一些特殊方法名称将触发特定的运行顺序。
让我们添加一些方法:
module.exports = class extends Generator {
method1() {
this.log('method 1 just ran');
}
method2() {
this.log('method 2 just ran');
}
};当我们稍后运行生成器时,您将看到这些线路记录到控制台
运行 generator
本地调试使用 yarn link 软链
运行上下文
编写生成器时需要掌握的最重要的概念之一是方法如何运行以及处于哪个上下文中。
原型方法作为操作
直接连接到发电机原型的每个方法都被视为一项任务。每个任务都由Yeoman环境运行循环按顺序运行。
换句话说,Object.getPrototypeOf(Generator)返回的对象上的每个函数都将自动运行。
Helper 和 私有方法
现在您知道原型方法被视为一项任务,您可能想知道如何定义不会自动调用的助手或私有方法。
有三种不同的方式可以实现这一点。
用下划线作为前缀方法名称(例如_private_method)。e_method`)
class extends Generator {
method1() {
console.log('hey 1');
}
_private_method() {
console.log('private hey');
}
}使用实例方法
class extends Generator {
constructor(args, opts) {
// Calling the super constructor is important so our generator is correctly set up
super(args, opts)
this.helperMethod = function () {
console.log('won\'t be called automatically');
};
}
}扩展父生成器
class MyBase extends Generator {
helper() {
console.log('methods on the parent generator won\'t be called automatically');
}
}
module.exports = class extends MyBase {
exec() {
this.helper();
}
};运行循环
如果有一台发电机,可以按顺序运行任务。但一旦你开始一起编写发电机,这还不够。
这就是Yeoman使用运行循环的原因。
运行循环是一个优先级支持的队列系统。我们使用分组队列模块来处理运行循环。
优先级在您的代码中定义为特殊的原型方法名称。当方法名称与优先级名称相同时,运行循环会将方法推送到此特殊队列中。如果方法名称与优先级不匹配,则在默认组中推送
class extends Generator {
priorityName() {}
}
您还可以使用哈希而不是单个方法将多个方法分组,以便在队列中一起运行:
Generator.extend({
priorityName: {
method() {},
method2() {}
}
});可用的优先级是(按运行顺序):
-
初始化 initializing -您的初始化方法(检查当前项目状态、获取配置等)
-
提示 prompting -您提示用户提供选项的地方(您在哪里调用 this.prompt())
-
配置 configuring -保存配置并配置项目(创建.editorconfig文件和其他元数据文件)
-
默认 default - 如果方法名称与优先级不匹配,它将被推送到此组。
-
写入 writing -在哪里编写生成器特定的文件(路由、控制器等)
-
冲突 conflicts - 处理冲突的地方(内部使用)
-
安装 install - 运行安装的地方(npm, bower)
-
结束 end -最后打电话,清理,说再见,等等
遵循这些优先级指南,您的生成器将与他人相处得很好
异步任务
有多种方法可以暂停运行循环,直到任务异步完成工作。
最简单的方法是回报承诺。一旦承诺得到解决,循环将继续,或者它会引发异常,如果失败,它将停止。
如果您依赖的异步API不支持promise,那么您可以依赖遗留的 this.async()方式。一旦任务完成,调用 this.async() 将返回要调用的函数。
asyncTask() {
var done = this.async();
getUserEmail(function (err, name) {
done(err);
});
}如果使用错误参数调用已完成函数,则运行循环将停止并引发异常
与用户互动时
您的生成器将与最终用户进行大量交互。默认情况下,Yeoman在终端上运行,但它也支持不同工具可以提供的自定义用户界面。例如,没有什么可以阻止Yeoman生成器在编辑器或独立应用程序等图形工具中运行。
为了允许这种灵活性,Yeoman提供了一组用户界面元素抽象。作为作者,您有责任仅在与最终用户互动时使用这些抽象。使用其他方法可能会阻止您的发电机在不同的Yeoman工具中正常运行。
例如,切勿使用console.log()或process.stdout.write()来输出内容。使用它们会向不使用终端的用户隐藏输出。相反,始终依赖UI通用 this.log()方法,这是当前生成器的上下文
用户互动
Prompts
提示是生成器与用户交互的主要方式。提示模块由Inquirer.js提供,您应该参考其API以获取可用的提示选项列表。
提示方法是异步的,并返回一个promise。您需要从任务中返回promise,以便在运行下一个任务之前等待其完成。(了解有关异步任务的更多信息)
module.exports = class extends Generator {
async prompting() {
const answers = await this.prompt([
{
type: "input",
name: "name",
message: "Your project name",
default: this.appname // Default to current folder name
},
{
type: "confirm",
name: "cool",
message: "Would you like to enable the Cool feature?"
}
]);
this.log("app name", answers.name);
this.log("cool feature", answers.cool);
}
};在稍后阶段使用用户答案
一个非常常见的场景是在稍后阶段使用用户答案,例如在写作队列中。通过将它们添加到此上下文中,可以轻松实现这一点:
module.exports = class extends Generator {
async prompting() {
this.answers = await this.prompt([
{
type: "confirm",
name: "cool",
message: "Would you like to enable the Cool feature?"
}
]);
}
writing() {
this.log("cool feature", this.answers.cool); // user answer `cool` used
}
};记住用户偏好
用户每次运行您的生成器时,都可以对某些问题提供相同的输入。对于这些问题,您可能希望记住用户之前回答的内容,并使用该答案作为新的 default
Yeoman通过添加 stroe 属性来质疑对象来扩展Inquirer.js API。此属性允许您指定用户提供的答案将来应用作默认答案。这可以按以下方式进行:
this.prompt({
type: "input",
name: "username",
message: "What's your GitHub username",
store: true
});yeoman 方法文档 (opens in a new tab)
参数 `this.argument
参数直接从命令行传递
yo webapp my-project为了通知系统我们期望一个参数,我们使用 this.argument() 方法 此方法接受名称(字符串)和可选的选项散列。
然后,名称参数将可用为: this.options[name]
此方法必须在构造函数方法内调用。否则,当用户使用帮助选项调用您的生成器时,Yeoman将无法输出相关的帮助信息:例如yo webapp --help
The name argument will then be available as: this.options[name].
The options hash accepts multiple key-value pairs:
descDescription for the argumentrequiredBoolean whether it is requiredtypeString, Number, Array (can also be a custom function receiving the raw string value and parsing it)defaultDefault value for this argument
module.exports = class extends Generator {
// note: arguments and options should be defined in the constructor.
constructor(args, opts) {
super(args, opts);
// This makes `appname` a required argument.
this.argument("appname", { type: String, required: true });
// And you can then access it later; e.g.
this.log(this.options.appname);
}
};类型数组的参数将包含传递给生成器的所有剩余参数
Options this.option()
选项看起来很像参数,但它们是写成命令行标志的 -- 中横线
yo webapp --coffeemodule.exports = class extends Generator {
// note: arguments and options should be defined in the constructor.
constructor(args, opts) {
super(args, opts);
// This method adds support for a `--coffee` flag
this.option("coffee");
// And you can then access it later; e.g.
this.scriptSuffix = this.options.coffee ? ".coffee" : ".js";
}
};输出信息 this.log
输出信息由 this.log 模块处理
您将使用的主要方法是简单的 this.log(例如 this.log('嘿!欢迎来到我很棒的发电机'))。它需要一个字符串并将其输出给用户;基本上,当它在终端会话中使用时,它会模仿console.log()。你可以这样使用它:
module.exports = class extends Generator {
myAction() {
this.log("Something has gone wrong!");
}
};可组合性
组合性是把小的部分组合在一起成为一个大的东西的一种方式。可说是像 Voltron® (opens in a new tab)
Yeoman 建立在共同的基础上为 generator 提供了多种方式。重写相同的功能是没有意义的,所以一个API提供给其他 generator 内的 generator 使用
generator.composeWith()
composewith方法允许 generator 与另一个 generator 并行(或者 subgenerator)。这样,它可以使用其他 generator 的功能,而不是必须自己做这一切。
在组合时,不要忘记运行上下文和运行循环 (opens in a new tab)。在一个给定的优先级组执行时,所有组成的 generator 将执行该组的功能。之后,这将重复为下一组。generator 之间执行相同的命令,叫composewith ,请看执行例子 (opens in a new tab)
API
composeWith 需要三个参数。
-
namespace- 一个 generator 组合的字符串声明的命名空间。默认的匹配 generator 安装在最终用户的系统上。使用peerDependencies(opens in a new tab) 去安装需要的 generator。 -
options- 包含options的对象和(或者)args数组。在运行时调用的 generator 将收到这些。 -
settings- 用于声明成分设置的对象。generator 在确定其他 generator 如何运行时使用这些。settings.local- 定义所请求 generator 的路径的字符串。它允许使用 sub-generators。它还允许使用一个特定版本的 generator。这样做,在package.json里的dependencies部分 (opens in a new tab)声明它。然后引用 generator 的路径,通常是node_modules/generator-name。settings.link- 一个字符串,可能是weak(default),或strong。
当这组合为用户初始化一个
weak链接时将无法运行。一个strong链接将始终运行。一个
weak链接与 generator的核心功能无关,像后端框架或CSS预处理器。一个strong链接是需要由操作发生。一个例子是通过构建一个_路由_ generator 和一个模型 generator 的脚手架_模块_
当用peerDependencies组成 generator
this.composeWith('backbone:route', { options: {
rjs: true
}});当用dependencies组成 generator
this.composeWith('backbone:route', {}, {
local: require.resolve('generator-bootstrap')
});
require.resolve()从Node.js提供模块那里将路径返回
暂时用不到这些高级属性
dependencies 或者 peerDependencies
npm 允许这三种类型的 dependencies:
-
dependencies被安装到本地的 generator。它是控制使用的依赖关系的版本的最佳选择。这是首选项。 -
peerDependencies被安装到 generator 的旁边,作为同级 (***** 底部见注)。如果generator-backbone定义generator-gruntfile作为一个同级的依赖,这个文件树将如下面这样:├───generator-backbone/ └───generator-gruntfile/ -
devDependencies用于测试和开发工具。这是不需要的在这里
管理依赖
一旦你运行你的 generator,你需要经常运行 npm (opens in a new tab) 和 Bower (opens in a new tab) 去安装 generator 需要的依赖。
由于这些任务是非常频繁的,Yeoman 已经把它抽象掉。我们还将介绍如何通过其他工具启动安装。
需要注意的是 Yeoman 提供的安装助手会自动安装为install的队列,作为一部分运行一次。如果你需要在它之后运行一些其它的东西,使用 end 队列
npm
你只需要运行generator.npmInstall()来启动一个npm安装。Yeoman将确保npm install命令只运行一次,即使它是由多个 generators 多次调用运行。
比如你想安装 lodash 作为开发依赖
generators.Base.extend({
installingLodash: function() {
this.npmInstall(['lodash'], { 'saveDev': true });
}
});文件系统交互
位置上下文和路径
Yeoman文件实用程序基于这样一个想法,您总是在磁盘上有两个位置上下文。这些上下文是您的生成器最有可能读取和写入的文件夹
目的地上下文
第一个上下文就是_目标上下文_。这个目标文件夹将由 Yeoman 脚手架搭建的一个新运用。
这就是你的项目文件夹,它将写入大量由脚手架生成的文件
你可以使用generator.destinationRoot() 得到目标路径,或者使用generator.destinationPath('sub/path')加入路径
// Given destination root is ~/projects
generators.Base.extend({
paths: function () {
this.destinationRoot();
// returns '~/projects'
this.destinationPath('index.js');
// returns '~/projects/index.js'
}
});模板上下文
模板上下文是你存储模板文件的文件夹。它通常是你读和复制的文件夹
模板上下文默认被定义为./templates/。你可以使用generator.sourceRoot('new/template/path')去重写这个默认值
generators.Base.extend({
paths: function () {
this.sourceRoot();
// returns './templates'
this.templatePath('index.js');
// returns './templates/index.js'
}
});内存中的文件系统
Yeoman 是非常小心的,当要去重写用户文件时。基本上,在一个预先存在的文件上发生的每一次写入都会经历一个冲突解决过程。这个过程要求用户验证每一个重写内容的文件的写入。
这种行为可以防止坏的惊喜,并限制了错误的风险。另一方面,这意味着每一个文件都是异步写入磁盘的。
文件工具
Generators 在this.fs暴露了所有的文件的方法,这是一个实例,mem-fs editor (opens in a new tab) - 确保为所有可获得的方法选择模块文件 (opens in a new tab)。
值得注意的是,通过
this.fs暴露commit,你不应该在你的generator去调用它。Yeoman 在运行循环的冲突阶段结束后,在内部调用它
举例:复制一个模板文件
这里有一个例子,我们希望复制和处理一个模板文件。
./templates/index.html 的内容是
<html>
<head>
<title><%= title %></title>
</head>
</html>然后,我们将使用copytpl (opens in a new tab)方法去复制作为模板的处理中的文件。copyTpl使用的是ejs 模板引擎 (opens in a new tab)
generators.Base.extend({
writing: function () {
this.fs.copyTpl(
this.templatePath('index.html'),
this.destinationPath('public/index.html'),
{ title: 'Templating with Yeoman' }
);
}
});通过流转换输出文件
该generator系统允许你在每一个文件上应用自定义筛选器去写入文件。自动美化文件,规范空格,等等,是完全可能的。
Yeoman每一次处理,我们将把每一个修改过的文件都写到磁盘上。这个过程是通过一个vinyl (opens in a new tab)对象流(就像gulp (opens in a new tab))。一些generator作者可能会注册一个transformStream去修改文件路径,和内容。
通过registerTransformStream()方法,注册一个新的修改器。这儿有个例子
var beautify = require('gulp-beautify');
this.registerTransformStream(beautify({indentSize: 2 }));请注意,任何类型的每一个文件都将通过该流。确保任何变换流将通过不支持的文件。像gulp-if (opens in a new tab) 或 gulp-filter (opens in a new tab)工具将帮助过滤不合法类型,并且将它们排出。
你基本上可以使用一些gulp插件与Yeoman变换流去处理在写入阶段中生成的文件
更新现有文件的内容
更新预先存在的文件不总是一个简单的任务。最可靠的方法是这样做,为了解析AST(抽象语法树 (opens in a new tab))文件,并对其进行编辑。这种解决方案的主要问题是,编辑AST可以是冗长,有点难以把握。
一些流行的AST分析器:
- Cheerio (opens in a new tab) 解析 HTML.
- Esprima (opens in a new tab) 解析 JavaScript - 你可能对AST-Query (opens in a new tab)感兴趣,其提供较低级别的API来编辑Esprima语法树。
- JSON 文件,你可能使用原生的
JSON对象方法 (opens in a new tab).
用正则表达式去解析一个代码文件是一种危险的方法,这样做之前,你应该阅读CS人类学答案 (opens in a new tab),并掌握正则表达式解析的缺陷。如果你选择使用正则表达式编辑已有的文件,而不是AST树,请小心,并提供完整的单元测试。- Please please,不要打断你的用户的代码
创建 Gruntfile
写入一个文件通常是一个简单的任务:编写一个字符串,并使用文件系统API (opens in a new tab)将其写入到输出文件中。然而,当遇到不同时,问题将会发生,(希望可组合的 (opens in a new tab))generators必须写入到相同的文件。每次写动作都会出现冲突提示,这不是一个终端用户好的体验。
这就是为什么Yeoman包含了最常用的编辑过的文件,Gruntfile.js只是个假象。接下来,介绍Yeoman Gruntfile编辑器的API
Gruntfile 编辑器 API
在generator上下文中有一个新的对象,this.gruntfile对象,它现在是可以访问的。这个对象是一个由gruntfile-editor (opens in a new tab)模块提供的GruntfileEditor实例。
作为一个快速的例子,你可能会用这种方式
module.exports = generators.Base.extend({
writing: function () {
this.gruntfile.insertConfig("compass", "{ watch: { watch: true } }");
}
});一个非常常见的场景是在提示阶段存储用户答案,并使用它们来模板
class extends Generator {
async prompting() {
this.answers = await this.prompt([{
type : 'input',
name : 'title',
message : 'Your project title',
}]);
}
writing() {
this.fs.copyTpl(
this.templatePath('index.html'),
this.destinationPath('public/index.html'),
{ title: this.answers.title } // user answer `title` used
);
}
}通过流转换输出文件
生成器系统允许您对每个文件写入应用自定义过滤器。自动美化文件、规范化空格等是完全可能的。
根据Yeoman进程,我们将把每个修改后的文件写入磁盘。这个过程通过乙烯基对象流传递(就像吞下一样)。任何生成器作者都可以注册transformStream来修改文件路径和/或内容。
注册新修饰符是通过registerTransformStream()方法完成的。这里有一个例子:
var beautify = require("gulp-beautify");
this.registerTransformStream(beautify({ indent_size: 2 }));请注意,任何类型的每个文件都将通过此流传递。确保任何转换流都会通过它不支持的文件。Gulp-if或gulp-filter等工具将帮助过滤无效类型并传递它们。
您基本上可以使用任何带有Yeoman转换流的gulp插件在写入阶段处理生成的文件。
提示:更新现有文件的内容
更新先前存在的文件并不总是一项简单的任务。最可靠的方法是解析文件AST(抽象语法树)并对其进行编辑。此解决方案的主要问题是,编辑AST可能冗长,有点难以理解。
一些流行的AST解析器是:
-
Cheerio解析HTML。
-
Esprima用于解析JavaScript-您可能对AST-Query感兴趣,AST-Query提供了较低级别的API来编辑Esprima语法树。
-
对于JSON文件,您可以使用原生JSON对象方法。
-
Gruntfile编辑器可以动态修改Gruntfile。
使用RegEx解析代码文件是一个危险的路径,在此之前,您应该阅读此CS人类学答案,并掌握RegEx解析的缺陷。如果您确实选择使用RegEx而不是AST树编辑现有文件,请小心并提供完整的单元测试。-请不要破坏用户的代码
配置管理
存储用户配置选项并在子生成器之间共享它们是一项常见的任务。例如,共享首选项很常见,如语言(用户是否使用CoffeeScript?)、样式选项(缩进空格或选项卡)等。
这些配置可以通过Yeoman Storage API存储在.yo-rc.json文件中。此API可通过 generator.config 对象访问。
以下是您将使用的一些常见方法。
Methods
this.config.save()
此方法将把配置写入.yo-rc.json文件。如果文件还不存在,保存方法将创建它。
.Yo-rc.json文件还决定了项目的根。因此,即使您没有将存储用于任何内容,始终在:app生成器内调用保存被认为是最佳实践。
另请注意,每次设置配置选项时,都会自动调用保存方法。所以你通常不需要明确地称呼它
this.config.set()
设置要么接受密钥和关联值,要么接受多个键/值的对象哈希值。
请注意,值必须是JSON序列化的(字符串、数字或非递归对象)。
this.config.get()
this.config.getAll()
this.config.delete()
this.config.defaults()
.yo-rc.json 结构
.yo-rc.json文件是一个JSON文件,其中存储来自多个生成器的配置对象。每个生成器配置都经过命名空间,以确保生成器之间不会发生命名冲突。
这也意味着每个生成器配置都是沙盒的,只能在子生成器之间共享。您无法使用存储API在不同生成器之间共享配置。在调用期间使用选项和参数在不同生成器之间共享数据。
以下是.yo-rc.json文件内部的样子
{
"generator-backbone": {
"requirejs": true,
"coffee": true
},
"generator-gruntfile": {
"compass": false
}
}对于您的最终用户来说,这个结构非常全面。这意味着,您可能希望将高级配置存储在此文件中,并在对每个选项使用提示没有意义时要求高级用户直接编辑文件
单元测试
以下示例假设您在BDD模式下使用 Mocha (opens in a new tab)。全局概念应该很容易应用于您选择的单元测试框架
组织您的测试
保持测试简单且易于编辑很重要。
通常,组织测试的最佳方法是将每个生成器和子生成器分离到自己的描述块中。然后,为您的生成器接受的每个选项添加一个描述块。然后,对每个断言(或相关断言)使用它块。
在代码中,您最终应该得到一个类似的结构
describe('backbone:app', function () {
it('generates a project with require.js', function () {
// assert the file exist
// assert the file uses AMD definition
});
it('generates a project with webpack');
});Test helpers
Yeoman provide test helpers methods. They’re contained inside the yeoman-test package
var helpers = require('yeoman-test');You can check the full helpers API here (opens in a new tab).
The most useful method when unit testing a generator is helpers.run(). This method will return a RunContext (opens in a new tab) instance on which you can call method to setup a directory, mock prompt, mock arguments, etc
var path = require('path');
it('generate a project', function () {
// The object returned acts like a promise, so return it to wait until the process is done
return helpers.run(path.join(__dirname, '../app'))
.withOptions({ foo: 'bar' }) // Mock options passed in
.withArguments(['name-x']) // Mock the arguments
.withPrompts({ coffee: false }) // Mock the prompt answers
.withLocalConfig({ lang: 'en' }) // Mock the local config
.then(function() {
// assert something about the generator
});
})Sometimes you may want to construct(建造) a test scenario(场景) for the generator to run with existing contents in the target directory. In which case, you could invoke inTmpDir() with a callback function, like so
var path = require('path');
var fs = require('fs-extra');
helpers.run(path.join(__dirname, '../app'))
.inTmpDir(function (dir) {
// `dir` is the path to the new temporary directory
fs.copySync(path.join(__dirname, '../templates/common'), dir)
})
.withPrompts({ coffee: false })
.then(function () {
assert.file('common/file.txt');
});You can also perform(执行) asynchronous task in your callback:
var path = require('path');
var fs = require('fs-extra');
helpers.run(path.join(__dirname, '../app'))
.inTmpDir(function (dir) {
var done = this.async(); // `this` is the RunContext object.
fs.copy(path.join(__dirname, '../templates/common'), dir, done);
})
.withPrompts({ coffee: false });The run Promise will resolve with the directory that the generator was run in. This can be useful if you want to use a temporary(临时的) directory that the generator was run in:
helpers.run(path.join(__dirname, '../app'))
.inTmpDir(function (dir) {
var done = this.async(); // `this` is the RunContext object.
fs.copy(path.join(__dirname, '../templates/common'), dir, done);
})
.withPrompts({ coffee: false })
.then(function (dir) {
// assert something about the stuff in `dir`
});If your generator calls composeWith(), you may want to mock those dependent(依靠的) generators. Using #withGenerators(), pass in array of arrays that use #createDummyGenerator() as the first item and a namespace for the mocked generator as a second item:
If you hate(不喜欢) promises, you can use the 'ready', 'error', and 'end' Events emitted:
helpers.run(path.join(__dirname, '../app'))
.on('error', function (error) {
console.log('Oh Noes!', error);
})
.on('ready', function (generator) {
// This is called right before `generator.run()` is called
})
.on('end', done);You can also run a generator importing it as a module. This is usefull if the source code of your generator is transpiled(编译).
You will need to provide the following settings to run:
resolved: Path to the generator, e.g.../src/app/index.jsnamespace: Namespace of the generator, e.g.mygenerator:app
var MyGenerator = require('../src/app');
helpers.run(MyGenerator, {
resolved: require.resolve(__dirname, '../src/app/index.js'),
namespace: 'mygenerator:app'
});Assertions helpers(断言助手)
Yeoman extends the native assert module (opens in a new tab) with generator related assertions helpers. You can see the full list of assertions helpers on the yeoman-assert repository (opens in a new tab).
Require the assertion helpers
var assert = require('yeoman-assert');Assert files exists(断言文件存在)
assert.file(['Gruntfile.js', 'app/router.js', 'app/views/main.js']);assert.noFile() assert the contrary(相反)
Assert a file content(断言文件内容)
assert.fileContent('controllers/user.js', /App\.UserController = Ember\.ObjectController\.extend/);assert.noFileContent() assert the contrary
Debugging Generators
To debug a generator, you can pass Node.js debug flags(标志) by running it like this:
## OS X / Linux / Windows
npx --node-arg=--inspect yo <generator> [arguments]You can then debug your generator using the Chrome Devtools or your preferred(优先选取的) IDE. See Node Debugging Guide (opens in a new tab) for more info.
Yeoman generators also provide a debug mode to log relevant(有关的) lifecycle information. You can activate(启动) it by setting the DEBUG environment variable to the desired scope (the scope of the generator system is yeoman:generator).
## OS X / Linux
DEBUG=yeoman:generator
## Windows
set DEBUG=yeoman:generatorIntegrating(集成) Yeoman in other tools
Every time you run a generator, you’re actually(实际上) using the yeoman-environment. The environment is a base system that is decoupled(解耦) from any UI component and can be abstracted(抽象) away by any tool. When you run yo, you’re basically(基本上) just running a terminal UI façade on top of the core Yeoman environment.