Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fs-router for non-Node envs #575

Merged
merged 9 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading