Skip to content

Commit 3646c8f

Browse files
committed
chore: a bit of transform phase
1 parent d6ea09d commit 3646c8f

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed

DEVELOPER_GUIDE.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,3 +268,119 @@ for (const scope of [module.scope, instance.scope]) {
268268
### CSS Analysis
269269

270270
While we didn't go deep in how the analysis works for every single step of the analysis it's worth exploring the CSS analysis a bit more in depth. This phase in itself is subdivided in three phases: we first analyze the css, during this phase we validate the structure of svelte specific css (eg. the `:global` selector) is valid also marking every node within global as `used`. We then proceed to `prune` the css. In this phase we match every selector with the html structure...if a selector doesn't match any element we `prune` it away either by commenting it out in dev (so that the users can actually see what's being removed) or by completely remove it in prod. Finally we walk the css AST once more to warn about all the `unused` css nodes accessing the metadata we collected in those two phases.
271+
272+
## Phase 3: Transform
273+
274+
After the analysis phase we can now move to the last compilation phase: the transform. However this phase is really two phases: svelte components generate completely different code based on the `compilerOption.generate` option...the function is small enough that we can just dump it here
275+
276+
```ts
277+
export function transform_component(analysis, source, options) {
278+
if (options.generate === false) {
279+
return {
280+
js: /** @type {any} */ null,
281+
css: null,
282+
warnings: state.warnings, // set afterwards
283+
metadata: {
284+
runes: analysis.runes
285+
},
286+
ast: /** @type {any} */ null // set afterwards
287+
};
288+
}
289+
290+
const program =
291+
options.generate === 'server'
292+
? server_component(analysis, options)
293+
: client_component(analysis, options);
294+
295+
const js_source_name = get_source_name(options.filename, options.outputFilename, 'input.svelte');
296+
297+
const js = print(/** @type {Node} */ program, ts({ comments: analysis.comments }), {
298+
// include source content; makes it easier/more robust looking up the source map code
299+
// (else esrap does return null for source and sourceMapContent which may trip up tooling)
300+
sourceMapContent: source,
301+
sourceMapSource: js_source_name
302+
});
303+
304+
merge_with_preprocessor_map(js, options, js_source_name);
305+
306+
const css =
307+
analysis.css.ast && !analysis.inject_styles
308+
? render_stylesheet(source, analysis, options)
309+
: null;
310+
311+
return {
312+
js,
313+
css,
314+
warnings: state.warnings, // set afterwards. TODO apply preprocessor sourcemap
315+
metadata: {
316+
runes: analysis.runes
317+
},
318+
ast: /** @type {any} */ null // set afterwards
319+
};
320+
}
321+
```
322+
323+
As you can see the key part of this function is this bit here
324+
325+
```ts
326+
const program =
327+
options.generate === 'server'
328+
? server_component(analysis, options)
329+
: client_component(analysis, options);
330+
```
331+
332+
Here we just delegate to it's own method to generate either a client component or a server component. Before we jump into exploring those two let's take a look at the output of the compilation of a very simple counter component.
333+
334+
```svelte
335+
<script>
336+
let count = $state(0);
337+
</script>
338+
339+
<button onclick={() => (count += 1)}>
340+
clicks: {count}
341+
</button>
342+
```
343+
344+
here's the compiled output for the client
345+
346+
```ts
347+
import 'svelte/internal/disclose-version';
348+
import 'svelte/internal/flags/async';
349+
import * as $ from 'svelte/internal/client';
350+
351+
var on_click = (_, count) => $.set(count, $.get(count) + 1);
352+
var root = $.from_html(`<button> </button>`);
353+
354+
export default function App($$anchor) {
355+
let count = $.state(0);
356+
var button = root();
357+
358+
button.__click = [on_click, count];
359+
360+
var text = $.child(button);
361+
362+
$.reset(button);
363+
$.template_effect(() => $.set_text(text, `clicks: ${$.get(count) ?? ''}`));
364+
$.append($$anchor, button);
365+
}
366+
367+
$.delegate(['click']);
368+
```
369+
370+
and here's the one for the server
371+
372+
```ts
373+
import * as $ from 'svelte/internal/server';
374+
375+
export default function App($$payload) {
376+
let count = 0;
377+
378+
$$payload.out.push(`<button>clicks: ${$.escape(count)}</button>`);
379+
}
380+
```
381+
382+
As you can see the server mostly concern itself with generating the html for the component, runes are basically removed, event listeners just vanish. It makes sense because on the server we don't add any listener, we just produce the html that will be hydrated by the client component.
383+
384+
Let's start from the simpler of the two...
385+
386+
### Transform server component

0 commit comments

Comments
 (0)