Angular-cli: Schematics: allow creation of custom tasks and executors

Created on 22 Oct 2018  路  8Comments  路  Source: angular/angular-cli

Bug Report or Feature Request

- [ ] bug report -> please search issues before submitting
- [x] feature request

Command

- [x] generate

Versions

Node: 10.11.0
Npm: 6.4.1
Ng: 7.0.2
Windows: 10, version 10.0.17134.345

Desired functionality

Right now there is no supported way to implement a new task, as we can't easily register an executor for the task. We are then limited to use only the ones created by the Angular team.

It should be possible to create custom tasks and easily register executors.

Other details that might be useful

I have found that I can plug in a custom executor if I register it using the engine host from context.engine._host (context passed on to the Rule function). Like so:

ts const host = <NodeModulesEngineHost>(<any>context.engine)._host; // this line is not supported host.registerTaskExecutor<YourFactoryOptions>({ name: "your-executor-name", create: (opt) => import('../path/to/executor').then(mod => mod.default(opt)) });

The problem is that:

  1. This is too hard;
  2. This is being done for each schematic, it could be global for the collection (or not, I'd like to hear your opinion on this one);
  3. It is clearly not supported, as _host is private.

Is this intentionally hard? If so, why is it? Do you plan to let we extend this somehow on the future? I'd like to understand the plans there.

devkischematics triage #1 feature

All 8 comments

This is intentional. The tooling that hosts the schematic runtime controls the available set of tasks and their respective factories. Schematics should not make any assumptions as to execution platform. It could be node, it could be a web browser, or something else entirely. Only the tooling knows which factory for a particular task needs to be registered to allow that task to function on the tooling's platform. Schematics are also intended to be isolated from the system as a whole to protect the user of the schematic from potentially malicious behavior. A schematic should never be accessing or manipulating anything beyond the bounds of the public API of the schematic framework. And to that end, additional sandboxing is planned to make this a hard requirement in the future.

I understand the goal, but shouldn't the people who build their own schematics be able to extend it by adding tasks? Is there a way that you could enable us and still keep those goals?

This would be ridiculously awesome. I am writing a small book on schematics and was saddened to see that this feature is not supported! It would be incredibly useful for developers if there was some sort of hook that allowed for the creation of custom Tasks.

Thanks giggio for the workaround.
I want to apply prettier on the files generated by the schematics. In my context it consist in calling yarn format

What a pity there's no "official mean" to do it !

You could just use RunSchematicTask and then make a schematic that does what ever you want:

export function myCommandSchematic({ command, args }: { command: string; args: string[]; }): Rule {
  return (host: Tree, context: SchematicContext) => {
    return new Observable<Tree>(subscriber => {
      console.log(options.args);
      const child = spawn(command, args, { stdio: 'inherit' });
      child.on('error', error => {
        subscriber.error(error);
      });
      child.on('close', () => {
        subscriber.next(host);
        subscriber.complete();
      });
      return () => {
        child.kill();
      };
    });
  };
}
...
context.addTask(new RunSchematicTask('my-command', { command: 'yarn', args: ['format'] }));

Very desired feature.

@clydin Could you please suggest any legal way to register custom task?

I found this solution: https://stackoverflow.com/questions/52812085/how-can-i-create-a-task-for-schematics. Probably it is @giggio 's solution.

I think TaskExecutor can declare supported hosts when registering, for example:

host.registerTaskExecutor<YourFactoryOptions>({
  name: "your-executor-name",
  hosts: ['node'],
  create: (opt) => import('../path/to/executor').then(mod => mod.default(opt))
});

Then when executing Engine.executePostTasks, Engine can check if all added tasks support execution under the current EngineHost, if not, throw an error.

I independently discovered the same solution as @giggio
However, it obviously violates the architecture. Perhaps another approach would be to make the schematics cli tool and the node workflow easily extensible so that you can just implement your own cli extending the given one that passes the calls to engineHost.registerTaskExecutor(MyTaskExecutor); into the cli > workflow. By restricting the schematics collection to the one you're writing you could easily make a specific command line tool to run just your schematics.

Was this page helpful?
0 / 5 - 0 ratings