Skip to content

Commit 2852bd1

Browse files
authored
fix: fs-router for non-Node envs (#575)
* breaking: change renderEntries signature * buildConfig in entries.js * rewrite defineRouter * fix createPages * wip: oh no!!! this approach does not work. * a hacky solution and far from ideal, but works? * fix regex for windows * do not bundle hack
1 parent 09966e3 commit 2852bd1

File tree

10 files changed

+276
-157
lines changed

10 files changed

+276
-157
lines changed

e2e/fixtures/rsc-basic/waku.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
const DO_NOT_BUNDLE = '';
2+
13
/** @type {import('waku/config').Config} */
24
export default {
35
middleware: (cmd: 'dev' | 'start') => [
46
...(cmd === 'dev'
57
? [
68
import(
7-
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
8-
'waku/middleware/dev-server'
9+
/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server'
910
),
1011
]
1112
: []),

e2e/fixtures/rsc-router/waku.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
const DO_NOT_BUNDLE = '';
2+
13
/** @type {import('waku/config').Config} */
24
export default {
35
middleware: (cmd: 'dev' | 'start') => [
46
...(cmd === 'dev'
57
? [
68
import(
7-
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
8-
'waku/middleware/dev-server'
9+
/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server'
910
),
1011
]
1112
: []),

examples/08_cookies/waku.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1+
const DO_NOT_BUNDLE = '';
2+
13
/** @type {import('waku/config').Config} */
24
export default {
35
middleware: (cmd: 'dev' | 'start') => [
46
import('./src/middleware/cookie.js'),
57
...(cmd === 'dev'
68
? [
79
import(
8-
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
9-
'waku/middleware/dev-server'
10+
/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server'
1011
),
1112
]
1213
: []),

packages/waku/src/lib/builder/build.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import viteReact from '@vitejs/plugin-react';
66
import type { LoggingFunction, RollupLog } from 'rollup';
77

88
import type { Config } from '../../config.js';
9-
import type { EntriesPrd } from '../../server.js';
9+
import type { BuildConfig, EntriesPrd } from '../../server.js';
1010
import type { ResolvedConfig } from '../config.js';
1111
import { resolveConfig } from '../config.js';
1212
import type { PathSpec } from '../utils/path.js';
@@ -374,7 +374,7 @@ const emitRscFiles = async (
374374
rootDir: string,
375375
config: ResolvedConfig,
376376
distEntries: EntriesPrd,
377-
buildConfig: Awaited<ReturnType<typeof getBuildConfig>>,
377+
buildConfig: BuildConfig,
378378
) => {
379379
const clientModuleMap = new Map<string, Set<string>>();
380380
const addClientModule = (input: string, id: string) => {
@@ -452,7 +452,7 @@ const emitHtmlFiles = async (
452452
config: ResolvedConfig,
453453
distEntriesFile: string,
454454
distEntries: EntriesPrd,
455-
buildConfig: Awaited<ReturnType<typeof getBuildConfig>>,
455+
buildConfig: BuildConfig,
456456
getClientModules: (input: string) => string[],
457457
) => {
458458
const basePrefix = config.basePath + config.rscPath + '/';
@@ -639,6 +639,10 @@ export async function build(options: {
639639

640640
const distEntries = await import(filePathToFileURL(distEntriesFile));
641641
const buildConfig = await getBuildConfig({ config, entries: distEntries });
642+
await appendFile(
643+
distEntriesFile,
644+
`export const buildConfig = ${JSON.stringify(buildConfig)};`,
645+
);
642646
const { getClientModules } = await emitRscFiles(
643647
rootDir,
644648
config,

packages/waku/src/lib/config.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,11 @@ const DEFAULT_HTML_HEAD = `
1313
<meta name="viewport" content="width=device-width, initial-scale=1" />
1414
`.trim();
1515

16+
const DO_NOT_BUNDLE = '';
17+
1618
const DEFAULT_MIDDLEWARE = (cmd: 'dev' | 'start') => [
1719
...(cmd === 'dev'
18-
? [
19-
import(
20-
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
21-
'waku/middleware/dev-server'
22-
),
23-
]
20+
? [import(/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server')]
2421
: []),
2522
import('waku/middleware/ssr'),
2623
import('waku/middleware/rsc'),

packages/waku/src/lib/renderers/rsc-renderer.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,12 +71,15 @@ export async function renderRsc(
7171
const {
7272
default: { renderEntries },
7373
loadModule,
74-
} = entries as (EntriesDev & { loadModule: undefined }) | EntriesPrd;
74+
buildConfig,
75+
} = entries as
76+
| (EntriesDev & { loadModule: never; buildConfig: never })
77+
| EntriesPrd;
7578

7679
const loadServerModule = <T>(key: keyof typeof SERVER_MODULE_MAP) =>
7780
(isDev
7881
? import(/* @vite-ignore */ SERVER_MODULE_MAP[key])
79-
: loadModule!(key)) as Promise<T>;
82+
: loadModule(key)) as Promise<T>;
8083

8184
const [
8285
{
@@ -95,7 +98,7 @@ export async function renderRsc(
9598
loadServerModule<{ default: typeof RSDWClientType }>('rsdw-client'),
9699
(isDev
97100
? opts.loadServerModule(SERVER_MODULE_MAP['waku-server'])
98-
: loadModule!('waku-server')) as Promise<{
101+
: loadModule('waku-server')) as Promise<{
99102
setRenderContext: typeof setRenderContextType;
100103
}>,
101104
]);
@@ -157,7 +160,7 @@ export async function renderRsc(
157160
},
158161
};
159162
const elements = await runWithRenderContext(renderContext, () =>
160-
renderEntries(input, searchParams),
163+
renderEntries(input, { searchParams, buildConfig }),
161164
);
162165
if (elements === null) {
163166
const err = new Error('No function component found');
@@ -186,7 +189,7 @@ export async function renderRsc(
186189
}
187190
elementsPromise = Promise.all([
188191
elementsPromise,
189-
renderEntries(input, searchParams),
192+
renderEntries(input, { searchParams, buildConfig }),
190193
]).then(([oldElements, newElements]) => ({
191194
...oldElements,
192195
// FIXME we should actually check if newElements is null and send an error
@@ -240,7 +243,7 @@ export async function renderRsc(
240243
if (!fileId.startsWith('@id/')) {
241244
throw new Error('Unexpected server entry in PRD');
242245
}
243-
mod = await loadModule!(fileId.slice('@id/'.length));
246+
mod = await loadModule(fileId.slice('@id/'.length));
244247
}
245248
const fn = mod[name] || mod;
246249
const elements = await renderWithContextWithAction(context, () =>
@@ -334,12 +337,18 @@ export async function getSsrConfig(
334337
const {
335338
default: { getSsrConfig },
336339
loadModule,
337-
} = entries as (EntriesDev & { loadModule: undefined }) | EntriesPrd;
340+
buildConfig,
341+
} = entries as
342+
| (EntriesDev & { loadModule: never; buildConfig: never })
343+
| EntriesPrd;
338344
const { renderToReadableStream } = await (isDev
339345
? import(/* @vite-ignore */ SERVER_MODULE_MAP['rsdw-server'])
340-
: loadModule!('rsdw-server').then((m: any) => m.default));
346+
: loadModule('rsdw-server').then((m: any) => m.default));
341347

342-
const ssrConfig = await getSsrConfig?.(pathname, { searchParams });
348+
const ssrConfig = await getSsrConfig?.(pathname, {
349+
searchParams,
350+
buildConfig,
351+
});
343352
if (!ssrConfig) {
344353
return null;
345354
}

packages/waku/src/router/create-pages.ts

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
parsePathWithSlug,
99
getPathMapping,
1010
} from '../lib/utils/path.js';
11+
import type { BuildConfig } from '../server.js';
1112
import type { PathSpec } from '../lib/utils/path.js';
1213

1314
// createPages API (a wrapper around unstable_defineRouter)
@@ -86,19 +87,26 @@ type CreateLayout = <T extends string>(layout: {
8687
}) => void;
8788

8889
export function createPages(
89-
fn: (fns: {
90-
createPage: CreatePage;
91-
createLayout: CreateLayout;
92-
}) => Promise<void>,
90+
fn: (
91+
fns: {
92+
createPage: CreatePage;
93+
createLayout: CreateLayout;
94+
unstable_setBuildData: (path: string, data: unknown) => void;
95+
},
96+
opts: {
97+
unstable_buildConfig: BuildConfig | undefined;
98+
},
99+
) => Promise<void>,
93100
) {
94101
let configured = false;
95102

96103
// TODO I think there's room for improvement to refactor these structures
97-
const staticPathSet = new Set<PathSpec>();
104+
const staticPathSet = new Set<[string, PathSpec]>();
98105
const dynamicPathMap = new Map<string, [PathSpec, FunctionComponent<any>]>();
99106
const wildcardPathMap = new Map<string, [PathSpec, FunctionComponent<any>]>();
100107
const staticComponentMap = new Map<string, FunctionComponent<any>>();
101108
const noSsrSet = new WeakSet<PathSpec>();
109+
const buildDataMap = new Map<string, unknown>();
102110

103111
const registerStaticComponent = (
104112
id: string,
@@ -126,7 +134,7 @@ export function createPages(
126134
({ type }) => type === 'wildcard',
127135
).length;
128136
if (page.render === 'static' && numSlugs === 0) {
129-
staticPathSet.add(pathSpec);
137+
staticPathSet.add([page.path, pathSpec]);
130138
const id = joinPath(page.path, 'page').replace(/^\//, '');
131139
registerStaticComponent(id, page.component);
132140
} else if (page.render === 'static' && numSlugs > 0 && numWildcards === 0) {
@@ -151,7 +159,10 @@ export function createPages(
151159
}
152160
return name;
153161
});
154-
staticPathSet.add(pathItems.map((name) => ({ type: 'literal', name })));
162+
staticPathSet.add([
163+
page.path,
164+
pathItems.map((name) => ({ type: 'literal', name })),
165+
]);
155166
const id = joinPath(...pathItems, 'page');
156167
const WrappedComponent = (props: Record<string, unknown>) =>
157168
createElement(page.component as any, { ...props, ...mapping });
@@ -180,33 +191,66 @@ export function createPages(
180191
registerStaticComponent(id, layout.component);
181192
};
182193

183-
const ready = fn({ createPage, createLayout }).then(() => {
184-
configured = true;
185-
});
194+
const unstable_setBuildData = (path: string, data: unknown) => {
195+
buildDataMap.set(path, data);
196+
};
197+
198+
let ready: Promise<void> | undefined;
199+
const configure = async (buildConfig?: BuildConfig) => {
200+
if (!configured && !ready) {
201+
ready = fn(
202+
{ createPage, createLayout, unstable_setBuildData },
203+
{ unstable_buildConfig: buildConfig },
204+
);
205+
await ready;
206+
configured = true;
207+
}
208+
await ready;
209+
};
186210

187211
return defineRouter(
188212
async () => {
189-
await ready;
190-
const paths: { path: PathSpec; isStatic: boolean; noSsr: boolean }[] = [];
191-
for (const pathSpec of staticPathSet) {
213+
await configure();
214+
const paths: {
215+
path: PathSpec;
216+
isStatic: boolean;
217+
noSsr: boolean;
218+
data: unknown;
219+
}[] = [];
220+
for (const [path, pathSpec] of staticPathSet) {
192221
const noSsr = noSsrSet.has(pathSpec);
193-
paths.push({ path: pathSpec, isStatic: true, noSsr });
222+
paths.push({
223+
path: pathSpec,
224+
isStatic: true,
225+
noSsr,
226+
data: buildDataMap.get(path),
227+
});
194228
}
195-
for (const [pathSpec] of dynamicPathMap.values()) {
229+
for (const [path, [pathSpec]] of dynamicPathMap) {
196230
const noSsr = noSsrSet.has(pathSpec);
197-
paths.push({ path: pathSpec, isStatic: false, noSsr });
231+
paths.push({
232+
path: pathSpec,
233+
isStatic: false,
234+
noSsr,
235+
data: buildDataMap.get(path),
236+
});
198237
}
199-
for (const [pathSpec] of wildcardPathMap.values()) {
238+
for (const [path, [pathSpec]] of wildcardPathMap) {
200239
const noSsr = noSsrSet.has(pathSpec);
201-
paths.push({ path: pathSpec, isStatic: false, noSsr });
240+
paths.push({
241+
path: pathSpec,
242+
isStatic: false,
243+
noSsr,
244+
data: buildDataMap.get(path),
245+
});
202246
}
203247
return paths;
204248
},
205-
async (id, setShouldSkip) => {
206-
await ready;
249+
async (id, { unstable_setShouldSkip, unstable_buildConfig }) => {
250+
await configure(unstable_buildConfig);
207251
const staticComponent = staticComponentMap.get(id);
208252
if (staticComponent) {
209-
setShouldSkip({});
253+
unstable_setShouldSkip({});
210254
return staticComponent;
211255
}
212256
for (const [pathSpec, Component] of dynamicPathMap.values()) {
@@ -216,12 +260,12 @@ export function createPages(
216260
);
217261
if (mapping) {
218262
if (Object.keys(mapping).length === 0) {
219-
setShouldSkip();
263+
unstable_setShouldSkip();
220264
return Component;
221265
}
222266
const WrappedComponent = (props: Record<string, unknown>) =>
223267
createElement(Component, { ...props, ...mapping });
224-
setShouldSkip();
268+
unstable_setShouldSkip();
225269
return WrappedComponent;
226270
}
227271
}
@@ -233,11 +277,11 @@ export function createPages(
233277
if (mapping) {
234278
const WrappedComponent = (props: Record<string, unknown>) =>
235279
createElement(Component, { ...props, ...mapping });
236-
setShouldSkip();
280+
unstable_setShouldSkip();
237281
return WrappedComponent;
238282
}
239283
}
240-
setShouldSkip({}); // negative cache
284+
unstable_setShouldSkip({}); // negative cache
241285
return null; // not found
242286
},
243287
);

0 commit comments

Comments
 (0)