Node.js yeoman发生器中的ComposeWith不发出结束事件,因此不驱动';在';方法 背景

Node.js yeoman发生器中的ComposeWith不发出结束事件,因此不驱动';在';方法 背景,node.js,yeoman,yeoman-generator,yeoman-generator-angular,Node.js,Yeoman,Yeoman Generator,Yeoman Generator Angular,我正在为Angular SPA(单页应用程序)创建一个脚手架生成器。它将依赖于标准角度生成器(“yo angular”)设置的环境,也依赖于标准角度子生成器生成应用程序所需的一些额外服务和控制器。换句话说,我正在“装饰”一个基本的角度应用程序 如果用户以前安装过angularAppFound应用程序(我查找标记文件并在代码中设置booleon“angularAppFound”),那么生成器将正常工作。然而,我希望它也是“一站式”的,因为如果他们还没有安装angular应用程序,我的生成器将为他们

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

如果用户以前安装过angularAppFound应用程序(我查找标记文件并在代码中设置booleon“angularAppFound”),那么生成器将正常工作。然而,我希望它也是“一站式”的,因为如果他们还没有安装angular应用程序,我的生成器将为他们调用angular generator,然后我在一次运行中安装额外的angular artifacts

显然,如果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(),并且生成器在角基座安装后停止,不会驱动后续步骤(因为生成器认为该步骤永远不会完成)

我可以使用invoke,但不推荐使用:

(!) generator#invoke() is deprecated. Use generator#composeWith() - see http://yeoman.io/authoring/composability.html
问题 我读过,发电机不应该相互依赖:

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

既然排序很重要(而不是使用“invoke”),那么我应该如何处理我的情况呢?在不牺牲“一站式”处理的情况下,我想不出任何其他方法来重新组织我的生成器

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

“composeWith”是否设计为不发出“end”事件

如果不是,您建议我打开一个bug报告,还是有其他方法来做(这不是不推荐的)


非常感谢。

发电机的可组合性是通过以下方式订购的:。因此,在运行您的发电机之前,可以等待其他发电机的运行

尽管这里需要技巧的部分是,一旦结束事件被触发,生成器就完成了运行。它不会安排任何未来的任务,
end
事件意味着一切都完成了,是时候结束了。公平地说,你不应该需要结束活动。它仍在Yeoman中,仅用于向后兼容

在您的情况下,您需要两个发电机。
应用程序
生成器(根生成器)和自定义功能生成器。然后将它们组合在一起:

因此,在
generator/app/index.js
中,您将按如下方式组织代码:

writing: {
  this.composeWith('angular:app');
},

end: function () {
  this.composeWith('my:subgen');
}
发电机的角度是巨大的,相当复杂。它仍然基于Yeoman的旧版本,这意味着它更难用作基本生成器。我相信这些项目的所有者会很高兴得到一些帮助来升级和改进合成故事如果你想知道一个更好的开发者用户体验在Yeoman合成中会是什么样子,那么看看谁被设计成合成的基本生成器。

概述: 西蒙·布德里亚斯(Simon Boudrias)于2013年10月发布的帖子是公认的答案。他为我提供了足够的理论背景,使我能够掌握情况。我提供这个额外的答案是为了提供一些额外的实际信息

分析 我首先要了解的是“基本”生成器(不调用其他生成器的生成器)和“元”生成器(调用其他生成器的生成器)之间的区别。注意:当我提到“生成器”时,我不是指子生成器:基本生成器可以调用子生成器。基本生成器不应调用“composeWith”。它应该只做一件事。我的基本问题是我试图从基本生成器调用composeWith。我需要创建另一个生成器,一个叫做composeWith的元生成器

注:基本生成器和元生成器之间的区别是逻辑上的。就约曼而言,它们都只是“发电机”

我还发现区分依赖的生成器(那些需要预先存在的环境而不是以前的生成器)和独立的生成器(那些独立的生成器)很有用

我还意识到我把我的基本发电机和角度发电机紧密地耦合在一起。我决定重新考虑我的设计,让我的基本生成器为我可能要安装到的每种不同类型的平台调用子生成器。例如,我将为angular创建一个子生成器,然后为webapp创建另一个子生成器。通过这样做,我的基本生成器的所有公共组件都在一个生成器中,特定于子环境的内容将在子生成器中

然后,我将为每个目标平台配备一个元生成器,“角度元”将调用(通过composeWith)角度生成器,然后调用我的基本生成器,然后驱动“角度”子生成器,“webapp元”生成器将调用“webapp”,然后调用我的基本生成器,这将驱动“webapp”子生成器

简言之,这是一个更好的设计

问题: 然而,正如在文章中提到的,角基极发生器不是“composeWith”友好型的。事实上,它是用yeoman generator 0.16构建的,文档中明确说明composeWith需要yeoman generator 0.17或更高版本

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

我按照建议使用node:app进行了测试。这确实工作正常:它同步运行,启动了我的
'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();
     }
    );
  }
});
  "dependencies": {
    "async": "^1.4.2",
    "chalk": "^1.0.0",
    "yeoman-generator": "^0.19.0",
    "yosay": "^1.0.2"
  },