Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript visitor API for custom transforms #363

Merged
merged 85 commits into from
Dec 26, 2022
Merged

JavaScript visitor API for custom transforms #363

merged 85 commits into from
Dec 26, 2022

Conversation

devongovett
Copy link
Member

@devongovett devongovett commented Dec 17, 2022

This adds a visitor API to the node bindings which enables custom transforms to be written, similar to custom PostCSS plugins. I've re-created several popular PostCSS plugins in the tests to verify that it is possible to do so.

The API tries to let you be as specific as possible about what types of values you care about by defining functions for specific value types, property and rule names, etc. This lets us call from Rust into JS as little as possible, which helps with performance. An example plugin to convert pixels to rems is about 6x faster than an equivalent PostCSS plugin. JS visitors do have a cost however - it's about 2x slower than with no visitors. So this API is there when you want to do something custom, but transforms for standard CSS features should be implemented in Rust as part of core.

At the moment, a visitor runs in a single pass over the AST. We could later add support for multi-pass transforms if needed, but would be nice to avoid if possible. To support multiple visitor "plugins", there is a composeVisitors function included which combines multiple visitors into one on the JS side, which can then be passed to the Rust implementation. We try to make the order of the visitors not matter as much as possible. Each visitor has the opportunity to visit each value once. If a visitor returns a new value for a node, that value is visited by the other visitors but not again by the original visitor that created it. We keep track of which visitors have been visited and mutated the value to avoid infinite loops.

Implementation wise, it relies on Serde to serialize and deserialize parts of the AST into JS objects as needed. We also use the schemars crate to generate a JSON schema definition from the Rust types, which is then converted into TypeScript. A bulk of the changes in the PR are related to this, and making the AST types nicer to work with in JS.

One downside is that the binary size of lightningcss gets significantly larger with this feature - mostly due to serde derived implementations. I think the feature is worth it to include by default - but if we need to, we could always have an additional "lite" build available for users with strict size requirements that don't need this feature.

API feedback welcome! Check out the tests for some examples.

@devongovett devongovett marked this pull request as ready for review December 24, 2022 20:28
@devongovett devongovett merged commit 0445926 into master Dec 26, 2022
@devongovett devongovett deleted the js-visitor branch December 26, 2022 04:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant