Yeoman:使用用户提供的参数调用子生成器

时间:2021-11-16 06:48:16

I'm writing my first Yeoman generator, which prompts the user for various inputs and conditionally creates files based on their responses. I need to be able to call a subroutine (could be a Yeoman sub-generator) based on user input, and pass arguments to it.

我正在编写我的第一个Yeoman生成器,它会提示用户输入各种输入并根据响应有条件地创建文件。我需要能够根据用户输入调用子程序(可能是Yeoman子生成器),并将参数传递给它。

The reason I want to use named functions (which are not automatically run) is that sometimes the user's response should invoke a number of functions combined, and sometimes the function should be run alone.

我想使用命名函数(不自动运行)的原因是,有时用户的响应应该调用多个函数组合,有时候函数应该单独运行。

What I have tried:

我试过的:

I figured sub-generators were the way to go, since I'm creating sets of files only if the user requests them. But I'm having trouble calling them conditionally and passing them the user-supplied input. I've tried using hookFor, but I get the assertion error: hookFor must be used within the constructor only. (Because I don't want it to be run by default, I'm calling the sub-generator from my this.prompt(prompts, function (props)).

我认为子生成器是可行的方法,因为我只在用户请求时创建文件集。但我无法有条件地调用它们并将它们传递给用户提供的输入。我尝试过使用hookFor,但是我得到了断言错误:hookFor只能在构造函数中使用。 (因为我不希望它默认运行,我从my.prompt调用子生成器(提示,函数(props))。

The question:

How do I call a routine only if the user requests it (via a prompt), and pass that routine some user-supplied information?

如何仅在用户请求(通过提示)时调用例程,并将该例程传递给用户提供的一些信息?

If you're kind enough to answer, please don't assume that I've tried something obvious ;-).

如果你很友好地回答,请不要认为我尝试了一些明显的东西;-)。

3 个解决方案

#1


32  

Let's consider you have a generator generator-blog (BlogGenerator) with two sub generators (blog-server and blog-client):

让我们考虑你有一个带有两个子生成器(blog-server和blog-client)的生成器生成器博客(BlogGenerator):

app\index.js
client\index.js
server\index.js

So when you run yo blog you what to ask the user for some options and run (optionally) sub generators, right?

因此,当您运行yo博客时,您可以向用户询问某些选项并运行(可选)子生成器,对吧?

To run a subgenerator you need to call this.invoke("generator_namespace", {options: {}}). The second argument we passed can have options field - it's options object which will be passed to the generator.

要运行子生成器,您需要调用this.invoke(“generator_namespace”,{options:{}})。我们传递的第二个参数可以有选项字段 - 它的选项对象将被传递给生成器。

In app\index.js:

BlogGenerator.prototype.askFor = function askFor() {
  var cb = this.async();

  // have Yeoman greet the user.
  console.log(this.yeoman);

  var prompts = [{
    name: 'appName',
    message: 'Enter your app name',
    default: 'MyBlog'
  }, {
    type: 'confirm',
    name: 'createServer',
    message: 'Would you like to create server project?',
    default: true
  }, {
    type: 'confirm',
    name: 'createClient',
    message: 'Whould you like to create client project?',
    default: true
  }];

  this.prompt(prompts, function (props) {
    this.appName = props.appName;
    this.createServer = props.createServer;
    this.createClient = props.createClient;

    cb();
  }.bind(this));
}

BlogGenerator.prototype.main = function app() {
  if (this.createClient) {
    // Here: we'are calling the nested generator (via 'invoke' with options)
    this.invoke("blog:client", {options: {nested: true, appName: this.appName}});
  }
  if (this.createServer) {
    this.invoke("blog:server", {options: {nested: true, appName: this.appName}});
  }
};

In client\index.js:

var BlogGenerator = module.exports = function BlogGenerator(args, options, config) {
  var that = this;
  yeoman.Base.apply(this, arguments);
  // in this.options we have the object passed to 'invoke' in app/index.js:
  this.appName = that.options.appName;
  this.nested  = that.options.nested;
};

BlogGenerator .prototype.askFor = function askFor() {
  var cb = this.async();

  if (!this.options.nested) {
    console.log(this.yeoman);
  }
}

UPDATE 2015-12-21:
Using invoke is deprecated now and should be replaced with composeWith. But it's not as easy as it could be. The main difference between invoke and composeWith is that now you have no ability to control subgenerators. You could only declare using them.
Here's how main method from above should look like:

更新2015-12-21:现在不推荐使用invoke,应该用composeWith替换。但它并不像它可能那么容易。 invoke和composeWith之间的主要区别在于,现在您无法控制子生成器。您只能声明使用它们。以下是上面的主要方法应该是这样的:

BlogGenerator.prototype.main = function app() {
  if (this.createClient) {
    this.composeWith("blog:client", { 
        options: { 
          nested: true, 
          appName: this.appName
        } 
      }, {
        local: require.resolve("./../client")
      });
  }
  if (this.createServer) {
    this.composeWith("blog:server", { 
        options: { 
          nested: true, 
          appName: this.appName
        } 
      }, {
        local: require.resolve("./../server")
      });
  }
};

Also I removed replaced yeoman.generators.Base with yeoman.Base.

我也用yeoman.Base删除了替换的yeoman.generators.Base。

#2


7  

2015-04 update: The yeoman api now includes this.composeWith as the preferred method for linking generators.

2015-04更新:yeoman api现在包含this.composeWith作为链接生成器的首选方法。

docs: http://yeoman.io/authoring/composability.html

#3


2  

You can cover all possible execution scenarios, condition checking, prompting when composing generators together if you decouple generators and use a 'main-generator' the run context loop will help you. Use the options of .composeWith('my-genertor', { 'options' : options }) for passing configurations to composed generators.

您可以涵盖所有可能的执行方案,条件检查,在将生成器组合在一起时提示如果您将生成器解耦并使用“主生成器”运行上下文循环将帮助您。使用.composeWith('my-genertor',{'options':options})选项将配置传递给组合生成器。

When using .composeWith a priority group function (e.g.: prompting, writing...) will be executed for all the generators, then the next priority group. If you call .composeWith to generatorB from inside a generatorA, then execution will be, e.g.:

当使用.compose时,将对所有生成器执行优先级组功能(例如:提示,写入...),然后执行下一个优先级组。如果从generatorA中调用.composeWith到generatorB,则执行将是,例如:

generatorA.prompting => generatorB.prompting => generatorA.writing => generatorB.writing

generatorA.prompting => generatorB.prompting => generatorA.writing => generatorB.writing

If you want to control execution between different generators, I advise you to create a "main" generator which composes them together, like written on http://yeoman.io/authoring/composability.html#order:

如果你想控制不同生成器之间的执行,我建议你创建一个“主”生成器,将它们组合在一起,就像写在http://yeoman.io/authoring/composability.html#order上一样:

// In my-generator/generators/turbo/index.js
module.exports = require('yeoman-generator').Base.extend({
  'prompting' : function () {
    console.log('prompting - turbo');
  },

  'writing' : function () {
    console.log('prompting - turbo');
  }
});

// In my-generator/generators/electric/index.js
module.exports = require('yeoman-generator').Base.extend({
  'prompting' : function () {
    console.log('prompting - zap');
  },

  'writing' : function () {
    console.log('writing - zap');
  }
});

// In my-generator/generators/app/index.js
module.exports = require('yeoman-generator').Base.extend({
  'initializing' : function () {
    this.composeWith('my-generator:turbo');
    this.composeWith('my-generator:electric');
  }
});

#1


32  

Let's consider you have a generator generator-blog (BlogGenerator) with two sub generators (blog-server and blog-client):

让我们考虑你有一个带有两个子生成器(blog-server和blog-client)的生成器生成器博客(BlogGenerator):

app\index.js
client\index.js
server\index.js

So when you run yo blog you what to ask the user for some options and run (optionally) sub generators, right?

因此,当您运行yo博客时,您可以向用户询问某些选项并运行(可选)子生成器,对吧?

To run a subgenerator you need to call this.invoke("generator_namespace", {options: {}}). The second argument we passed can have options field - it's options object which will be passed to the generator.

要运行子生成器,您需要调用this.invoke(“generator_namespace”,{options:{}})。我们传递的第二个参数可以有选项字段 - 它的选项对象将被传递给生成器。

In app\index.js:

BlogGenerator.prototype.askFor = function askFor() {
  var cb = this.async();

  // have Yeoman greet the user.
  console.log(this.yeoman);

  var prompts = [{
    name: 'appName',
    message: 'Enter your app name',
    default: 'MyBlog'
  }, {
    type: 'confirm',
    name: 'createServer',
    message: 'Would you like to create server project?',
    default: true
  }, {
    type: 'confirm',
    name: 'createClient',
    message: 'Whould you like to create client project?',
    default: true
  }];

  this.prompt(prompts, function (props) {
    this.appName = props.appName;
    this.createServer = props.createServer;
    this.createClient = props.createClient;

    cb();
  }.bind(this));
}

BlogGenerator.prototype.main = function app() {
  if (this.createClient) {
    // Here: we'are calling the nested generator (via 'invoke' with options)
    this.invoke("blog:client", {options: {nested: true, appName: this.appName}});
  }
  if (this.createServer) {
    this.invoke("blog:server", {options: {nested: true, appName: this.appName}});
  }
};

In client\index.js:

var BlogGenerator = module.exports = function BlogGenerator(args, options, config) {
  var that = this;
  yeoman.Base.apply(this, arguments);
  // in this.options we have the object passed to 'invoke' in app/index.js:
  this.appName = that.options.appName;
  this.nested  = that.options.nested;
};

BlogGenerator .prototype.askFor = function askFor() {
  var cb = this.async();

  if (!this.options.nested) {
    console.log(this.yeoman);
  }
}

UPDATE 2015-12-21:
Using invoke is deprecated now and should be replaced with composeWith. But it's not as easy as it could be. The main difference between invoke and composeWith is that now you have no ability to control subgenerators. You could only declare using them.
Here's how main method from above should look like:

更新2015-12-21:现在不推荐使用invoke,应该用composeWith替换。但它并不像它可能那么容易。 invoke和composeWith之间的主要区别在于,现在您无法控制子生成器。您只能声明使用它们。以下是上面的主要方法应该是这样的:

BlogGenerator.prototype.main = function app() {
  if (this.createClient) {
    this.composeWith("blog:client", { 
        options: { 
          nested: true, 
          appName: this.appName
        } 
      }, {
        local: require.resolve("./../client")
      });
  }
  if (this.createServer) {
    this.composeWith("blog:server", { 
        options: { 
          nested: true, 
          appName: this.appName
        } 
      }, {
        local: require.resolve("./../server")
      });
  }
};

Also I removed replaced yeoman.generators.Base with yeoman.Base.

我也用yeoman.Base删除了替换的yeoman.generators.Base。

#2


7  

2015-04 update: The yeoman api now includes this.composeWith as the preferred method for linking generators.

2015-04更新:yeoman api现在包含this.composeWith作为链接生成器的首选方法。

docs: http://yeoman.io/authoring/composability.html

#3


2  

You can cover all possible execution scenarios, condition checking, prompting when composing generators together if you decouple generators and use a 'main-generator' the run context loop will help you. Use the options of .composeWith('my-genertor', { 'options' : options }) for passing configurations to composed generators.

您可以涵盖所有可能的执行方案,条件检查,在将生成器组合在一起时提示如果您将生成器解耦并使用“主生成器”运行上下文循环将帮助您。使用.composeWith('my-genertor',{'options':options})选项将配置传递给组合生成器。

When using .composeWith a priority group function (e.g.: prompting, writing...) will be executed for all the generators, then the next priority group. If you call .composeWith to generatorB from inside a generatorA, then execution will be, e.g.:

当使用.compose时,将对所有生成器执行优先级组功能(例如:提示,写入...),然后执行下一个优先级组。如果从generatorA中调用.composeWith到generatorB,则执行将是,例如:

generatorA.prompting => generatorB.prompting => generatorA.writing => generatorB.writing

generatorA.prompting => generatorB.prompting => generatorA.writing => generatorB.writing

If you want to control execution between different generators, I advise you to create a "main" generator which composes them together, like written on http://yeoman.io/authoring/composability.html#order:

如果你想控制不同生成器之间的执行,我建议你创建一个“主”生成器,将它们组合在一起,就像写在http://yeoman.io/authoring/composability.html#order上一样:

// In my-generator/generators/turbo/index.js
module.exports = require('yeoman-generator').Base.extend({
  'prompting' : function () {
    console.log('prompting - turbo');
  },

  'writing' : function () {
    console.log('prompting - turbo');
  }
});

// In my-generator/generators/electric/index.js
module.exports = require('yeoman-generator').Base.extend({
  'prompting' : function () {
    console.log('prompting - zap');
  },

  'writing' : function () {
    console.log('writing - zap');
  }
});

// In my-generator/generators/app/index.js
module.exports = require('yeoman-generator').Base.extend({
  'initializing' : function () {
    this.composeWith('my-generator:turbo');
    this.composeWith('my-generator:electric');
  }
});