This transformation technique is used in @effectful/js and I use the same for other compilers (not based on babel though).
As the first step, this splits AST into a stream of tokens (simle ECMAScript Iterator), and as the last step, it builds the node back from the argument Iterator. There are a few functions for transforming the streams in the middle (stream transducers). They take an Iterable object and return some other (transformed) Iteratable. In most cases, the transformer function (transducer) is a Generator.
This way, transformations composition is just a plain function's composition. E.g. node => consume(transform1(transform2(produce(node))))
or with any pipe function/operator. And it is just a single pass. All transforms run simultaneously passing the transformed tokens to the next one.