svelte
svelte copied to clipboard
feat: add a way to compile from an AST
Allows the developer to compile directly from an AST, returned previously from svelte.parse
.
There is currently no way to compile a modified AST to source code, this adds a function, compileAST
, that provides that functionality.
Notes:
- The second argument to
new Component
is currently set to null, this might break something, but for our purposes seems to be working. -
@param {object}
could be typed better. -
compile
could callcompileAST
underneath so we don't have to copy/paste code.
Hopefully someone has useful feedback!
Thank you for the quick response! Hopefully those patches address your review notes appropriately.
Since there's no previous issue for this: What's the use case for this feature?
We're implementing an internal GUI tool to modify Svelte files similar to #5972. This tool gets the AST with parse
then mutates it, we're looking for a way to get that AST back into Svelte to compile .
Although the AST isn't a public API, because parse
exists at all, we'd expect there to be a way to "unparse".
As suggested it would be if compileAst would be called by compile if that doesn't result in more complex code.
My only concern with this is that we'd be always passing null
as the second argument to new Component
, unless we refactored out into a common function. We haven't seen anything affected by using null
here, but haven't done any testing outside of our tool.
@Rich-Harris is there any chance this could be included in svelte 5?
As suggested it would be if
compileAst
would be called bycompile
if that doesn't result in more complex code.Since there's no previous issue for this: What's the use case for this feature?
The use case for this feature would be to be able to do AST transforms. It is much easier to do transforms on an AST than on strings using regexes. Here is a little example:
<script>
import Component2 from './Component2.svelte'
let name = 'world';
</script>
<h1>Hello {name}!</h1>
{#if name}
{@const someVar = "literal"}
<Component2 some-prop="literal" some-other-prop={someVar}>test</Component2>
{/if}
That I'd like to transform to:
<script>
import Component2 from './Component2.svelte'
import {importedVar} from './some/path.js'
let name = 'world';
</script>
<h1>Hello {name}!</h1>
{#if name}
{@const someVar = "literal"}
<Component2 some-prop={importedVar} some-other-prop={someVar}>test</Component2>
{/if}
Now, that means I want to replace ONLY the attribute literal value "literal"
, but not the "literal"
in the variable definition.
I could do that by finding the AST type="Attribute"
node with a child of type="Text"
and value="literal"
. Then I would replace the type="Text"
node with the desired new AST.
If we need to do this with something like @Rich-Harris Magic-String library, it gets complicated very quickly. One could argue, that the string positions found in the AST nodes would suffice to do accurate replacements. But that cannot be done accurately, as the positions exclude the parenthesis surrounding the attribute literal (as you can see in the screenshot below)
There are several use cases for wanting to transform the Svelte AST, e.g. we (@ivanhofer and me) were using AST transforms for inlangs i18n library.
@Rich-Harris what is your view on this?
One way to achieve printing the AST would be to write code generators for all of these types:
https://github.com/sveltejs/svelte/blob/main/packages/svelte/src/compiler/types/template.d.ts
And then use these generators in https://github.com/davidbonnet/astring?tab=readme-ov-file#extending
Edit:
Maybe a relatively easy way to achieve svelte AST printing is to take the svelte prettier plugin and adapting the printer
so that it can be passed to astring
as a custom generator?
Just so everybody who ends up here may know, this library is now a reality: https://github.com/xeho91/svelte-ast-print It does what we need, AST -> Code for Svelte stuff.