【问题标题】:ComposeWith in yeoman generator not emitting an end event and thus not driving 'on' methodyeoman 生成器中的 ComposeWith 不发出结束事件,因此不驱动“on”方法
【发布时间】:2015-10-08 08:29:36
【问题描述】:

背景

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

如果用户之前安装了 Angular 应用程序(我查找标记文件并在我的代码中设置布尔值“angularAppFound”),生成器将正常工作。但是,我希望它也是“一站式”的,因为如果他们还没有设置角度应用程序,我的生成器会在我安装额外的角度工件之前为他们调用角度生成器 在一次运行中

显然,如果没有 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 base之后停止并且不驱动后续步骤(因为生成器认为步骤永远不会结束)。

我可以使用调用,但它已被弃用:

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

问题

我已阅读 herehere 生成器不应相互依赖:

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

然后我应该如何处理我的情况,因为订购 很重要(除了使用“调用”)?在不牺牲“一站式”处理的情况下,我想不出任何其他方式来重新组织我的生成器。

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

'composeWith' 不发出 'end' 事件是设计使然吗?

如果没有,您建议我打开一个错误报告,还是有其他方法可以做到(不推荐使用)?

非常感谢。

【问题讨论】:

  • 在查看 base.js (github.com/yeoman/generator/blob/master/lib/base.js) 的源代码时,我确实看到了一个 'this.emit('end');'在运行路径上,由 composeWith() 函数调用。据我所知,composeWith 没有理由不发出“结束”事件。

标签: node.js yeoman yeoman-generator yeoman-generator-angular


【解决方案1】:

生成器可组合性使用priority base run loop 排序。因此,在运行你的之前,有可能等待另一个生成器完成。

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

在您的情况下,您需要两个生成器。 app 生成器(根)和您的自定义功能生成器。然后你把它们组合在一起:

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

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

end: function () {
  this.composeWith('my:subgen');
}

generator-angular 非常庞大且相当复杂。它仍然基于旧版本的 Yeoman,这意味着它可能更难用作基础生成器。我相信项目的所有者会很乐意为升级和改进作曲故事提供一些帮助。 - 如果您想知道对于 Yeoman 组合来说更好的开发人员用户体验是什么样的,请查看 generator-node,他被设计为组合的基础生成器。

【讨论】:

  • 感谢您的反馈!我一定会试试你的建议。但是,由于我目前在 yeoman 的其他问题上处于紧张状态(不是错误,只是标准学习曲线的东西),而且我有调用解决方法,所以我现在无法测试这个。但这听起来肯定是对的,一旦我确认,我将更新这张票。再次感谢。
  • 结束函数为我节省了一天的时间。非常感谢 Simon Boudrias!
【解决方案2】:

概述:

Simon Boudrias 在 10 月 13 日的帖子是公认的答案。他为我提供了足够的理论背景来掌握情况。我提供这个额外的答案是为了提供一些额外的实用信息。

分析

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

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

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

我还意识到我将基本生成器与角度生成器紧密耦合。我决定重构我的设计,让我的基本生成器为我可能想要安装的每种不同类型的平台调用子生成器。例如,我会有一个 angular 的子生成器,然后是 webapp 的另一个子生成器。通过这样做,我的基本生成器的所有公共组件都在一个生成器中,子环境特定的东西将在子生成器中。

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

简而言之,它带来了更好的设计。

问题:

但是,正如帖子中提到的,角度基础生成器对“composeWith”不友好。事实上,它是使用 yeoman-generator 0.16 构建的,文档明确指出 composeWith 需要 yeoman-generator 0.17 或更高版本。

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

我按照建议使用 node:app 进行了测试。这确实工作正常:它同步运行,并且仅在完成后才启动我的基本安装程序。因此,这证明了 composeWith 只能根据具体情况工作,具体取决于基础安装程序设计的好坏与可组合性。

我确实尝试使用 v 0.17 及更高版本在本地“构建”角度生成器。下划线函数存在一些问题,但即使在编译后仍然无法正常工作。所以即使有一个元生成器,我还是回到了我原来的问题。

更糟糕的是,我意识到我的“调用”解决方法,虽然至少触发了“结束”,但在安装基本应用程序文件之后,但在库的 npm 安装完成之前执行此操作。因此,角度生成器的 npm install 干扰了我的基本生成器的提示。

解决办法:

我基本上只是在我的元安装程序中近似了“composeWith”功能,通过交互式进程启动生成器。换句话说,模仿如果您手动运行一个生成器然后另一个生成器会做什么。在“大男孩”生成器可靠组合之前,我想不出任何其他方法来实现这一点。 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 下找到。

【讨论】:

  • 天哪,我是否过度分析了这一点。最后,我永远无法获得使用角度生成器的可组合性。我只是要求他们在现有的角度安装上运行我的生成器。没有“一站式”处理,但它让我作为开发人员的工作变得更加轻松。
猜你喜欢
  • 1970-01-01
  • 2022-07-29
  • 1970-01-01
  • 1970-01-01
  • 2015-02-06
  • 2014-08-26
  • 1970-01-01
  • 1970-01-01
  • 2016-06-21
相关资源
最近更新 更多