在yeoman生成器中,不发出结束事件,因此不会驱动“on”方法

背景

我正在为Angular SPA创build脚手架生成器(单页应用程序)。 它将依赖于由标准angular度发生器(“angular度”)设置的环境,并且还依赖于标准angular度子发生器来生成应用所需的一些额外的服务和控制器。 换句话说,我正在“装饰”一个基本的angular度应用程序。

如果用户以前安装了一个angular度的应用程序(我寻找标记文件,并在我的代码中设置booleon'angularAppFound'),生成器将正常工作。 但是,我希望它也是'一站式',因为如果他们没有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()不会被驱动,并且在angular度基本安装之后停止生成器,并且不会驱动后续步骤(因为生成器认为该步骤永远不会结束)。

我很好,使用调用,除了它已被弃用:

 (!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html 

问题

我在这里和这里读到发电机不应该相互依赖:

构成生成器时,核心思想是保持解耦。 他们不应该关心sorting,他们应该以任何顺序运行,并输出相同的结果。

我应该如何处理我的情况,因为sorting重要的(除了使用“调用”)? 我想不出任何其他方式来重新组织我的发电机而不牺牲“一站式”的处理。

我是否应该简单地说用户必须在单独的步骤中安装angular度并且不允许“一站式”处理?

“composeWith”不是发出“结束”事件吗?

如果没有,你是否build议我打开一个错误报告,或者有其他的方式来做到这一点(这是不被弃用)?

非常感谢。

发电机可组合性是使用优先级基本运行循环来sorting的。 因此,在运行之前,可以等待另一台发电机完成。

虽然这里棘手的部分是,一旦结束事件触发,发电机完成运行。 它不会安排任何未来的任务 – end事件意味着一切都已经完成,现在是时候结束了。 公平地说,你不应该需要结束事件。 只有向后兼容性仍然在Yeoman。

在你的情况下,你想要两个发电机。 app生成器(根用户)和您的自定义function生成器。 然后你把它们组合在一起:

所以在generator/app/index.js ,你可以像下面这样组织你的代码:

 writing: { this.composeWith('angular:app'); }, end: function () { this.composeWith('my:subgen'); } 

发电机的angular度是巨大的,相当复杂。 它仍然是基于旧版本的Yeoman,这意味着它可能会更难使用它作为一个基地发电机。 我敢肯定,项目的业主会很乐意帮助升级和改善作曲的故事。 – 如果您想知道更好的开发UX可以看起来像Yeoman组成,请看看被devise为组合的基本生成器的生成器节点 。

概述:

西蒙·布德里亚斯(Simon Boudrias)于10/13发表的文章是被接受的答案。 他为我提供了足够的理论背景来掌握情况。 我正在提供这个额外的答案来提供一些额外的实用信息。

分析

我不得不欣赏的第一件事是“基本”发电机(不调用其他发电机的发电机)和“元”发电机(调用其他发电机的发电机)之间的区别。 注意:当我引用“生成器”时,我并不是指子生成器:基本生成器可以调用子生成器。 基础生成器不应该调用“composeWith”。 它应该只做一件事。 我的基本问题是我想从基础生成器调用composeWith。 我需要创build另一个生成器,一个元生成器,称为composeWith。

注意:基本和元生成器之间的区别是合乎逻辑的。 就Yeoman而言,他们都是简单的“发电机”。

我还发现区分从属发电机(那些需要先前发电机的预先存在的环境)和独立发电机( 独立发电机)

我也意识到我是紧密耦合我的基地发电机angular发电机。 我决定重新devise我的devise,让我的基本生成器调用我想要安装到的不同types平台的每个子生成器。 例如,我将有一个angular度的子发电机,然后另一个子发电机的web应用程序。 通过这样做,我的基本生成器的所有常见组件都在一个生成器中,并且子环境特定的东西将在子生成器中。

然后,我将为每个目标平台创build一个元生成器,其中'angular-meta'将通过composeWith调用angular生成器,然后生成我的基生成器,然后驱动“angular”子生成器, webapp-meta“生成器会调用”webapp“,然后是我的基础生成器,然后驱动”webapp“子生成器。

总而言之,这是一个更好的devise。

问题:

但是,正如在文章中提到的那样,angular度发生器不是“友好的”。 事实上,它是用自动生成器0.16构build的,文档清楚地表明,要求自动生成器0.17或以上。

每当我尝试调用angular度composeWith它asynchronous运行。 也就是说,它会立即返回,然后在安装angular之前,在“end:”下启动我的基本安装程序。

我build议使用node:app进行testing。 这确实工作正常:它同步运行,并且只有在完成后才启动我的基本安装程序。 因此,这certificate了composeWith只能根据具体情况进行工作,具体取决于基本安装程序的可configuration性。

我确实尝试在本地“build立”使用v 0.17或更大的angular发生器。 下划线函数有一些问题,但是即使在编译之后仍然不能正确工作。 所以即使有一个元生成器,我也回到了原来的问题。

更糟糕的是,我意识到我的'调用'解决方法,至less触发'结束',是在安装基本应用程序文件之后,但在库的npm安装完成之前完成的。 因此angular发生器的npm安装会干扰我的发生器的提示。

解:

我基本上只是在元安装程序中用“composeWith”function来近似,通过交互式进程启动生成器。 换句话说,如果你手动运行一个生成器,然后再运行一个生成器,则模仿你将要做的事情。 在“大男孩”生成器可靠组合之前,我想不出任何其他方式来实现这一点。 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(); } ); } }); 

这个元安装程序依赖于正在安装的asynchronous,所以我在package.json中的依赖关系如下所示:

  "dependencies": { "async": "^1.4.2", "chalk": "^1.0.0", "yeoman-generator": "^0.19.0", "yosay": "^1.0.2" }, 

完整的项目在github下可用。