Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
110 changes: 107 additions & 3 deletions packages/waku/src/lib/utils/managed.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,113 @@
import fs from 'node:fs';
import { readdir, readFile } from 'node:fs/promises';
import path from 'node:path';
import { EXTENSIONS, SRC_MIDDLEWARE, SRC_PAGES } from '../constants.js';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove changes in this file.

export const getManagedServerEntry = (srcDir: string) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if I 100% follow the idea, but it shouldn't be managed mode only. So, changing code here seems not ideal.

type ManagedServerEntryMode = 'runtime' | 'build';

const toPosix = (p: string) => p.split(path.sep).join('/');

const GET_CONFIG_EXPORT_PATTERNS = [
/export\s+(?:async\s+)?function\s+getConfig\b/m,
/export\s+(?:const|let|var)\s+getConfig\b/m,
/export\s*\{[^}]*\bgetConfig\b[^}]*\}/m,
];

const HAS_DYNAMIC_RENDER_LITERAL_PATTERN = /render\s*:\s*['"]dynamic['"]/m;
const HAS_STATIC_RENDER_LITERAL_PATTERN = /render\s*:\s*['"]static['"]/m;

const hasGetConfigExport = (code: string) =>
GET_CONFIG_EXPORT_PATTERNS.some((pattern) => pattern.test(code));

const collectPageFiles = async (
dir: string,
baseDir: string,
): Promise<string[]> => {
const entries = await readdir(dir, { withFileTypes: true });
const files: string[] = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...(await collectPageFiles(fullPath, baseDir)));
continue;
}
if (!EXTENSIONS.some((ext) => entry.name.endsWith(ext))) {
continue;
}
files.push(toPosix(path.relative(baseDir, fullPath)));
}
return files;
};

const getRuntimeExcludedPageFiles = async ({
rootDir,
srcDir,
apiDir = '_api',
slicesDir = '_slices',
}: {
rootDir: string;
srcDir: string;
apiDir?: string;
slicesDir?: string;
}) => {
const pagesDir = path.join(rootDir, srcDir, SRC_PAGES);
if (!fs.existsSync(pagesDir)) {
return [] as string[];
}
const files = await collectPageFiles(pagesDir, pagesDir);
const excluded: string[] = [];
for (const file of files) {
const parts = file.split('/');
const stem = parts[parts.length - 1]!.replace(/\.[^.]+$/, '');
if (
parts[0] === apiDir ||
parts[0] === slicesDir ||
stem === '_layout' ||
stem === '_root'
) {
continue;
}
const code = await readFile(path.join(pagesDir, file), 'utf8');
if (!hasGetConfigExport(code)) {
excluded.push(file);
continue;
}
if (HAS_DYNAMIC_RENDER_LITERAL_PATTERN.test(code)) {
continue;
}
if (HAS_STATIC_RENDER_LITERAL_PATTERN.test(code)) {
excluded.push(file);
continue;
}
}
return excluded;
};

export const getManagedServerEntry = async ({
srcDir,
rootDir,
mode,
}: {
srcDir: string;
rootDir: string;
mode: ManagedServerEntryMode;
}) => {
const globBase = `/${srcDir}/${SRC_PAGES}`;
const exts = EXTENSIONS.map((ext) => ext.slice(1)).join(',');
const globPattern = `${globBase}/**/*.{${exts}}`;
const fullPagesGlobPattern = `${globBase}/**/*.{${exts}}`;
let pagesGlob: string | string[] = fullPagesGlobPattern;
if (mode === 'runtime') {
const excludedPageFiles = await getRuntimeExcludedPageFiles({
rootDir,
srcDir,
});
if (excludedPageFiles.length) {
pagesGlob = [
fullPagesGlobPattern,
...excludedPageFiles.map((file) => `!${globBase}/${toPosix(file)}`),
];
}
}
const middlewareGlob = [
`/${srcDir}/${SRC_MIDDLEWARE}/*.{${exts}}`,
`!/${srcDir}/${SRC_MIDDLEWARE}/*.{test,spec}.{${exts}}`,
Expand All @@ -15,7 +119,7 @@ import adapter from 'waku/adapters/default';
export default adapter(
fsRouter(
import.meta.glob(
${JSON.stringify(globPattern)},
${JSON.stringify(pagesGlob)},
{ base: ${JSON.stringify(globBase)} }
)
),
Expand Down
2 changes: 1 addition & 1 deletion packages/waku/src/lib/vite-entries/entry.build.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { createRequire } from 'node:module';
import { pathToFileURL } from 'node:url';
import serverEntry from 'virtual:vite-rsc-waku/server-entry';
import serverEntry from 'virtual:vite-rsc-waku/server-entry-build';
import { INTERNAL_setAllEnv } from '../../server.js';
import type { Unstable_EmitFile } from '../types.js';
import { joinPath } from '../utils/path.js';
Expand Down
2 changes: 1 addition & 1 deletion packages/waku/src/lib/vite-entries/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import serverEntry from 'virtual:vite-rsc-waku/server-entry';
import serverEntry from 'virtual:vite-rsc-waku/server-entry-runtime';
import { INTERNAL_setAllEnv } from '../../server.js';

export { serverEntry as unstable_serverEntry };
Expand Down
50 changes: 44 additions & 6 deletions packages/waku/src/lib/vite-plugins/user-entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,33 @@ import {
} from '../utils/managed.js';

export function userEntriesPlugin({ srcDir }: { srcDir: string }): Plugin {
let rootDir = '';

return {
name: 'waku:vite-plugins:user-entries',
configResolved(config) {
rootDir = config.root;
},
// resolve user entries and fallbacks to "managed mode" if not found.
async resolveId(source, _importer, options) {
if (source === 'virtual:vite-rsc-waku/server-entry') {
return '\0virtual:vite-rsc-waku/server-entry-runtime';
}
if (source === 'virtual:vite-rsc-waku/server-entry-runtime') {
return '\0' + source;
}
if (source === 'virtual:vite-rsc-waku/server-entry-build') {
return '\0' + source;
}
if (source === 'virtual:vite-rsc-waku/server-entry-inner') {
if (source === 'virtual:vite-rsc-waku/server-entry-runtime-inner') {
const resolved = await this.resolve(
`/${srcDir}/${SRC_SERVER_ENTRY}`,
undefined,
options,
);
return resolved ? resolved : '\0' + source;
}
if (source === 'virtual:vite-rsc-waku/server-entry-build-inner') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any difference between -runtime-inner and -build-inner?

const resolved = await this.resolve(
`/${srcDir}/${SRC_SERVER_ENTRY}`,
undefined,
Expand All @@ -30,17 +49,36 @@ export function userEntriesPlugin({ srcDir }: { srcDir: string }): Plugin {
return resolved ? resolved : '\0' + source;
}
},
load(id) {
if (id === '\0virtual:vite-rsc-waku/server-entry') {
async load(id) {
if (id === '\0virtual:vite-rsc-waku/server-entry-runtime') {
return `\
export { default } from 'virtual:vite-rsc-waku/server-entry-inner';
export { default } from 'virtual:vite-rsc-waku/server-entry-runtime-inner';
if (import.meta.hot) {
import.meta.hot.accept()
}
`;
}
if (id === '\0virtual:vite-rsc-waku/server-entry-inner') {
return getManagedServerEntry(srcDir);
if (id === '\0virtual:vite-rsc-waku/server-entry-build') {
return `\
export { default } from 'virtual:vite-rsc-waku/server-entry-build-inner';
if (import.meta.hot) {
import.meta.hot.accept()
}
`;
}
if (id === '\0virtual:vite-rsc-waku/server-entry-runtime-inner') {
return getManagedServerEntry({
srcDir,
rootDir,
mode: 'runtime',
});
}
if (id === '\0virtual:vite-rsc-waku/server-entry-build-inner') {
return getManagedServerEntry({
srcDir,
rootDir,
mode: 'build',
});
}
if (id === '\0virtual:vite-rsc-waku/client-entry') {
return getManagedClientEntry();
Expand Down
10 changes: 10 additions & 0 deletions packages/waku/src/lib/vite-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ declare module 'virtual:vite-rsc-waku/server-entry' {
export default default_;
}

declare module 'virtual:vite-rsc-waku/server-entry-runtime' {
const default_: import('./types.ts').Unstable_ServerEntry;
export default default_;
}

declare module 'virtual:vite-rsc-waku/server-entry-build' {
const default_: import('./types.ts').Unstable_ServerEntry;
export default default_;
}

declare module 'virtual:vite-rsc-waku/client-entry' {}

declare module 'virtual:vite-rsc-waku/build-metadata' {
Expand Down
15 changes: 13 additions & 2 deletions packages/waku/src/router/create-pages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,8 @@ export const createPages = <
};

const definedRouter = unstable_defineRouter({
getConfigs: async () => {
getConfigs: async (context?: { mode?: 'runtime' | 'build' }) => {
const mode = context?.mode || 'runtime';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change here is not nice and not acceptable. only define-router should be responsible to this capability.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing this out!

I've updated the implementation so that runtime/build filtering is handled entirely in define-router.
create-pages.tsx is now mode-agnostic as suggested.

Let me know if this looks better.

await configure();
type ElementSpec = {
isStatic: boolean;
Expand Down Expand Up @@ -893,7 +894,17 @@ export const createPages = <
const pathConfigs = [...routeConfigs, ...apiConfigs]
// Sort routes by priority: "standard routes" -> api routes -> api wildcard routes -> standard wildcard routes
.sort((configA, configB) => routePriorityComparator(configA, configB));
return [...pathConfigs, ...sliceConfigs];
const filteredPathConfigs =
mode === 'runtime'
? pathConfigs.filter(
(config) =>
!(
(config.type === 'route' || config.type === 'api') &&
config.isStatic
),
)
: pathConfigs;
return [...filteredPathConfigs, ...sliceConfigs];
},
});

Expand Down
Loading