feat: add print(...) function
#16188
print(...) functionPull request
Over on sveltejs/esrap#68 we're working on making esrap pluggable, so that it can be used to print any AST composed of { type: string, ... } nodes rather than just estree and its TypeScript extensions. That includes Svelte ASTs.
The main motivation for exposing this is so that we can make it easier to write preprocessors. Historically, Svelte exposed a preprocess API, but it's all strings and duct tape, and it's difficult to integrate preprocessors cleanly with bundlers as we've seen with enhanced-img.
When the preprocessor API was introduced, things looked very different. Preprocessing was necessary to support things like TypeScript and Sass. Today, TypeScript is supported natively, and CSS is sufficiently capable that Sass is little more than a historical curiosity.
In the long term, we'd therefore like to move away from the preprocessor API in favour of providing more robust lower-level utilities. For example enhanced-img, which can only be used in a Vite context, really should just be a Vite plugin:
import { parse, print } from 'svelte/compiler';
import { walk } from 'zimmerframe';
function transform(code) {
const ast = parse(code);
const transformed = walk(ast, null, {
RegularElement(node, context) {
if (node.name !== 'enhanced:img') return;
// ...
}
});
return print(ast);
}
There are other potential uses, such as migrations or sv add or having a 'format' button in the playground.
As a side-effect, quality of compiler output will be slightly better in certain cases, such as when encountering comments inside nodes. (Today, we attach leadingComments and trailingComments to each node, but this is a brittle and not-very-widely-used convention. The new esrap API expects an array of comments to be passed instead.)
This functionality already exists in svelte-ast-print (thank you @xeho91!), but having it in core means we can re-use the esrap version that's already installed alongside svelte, and will help ensure it stays current with new features.
Before submitting the PR, please make sure you do the following
- It's really useful if your PR references an issue where it is discussed ahead of time. In many cases, features are absent for a reason. For large changes, please create an RFC: https://github.com/sveltejs/rfcs
- Prefix your PR title with
feat:,fix:,chore:, ordocs:. - This message body should clearly illustrate what problems it solves.
- Ideally, include a test that fails without this PR but passes with it.
- If this PR changes code within
packages/svelte/src, add a changeset (npx changeset).
Tests and linting
- Run the tests with
pnpm testand lint the project withpnpm lint
Info
🦋 Changeset detected
Latest commit: 7e0ee0c
The changes in this PR will be included in the next version bump.
This PR includes changesets to release 1 package
| Name | Type |
|---|---|
| svelte | Minor |
Not sure what this means? Click here to learn what changesets are.
Click here if you're a maintainer who wants to add another changeset to this PR
Nice!
I'll be happy to sunset svelte-ast-print in favour of built-in into the core print() function. 👍
I'm glad the need for it finally reached this point. I'm swamped right now with my real-life stuff, but just in case, both @manuel3108 and @paoloricciuti have access to the svelte-ast-print repository in case I don't manage to announce it on time.
pnpm add https://pkg.pr.new/svelte@16188I think this is ready now.
Here is a .
This will enable sv to use the svelte compiler directly, instead of doing a bunch of magic to parse and serialize svelte files: sveltejs/cli#751
The first example provided in the playground is from the demo integration from paraglide which i did also prepare in sveltejs/cli#751
Once this gets merged, you could do stuff like this:
import { parse, print } from 'svelte/compiler'
const ast = parse('<Whatever>svelte<span>code</span></Whatever>', { modern: true })
const output = print(ast).code
console.log(output)
The are pretty bare-bones, as with the other topics on the svelte/compiler page. But I do think they explain the most important points.
There is one major open point though, which I'm happy to discuss on Friday. It's related to adding new comments to the js-ast for the script-tag. This is currently a flaw in esrap, which we should solve before merging this: sveltejs/esrap#79. Whatever we do in esrap, should probably also be exposed here.
Disclaimer:
- formatting is way from perfect in certain cases, but that's not a problem:
- we can easily adjust that later on in follow-up prs
- you should run
prettier(or any code formatter) over your files as soon as they got modified. The intent here is not to produce a 1:1 output,
- I'm not sure if we currently support all nodes / types that we have. But support should be pretty good, and it's easy to add stuff later on.
Awesome! I opened sveltejs/esrap#90 as an alternative to sveltejs/esrap#79; should be easier enough to bring either over.
Spotted one thing we have to fix before we can merge this — we mustn't generate self-closing tags for HTML elements. Certainly not the non-void ones. <div></div> and <div /> are not the same as far as HTML is concerned. For void elements it's less clear-cut — <input> and <input /> are equivalent, but only because the closing solidus is ignored. Technically, <input> is correct.
Couple of other nits I'd love to see us fix at some point (though as you say, it needn't block this PR) — multiline blocks ought to have a margin between them:
if (true) {
console.log('multiline blocks in JS get a margin between them');
}
if (true) {
console.log('like that');
}
<div>
<p>but multiline blocks in Svelte...</p>
</div>
<div>
<p>...don't. It looks very cramped</p>
</div>
And this...
<div data-one="1" data-two="two" data-three="3"></div>
<div data-one="1" data-two="two" data-three="3">text</div>
...gets turned into this:
<div
data-one="1"
data-two="two"
data-three="3" />
<div
data-one="1"
data-two="two"
data-three="3">
text
</div>
Aside from the self-closing thing, I think this is arguably more conventional — the > of the opening tag at the same indentation level as the <:
<div
data-one="1"
data-two="two"
data-three="3"
></div>
<div
data-one="1"
data-two="two"
data-three="3"
>
text
</div>If we do get the formatting looking nice enough, we could add a 'format' button to the playground. that'd be cool
Committed a fix for the self-closing tags. I was wondering since some us <slot ... />, but we shouldn't bother about this since that's a legacy api anyway.
<div data-one="1" data-two="two" data-three="3"></div>
<div data-one="1" data-two="two" data-three="3">text</div>
This getting turned into a multiline statement is due to the line / attribute length. Currently, we have LINE_BREAK_THRESHOLD = 30 which is based on your original implementation, i just extracted it into a const to re-use it. In esrap we have this at 50, which might be the more sensible default. But changing that shifts around a lot of formatting, will need to check.
Aside from the self-closing thing, I think this is arguably more conventional — the > of the opening tag at the same indentation level as the <:
Done!
Edit: Forgot about this:
<div>
<p>but multiline blocks in Svelte...</p>
</div> <!-- potential new line here -->
<div>
<p>...don't. It looks very cramped</p>
</div>
Will check this as well, but that's probably going to be a hard one for a follow-up
Change the default LINE_BREAK_THRESHOLD to 50 which makes some of the code more readable, some is harder. I do think this is better now, than it was before.
There is definitely need for a follow-up regarding the line-breaks. We can merge this or i can make another pr against this branch. However, it's probably going to be next week.
I say let's ship it and iterate from there. If you slap a 👍 on it we can merge and cut a release. Amazing work!
Somehow the abstract keyword is getting left in place with the latest version of esrap. Will investigate
Pro tip: You can prefix GitHub URLs of issues, PRs or discussions with svcl.dev/ to view them on this page! Also try it on a GitHub release ;)