-
-
Notifications
You must be signed in to change notification settings - Fork 227
feat(plugin-rsc): add import.meta.viteRsc.importAsset API
#1069
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
base: main
Are you sure you want to change the base?
Conversation
Add a new `importAsset` API that allows importing client assets from server
environments (SSR/RSC), returning the asset URL. This provides a more flexible,
specifier-based approach that can eventually replace `loadBootstrapScriptContent`.
API: `importAsset(specifier, options?) => Promise<{ url: string }>`
Features:
- Dev mode with `entry: true`: Uses virtual wrapper with HMR support
- Dev mode with `entry: false`: Returns direct file URL
- Build mode: Returns URL from generated manifest
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Instead of generating a separate __vite_rsc_asset_imports_manifest.js file, include importAssets in the existing __vite_rsc_assets_manifest.js as AssetsManifest.importAssets. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| // Build: use existing assets manifest | ||
| // Use relative ID for stable builds across different machines | ||
| const relativeId = manager.toRelativeId(resolvedId) | ||
| replacement = `(async () => (await import("virtual:vite-rsc/assets-manifest")).default.importAssets[${JSON.stringify(relativeId)}])()` |
Check warning
Code scanning / CodeQL
Improper code sanitization Medium
improperly sanitized value
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 7 days ago
In general, whenever user-influenced strings are embedded into dynamically constructed JavaScript source, you must escape additional “unsafe” characters beyond what JSON.stringify handles for HTML/script contexts, particularly <, >, /, backslash, control characters, and U+2028/U+2029. The referenced pattern uses a small helper (escapeUnsafeChars) applied after JSON.stringify to ensure the resulting string literal is safe even when inlined into <script>.
The best targeted fix here is to introduce a small local escape helper in packages/plugin-rsc/src/plugins/import-asset.ts and apply it to JSON.stringify(relativeId) in the build-mode replacement expression. We will: (1) define a charMap and escapeUnsafeChars function near the top of this file, using the same mapping as in the background, and (2) change line 188 so that it uses escapeUnsafeChars(JSON.stringify(relativeId)). This preserves all existing behavior (the manifest still uses the same key value from relativeId) while ensuring the generated virtual module source cannot contain problematic raw characters in that location.
No new external imports are needed; we can implement the helper with String.prototype.replace. All edits stay within packages/plugin-rsc/src/plugins/import-asset.ts in the provided regions.
-
Copy modified lines R17-R38 -
Copy modified lines R210-R212
| @@ -14,6 +14,28 @@ | ||
| const ASSET_IMPORTS_CLIENT_ENTRY_FALLBACK = | ||
| 'virtual:vite-rsc/asset-imports-client-entry-fallback' | ||
|
|
||
| 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 type AssetImportMeta = { | ||
| resolvedId: string | ||
| sourceEnv: string | ||
| @@ -185,7 +207,9 @@ | ||
| // Build: use existing assets manifest | ||
| // Use relative ID for stable builds across different machines | ||
| const relativeId = manager.toRelativeId(resolvedId) | ||
| replacement = `(async () => (await import("virtual:vite-rsc/assets-manifest")).default.importAssets[${JSON.stringify(relativeId)}])()` | ||
| replacement = `(async () => (await import("virtual:vite-rsc/assets-manifest")).default.importAssets[${escapeUnsafeChars( | ||
| JSON.stringify(relativeId), | ||
| )}])()` | ||
| } | ||
|
|
||
| const [start, end] = match.indices![0]! |
Ensure the client environment has at least one entry when no other entries exist, similar to how import-environment.ts handles non-client environments. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Test that importAsset can replace loadBootstrapScriptContent for loading client entry URLs in both dev and build modes. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Summary
import.meta.viteRsc.importAssetAPI for importing client assets from server environments (SSR/RSC)loadBootstrapScriptContententry: trueoption for HMR support in dev mode via virtual wrapperAPI Signature:
Usage Example:
Test plan
pnpm -C packages/plugin-rsc tsc --noEmit)dist/ssr/__vite_rsc_asset_imports_manifest.jsentry: true(virtual wrapper with HMR)entry: false(direct URL)🤖 Generated with Claude Code