Skip to content

Commit 9fb8d5d

Browse files
Add server island metadata (#1008)
* Add server island metadata * Append servercomponent metadata * Add localName to serverComponents output * Add tests * linting * Updated changeset * fix: ormatting --------- Co-authored-by: Princesseuh <3019731+Princesseuh@users.noreply.github.com>
1 parent 3e25858 commit 9fb8d5d

File tree

6 files changed

+102
-0
lines changed

6 files changed

+102
-0
lines changed

.changeset/yellow-cooks-deliver.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
'@astrojs/compiler': minor
3+
---
4+
5+
Adds `serverComponents` metadata
6+
7+
This adds a change necessary to support server islands. During transformation the compiler discovers `server:defer` directives and appends them to the `serverComponents` array. This is exported along with the other metadata so that it can be used inside of Astro.

cmd/astro-wasm/astro-wasm.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ type HoistedScript struct {
183183

184184
type HydratedComponent struct {
185185
ExportName string `js:"exportName"`
186+
LocalName string `js:"localName"`
186187
Specifier string `js:"specifier"`
187188
ResolvedPath string `js:"resolvedPath"`
188189
}
@@ -208,6 +209,7 @@ type TransformResult struct {
208209
Scripts []HoistedScript `js:"scripts"`
209210
HydratedComponents []HydratedComponent `js:"hydratedComponents"`
210211
ClientOnlyComponents []HydratedComponent `js:"clientOnlyComponents"`
212+
ServerComponents []HydratedComponent `js:"serverComponents"`
211213
ContainsHead bool `js:"containsHead"`
212214
StyleError []string `js:"styleError"`
213215
Propagation bool `js:"propagation"`
@@ -358,6 +360,7 @@ func Transform() any {
358360
scripts := []HoistedScript{}
359361
hydratedComponents := []HydratedComponent{}
360362
clientOnlyComponents := []HydratedComponent{}
363+
serverComponents := []HydratedComponent{}
361364
css_result := printer.PrintCSS(source, doc, transformOptions)
362365
for _, bytes := range css_result.Output {
363366
css = append(css, string(bytes))
@@ -438,6 +441,15 @@ func Transform() any {
438441
})
439442
}
440443

444+
for _, c := range doc.ServerComponents {
445+
serverComponents = append(serverComponents, HydratedComponent{
446+
ExportName: c.ExportName,
447+
LocalName: c.LocalName,
448+
Specifier: c.Specifier,
449+
ResolvedPath: c.ResolvedPath,
450+
})
451+
}
452+
441453
var value vert.Value
442454
result := printer.PrintToJS(source, doc, len(css), transformOptions, h)
443455
transformResult := &TransformResult{
@@ -446,6 +458,7 @@ func Transform() any {
446458
Scripts: scripts,
447459
HydratedComponents: hydratedComponents,
448460
ClientOnlyComponents: clientOnlyComponents,
461+
ServerComponents: serverComponents,
449462
ContainsHead: doc.ContainsHead,
450463
StyleError: styleError,
451464
Propagation: doc.HeadPropagation,

internal/node.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ var scopeMarker = Node{Type: scopeMarkerNode}
6565

6666
type HydratedComponentMetadata struct {
6767
ExportName string
68+
LocalName string
6869
Specifier string
6970
ResolvedPath string
7071
}
@@ -98,6 +99,7 @@ type Node struct {
9899
ClientOnlyComponentNodes []*Node
99100
ClientOnlyComponents []*HydratedComponentMetadata
100101
HydrationDirectives map[string]bool
102+
ServerComponents []*HydratedComponentMetadata
101103
ContainsHead bool
102104
HeadPropagation bool
103105

internal/transform/transform.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,39 @@ func AddComponentProps(doc *astro.Node, n *astro.Node, opts *TransformOptions) {
523523
}
524524

525525
break
526+
} else if strings.HasPrefix(attr.Key, "server:") {
527+
parts := strings.Split(attr.Key, ":")
528+
directive := parts[1]
529+
530+
hydrationAttr := astro.Attribute{
531+
Key: "server:component-directive",
532+
Val: directive,
533+
}
534+
n.Attr = append(n.Attr, hydrationAttr)
535+
536+
match := matchNodeToImportStatement(doc, n)
537+
if match != nil {
538+
doc.ServerComponents = append(doc.ServerComponents, &astro.HydratedComponentMetadata{
539+
ExportName: match.ExportName,
540+
LocalName: n.Data,
541+
Specifier: match.Specifier,
542+
ResolvedPath: ResolveIdForMatch(match.Specifier, opts),
543+
})
544+
545+
pathAttr := astro.Attribute{
546+
Key: "server:component-path",
547+
Val: fmt.Sprintf(`"%s"`, ResolveIdForMatch(match.Specifier, opts)),
548+
Type: astro.ExpressionAttribute,
549+
}
550+
n.Attr = append(n.Attr, pathAttr)
551+
552+
exportAttr := astro.Attribute{
553+
Key: "server:component-export",
554+
Val: fmt.Sprintf(`"%s"`, match.ExportName),
555+
Type: astro.ExpressionAttribute,
556+
}
557+
n.Attr = append(n.Attr, exportAttr)
558+
}
526559
}
527560
}
528561
}

packages/compiler/src/shared/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export type HoistedScript = { type: string } & (
9595

9696
export interface HydratedComponent {
9797
exportName: string;
98+
localName: string;
9899
specifier: string;
99100
resolvedPath: string;
100101
}
@@ -109,6 +110,7 @@ export interface TransformResult {
109110
scripts: HoistedScript[];
110111
hydratedComponents: HydratedComponent[];
111112
clientOnlyComponents: HydratedComponent[];
113+
serverComponents: HydratedComponent[];
112114
containsHead: boolean;
113115
propagation: boolean;
114116
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { fileURLToPath } from 'node:url';
2+
import { transform } from '@astrojs/compiler';
3+
import { test } from 'uvu';
4+
import * as assert from 'uvu/assert';
5+
6+
const FIXTURE = `
7+
---
8+
import Avatar from './Avatar.astro';
9+
import {Other} from './Other.astro';
10+
---
11+
12+
<Avatar server:defer />
13+
<Other server:defer />
14+
`;
15+
16+
let result: Awaited<ReturnType<typeof transform>>;
17+
test.before(async () => {
18+
result = await transform(FIXTURE, {
19+
resolvePath: async (s: string) => {
20+
const out = new URL(s, import.meta.url);
21+
return fileURLToPath(out);
22+
},
23+
});
24+
});
25+
26+
test('component metadata added', () => {
27+
assert.equal(result.serverComponents.length, 2);
28+
});
29+
30+
test('path resolved to the filename', () => {
31+
const m = result.serverComponents[0];
32+
assert.ok(m.specifier !== m.resolvedPath);
33+
});
34+
35+
test('localName is the name used in the template', () => {
36+
assert.equal(result.serverComponents[0].localName, 'Avatar');
37+
assert.equal(result.serverComponents[1].localName, 'Other');
38+
});
39+
40+
test('exportName is the export name of the imported module', () => {
41+
assert.equal(result.serverComponents[0].exportName, 'default');
42+
assert.equal(result.serverComponents[1].exportName, 'Other');
43+
});
44+
45+
test.run();

0 commit comments

Comments
 (0)