yeoman 生成器中的 ComposeWith 不发出结束事件,因此不驱动 'on' 方法
ComposeWith in yeoman generator not emitting an end event and thus not driving 'on' method
背景
我正在为 Angular SPA(单页应用程序)创建脚手架生成器。它将依赖于标准 angular 生成器 ('yo angular') 设置的环境,并且还依赖于标准 angular 子生成器来生成应用程序所需的一些额外服务和控制器。换句话说,我正在“装饰”一个基本的 angular 应用程序。
如果用户之前安装了 angular 应用程序,生成器将正常工作(我查找标记文件并在我的代码中设置布尔值 'angularAppFound')。但是,我希望它也是 'one-stop',因为如果他们还没有设置 angular 应用程序,我的生成器会在我之前为他们调用 angular 生成器在单个 运行.
中安装我的额外 angular 工件
显然,如果没有 angular 应用程序,我的依赖任务将无法运行。
数据
我的代码如下所示:
// need this to complete before running other task
subgeneratorsApp: function () {
if (!this.angularAppFound) {
var done = this.async();
this.log('now creating base Angular app...');
// doesn't work (does not drive .on)
//this.composeWith('angular', {args: [ this.appName ]} )
// works (drives .on)
this.invoke('angular', {args: [ this.appName ]} )
.on('end',function(){
this.log('>>>in end handler of angular base install');
done();
}.bind(this));
}
},
// additional steps to only run after full angular install
subgeneratorServices: function () {
Object.keys(this.artifacts.services).forEach( function (key, index, array) {
this.composeWith('angular:service', {args: [ this.artifacts.services[key] ]} );
}.bind(this));
},
subgeneratorControllers: function () {
Object.keys(this.artifacts.controllers).forEach( function (key, index, array) {
this.composeWith('angular:controller', {args: [ this.artifacts.controllers[key] ]} );
}.bind(this));
},
我根据经验确定,通过查看日志和结果,'composeWith' 不会驱动 .on 方法,而 'invoke' 会。
如果不驱动.on方法,不驱动done(),generator在angular base install后停止,不驱动后续步骤(因为generator认为该步骤永远不会结束).
我可以使用调用,但它已被弃用:
(!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html
问题
我读过 here and here 生成器不应该相互依赖:
When composing generators, the core idea is to keep both decoupled. They shouldn't care about the ordering, they should run in any order and output the same result.
我应该如何处理我的情况,因为订购 很重要(除了使用 'invoke')?我想不出任何其他方式来重新组织我的生成器而不牺牲 'one-stop' 处理。
我应该简单地说用户必须在单独的步骤中安装 angular 并且不允许 'one stop' 处理吗?
'composeWith' 不会发出 'end' 事件是设计使然吗?
如果没有,您是否建议我打开错误报告,或者是否有其他方法可以做到这一点(未弃用)?
非常感谢。
生成器的可组合性使用 priority base run loop 排序。因此,有可能在 运行 你的之前等待另一个生成器完成。
虽然这里棘手的部分是,一旦触发结束事件,生成器就完成了 运行。它不会安排任何未来的任务 - end
事件意味着一切都已完成,是时候结束了。公平地说,你不应该需要结束事件。它仍在 Yeoman 中,只是为了向后兼容。
在您的情况下,您需要两个发电机。 app
生成器(根生成器)和您的自定义功能生成器。然后将它们组合在一起:
所以在 generator/app/index.js
中,您将按如下方式组织代码:
writing: {
this.composeWith('angular:app');
},
end: function () {
this.composeWith('my:subgen');
}
生成器-angular 庞大且相当复杂。它仍然基于 Yeoman 的旧版本,这意味着将它用作基础生成器可能更难。我相信项目的所有者会很乐意得到一些帮助来升级和改进作曲故事。 - 如果您想知道更好的开发者用户体验对于 Yeoman 合成来说是什么样的,请查看 generator-node 被设计为合成的基础生成器。
概览:
10 月 13 日来自 Simon Boudrias 的 post 是公认的答案。他为我提供了足够的理论背景来掌握情况。我提供这个额外的答案是为了提供一些额外的实用信息。
分析
我首先要理解的是 "base" 生成器(不调用其他生成器的生成器)和 "meta" 生成器(调用其他生成器的生成器)之间的区别。注意:当我提到 "generators" 时,我并不是指子生成器:基础生成器调用子生成器是可以的。基础生成器永远不应该调用 'composeWith'。它应该只做一件事。我的基本问题是我试图从基础生成器调用 composeWith。我需要创建另一个生成器,一个名为 composeWith 的元生成器。
注意:基本生成器和元生成器之间的区别是合乎逻辑的。就 Yeoman 而言,它们都是 "generators".
我还发现区分 dependent 生成器(那些需要先前生成器的预先存在的环境)和 independent 生成器(独立的)
我还意识到我将基础生成器与 angular 生成器紧密耦合。我决定重构我的设计,让我的基础生成器为我可能想要安装的每个不同类型的平台调用子生成器。例如,我会有一个用于 angular 的子生成器,然后是另一个用于 webapp 的子生成器。通过这样做,我的基础生成器的所有通用组件都在一个生成器中,而子环境特定的东西将在子生成器中。
然后我将为每个目标平台创建一个元生成器,其中 'angular-meta' 将调用(通过 composeWith)angular 生成器,然后是我的基础生成器,后者将驱动 'angular' 子生成器,'webapp-meta' 生成器会调用 'webapp',然后是我的基础生成器,它会驱动 'webapp' 子生成器'。
简而言之,它实现了更好的设计。
问题:
但是,如 post 中所述,angular 基础生成器并不 'composeWith' 友好。事实上,它是使用 yeoman-generator 0.16 构建的,文档中明确指出 composeWith 需要 yeoman-generator 0.17 或更高版本。
每当我尝试用 composeWith 调用 angular 时,它 运行 异步。也就是说,它会立即 return,然后在 angular 安装之前,在 'end:' 下启动我的基本安装程序。
我按照建议用 node:app 进行了测试。 did 正常工作:它 运行 同步,并且仅在完成后才启动我的基本安装程序。因此,这证明 composeWith 只能根据具体情况工作,具体取决于基础安装程序在可组合性方面的设计。
我确实在本地尝试 "building" 一个使用 v 0.17 及更高版本的 angular 生成器。下划线函数存在一些问题,但即使在编译之后它仍然无法正常工作。所以即使有了元生成器,我还是回到了原来的问题。
更糟糕的是,我意识到我的 'invoke' 解决方法,同时至少触发 'end',是在安装基本应用程序文件之后,但在库的 npm 安装之前执行的已经完成。因此,angular 生成器的 npm 安装干扰了我的基本生成器的提示。
解决方案:
我基本上只是在我的元安装程序中近似 'composeWith' 功能,通过交互式进程启动生成器。换句话说,模拟如果您手动 运行 一个生成器接着另一个生成器会做什么。在 "big boy" 生成器可以可靠地组合之前,我想不出任何其他方法来实现这一点。 CLI 界面肯定不如 API 界面好,因为将参数传递给子生成器的唯一方法是通过命令行参数。
这是我的元生成器现在的样子:
'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
module.exports = yeoman.generators.Base.extend({
initializing: function () {
if( this.fs.exists( this.destinationPath('app/scripts/app.js')) || this.options.skipBaseAppInstall) {
this.log("Angular base app found. Skipping angular install.\n");
this.angularAppFound = true;
}
else {
this.log("angular base app not found");
this.angularAppFound = false;
}
},
prompting: function () {
var done = this.async();
// Have Yeoman greet the user.
this.log(yosay(
'Welcome to the epic ' + chalk.red('angular-vr (meta)') + ' 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: {
app: function () {
this.fs.copy(
this.templatePath('_package.json'),
this.destinationPath('package.json')
);
},
},
install: function () {
this.installDependencies();
},
end: function () {
var spawn = require('child_process').spawn;
var tty = require('tty');
var async = require('async');
var shell = function(cmd, opts, callback) {
var p;
process.stdin.pause();
process.stdin.setRawMode(false);
p = spawn(cmd, opts, {
stdio: [0, 1, 2]
});
return p.on('exit', function() {
process.stdin.setRawMode(true);
process.stdin.resume();
return callback();
});
};
async.series([
function(cb) {
if (!this.angularAppFound) {
shell('yo', ['angular'], function() {
cb(null,'a');
});
}
else {
cb(null, 'a');
}
}.bind(this),
function(cb) {
shell('yo', ['angular-vr-old'], function() {
cb(null,'b');
});
}
],
function(err, results){
// final callback code
return process.exit();
}
);
}
});
这个元安装程序依赖于正在安装的异步,所以我在 package.json 中的依赖项看起来像:
"dependencies": {
"async": "^1.4.2",
"chalk": "^1.0.0",
"yeoman-generator": "^0.19.0",
"yosay": "^1.0.2"
},
full project 在 github 下可用。
背景
我正在为 Angular SPA(单页应用程序)创建脚手架生成器。它将依赖于标准 angular 生成器 ('yo angular') 设置的环境,并且还依赖于标准 angular 子生成器来生成应用程序所需的一些额外服务和控制器。换句话说,我正在“装饰”一个基本的 angular 应用程序。
如果用户之前安装了 angular 应用程序,生成器将正常工作(我查找标记文件并在我的代码中设置布尔值 'angularAppFound')。但是,我希望它也是 'one-stop',因为如果他们还没有设置 angular 应用程序,我的生成器会在我之前为他们调用 angular 生成器在单个 运行.
中安装我的额外 angular 工件显然,如果没有 angular 应用程序,我的依赖任务将无法运行。
数据
我的代码如下所示:
// need this to complete before running other task
subgeneratorsApp: function () {
if (!this.angularAppFound) {
var done = this.async();
this.log('now creating base Angular app...');
// doesn't work (does not drive .on)
//this.composeWith('angular', {args: [ this.appName ]} )
// works (drives .on)
this.invoke('angular', {args: [ this.appName ]} )
.on('end',function(){
this.log('>>>in end handler of angular base install');
done();
}.bind(this));
}
},
// additional steps to only run after full angular install
subgeneratorServices: function () {
Object.keys(this.artifacts.services).forEach( function (key, index, array) {
this.composeWith('angular:service', {args: [ this.artifacts.services[key] ]} );
}.bind(this));
},
subgeneratorControllers: function () {
Object.keys(this.artifacts.controllers).forEach( function (key, index, array) {
this.composeWith('angular:controller', {args: [ this.artifacts.controllers[key] ]} );
}.bind(this));
},
我根据经验确定,通过查看日志和结果,'composeWith' 不会驱动 .on 方法,而 'invoke' 会。
如果不驱动.on方法,不驱动done(),generator在angular base install后停止,不驱动后续步骤(因为generator认为该步骤永远不会结束).
我可以使用调用,但它已被弃用:
(!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html
问题
我读过 here and here 生成器不应该相互依赖:
When composing generators, the core idea is to keep both decoupled. They shouldn't care about the ordering, they should run in any order and output the same result.
我应该如何处理我的情况,因为订购 很重要(除了使用 'invoke')?我想不出任何其他方式来重新组织我的生成器而不牺牲 'one-stop' 处理。
我应该简单地说用户必须在单独的步骤中安装 angular 并且不允许 'one stop' 处理吗?
'composeWith' 不会发出 'end' 事件是设计使然吗?
如果没有,您是否建议我打开错误报告,或者是否有其他方法可以做到这一点(未弃用)?
非常感谢。
生成器的可组合性使用 priority base run loop 排序。因此,有可能在 运行 你的之前等待另一个生成器完成。
虽然这里棘手的部分是,一旦触发结束事件,生成器就完成了 运行。它不会安排任何未来的任务 - end
事件意味着一切都已完成,是时候结束了。公平地说,你不应该需要结束事件。它仍在 Yeoman 中,只是为了向后兼容。
在您的情况下,您需要两个发电机。 app
生成器(根生成器)和您的自定义功能生成器。然后将它们组合在一起:
所以在 generator/app/index.js
中,您将按如下方式组织代码:
writing: {
this.composeWith('angular:app');
},
end: function () {
this.composeWith('my:subgen');
}
生成器-angular 庞大且相当复杂。它仍然基于 Yeoman 的旧版本,这意味着将它用作基础生成器可能更难。我相信项目的所有者会很乐意得到一些帮助来升级和改进作曲故事。 - 如果您想知道更好的开发者用户体验对于 Yeoman 合成来说是什么样的,请查看 generator-node 被设计为合成的基础生成器。
概览:
10 月 13 日来自 Simon Boudrias 的 post 是公认的答案。他为我提供了足够的理论背景来掌握情况。我提供这个额外的答案是为了提供一些额外的实用信息。
分析
我首先要理解的是 "base" 生成器(不调用其他生成器的生成器)和 "meta" 生成器(调用其他生成器的生成器)之间的区别。注意:当我提到 "generators" 时,我并不是指子生成器:基础生成器调用子生成器是可以的。基础生成器永远不应该调用 'composeWith'。它应该只做一件事。我的基本问题是我试图从基础生成器调用 composeWith。我需要创建另一个生成器,一个名为 composeWith 的元生成器。
注意:基本生成器和元生成器之间的区别是合乎逻辑的。就 Yeoman 而言,它们都是 "generators".
我还发现区分 dependent 生成器(那些需要先前生成器的预先存在的环境)和 independent 生成器(独立的)
我还意识到我将基础生成器与 angular 生成器紧密耦合。我决定重构我的设计,让我的基础生成器为我可能想要安装的每个不同类型的平台调用子生成器。例如,我会有一个用于 angular 的子生成器,然后是另一个用于 webapp 的子生成器。通过这样做,我的基础生成器的所有通用组件都在一个生成器中,而子环境特定的东西将在子生成器中。
然后我将为每个目标平台创建一个元生成器,其中 'angular-meta' 将调用(通过 composeWith)angular 生成器,然后是我的基础生成器,后者将驱动 'angular' 子生成器,'webapp-meta' 生成器会调用 'webapp',然后是我的基础生成器,它会驱动 'webapp' 子生成器'。
简而言之,它实现了更好的设计。
问题:
但是,如 post 中所述,angular 基础生成器并不 'composeWith' 友好。事实上,它是使用 yeoman-generator 0.16 构建的,文档中明确指出 composeWith 需要 yeoman-generator 0.17 或更高版本。
每当我尝试用 composeWith 调用 angular 时,它 运行 异步。也就是说,它会立即 return,然后在 angular 安装之前,在 'end:' 下启动我的基本安装程序。
我按照建议用 node:app 进行了测试。 did 正常工作:它 运行 同步,并且仅在完成后才启动我的基本安装程序。因此,这证明 composeWith 只能根据具体情况工作,具体取决于基础安装程序在可组合性方面的设计。
我确实在本地尝试 "building" 一个使用 v 0.17 及更高版本的 angular 生成器。下划线函数存在一些问题,但即使在编译之后它仍然无法正常工作。所以即使有了元生成器,我还是回到了原来的问题。
更糟糕的是,我意识到我的 'invoke' 解决方法,同时至少触发 'end',是在安装基本应用程序文件之后,但在库的 npm 安装之前执行的已经完成。因此,angular 生成器的 npm 安装干扰了我的基本生成器的提示。
解决方案:
我基本上只是在我的元安装程序中近似 'composeWith' 功能,通过交互式进程启动生成器。换句话说,模拟如果您手动 运行 一个生成器接着另一个生成器会做什么。在 "big boy" 生成器可以可靠地组合之前,我想不出任何其他方法来实现这一点。 CLI 界面肯定不如 API 界面好,因为将参数传递给子生成器的唯一方法是通过命令行参数。
这是我的元生成器现在的样子:
'use strict';
var yeoman = require('yeoman-generator');
var chalk = require('chalk');
var yosay = require('yosay');
module.exports = yeoman.generators.Base.extend({
initializing: function () {
if( this.fs.exists( this.destinationPath('app/scripts/app.js')) || this.options.skipBaseAppInstall) {
this.log("Angular base app found. Skipping angular install.\n");
this.angularAppFound = true;
}
else {
this.log("angular base app not found");
this.angularAppFound = false;
}
},
prompting: function () {
var done = this.async();
// Have Yeoman greet the user.
this.log(yosay(
'Welcome to the epic ' + chalk.red('angular-vr (meta)') + ' 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: {
app: function () {
this.fs.copy(
this.templatePath('_package.json'),
this.destinationPath('package.json')
);
},
},
install: function () {
this.installDependencies();
},
end: function () {
var spawn = require('child_process').spawn;
var tty = require('tty');
var async = require('async');
var shell = function(cmd, opts, callback) {
var p;
process.stdin.pause();
process.stdin.setRawMode(false);
p = spawn(cmd, opts, {
stdio: [0, 1, 2]
});
return p.on('exit', function() {
process.stdin.setRawMode(true);
process.stdin.resume();
return callback();
});
};
async.series([
function(cb) {
if (!this.angularAppFound) {
shell('yo', ['angular'], function() {
cb(null,'a');
});
}
else {
cb(null, 'a');
}
}.bind(this),
function(cb) {
shell('yo', ['angular-vr-old'], function() {
cb(null,'b');
});
}
],
function(err, results){
// final callback code
return process.exit();
}
);
}
});
这个元安装程序依赖于正在安装的异步,所以我在 package.json 中的依赖项看起来像:
"dependencies": {
"async": "^1.4.2",
"chalk": "^1.0.0",
"yeoman-generator": "^0.19.0",
"yosay": "^1.0.2"
},
full project 在 github 下可用。