August 1st
八月一日 (discuss)
在 Slack 中 (当时)
Potential API changes for traversal
Editor: This a combination of notes of Slack and a general elaboration on the current experimentation Logan is doing.
Building on previous conversations in:
and continuing somewhat from yesterday's discussion on versioning, Babel's current approach to plugins, e.g.
export default function(babel) {
return {
visitor: {
Identifier(path, state) { },
},
};
}
with Babel's simplified core algorithm being:
const visitors = plugins.map(plugin => plugin.visitor);
const mergedVisitors = merge(visitors);
traverse(ast, mergedVisitors);
This has a few primary issues that make things difficult for us:
-
The
visitor
approach to AST mutation is baked directly into the core of our plugin system. This prevents us from iterating on the core transformation system because it is tied to the version ofbabel-core
, it requires plugins to run overtop of eachother, because plugins all run in the same traversal pass (usually anyway). -
We pass in the
babel
param, which our internal plugins at least, rely on for access to things likebabel-types
andbabel-traverse
. This also prevents us from modifying those easily, unless we want to lockbabel-core
to an old version ofbabel-traverse
, with plugins using some new major version. -
Plugin ordering is user-defined based on their Babel configuration, which most don't realize, and makes it hard to reason about what could be affected by any given change to a plugin.
-
Because transforms run overtop of eachother, it is hard to provide useful debug feedback, since anything could be mutating the AST at any time.
Proposed resolution
To address both of these, Logan's proposed approach would be to trim down Babel's core plugin architecture to expose the AST root directly, allowing plugins to mutate the AST however they may like, with a full lock on the data structure, with plugins running based on their priority ordering. The primary issue that this does not address is that transformation is not a linear process. Our existing infrastructure causes sections of the tree to be "requeued" so that all plugins have the opportunity to react to changes made by all plugins, whatever the ordering.
With that in mind, a slightly more complex approach would be required, along the lines of:
import traverse from "babel-traverse";
export default function(ast, options) {
traverse(ast, {
Identifier(path, state) { },
},
});
// return 'true' to indicate that a change was made, or 'false' otherwise
return true;
}
which allows plugins to be run
for (let i = 0; i < plugins; i++) {
if (plugins[i](ast)) i = -1;
}