Skip to content

refactor: extract generateRscEntry, generateServerEntry, generateClientEntry into entries/ (#253 batch 2)#378

Merged
james-elicx merged 2 commits intomainfrom
refactor/extract-more-entries
Mar 9, 2026
Merged

refactor: extract generateRscEntry, generateServerEntry, generateClientEntry into entries/ (#253 batch 2)#378
james-elicx merged 2 commits intomainfrom
refactor/extract-more-entries

Conversation

@james-elicx
Copy link
Collaborator

Summary

Continues #253 (second batch, following #377).

Extracts three large code-generator functions out of app-dev-server.ts and index.ts into dedicated modules under src/entries/:

New file Function Lines
entries/app-rsc-entry.ts generateRscEntry() ~2 400
entries/pages-server-entry.ts generateServerEntry() ~985
entries/pages-client-entry.ts generateClientEntry() ~85
entries/pages-entry-helpers.ts findFileWithExts() (shared helper) ~20

app-dev-server.ts and index.ts become thin delegation wrappers (re-exports / single-line delegates).

Verification

  • All 10 entry-template snapshot tests pass unchanged (pnpm test tests/entry-templates.test.ts)
  • pnpm run typecheck clean
  • No snapshot diffs

Notes

  • Import paths inside the extracted generators were updated where necessary (./foo../server/foo, config/config-matchers.js../config/config-matchers.js)
  • The body of generateServerEntry was kept with its original 4-space indentation to preserve template-string content exactly

…ntEntry into entries/

Move the three remaining large code generators out of app-dev-server.ts and
index.ts into dedicated modules under src/entries/, matching the pattern
established by PR #377 for generateSsrEntry and generateBrowserEntry.

- entries/app-rsc-entry.ts       — generateRscEntry() (~2400 lines)
- entries/pages-server-entry.ts  — generateServerEntry() (~985 lines)
- entries/pages-client-entry.ts  — generateClientEntry() (~85 lines)
- entries/pages-entry-helpers.ts — shared findFileWithExts() helper

app-dev-server.ts and index.ts become thin re-export/delegation wrappers.
All 10 entry-template snapshot tests pass unchanged.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 9, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@378

commit: afeef13

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original

// JSON.stringify safely escapes quotes, backslashes, and special chars in
// both the route pattern and the absolute file path.
// lgtm[js/bad-code-sanitization]
return ` ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`;

Check warning

Code scanning / CodeQL

Improper code sanitization Medium

Code construction depends on an
improperly sanitized value
.

Copilot Autofix

AI 1 day ago

In general, when constructing JavaScript source code by concatenating/interpolating strings that may contain arbitrary characters, you should escape additional characters beyond what JSON.stringify handles for JavaScript string literals, particularly characters that can break out of <script> tags or cause parsing surprises (like <, >, /, \u2028, \u2029, etc.). This is done by passing the string through an “unsafe character” escaper before injecting it into code.

For this specific code, the best fix with minimal behavior change is:

  1. Introduce a small helper escapeUnsafeChars in this file that replaces problematic characters with safe Unicode escape sequences, using a charMap similar to the background example.
  2. Use this helper to post-process the result of JSON.stringify(absPath) when building the dynamic import expression in loaderEntries. That is, change the line returning the loader entry from
    import(${JSON.stringify(absPath)})
    to
    import(${escapeUnsafeChars(JSON.stringify(absPath))}).
  3. Do not alter the runtime behavior of which modules are imported; we only change how the generated source text is encoded.
  4. The helper can be defined near the top of the file (after imports) and does not require new external dependencies or new imports, since it only uses built-in string/regex functionality.

Lines to change:

  • Add charMap and escapeUnsafeChars after the existing imports (around lines 16–17).
  • Update line 36 to wrap JSON.stringify(absPath) with escapeUnsafeChars(...).
Suggested changeset 1
packages/vinext/src/entries/pages-client-entry.ts

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/packages/vinext/src/entries/pages-client-entry.ts b/packages/vinext/src/entries/pages-client-entry.ts
--- a/packages/vinext/src/entries/pages-client-entry.ts
+++ b/packages/vinext/src/entries/pages-client-entry.ts
@@ -14,6 +14,25 @@
 import { type ResolvedNextConfig } from "../config/next-config.js";
 import { findFileWithExts } from "./pages-entry-helpers.js";
 
+const unsafeCharMap: Record<string, string> = {
+  "<": "\\u003C",
+  ">": "\\u003E",
+  "/": "\\u002F",
+  "\\": "\\\\",
+  "\b": "\\b",
+  "\f": "\\f",
+  "\n": "\\n",
+  "\r": "\\r",
+  "\t": "\\t",
+  "\0": "\\0",
+  "\u2028": "\\u2028",
+  "\u2029": "\\u2029",
+};
+
+function escapeUnsafeChars(str: string): string {
+  return str.replace(/[<>\/\\\b\f\n\r\t\0\u2028\u2029]/g, (ch) => unsafeCharMap[ch] ?? ch);
+}
+
 export async function generateClientEntry(
   pagesDir: string,
   nextConfig: ResolvedNextConfig,
@@ -31,9 +50,10 @@
     const absPath = r.filePath.replace(/\\/g, "/");
     const nextFormatPattern = pagesPatternToNextFormat(r.pattern);
     // JSON.stringify safely escapes quotes, backslashes, and special chars in
-    // both the route pattern and the absolute file path.
+    // both the route pattern and the absolute file path. We additionally
+    // escape characters that could break out of a <script> context.
     // lgtm[js/bad-code-sanitization]
-    return `  ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`;
+    return `  ${JSON.stringify(nextFormatPattern)}: () => import(${escapeUnsafeChars(JSON.stringify(absPath))})`;
   });
 
   const appFileBase = appFilePath?.replace(/\\/g, "/");
EOF
@@ -14,6 +14,25 @@
import { type ResolvedNextConfig } from "../config/next-config.js";
import { findFileWithExts } from "./pages-entry-helpers.js";

const unsafeCharMap: Record<string, string> = {
"<": "\\u003C",
">": "\\u003E",
"/": "\\u002F",
"\\": "\\\\",
"\b": "\\b",
"\f": "\\f",
"\n": "\\n",
"\r": "\\r",
"\t": "\\t",
"\0": "\\0",
"\u2028": "\\u2028",
"\u2029": "\\u2029",
};

function escapeUnsafeChars(str: string): string {
return str.replace(/[<>\/\\\b\f\n\r\t\0\u2028\u2029]/g, (ch) => unsafeCharMap[ch] ?? ch);
}

export async function generateClientEntry(
pagesDir: string,
nextConfig: ResolvedNextConfig,
@@ -31,9 +50,10 @@
const absPath = r.filePath.replace(/\\/g, "/");
const nextFormatPattern = pagesPatternToNextFormat(r.pattern);
// JSON.stringify safely escapes quotes, backslashes, and special chars in
// both the route pattern and the absolute file path.
// both the route pattern and the absolute file path. We additionally
// escape characters that could break out of a <script> context.
// lgtm[js/bad-code-sanitization]
return ` ${JSON.stringify(nextFormatPattern)}: () => import(${JSON.stringify(absPath)})`;
return ` ${JSON.stringify(nextFormatPattern)}: () => import(${escapeUnsafeChars(JSON.stringify(absPath))})`;
});

const appFileBase = appFilePath?.replace(/\\/g, "/");
Copilot is powered by AI and may make mistakes. Always verify output.
@james-elicx
Copy link
Collaborator Author

/bigbonk review

@james-elicx james-elicx marked this pull request as ready for review March 9, 2026 13:42
Copy link
Contributor

@ask-bonk ask-bonk bot left a comment

Choose a reason for hiding this comment

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

Clean mechanical extraction — all import paths correctly updated, delegation wrappers pass the right parameters, and all 10 entry-template snapshot tests pass unchanged (confirmed locally). Typecheck is clean.

CodeQL alert on pages-client-entry.ts:36: False positive. Both interpolated values use JSON.stringify() which safely escapes all special characters. The // lgtm[js/bad-code-sanitization] suppression was carried over from the original code in index.ts.

Minor note (non-blocking): findFileWithExts is now duplicated — the new shared helper in pages-entry-helpers.ts and the original private copy still in index.ts:3217 (used for finding global-error). Could be consolidated in a follow-up.

LGTM.

@ask-bonk
Copy link
Contributor

ask-bonk bot commented Mar 9, 2026

PR #378 has been approved. The extraction is correct — all import paths resolve properly, the delegation wrappers pass the right parameters, snapshot tests and typecheck both pass clean. The CodeQL alert is a false positive (values are safely escaped via JSON.stringify).

github run

@james-elicx james-elicx merged commit 51730b7 into main Mar 9, 2026
25 checks passed
@james-elicx james-elicx deleted the refactor/extract-more-entries branch March 9, 2026 13:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor: Extract template string code generation from index.ts and app-dev-server.ts into separate modules

1 participant