我正在为Angular SPA(单页面应用程序)创建一个脚手架生成器。它将依赖于标准角度生成器('yo angular')设置的环境,并且还依赖于标准角度子生成器来生成应用程序所需的一些额外服务和控制器。换句话说,我正在“装饰”一个基本的角度应用程序。
如果用户之前安装了一个角度应用程序(我在我的代码中查找标记文件并设置了一个booleon'angleAppFound'),生成器将正常工作。但是,我希望它也是“一站式”,因为如果他们没有设置角度应用程序,我的生成器将为他们调用角度生成器,然后我安装我的附加角度工件在一次运行中。
显然,如果没有有角度的应用程序,我的依赖任务将无效。
我的代码如下:
// 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(),并且在角度基础安装之后生成器停止并且不驱动后续步骤(因为生成器认为步骤永远不会完成)。
我使用invoke很好,除了它已被弃用:
(!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html
在编写生成器时,核心思想是保持两者分离。他们不应该关心排序,他们应该以任何顺序运行并输出相同的结果。
我应该如何处理我的情况,因为命令 很重要(除了使用'invoke')?在不牺牲“一站式”处理的情况下,我想不出任何其他方式来重新组织我的发电机。
我应该简单地说用户必须在单独的步骤中安装角度而不允许“一站式”处理吗?
设计是否'composeWith'不会发出'结束'事件?
如果没有,您是否建议我打开错误报告,或者是否有其他方法(不推荐使用)?
非常感谢。
答案 0 :(得分:4)
使用priority base run loop对发电机可组合性进行排序。因此,有可能在运行之前等待另一台发电机完成。
虽然这里棘手的部分是,一旦触发了结束事件,发电机就完成了运行。它不会安排任何未来的任务 - $cfg['servers'][$i]['password'] = '1234';
事件意味着一切都已完成,现在是时候结束了。公平地说,你不应该需要结束活动。为了向后兼容,它仍然在Yeoman中。
在你的情况下,你想要两台发电机。 end
生成器(根目录)和您的自定义功能生成器。然后你把它们组合在一起:
因此,在app
内,您可以按照以下方式组织代码:
generator/app/index.js
发电机角度巨大且相当复杂。它仍然基于旧版的Yeoman,这意味着它可能更难用作基础生成器。我确信项目的所有者会很乐意为升级和改进作文故事提供一些帮助。 - 如果你想知道一个更好的开发者用户体验对于Yeoman组合来说是什么样子,那么看一下generator-node谁被设计为组合的基础生成器。
答案 1 :(得分:1)
Simon Boudrias在10月13日的帖子是公认的答案。他为我提供了足够的理论背景来掌握情况。我提供这个额外的答案,以提供一些额外的实用信息。
我必须要了解的第一件事是“基础”生成器(不调用其他生成器的生成器)和“元”生成器(调用其他生成器的生成器)之间的区别。注意:当我提到“发电机”时,我并不意味着子发电机:基础发电机可以调用子发电机。基础生成器永远不应该调用'composeWith'。它应该只做一件事。我的基本问题是我试图从基础生成器调用composeWith。我需要创建另一个生成器,一个元生成器,它调用composeWith。
注意:base和meta生成器之间的区别是合乎逻辑的。就Yeoman而言,它们都只是“发电机”。
我还发现区分依赖生成器(需要先前生成器预先存在的环境的生成器)和独立生成器(那些是站立的生成器)很有用。 - 单独)
我也意识到我正在将我的基础发生器与角度发生器紧密耦合。我决定重新设计我的设计,让我的基础生成器为我可能想要安装的每种不同类型的平台调用子生成器。例如,我有一个用于角度的子生成器,然后是另一个用于webapp的子生成器。通过这样做,我的基础生成器的所有常见组件都在一个生成器中,而子环境特定的东西将在子生成器中。
然后我会为每个目标平台设置一个元生成器,其中'angular-meta'将调用(通过composeWith)角度生成器,然后调用我的基本生成器,然后驱动'angular'子生成器,并且'webapp-meta'生成器将调用'webapp'然后调用我的base生成器,然后它将驱动'webapp'子生成器'。
简而言之,它设计得更好。
然而,正如帖子中所提到的,角度基础生成器不是'composeWith'友好的。事实上,它是用yeoman-generator 0.16构建的,文档清楚地说明composeWith需要自己的生成器0.17或更高。
每当我尝试使用 composeWith 调用angular时,它就会异步运行。也就是说它会立即返回,然后在安装角度之前在'end:'下启动我的基本安装程序。
我按照建议使用node:app进行了测试。这个 正常工作:它同步运行,并在完成后启动我的基本安装程序。因此,这证明composeWith只能根据具体情况进行工作,具体取决于基本安装程序的可组合性设计。
我尝试使用v 0.17及更大版本在本地“构建”角度生成器。下划线函数存在一些问题,但即使在编译它之后仍然无法正常工作。因此,即使使用元生成器,我也回到了原来的问题。
更糟糕的是,我意识到我的'调用'解决方法,至少触发'结束',是在安装基本应用程序文件之后,但在完成库的npm安装之前完成的。因此角度发生器的npm安装干扰了我的基础生成器的提示。
我基本上只是在我的元安装程序中使用'composeWith'功能,通过交互式进程启动生成器。换句话说,如果手动只运行一个生成器,然后运行另一个生成器,则模仿您将要执行的操作。直到“大男孩”发电机可靠地组合,我才能想到任何其他方式来实现这一目标。 CLI界面肯定不如API接口好,因为将parms传递给子生成器的唯一方法是通过命令行参数。
这是我的元生成器现在的样子:
'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下可用。