Skip to content

Commit

Permalink
feat: separate build data into files (#1181)
Browse files Browse the repository at this point in the history
Previously, it was a single JSON object. Separating it into multiple
files theoretically enables lazy loading.
  • Loading branch information
dai-shi authored Jan 22, 2025
1 parent 437a89f commit 9e39b40
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 22 deletions.
35 changes: 33 additions & 2 deletions packages/waku/src/lib/builder/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@ import type { LoggingFunction, RollupLog } from 'rollup';
import type { ReactNode } from 'react';

import type { Config } from '../../config.js';
import { setAllEnvInternal, unstable_getPlatformObject } from '../../server.js';
import {
setAllEnvInternal,
iterateAllPlatformDataInternal,
unstable_getPlatformObject,
} from '../../server.js';
import type { EntriesPrd } from '../types.js';
import type { ResolvedConfig } from '../config.js';
import { resolveConfig } from '../config.js';
Expand Down Expand Up @@ -763,9 +767,36 @@ export async function build(options: {
delete platformObject.buildOptions.unstable_phase;

if (existsSync(distEntriesFile)) {
const DIST_PLATFORM_DATA = 'platform-data';
const keys = new Set<string>();
await mkdir(joinPath(rootDir, config.distDir, DIST_PLATFORM_DATA), {
recursive: true,
});
for (const [key, data] of iterateAllPlatformDataInternal()) {
keys.add(key);
const destFile = joinPath(
rootDir,
config.distDir,
DIST_PLATFORM_DATA,
key + '.js',
);
await writeFile(destFile, `export default ${JSON.stringify(data)};`);
}
await appendFile(
distEntriesFile,
`export const buildData = ${JSON.stringify(platformObject.buildData)};`,
`
export function loadPlatformData(key) {
switch (key) {
${Array.from(keys)
.map(
(k) =>
`case '${k}': return import('./${DIST_PLATFORM_DATA}/${k}.js').then((m) => m.default);`,
)
.join('\n')}
default: throw new Error('Cannot find platform data: ' + key);
}
}
`,
);
}
}
9 changes: 6 additions & 3 deletions packages/waku/src/lib/middleware/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { ReactNode } from 'react';

import { resolveConfig, extractPureConfig } from '../config.js';
import type { PureConfig } from '../config.js';
import { setAllEnvInternal, unstable_getPlatformObject } from '../../server.js';
import {
setAllEnvInternal,
setPlatformDataLoaderInternal,
} from '../../server.js';
import type { HandleRequest, HandlerRes } from '../types.js';
import type { Middleware, HandlerContext } from './types.js';
import { renderRsc, decodeBody, decodePostAction } from '../renderers/rsc.js';
Expand Down Expand Up @@ -76,8 +79,8 @@ export const handler: Middleware = (options) => {
const configPromise =
options.cmd === 'start'
? entriesPromise.then(async (entries) => {
if (entries.buildData) {
unstable_getPlatformObject().buildData = entries.buildData;
if (entries.loadPlatformData) {
setPlatformDataLoaderInternal(entries.loadPlatformData);
}
return resolveConfig(await entries.loadConfig());
})
Expand Down
32 changes: 30 additions & 2 deletions packages/waku/src/lib/plugins/vite-plugin-deploy-cloudflare.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ import { randomBytes } from 'node:crypto';

import type { Plugin } from 'vite';

import { unstable_getPlatformObject } from '../../server.js';
import {
iterateAllPlatformDataInternal,
unstable_getPlatformObject,
} from '../../server.js';
import { SRC_ENTRIES } from '../constants.js';
import { DIST_ENTRIES_JS, DIST_PUBLIC } from '../builder/constants.js';

Expand Down Expand Up @@ -207,9 +210,34 @@ export function deployCloudflarePlugin(opts: {
functionDir: workerDistDir,
});

const DIST_PLATFORM_DATA = 'platform-data';
const keys = new Set<string>();
mkdirSync(path.join(workerDistDir, DIST_PLATFORM_DATA), {
recursive: true,
});
for (const [key, data] of iterateAllPlatformDataInternal()) {
keys.add(key);
const destFile = path.join(
workerDistDir,
DIST_PLATFORM_DATA,
key + '.js',
);
writeFileSync(destFile, `export default ${JSON.stringify(data)};`);
}
appendFileSync(
path.join(workerDistDir, DIST_ENTRIES_JS),
`export const buildData = ${JSON.stringify(platformObject.buildData)};`,
`
export function loadPlatformData(key) {
switch (key) {
${Array.from(keys)
.map(
(k) => `case '${k}': return import('./${DIST_PLATFORM_DATA}/${k}.js');`,
)
.join('\n')}
default: throw new Error('Cannot find platform data: ' + key);
}
}
`,
);

const wranglerTomlFile = path.join(rootDir, 'wrangler.toml');
Expand Down
2 changes: 1 addition & 1 deletion packages/waku/src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export type EntriesPrd = EntriesDev & {
loadModule: (id: string) => Promise<unknown>;
dynamicHtmlPaths: [pathSpec: PathSpec, htmlHead: string][];
publicIndexHtml: string;
buildData?: Record<string, unknown>; // must be JSON serializable
loadPlatformData?: (key: string) => Promise<unknown>;
};

export type HandlerReq = {
Expand Down
11 changes: 6 additions & 5 deletions packages/waku/src/router/define-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import { createElement } from 'react';
import type { ReactNode } from 'react';

import {
unstable_getPlatformObject,
unstable_getPlatformData,
unstable_setPlatformData,
unstable_createAsyncIterable as createAsyncIterable,
} from '../server.js';
import { unstable_defineEntries as defineEntries } from '../minimal/server.js';
Expand Down Expand Up @@ -112,7 +113,6 @@ export function unstable_defineRouter(fns: {
status?: number;
}>;
}) {
const platformObject = unstable_getPlatformObject();
type MyPathConfig = {
pathSpec: PathSpec;
pathname: string | undefined;
Expand All @@ -127,7 +127,9 @@ export function unstable_defineRouter(fns: {
}[];
let cachedPathConfig: MyPathConfig | undefined;
const getMyPathConfig = async (): Promise<MyPathConfig> => {
const pathConfig = platformObject.buildData?.defineRouterPathConfigs;
const pathConfig = await unstable_getPlatformData(
'defineRouterPathConfigs',
);
if (pathConfig) {
return pathConfig as MyPathConfig;
}
Expand Down Expand Up @@ -432,8 +434,7 @@ globalThis.__WAKU_ROUTER_PREFETCH__ = (path) => {
});
}

platformObject.buildData ||= {};
platformObject.buildData.defineRouterPathConfigs = pathConfig;
await unstable_setPlatformData('defineRouterPathConfigs', pathConfig);
return tasks;
});

Expand Down
12 changes: 7 additions & 5 deletions packages/waku/src/router/fs-router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { unstable_getPlatformObject } from '../server.js';
import {
unstable_getPlatformData,
unstable_setPlatformData,
unstable_getPlatformObject,
} from '../server.js';
import { createPages, METHODS } from './create-pages.js';
import type { Method } from './create-pages.js';

Expand All @@ -14,8 +18,7 @@ export function fsRouter(
const platformObject = unstable_getPlatformObject();
return createPages(
async ({ createPage, createLayout, createRoot, createApi }) => {
let files: string[] | undefined = platformObject.buildData
?.fsRouterFiles as string[] | undefined;
let files = await unstable_getPlatformData<string[]>('fsRouterFiles');
if (!files) {
// dev and build only
const [
Expand Down Expand Up @@ -56,8 +59,7 @@ export function fsRouter(
}
// build only - skip in dev
if (platformObject.buildOptions?.unstable_phase) {
platformObject.buildData ||= {};
platformObject.buildData.fsRouterFiles = files;
await unstable_setPlatformData('fsRouterFiles', files);
}
for (const file of files) {
const mod = await loadPage(file);
Expand Down
46 changes: 42 additions & 4 deletions packages/waku/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { getContext } from './middleware/context.js';

// The use of `globalThis` in this file is more or less a hack.
// It should be revisited with a better solution.

/**
* This is an internal function and not for public use.
*/
Expand All @@ -11,12 +14,49 @@ export function getEnv(key: string): string | undefined {
return (globalThis as any).__WAKU_SERVER_ENV__?.[key];
}

/**
* This is an internal function and not for public use.
*/
export function iterateAllPlatformDataInternal(): Iterable<[string, unknown]> {
const platformData: Record<string, unknown> =
(globalThis as any).__WAKU_SERVER_PLATFORM_DATA__ || {};
return Object.entries(platformData);
}

/**
* This is an internal function and not for public use.
*/
export function setPlatformDataLoaderInternal(
loader: (key: string) => Promise<unknown>,
): void {
(globalThis as any).__WAKU_SERVER_PLATFORM_DATA_LOADER__ = loader;
}

// data must be JSON serializable
export async function unstable_setPlatformData<T>(
key: string,
data: T,
): Promise<void> {
((globalThis as any).__WAKU_SERVER_PLATFORM_DATA__ ||= {})[key] = data;
}

export async function unstable_getPlatformData<T>(
key: string,
): Promise<T | undefined> {
const loader: ((key: string) => Promise<unknown>) | undefined = (
globalThis as any
).__WAKU_SERVER_PLATFORM_DATA_LOADER__;
if (loader) {
return loader(key) as T;
}
return (globalThis as any).__WAKU_SERVER_PLATFORM_DATA__?.[key];
}

export function unstable_getHeaders(): Readonly<Record<string, string>> {
return getContext().req.headers;
}

type PlatformObject = {
buildData?: Record<string, unknown>; // must be JSON serializable
buildOptions?: {
deploy?:
| 'vercel-static'
Expand All @@ -38,11 +78,9 @@ type PlatformObject = {
};
} & Record<string, unknown>;

(globalThis as any).__WAKU_PLATFORM_OBJECT__ ||= {};

// TODO tentative name
export function unstable_getPlatformObject(): PlatformObject {
return (globalThis as any).__WAKU_PLATFORM_OBJECT__;
return ((globalThis as any).__WAKU_PLATFORM_OBJECT__ ||= {});
}

export function unstable_createAsyncIterable<T extends () => unknown>(
Expand Down

0 comments on commit 9e39b40

Please sign in to comment.