From 862486b8e16ed96a67c87f93632bdeb86f2ee6cc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 27 Jan 2026 00:55:36 +0000 Subject: [PATCH] fix(plugin-rsc): preserve source maps in transform and renderChunk hooks Fixed four plugin hooks that were breaking source maps by modifying code without returning proper source maps: - rsc:vite-client-raw-import (transform) - rsc:virtual:vite-rsc/assets-manifest (renderChunk) - rsc:inject-async-local-storage (transform) - rsc:encryption-key (renderChunk) All hooks now use MagicString to perform code transformations and return both code and map with generateMap({ hires: "boundary" }). Transform hooks include source, id, and includeContent in the map generation. https://claude.ai/code/session_017TyWqNZcteH84BkF2BGMZx --- packages/plugin-rsc/src/plugin.ts | 70 ++++++++++++++++++++++++------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 808eb1032..393d26454 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -326,11 +326,27 @@ export function vitePluginRscMinimal( name: 'rsc:vite-client-raw-import', transform: { order: 'post', - handler(code) { + handler(code, id) { if (code.includes('__vite_rsc_raw_import__')) { // inject dynamic import last to avoid Vite adding `?import` query // to client references (and browser mode server references) - return code.replace('__vite_rsc_raw_import__', 'import') + const s = new MagicString(code) + const needle = '__vite_rsc_raw_import__' + let i = 0 + while (true) { + const start = code.indexOf(needle, i) + if (start === -1) break + s.overwrite(start, start + needle.length, 'import') + i = start + needle.length + } + return { + code: s.toString(), + map: s.generateMap({ + hires: 'boundary', + source: id, + includeContent: true, + }), + } } }, }, @@ -1137,11 +1153,19 @@ export function createRpcClient(params) { BUILD_ASSETS_MANIFEST_NAME, ), ) - code = code.replaceAll( - 'virtual:vite-rsc/assets-manifest', - () => replacement, - ) - return { code } + const s = new MagicString(code) + const needle = 'virtual:vite-rsc/assets-manifest' + let i = 0 + while (true) { + const start = code.indexOf(needle, i) + if (start === -1) break + s.overwrite(start, start + needle.length, replacement) + i = start + needle.length + } + return { + code: s.toString(), + map: s.generateMap({ hires: 'boundary' }), + } } return }, @@ -1273,7 +1297,7 @@ function globalAsyncLocalStoragePlugin(): Plugin[] { { name: 'rsc:inject-async-local-storage', transform: { - handler(code) { + handler(code, id) { if ( (this.environment.name === 'ssr' || this.environment.name === 'rsc') && @@ -1282,13 +1306,21 @@ function globalAsyncLocalStoragePlugin(): Plugin[] { !code.includes('__viteRscAsyncHooks') ) { // for build, we cannot use `import` as it confuses rollup commonjs plugin. - return ( + const s = new MagicString(code) + const prepend = (this.environment.mode === 'build' && !isRolldownVite ? `const __viteRscAsyncHooks = require("node:async_hooks");` : `import * as __viteRscAsyncHooks from "node:async_hooks";`) + - `globalThis.AsyncLocalStorage = __viteRscAsyncHooks.AsyncLocalStorage;` + - code - ) + `globalThis.AsyncLocalStorage = __viteRscAsyncHooks.AsyncLocalStorage;` + s.prepend(prepend) + return { + code: s.toString(), + map: s.generateMap({ + hires: 'boundary', + source: id, + includeContent: true, + }), + } } }, }, @@ -1760,8 +1792,18 @@ function vitePluginDefineEncryptionKey( const replacement = `import(${JSON.stringify( normalizedPath, )}).then(__m => __m.default)` - code = code.replaceAll(KEY_PLACEHOLDER, () => replacement) - return { code } + const s = new MagicString(code) + let i = 0 + while (true) { + const start = code.indexOf(KEY_PLACEHOLDER, i) + if (start === -1) break + s.overwrite(start, start + KEY_PLACEHOLDER.length, replacement) + i = start + KEY_PLACEHOLDER.length + } + return { + code: s.toString(), + map: s.generateMap({ hires: 'boundary' }), + } } }, writeBundle() {