Skip to content

Commit

Permalink
fix: fs-router for non-Node envs (#575)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
dai-shi authored Mar 7, 2024
1 parent 09966e3 commit 2852bd1
Show file tree
Hide file tree
Showing 10 changed files with 276 additions and 157 deletions.
5 changes: 3 additions & 2 deletions e2e/fixtures/rsc-basic/waku.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const DO_NOT_BUNDLE = '';

/** @type {import('waku/config').Config} */
export default {
middleware: (cmd: 'dev' | 'start') => [
...(cmd === 'dev'
? [
import(
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
'waku/middleware/dev-server'
/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server'
),
]
: []),
Expand Down
5 changes: 3 additions & 2 deletions e2e/fixtures/rsc-router/waku.config.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
const DO_NOT_BUNDLE = '';

/** @type {import('waku/config').Config} */
export default {
middleware: (cmd: 'dev' | 'start') => [
...(cmd === 'dev'
? [
import(
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
'waku/middleware/dev-server'
/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server'
),
]
: []),
Expand Down
5 changes: 3 additions & 2 deletions examples/08_cookies/waku.config.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
const DO_NOT_BUNDLE = '';

/** @type {import('waku/config').Config} */
export default {
middleware: (cmd: 'dev' | 'start') => [
import('./src/middleware/cookie.js'),
...(cmd === 'dev'
? [
import(
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
'waku/middleware/dev-server'
/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server'
),
]
: []),
Expand Down
10 changes: 7 additions & 3 deletions packages/waku/src/lib/builder/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import viteReact from '@vitejs/plugin-react';
import type { LoggingFunction, RollupLog } from 'rollup';

import type { Config } from '../../config.js';
import type { EntriesPrd } from '../../server.js';
import type { BuildConfig, EntriesPrd } from '../../server.js';
import type { ResolvedConfig } from '../config.js';
import { resolveConfig } from '../config.js';
import type { PathSpec } from '../utils/path.js';
Expand Down Expand Up @@ -374,7 +374,7 @@ const emitRscFiles = async (
rootDir: string,
config: ResolvedConfig,
distEntries: EntriesPrd,
buildConfig: Awaited<ReturnType<typeof getBuildConfig>>,
buildConfig: BuildConfig,
) => {
const clientModuleMap = new Map<string, Set<string>>();
const addClientModule = (input: string, id: string) => {
Expand Down Expand Up @@ -452,7 +452,7 @@ const emitHtmlFiles = async (
config: ResolvedConfig,
distEntriesFile: string,
distEntries: EntriesPrd,
buildConfig: Awaited<ReturnType<typeof getBuildConfig>>,
buildConfig: BuildConfig,
getClientModules: (input: string) => string[],
) => {
const basePrefix = config.basePath + config.rscPath + '/';
Expand Down Expand Up @@ -639,6 +639,10 @@ export async function build(options: {

const distEntries = await import(filePathToFileURL(distEntriesFile));
const buildConfig = await getBuildConfig({ config, entries: distEntries });
await appendFile(
distEntriesFile,
`export const buildConfig = ${JSON.stringify(buildConfig)};`,
);
const { getClientModules } = await emitRscFiles(
rootDir,
config,
Expand Down
9 changes: 3 additions & 6 deletions packages/waku/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,11 @@ const DEFAULT_HTML_HEAD = `
<meta name="viewport" content="width=device-width, initial-scale=1" />
`.trim();

const DO_NOT_BUNDLE = '';

const DEFAULT_MIDDLEWARE = (cmd: 'dev' | 'start') => [
...(cmd === 'dev'
? [
import(
/* @vite-ignore */ 'DO_NOT_BUNDLE'.slice(Infinity) +
'waku/middleware/dev-server'
),
]
? [import(/* @vite-ignore */ DO_NOT_BUNDLE + 'waku/middleware/dev-server')]
: []),
import('waku/middleware/ssr'),
import('waku/middleware/rsc'),
Expand Down
27 changes: 18 additions & 9 deletions packages/waku/src/lib/renderers/rsc-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,15 @@ export async function renderRsc(
const {
default: { renderEntries },
loadModule,
} = entries as (EntriesDev & { loadModule: undefined }) | EntriesPrd;
buildConfig,
} = entries as
| (EntriesDev & { loadModule: never; buildConfig: never })
| EntriesPrd;

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

const [
{
Expand All @@ -95,7 +98,7 @@ export async function renderRsc(
loadServerModule<{ default: typeof RSDWClientType }>('rsdw-client'),
(isDev
? opts.loadServerModule(SERVER_MODULE_MAP['waku-server'])
: loadModule!('waku-server')) as Promise<{
: loadModule('waku-server')) as Promise<{
setRenderContext: typeof setRenderContextType;
}>,
]);
Expand Down Expand Up @@ -157,7 +160,7 @@ export async function renderRsc(
},
};
const elements = await runWithRenderContext(renderContext, () =>
renderEntries(input, searchParams),
renderEntries(input, { searchParams, buildConfig }),
);
if (elements === null) {
const err = new Error('No function component found');
Expand Down Expand Up @@ -186,7 +189,7 @@ export async function renderRsc(
}
elementsPromise = Promise.all([
elementsPromise,
renderEntries(input, searchParams),
renderEntries(input, { searchParams, buildConfig }),
]).then(([oldElements, newElements]) => ({
...oldElements,
// FIXME we should actually check if newElements is null and send an error
Expand Down Expand Up @@ -240,7 +243,7 @@ export async function renderRsc(
if (!fileId.startsWith('@id/')) {
throw new Error('Unexpected server entry in PRD');
}
mod = await loadModule!(fileId.slice('@id/'.length));
mod = await loadModule(fileId.slice('@id/'.length));
}
const fn = mod[name] || mod;
const elements = await renderWithContextWithAction(context, () =>
Expand Down Expand Up @@ -334,12 +337,18 @@ export async function getSsrConfig(
const {
default: { getSsrConfig },
loadModule,
} = entries as (EntriesDev & { loadModule: undefined }) | EntriesPrd;
buildConfig,
} = entries as
| (EntriesDev & { loadModule: never; buildConfig: never })
| EntriesPrd;
const { renderToReadableStream } = await (isDev
? import(/* @vite-ignore */ SERVER_MODULE_MAP['rsdw-server'])
: loadModule!('rsdw-server').then((m: any) => m.default));
: loadModule('rsdw-server').then((m: any) => m.default));

const ssrConfig = await getSsrConfig?.(pathname, { searchParams });
const ssrConfig = await getSsrConfig?.(pathname, {
searchParams,
buildConfig,
});
if (!ssrConfig) {
return null;
}
Expand Down
94 changes: 69 additions & 25 deletions packages/waku/src/router/create-pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
parsePathWithSlug,
getPathMapping,
} from '../lib/utils/path.js';
import type { BuildConfig } from '../server.js';
import type { PathSpec } from '../lib/utils/path.js';

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

export function createPages(
fn: (fns: {
createPage: CreatePage;
createLayout: CreateLayout;
}) => Promise<void>,
fn: (
fns: {
createPage: CreatePage;
createLayout: CreateLayout;
unstable_setBuildData: (path: string, data: unknown) => void;
},
opts: {
unstable_buildConfig: BuildConfig | undefined;
},
) => Promise<void>,
) {
let configured = false;

// TODO I think there's room for improvement to refactor these structures
const staticPathSet = new Set<PathSpec>();
const staticPathSet = new Set<[string, PathSpec]>();
const dynamicPathMap = new Map<string, [PathSpec, FunctionComponent<any>]>();
const wildcardPathMap = new Map<string, [PathSpec, FunctionComponent<any>]>();
const staticComponentMap = new Map<string, FunctionComponent<any>>();
const noSsrSet = new WeakSet<PathSpec>();
const buildDataMap = new Map<string, unknown>();

const registerStaticComponent = (
id: string,
Expand Down Expand Up @@ -126,7 +134,7 @@ export function createPages(
({ type }) => type === 'wildcard',
).length;
if (page.render === 'static' && numSlugs === 0) {
staticPathSet.add(pathSpec);
staticPathSet.add([page.path, pathSpec]);
const id = joinPath(page.path, 'page').replace(/^\//, '');
registerStaticComponent(id, page.component);
} else if (page.render === 'static' && numSlugs > 0 && numWildcards === 0) {
Expand All @@ -151,7 +159,10 @@ export function createPages(
}
return name;
});
staticPathSet.add(pathItems.map((name) => ({ type: 'literal', name })));
staticPathSet.add([
page.path,
pathItems.map((name) => ({ type: 'literal', name })),
]);
const id = joinPath(...pathItems, 'page');
const WrappedComponent = (props: Record<string, unknown>) =>
createElement(page.component as any, { ...props, ...mapping });
Expand Down Expand Up @@ -180,33 +191,66 @@ export function createPages(
registerStaticComponent(id, layout.component);
};

const ready = fn({ createPage, createLayout }).then(() => {
configured = true;
});
const unstable_setBuildData = (path: string, data: unknown) => {
buildDataMap.set(path, data);
};

let ready: Promise<void> | undefined;
const configure = async (buildConfig?: BuildConfig) => {
if (!configured && !ready) {
ready = fn(
{ createPage, createLayout, unstable_setBuildData },
{ unstable_buildConfig: buildConfig },
);
await ready;
configured = true;
}
await ready;
};

return defineRouter(
async () => {
await ready;
const paths: { path: PathSpec; isStatic: boolean; noSsr: boolean }[] = [];
for (const pathSpec of staticPathSet) {
await configure();
const paths: {
path: PathSpec;
isStatic: boolean;
noSsr: boolean;
data: unknown;
}[] = [];
for (const [path, pathSpec] of staticPathSet) {
const noSsr = noSsrSet.has(pathSpec);
paths.push({ path: pathSpec, isStatic: true, noSsr });
paths.push({
path: pathSpec,
isStatic: true,
noSsr,
data: buildDataMap.get(path),
});
}
for (const [pathSpec] of dynamicPathMap.values()) {
for (const [path, [pathSpec]] of dynamicPathMap) {
const noSsr = noSsrSet.has(pathSpec);
paths.push({ path: pathSpec, isStatic: false, noSsr });
paths.push({
path: pathSpec,
isStatic: false,
noSsr,
data: buildDataMap.get(path),
});
}
for (const [pathSpec] of wildcardPathMap.values()) {
for (const [path, [pathSpec]] of wildcardPathMap) {
const noSsr = noSsrSet.has(pathSpec);
paths.push({ path: pathSpec, isStatic: false, noSsr });
paths.push({
path: pathSpec,
isStatic: false,
noSsr,
data: buildDataMap.get(path),
});
}
return paths;
},
async (id, setShouldSkip) => {
await ready;
async (id, { unstable_setShouldSkip, unstable_buildConfig }) => {
await configure(unstable_buildConfig);
const staticComponent = staticComponentMap.get(id);
if (staticComponent) {
setShouldSkip({});
unstable_setShouldSkip({});
return staticComponent;
}
for (const [pathSpec, Component] of dynamicPathMap.values()) {
Expand All @@ -216,12 +260,12 @@ export function createPages(
);
if (mapping) {
if (Object.keys(mapping).length === 0) {
setShouldSkip();
unstable_setShouldSkip();
return Component;
}
const WrappedComponent = (props: Record<string, unknown>) =>
createElement(Component, { ...props, ...mapping });
setShouldSkip();
unstable_setShouldSkip();
return WrappedComponent;
}
}
Expand All @@ -233,11 +277,11 @@ export function createPages(
if (mapping) {
const WrappedComponent = (props: Record<string, unknown>) =>
createElement(Component, { ...props, ...mapping });
setShouldSkip();
unstable_setShouldSkip();
return WrappedComponent;
}
}
setShouldSkip({}); // negative cache
unstable_setShouldSkip({}); // negative cache
return null; // not found
},
);
Expand Down
Loading

0 comments on commit 2852bd1

Please sign in to comment.