Skip to content

Commit 9cefea8

Browse files
committed
fixup!
1 parent fa1132b commit 9cefea8

5 files changed

Lines changed: 51 additions & 30 deletions

File tree

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,11 @@
11
/* eslint-disable jsdoc/require-jsdoc -- each entry is a bare dynamic import */
22

33
/**
4-
* @type {Record<string, () => import('preact').ComponentType | Promise<import('preact').ComponentType>>}
4+
* @type {Record<string, () => Promise<{ default: import('preact').ComponentType }>>}
55
*/
66
export default {
7-
Banner: () => import('../components/Banner').then(m => m.default),
8-
ThemeToggle: () =>
9-
import('../components/ThemeToggle.jsx').then(m => m.default),
10-
SearchBox: () => import('../components/SearchBox').then(m => m.default),
11-
SideBar: () => import('#theme/Sidebar').then(m => m.default),
12-
CodeBox: () => import('../components/CodeBox').then(m => m.default),
13-
14-
// Not an island, and never called. The shell is server-rendered and never
15-
// hydrated, so nothing else in the client graph reaches it — and CSS is only
16-
// emitted from the client build, so without this its stylesheet would never
17-
// exist. `cssCodeSplit: false` collects it into the page's one stylesheet
18-
// while leaving the JavaScript in a chunk the browser never requests.
19-
shell: () => import('#theme/Layout'),
7+
Banner: () => import('../components/Banner'),
8+
ThemeToggle: () => import('../components/ThemeToggle.jsx'),
9+
SearchBox: () => import('../components/SearchBox'),
10+
SideBar: () => import('#theme/Sidebar'),
2011
};

src/generators/web/ui/islands/runtime.mjs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@ import { h, hydrate } from 'preact';
33

44
import loaders from './loaders.mjs';
55

6+
/**
7+
* Adds the generator's component map to the registry.
8+
*
9+
* @param {Record<string, () => Promise<{ default: import('preact').ComponentType }>>} components
10+
*/
11+
export const registerIslands = components => Object.assign(loaders, components);
12+
613
/**
714
* Re-renders an island's server-rendered children as the markup they already
815
* are. Preact keeps the existing DOM because the HTML is identical, so nothing
@@ -26,7 +33,10 @@ Island.addInitType('preact', async island => {
2633
return;
2734
}
2835

29-
const script = island.querySelector(':scope > script[data-island-props]');
36+
const script = [...island.children].find(child =>
37+
child.matches('script[data-island-props]')
38+
);
39+
3040
const props = script ? JSON.parse(script.textContent) : {};
3141

3242
// Preact hydrates by walking the container's children in order, so the props
@@ -39,5 +49,13 @@ Island.addInitType('preact', async island => {
3949
props.children = h(Slot, { html: slot.innerHTML });
4050
}
4151

42-
hydrate(h(await loader(), props), island);
52+
try {
53+
const { default: Component } = await loader();
54+
55+
hydrate(h(Component, props), island);
56+
} catch (error) {
57+
// is-land awaits this callback, so a rejection would leave the island
58+
// silently stuck: never marked ready, and never reported anywhere.
59+
console.error(`[is-land] "${name}" failed to hydrate`, error);
60+
}
4361
});

src/generators/web/ui/islands/withIsland.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ export default (Component, { name, on }) => {
4545
)}
4646

4747
<Component {...props}>
48-
{children != null && <island-slot>{children}</island-slot>}
48+
{children != null && (
49+
<island-slot defer-hydration>{children}</island-slot>
50+
)}
4951
</Component>
5052
</is-land>
5153
);

src/generators/web/utils/generate.mjs

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,15 +46,7 @@ export const createImportDeclaration = (
4646
};
4747

4848
/**
49-
* The client entry, shared verbatim by every page.
50-
*/
51-
export const CLIENT_PROGRAM = [
52-
createImportDeclaration(null, resolve(ROOT, './ui/index.css')),
53-
createImportDeclaration(null, resolve(ROOT, './ui/islands/runtime.mjs')),
54-
].join('\n');
55-
56-
/**
57-
* Factory function that creates the server program generator.
49+
* Factory function that creates the page programs.
5850
*/
5951
export default () => {
6052
// User-configured components (for JSX-in-MDX), merged with the built-ins.
@@ -102,5 +94,23 @@ export default () => {
10294
].join('\n');
10395
};
10496

105-
return { buildServerProgram };
97+
// The client entry, shared verbatim by every page
98+
const clientProgram = [
99+
createImportDeclaration(null, resolve(ROOT, './ui/index.css')),
100+
101+
createImportDeclaration(
102+
'registerIslands',
103+
resolve(ROOT, './ui/islands/runtime.mjs'),
104+
false
105+
),
106+
107+
`registerIslands({${componentImports
108+
.map(
109+
({ name, source }) =>
110+
`${JSON.stringify(name)}: () => import(${JSON.stringify(source)})`
111+
)
112+
.join(', ')}});`,
113+
].join('\n');
114+
115+
return { buildServerProgram, clientProgram };
106116
};

src/generators/web/utils/processing.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import createConfigSource from './config.mjs';
2-
import createServerBuilder, { CLIENT_PROGRAM } from './generate.mjs';
2+
import createProgramBuilder from './generate.mjs';
33
import { relativeOrAbsolute } from './relativeOrAbsolute.mjs';
44
import getConfig from '../../../utils/configuration/index.mjs';
55
import { populate } from '../../../utils/configuration/templates.mjs';
@@ -94,7 +94,7 @@ export const buildHead = ({ meta = [], links = [], html = [] }) =>
9494
* @returns {{ add: (item: { data: import('../../metadata/types').MetadataEntry, code: string }) => void, serverCodeMap: Map<string, string>, clientCodeMap: Map<string, string> }}
9595
*/
9696
export function createCodeConverter() {
97-
const { buildServerProgram } = createServerBuilder();
97+
const { buildServerProgram, clientProgram } = createProgramBuilder();
9898

9999
const serverCodeMap = new Map();
100100
const clientCodeMap = new Map();
@@ -112,7 +112,7 @@ export function createCodeConverter() {
112112
serverCodeMap.set(fileName, buildServerProgram(code));
113113

114114
// Every page's entry is the same module; the bundler emits one chunk.
115-
clientCodeMap.set(fileName, CLIENT_PROGRAM);
115+
clientCodeMap.set(fileName, clientProgram);
116116
},
117117
serverCodeMap,
118118
clientCodeMap,

0 commit comments

Comments
 (0)