From 2adfad2d4360ff4b6598717cf571fcfacfc12eb9 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 3 Feb 2026 16:01:06 +0900 Subject: [PATCH 1/6] refactor(plugin-rsc): add hook filters --- packages/plugin-rsc/package.json | 1 + packages/plugin-rsc/src/core/plugin.ts | 1 + packages/plugin-rsc/src/plugin.ts | 29 +++++++++++++++++++ packages/plugin-rsc/src/plugins/cjs.ts | 4 +++ .../src/plugins/import-environment.ts | 3 ++ .../src/plugins/resolved-id-proxy.ts | 3 +- packages/plugin-rsc/src/plugins/utils.ts | 14 ++++++--- .../plugin-rsc/src/plugins/validate-import.ts | 6 ++++ pnpm-lock.yaml | 3 ++ 9 files changed, 59 insertions(+), 5 deletions(-) diff --git a/packages/plugin-rsc/package.json b/packages/plugin-rsc/package.json index f411c2e13..eb0116b92 100644 --- a/packages/plugin-rsc/package.json +++ b/packages/plugin-rsc/package.json @@ -39,6 +39,7 @@ "prepack": "tsdown" }, "dependencies": { + "@rolldown/pluginutils": "1.0.0-rc.2", "es-module-lexer": "^2.0.0", "estree-walker": "^3.0.3", "magic-string": "^0.30.21", diff --git a/packages/plugin-rsc/src/core/plugin.ts b/packages/plugin-rsc/src/core/plugin.ts index d36443aa8..b7a408b76 100644 --- a/packages/plugin-rsc/src/core/plugin.ts +++ b/packages/plugin-rsc/src/core/plugin.ts @@ -5,6 +5,7 @@ export default function vitePluginRscCore(): Plugin[] { { name: 'rsc:patch-react-server-dom-webpack', transform: { + filter: { code: '__webpack_require__' }, handler(originalCode, _id, _options) { let code = originalCode if (code.includes('__webpack_require__.u')) { diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 27286f42d..2f93e8088 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -4,6 +4,7 @@ import { createRequire } from 'node:module' import path from 'node:path' import { pathToFileURL } from 'node:url' import { createDebug } from '@hiogawa/utils' +import { exactRegex, prefixRegex } from '@rolldown/pluginutils' import * as esModuleLexer from 'es-module-lexer' import MagicString from 'magic-string' import { toNodeHandler } from 'srvx/node' @@ -326,6 +327,7 @@ export function vitePluginRscMinimal( name: 'rsc:vite-client-raw-import', transform: { order: 'post', + filter: { code: '__vite_rsc_raw_import__' }, handler(code) { if (code.includes('__vite_rsc_raw_import__')) { // inject dynamic import last to avoid Vite adding `?import` query @@ -343,6 +345,7 @@ export function vitePluginRscMinimal( name: 'rsc:reference-validation', apply: 'serve', load: { + filter: { id: prefixRegex('\0virtual:vite-rsc/reference-validation?') }, handler(id, _options) { if (id.startsWith('\0virtual:vite-rsc/reference-validation?')) { const parsed = parseReferenceValidationVirtual(id) @@ -824,6 +827,7 @@ export default function vitePluginRsc( name: 'rsc:react-server-dom-webpack-alias', resolveId: { order: 'pre', + filter: { id: prefixRegex(`${PKG_NAME}/vendor/react-server-dom/`) }, async handler(source, importer, options) { if ( hasReactServerDomWebpack && @@ -848,6 +852,7 @@ export default function vitePluginRsc( // - (build) rewriting to external `import("..//.js")` name: 'rsc:load-environment-module', transform: { + filter: { code: 'import.meta.viteRsc.loadModule' }, async handler(code) { if (!code.includes('import.meta.viteRsc.loadModule')) return const { server } = manager @@ -996,6 +1001,7 @@ export default function vitePluginRsc( { name: 'rsc:virtual:vite-rsc/rpc-client', resolveId: { + filter: { id: exactRegex('virtual:vite-rsc/rpc-client') }, handler(source) { if (source === 'virtual:vite-rsc/rpc-client') { return `\0${source}` @@ -1003,6 +1009,7 @@ export default function vitePluginRsc( }, }, load: { + filter: { id: exactRegex('\0virtual:vite-rsc/rpc-client') }, handler(id) { if (id === '\0virtual:vite-rsc/rpc-client') { const { server } = manager @@ -1026,6 +1033,7 @@ export function createRpcClient(params) { { name: 'rsc:virtual:vite-rsc/assets-manifest', resolveId: { + filter: { id: exactRegex('virtual:vite-rsc/assets-manifest') }, handler(source) { if (source === 'virtual:vite-rsc/assets-manifest') { if (this.environment.mode === 'build') { @@ -1036,6 +1044,7 @@ export function createRpcClient(params) { }, }, load: { + filter: { id: exactRegex('\0virtual:vite-rsc/assets-manifest') }, handler(id) { if (id === '\0virtual:vite-rsc/assets-manifest') { assert(this.environment.name !== 'client') @@ -1168,6 +1177,7 @@ export default assetsManifest.bootstrapScriptContent; { name: 'rsc:bootstrap-script-content', transform: { + filter: { code: 'loadBootstrapScriptContent' }, async handler(code) { if ( !code.includes('loadBootstrapScriptContent') || @@ -1287,6 +1297,7 @@ function globalAsyncLocalStoragePlugin(): Plugin[] { { name: 'rsc:inject-async-local-storage', transform: { + filter: { code: 'typeof AsyncLocalStorage' }, handler(code) { if ( (this.environment.name === 'ssr' || @@ -1350,6 +1361,7 @@ function vitePluginUseClient( { name: 'rsc:use-client', transform: { + filter: { code: 'use client' }, async handler(code, id) { if (this.environment.name !== serverEnvironmentName) return if (!code.includes('use client')) { @@ -1456,6 +1468,7 @@ function vitePluginUseClient( { name: 'rsc:use-client/build-references', resolveId: { + filter: { id: prefixRegex('virtual:vite-rsc/client-references') }, handler(source) { if (source.startsWith('virtual:vite-rsc/client-references')) { return '\0' + source @@ -1463,6 +1476,7 @@ function vitePluginUseClient( }, }, load: { + filter: { id: prefixRegex('\0virtual:vite-rsc/client-references') }, handler(id) { if (id === '\0virtual:vite-rsc/client-references') { // not used during dev @@ -1543,6 +1557,9 @@ function vitePluginUseClient( { name: 'rsc:virtual-client-in-server-package', load: { + filter: { + id: prefixRegex('\0virtual:vite-rsc/client-in-server-package-proxy/'), + }, async handler(id) { if ( id.startsWith('\0virtual:vite-rsc/client-in-server-package-proxy/') @@ -1583,6 +1600,7 @@ function vitePluginUseClient( }, }, load: { + filter: { id: prefixRegex('\0virtual:vite-rsc/client-package-proxy/') }, async handler(id) { if (id.startsWith('\0virtual:vite-rsc/client-package-proxy/')) { assert(this.environment.mode === 'dev') @@ -1764,6 +1782,7 @@ function vitePluginDefineEncryptionKey( } }, resolveId: { + filter: { id: exactRegex('virtual:vite-rsc/encryption-key') }, handler(source) { if (source === 'virtual:vite-rsc/encryption-key') { // encryption logic can be tree-shaken if action bind is not used. @@ -1772,6 +1791,7 @@ function vitePluginDefineEncryptionKey( }, }, load: { + filter: { id: exactRegex('\0virtual:vite-rsc/encryption-key') }, handler(id) { if (id === '\0virtual:vite-rsc/encryption-key') { if (this.environment.mode === 'build') { @@ -1828,6 +1848,7 @@ function vitePluginUseServer( { name: 'rsc:use-server', transform: { + filter: { code: 'use server' }, async handler(code, id) { if (!code.includes('use server')) { delete manager.serverReferenceMetaMap[id] @@ -2240,6 +2261,7 @@ function vitePluginRscCss( { name: 'rsc:rsc-css-export-transform', transform: { + filter: { id: /\.[tj]sx?$/ }, async handler(code, id) { if (this.environment.name !== 'rsc') return const filter = getRscCssTransformFilter({ id, code }) @@ -2267,6 +2289,9 @@ function vitePluginRscCss( apply: 'serve', transform: { order: 'post', + filter: { + id: /\.(css|less|sass|scss|styl|stylus|pcss|postcss|sss)(\?|$)/, + }, handler(_code, id, _options) { if ( this.environment.name === 'client' && @@ -2285,6 +2310,7 @@ function vitePluginRscCss( { name: 'rsc:css-virtual', resolveId: { + filter: { id: prefixRegex('virtual:vite-rsc/css?') }, handler(source) { if (source.startsWith('virtual:vite-rsc/css?')) { return '\0' + source @@ -2292,6 +2318,7 @@ function vitePluginRscCss( }, }, load: { + filter: { id: prefixRegex('\0virtual:vite-rsc/css?') }, async handler(id) { const parsed = parseCssVirtual(id) if (parsed?.type === 'ssr') { @@ -2334,6 +2361,7 @@ function vitePluginRscCss( } }, transform: { + filter: { code: 'import.meta.viteRsc.loadCss' }, async handler(code, id) { if (!code.includes('import.meta.viteRsc.loadCss')) return @@ -2401,6 +2429,7 @@ function vitePluginRscCss( }, }, load: { + filter: { id: prefixRegex('\0virtual:vite-rsc/css?') }, handler(id) { const { server } = manager const parsed = parseCssVirtual(id) diff --git a/packages/plugin-rsc/src/plugins/cjs.ts b/packages/plugin-rsc/src/plugins/cjs.ts index eda6394e5..07d0d1f58 100644 --- a/packages/plugin-rsc/src/plugins/cjs.ts +++ b/packages/plugin-rsc/src/plugins/cjs.ts @@ -18,6 +18,10 @@ export function cjsModuleRunnerPlugin(): Plugin[] { apply: 'serve', applyToEnvironment: (env) => env.config.dev.moduleRunnerTransform, transform: { + filter: { + id: /\.[cm]?js$/, + code: /\b(require|exports)\b/, + }, async handler(code, id) { if ( id.includes('/node_modules/') && diff --git a/packages/plugin-rsc/src/plugins/import-environment.ts b/packages/plugin-rsc/src/plugins/import-environment.ts index 9f8a238e1..80b273386 100644 --- a/packages/plugin-rsc/src/plugins/import-environment.ts +++ b/packages/plugin-rsc/src/plugins/import-environment.ts @@ -1,6 +1,7 @@ import assert from 'node:assert' import fs from 'node:fs' import path from 'node:path' +import { exactRegex } from '@rolldown/pluginutils' import MagicString from 'magic-string' import { stripLiteral } from 'strip-literal' import type { Plugin, ResolvedConfig } from 'vite' @@ -50,6 +51,7 @@ export function vitePluginImportEnvironment( { name: 'rsc:import-environment', resolveId: { + filter: { id: exactRegex(ENV_IMPORTS_MANIFEST_PLACEHOLDER) }, handler(source) { // Use placeholder as external, renderChunk will replace with correct relative path if (source === ENV_IMPORTS_MANIFEST_PLACEHOLDER) { @@ -80,6 +82,7 @@ export function vitePluginImportEnvironment( } }, transform: { + filter: { code: 'import.meta.viteRsc.import' }, async handler(code, id) { if (!code.includes('import.meta.viteRsc.import')) return diff --git a/packages/plugin-rsc/src/plugins/resolved-id-proxy.ts b/packages/plugin-rsc/src/plugins/resolved-id-proxy.ts index 4f2e8b67d..ca7761006 100644 --- a/packages/plugin-rsc/src/plugins/resolved-id-proxy.ts +++ b/packages/plugin-rsc/src/plugins/resolved-id-proxy.ts @@ -1,3 +1,4 @@ +import { prefixRegex } from '@rolldown/pluginutils' import type { Plugin } from 'vite' // Resolved ID proxy plugin @@ -130,7 +131,7 @@ export function vitePluginResolvedIdProxy(): Plugin { return { name: 'rsc:resolved-id-proxy', resolveId: { - // TODO: filter + filter: { id: prefixRegex(RESOLVED_ID_PROXY_PREFIX) }, handler(source) { const originalId = fromResolvedIdProxy(source) if (originalId !== undefined) { diff --git a/packages/plugin-rsc/src/plugins/utils.ts b/packages/plugin-rsc/src/plugins/utils.ts index 341a2c69d..6c993e732 100644 --- a/packages/plugin-rsc/src/plugins/utils.ts +++ b/packages/plugin-rsc/src/plugins/utils.ts @@ -1,5 +1,6 @@ import { createHash } from 'node:crypto' import path from 'node:path' +import { exactRegex } from '@rolldown/pluginutils' import { normalizePath, type Plugin, @@ -41,17 +42,22 @@ export function createVirtualPlugin( name: string, load: Plugin['load'], ): Plugin { - name = 'virtual:' + name + const virtualId = 'virtual:' + name + const resolvedId = '\0' + virtualId return { name: `rsc:virtual-${name}`, resolveId: { - handler(source, _importer, _options) { - return source === name ? '\0' + name : undefined + filter: { id: exactRegex(virtualId) }, + handler(source) { + if (source === virtualId) { + return resolvedId + } }, }, load: { + filter: { id: exactRegex(resolvedId) }, handler(id, options) { - if (id === '\0' + name) { + if (id === resolvedId) { return (load as Function).apply(this, [id, options]) } }, diff --git a/packages/plugin-rsc/src/plugins/validate-import.ts b/packages/plugin-rsc/src/plugins/validate-import.ts index d94f725dc..2c9c00504 100644 --- a/packages/plugin-rsc/src/plugins/validate-import.ts +++ b/packages/plugin-rsc/src/plugins/validate-import.ts @@ -1,4 +1,5 @@ import path from 'node:path' +import { prefixRegex } from '@rolldown/pluginutils' import type { DevEnvironment, Plugin, Rollup } from 'vite' // https://github.com/vercel/next.js/blob/90f564d376153fe0b5808eab7b83665ee5e08aaf/packages/next/src/build/webpack-config.ts#L1249-L1280 @@ -9,6 +10,7 @@ export function validateImportPlugin(): Plugin { name: 'rsc:validate-imports', resolveId: { order: 'pre', + filter: { id: /^(client-only|server-only)$/ }, async handler(source, _importer, options) { // optimizer is not aware of server/client boudnary so skip if ('scan' in options && options.scan) { @@ -36,6 +38,7 @@ export function validateImportPlugin(): Plugin { }, }, load: { + filter: { id: prefixRegex('\0virtual:vite-rsc/validate-imports/') }, handler(id) { if (id.startsWith('\0virtual:vite-rsc/validate-imports/invalid/')) { // it should surface as build error but we make a runtime error just in case. @@ -50,6 +53,9 @@ export function validateImportPlugin(): Plugin { // for dev, use DevEnvironment.moduleGraph during post transform transform: { order: 'post', + filter: { + id: prefixRegex('\0virtual:vite-rsc/validate-imports/invalid/'), + }, async handler(_code, id) { if (this.environment.mode === 'dev') { if (id.startsWith(`\0virtual:vite-rsc/validate-imports/invalid/`)) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fa25fcb8e..058e933c9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -436,6 +436,9 @@ importers: packages/plugin-rsc: dependencies: + '@rolldown/pluginutils': + specifier: 1.0.0-rc.2 + version: 1.0.0-rc.2 es-module-lexer: specifier: ^2.0.0 version: 2.0.0 From 6f1e4bb47f7875d29548522f60faa720a5b79b42 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 3 Feb 2026 16:15:34 +0900 Subject: [PATCH 2/6] fix: no use server/client filter --- packages/plugin-rsc/src/plugin.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 2f93e8088..20ea77e08 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -1361,7 +1361,10 @@ function vitePluginUseClient( { name: 'rsc:use-client', transform: { - filter: { code: 'use client' }, + // TODO: cannot use filter because handler has cleanup side effect + // (`delete manager.clientReferenceMetaMap[id]`) that must run + // even when directive is removed (HMR case) + // filter: { code: 'use client' }, async handler(code, id) { if (this.environment.name !== serverEnvironmentName) return if (!code.includes('use client')) { @@ -1848,7 +1851,10 @@ function vitePluginUseServer( { name: 'rsc:use-server', transform: { - filter: { code: 'use server' }, + // TODO: cannot use filter because handler has cleanup side effect + // (`delete manager.serverReferenceMetaMap[id]`) that must run + // even when directive is removed (HMR case) + // filter: { code: 'use server' }, async handler(code, id) { if (!code.includes('use server')) { delete manager.serverReferenceMetaMap[id] From 6266fb522389c3da12a37044cdb7783e6947f814 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 3 Feb 2026 16:26:20 +0900 Subject: [PATCH 3/6] fix: cjsModuleRunnerPlugin filter --- packages/plugin-rsc/src/plugins/cjs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/plugin-rsc/src/plugins/cjs.ts b/packages/plugin-rsc/src/plugins/cjs.ts index 07d0d1f58..93f8e6d26 100644 --- a/packages/plugin-rsc/src/plugins/cjs.ts +++ b/packages/plugin-rsc/src/plugins/cjs.ts @@ -1,6 +1,7 @@ import fs from 'node:fs' import path from 'node:path' import { createDebug } from '@hiogawa/utils' +import { makeIdFiltersToMatchWithQuery } from '@rolldown/pluginutils' import * as esModuleLexer from 'es-module-lexer' import { parseAstAsync, type Plugin } from 'vite' import { findClosestPkgJsonPath } from 'vitefu' @@ -19,7 +20,7 @@ export function cjsModuleRunnerPlugin(): Plugin[] { applyToEnvironment: (env) => env.config.dev.moduleRunnerTransform, transform: { filter: { - id: /\.[cm]?js$/, + id: makeIdFiltersToMatchWithQuery(['**/node_modules/**']), code: /\b(require|exports)\b/, }, async handler(code, id) { From a4cbe8334c9384a819a81246af04927528f890f9 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 3 Feb 2026 16:33:13 +0900 Subject: [PATCH 4/6] refactor: simplify --- packages/plugin-rsc/src/plugins/cjs.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/plugin-rsc/src/plugins/cjs.ts b/packages/plugin-rsc/src/plugins/cjs.ts index 93f8e6d26..40a6ba0f3 100644 --- a/packages/plugin-rsc/src/plugins/cjs.ts +++ b/packages/plugin-rsc/src/plugins/cjs.ts @@ -1,7 +1,6 @@ import fs from 'node:fs' import path from 'node:path' import { createDebug } from '@hiogawa/utils' -import { makeIdFiltersToMatchWithQuery } from '@rolldown/pluginutils' import * as esModuleLexer from 'es-module-lexer' import { parseAstAsync, type Plugin } from 'vite' import { findClosestPkgJsonPath } from 'vitefu' @@ -20,7 +19,7 @@ export function cjsModuleRunnerPlugin(): Plugin[] { applyToEnvironment: (env) => env.config.dev.moduleRunnerTransform, transform: { filter: { - id: makeIdFiltersToMatchWithQuery(['**/node_modules/**']), + id: /\/node_modules\//, code: /\b(require|exports)\b/, }, async handler(code, id) { From 4f15574b9d6107032150055d6e217514f89b6849 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 3 Feb 2026 16:37:38 +0900 Subject: [PATCH 5/6] fix: remove unsafe filter --- packages/plugin-rsc/src/plugin.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 20ea77e08..72c6ad3a7 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -2267,7 +2267,8 @@ function vitePluginRscCss( { name: 'rsc:rsc-css-export-transform', transform: { - filter: { id: /\.[tj]sx?$/ }, + // TODO: filter + filter: {}, async handler(code, id) { if (this.environment.name !== 'rsc') return const filter = getRscCssTransformFilter({ id, code }) From 3675a84590d20c1e61a0461d9b1cd84c0fbb6113 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 3 Feb 2026 16:38:15 +0900 Subject: [PATCH 6/6] chore: cleanup --- packages/plugin-rsc/src/plugin.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugin-rsc/src/plugin.ts b/packages/plugin-rsc/src/plugin.ts index 72c6ad3a7..fa065b047 100644 --- a/packages/plugin-rsc/src/plugin.ts +++ b/packages/plugin-rsc/src/plugin.ts @@ -2267,8 +2267,8 @@ function vitePluginRscCss( { name: 'rsc:rsc-css-export-transform', transform: { - // TODO: filter - filter: {}, + // TODO: + // filter: {}, async handler(code, id) { if (this.environment.name !== 'rsc') return const filter = getRscCssTransformFilter({ id, code })