How to run NX angular applicationGenerator synchronously

318 views Asked by At

I'm trying to replace an old angular-devkit schematic with a new nx generator using nx-devkit.

I'm following the guide here: https://nx.dev/generators/composing-generators

We should be able to await the nx generators, however when I do, the generator does not complete before await finishes.

Expected:

this should happen before
...application is created
this should happen after

Actual:

this should happen before
this should happen after
...application is created
export default async function(tree: Tree, schema: SchemaOptions) {
  logger.info('this should happen before');
  await applicationGenerator(tree, {...}); // this returns before the files are created
  logger.info('this should happen after');
}
2

There are 2 answers

0
Haris Skiadas On

This is probably too late to help the OP, but as I recently ran into a similar problem myself and searching for an answer led me here, I hope this will help someone in the future.

The general problem is this: We want to use an existing generator that creates some files, for example the @nx/js library generator. But following that creation we want to then do something further with those files, within the same run. The problem, as the OP and I discovered, is that the files are not immediately written to disc. This is intentional and happens for two reasons:

  • The generator my be running in dry-run mode, in which case there is an expectation that no changes will happen to the file system.
  • The generator my throw an error, in which case the most preferred behavior is that again nothing changes in the system, as the generator has effectively failed.

This "transaction" behavior is very desirable, so the authors of Nx have built that in. The generators don't interact directly with the file system, unless they do so by directly calling on Node's fs module methods, which would not be advisable. Instead what happens is the following:

  1. The generator-runner will run your generator, providing it with a virtual file system tree FsTree, exposed via its interface Tree.
  2. Your generator, and other generators you call, submit changes to this tree, either by directly calling on tree.write or by calling other methods that do so. These are all stored for later processing.
  3. When your generator has completed its run, the generator-runner will look at its options, and if it is NOT running in dry-run mode, and no exceptions were thrown, then it will actually flush the changes to the real file system with a call to flushChanges.

It has nothing to do with the await, which is needed anyway just so that the library generator actually queues these file changes up, but all to do with the fact that you are interacting with a virtual file system tree.

You have two ways to work around it:

  • Option 1: You can use the functionality provided by the Tree interface, and call on tree.read to read a file, even if that file only exists virtually at the time. The virtual tree will return either the pending changes if it is a newly created or updated file, or else it will fall back to the actual file on the real file system. You can similarly make changes to a "pending" file this way. In my instance I wanted to add to the contents of one of the files created by the libraryGenerator. So I had to use tree.read to read the pending contents, then combine those with my new contents before writing it all back with tree.write.
  • Option 2, not recommended: You can call flushChanges to immediately commit the changes. This prevents the dry-run functionality offered by the system, and would also still create those files even if the generator throws an error later on. Neither of these behaviors would be expected from users of your generator.
1
Jarek Krzemieński On

Isn't that simple as just puting await before calling applicationGenerator?