Skip to content

Commit a581680

Browse files
schiller-manuelcoderabbitai[bot]CodeRabbitautofix-ci[bot]nx-cloud[bot]
authored
fix: unify virtual module handling for Start Vite plugins (#7178)
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: CodeRabbit <noreply@coderabbit.ai> Co-authored-by: schiller-manuel <schiller-manuel@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: nx-cloud[bot] <71083854+nx-cloud[bot]@users.noreply.github.com>
1 parent c373feb commit a581680

16 files changed

Lines changed: 250 additions & 209 deletions

File tree

.changeset/rare-pigs-smash.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@tanstack/start-plugin-core': patch
3+
'@tanstack/react-start-rsc': patch
4+
---
5+
6+
Fix Start virtual module resolution in pnpm workspaces by serving the client entry through a real Vite virtual module.
7+
8+
Simplify Start virtual module handling by sharing a single `createVirtualModule` helper and collapsing internal `@tanstack/start-plugin-core` imports to the root export surface.

packages/react-start-rsc/src/plugin/vite.ts

Lines changed: 3 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,15 @@
11
import { fileURLToPath } from 'node:url'
22
import path from 'pathe'
3-
import { resolveViteId } from '@tanstack/start-plugin-core/utils'
3+
import { createVirtualModule } from '@tanstack/start-plugin-core'
44
import type {
55
TanStackStartVitePluginCoreOptions,
66
ViteRscForwardSsrResolverStrategy,
7-
} from '@tanstack/start-plugin-core/vite/types'
8-
import type { Plugin, PluginOption, UserConfig } from 'vite'
7+
} from '@tanstack/start-plugin-core'
8+
import type { PluginOption, UserConfig } from 'vite'
99

10-
type VirtualModuleLoadHandler = (this: {
11-
environment: { name: string }
12-
}) => string
1310
const isClientEnvironment = (env: { config: { consumer: string } }) =>
1411
env.config.consumer === 'client'
1512

16-
function escapeRegExp(value: string): string {
17-
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
18-
}
19-
20-
function createVirtualModule(opts: {
21-
name: string
22-
moduleId: string
23-
load: VirtualModuleLoadHandler
24-
apply?: Plugin['apply']
25-
applyToEnvironment?: Plugin['applyToEnvironment']
26-
}): Plugin {
27-
const resolvedId = resolveViteId(opts.moduleId)
28-
const idFilter = { id: new RegExp(escapeRegExp(opts.moduleId)) }
29-
30-
return {
31-
name: opts.name,
32-
apply: opts.apply,
33-
applyToEnvironment: opts.applyToEnvironment,
34-
resolveId: {
35-
filter: idFilter,
36-
handler() {
37-
return resolvedId
38-
},
39-
},
40-
load: {
41-
filter: idFilter,
42-
handler: opts.load,
43-
},
44-
}
45-
}
46-
4713
// Virtual module ids used by the React Start RSC runtime.
4814
const RSC_HMR_VIRTUAL_ID = 'virtual:tanstack-rsc-hmr'
4915
const RSC_RUNTIME_VIRTUAL_ID = 'virtual:tanstack-rsc-runtime'

packages/react-start/src/plugin/vite.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import {
22
START_ENVIRONMENT_NAMES,
33
tanStackStartVite,
44
} from '@tanstack/start-plugin-core'
5+
import type {
6+
TanStackStartViteInputConfig,
7+
TanStackStartVitePluginCoreOptions,
8+
} from '@tanstack/start-plugin-core'
59
import {
610
configureRsc,
711
reactStartRscVitePlugin,
812
} from '@tanstack/react-start-rsc/plugin/vite'
913
import path from 'pathe'
1014
import { reactStartDefaultEntryPaths, reactStartPluginDir } from './shared'
11-
import type { TanStackStartVitePluginCoreOptions } from '@tanstack/start-plugin-core/vite/types'
12-
import type { TanStackStartViteInputConfig } from '@tanstack/start-plugin-core'
1315
import type { PluginOption } from 'vite'
1416

1517
const isInsideRouterMonoRepo =

packages/solid-start/src/plugin/vite.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import {
22
START_ENVIRONMENT_NAMES,
33
tanStackStartVite,
44
} from '@tanstack/start-plugin-core'
5+
import type {
6+
TanStackStartViteInputConfig,
7+
TanStackStartVitePluginCoreOptions,
8+
} from '@tanstack/start-plugin-core'
59
import { solidStartDefaultEntryPaths } from './shared'
6-
import type { TanStackStartVitePluginCoreOptions } from '@tanstack/start-plugin-core/vite/types'
7-
import type { TanStackStartViteInputConfig } from '@tanstack/start-plugin-core'
810
import type { PluginOption } from 'vite'
911

1012
export function tanstackStart(

packages/start-plugin-core/package.json

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@
5656
"default": "./dist/esm/utils.js"
5757
}
5858
},
59-
"./vite/types": {
60-
"import": {
61-
"types": "./dist/esm/vite/types.d.ts"
62-
}
63-
},
6459
"./package.json": "./package.json"
6560
},
6661
"sideEffects": false,
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
export type { TanStackStartInputConfig } from './schema'
22
export type { TanStackStartCoreOptions } from './types'
3-
export type { TanStackStartVitePluginCoreOptions } from './vite/types'
3+
export type {
4+
TanStackStartVitePluginCoreOptions,
5+
ViteRscForwardSsrResolverStrategy,
6+
} from './vite/types'
47
export type { TanStackStartViteInputConfig } from './vite/schema'
58
export { START_ENVIRONMENT_NAMES, VITE_ENVIRONMENT_NAMES } from './constants'
9+
export { createVirtualModule } from './vite/createVirtualModule'
610
export { tanStackStartVite } from './vite/plugin'
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { resolveViteId } from '../utils'
2+
import type { Plugin } from 'vite'
3+
4+
type VirtualModuleLoadHandler = (
5+
this: any,
6+
id: string,
7+
) => string | null | undefined | Promise<string | null | undefined>
8+
9+
function escapeRegExp(value: string): string {
10+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
11+
}
12+
13+
export function createVirtualModule(opts: {
14+
name: string
15+
moduleId: string
16+
load: VirtualModuleLoadHandler
17+
apply?: Plugin['apply']
18+
applyToEnvironment?: Plugin['applyToEnvironment']
19+
enforce?: Plugin['enforce']
20+
sharedDuringBuild?: boolean
21+
}): Plugin {
22+
// Encode '#' as '%23' in the resolved ID to avoid browser treating it as URL fragment.
23+
// The browser requests /@id/__x00__%23tanstack-start-plugin-adapters instead of
24+
// /@id/__x00__#tanstack-start-plugin-adapters (which would truncate at #).
25+
const resolvedId = resolveViteId(opts.moduleId.replaceAll('#', '%23'))
26+
27+
return {
28+
name: opts.name,
29+
apply: opts.apply,
30+
applyToEnvironment: opts.applyToEnvironment,
31+
enforce: opts.enforce,
32+
sharedDuringBuild: opts.sharedDuringBuild,
33+
resolveId: {
34+
filter: { id: new RegExp(escapeRegExp(opts.moduleId)) },
35+
handler(id) {
36+
if (id === opts.moduleId) {
37+
return resolvedId
38+
}
39+
40+
return undefined
41+
},
42+
},
43+
load: {
44+
filter: { id: new RegExp(escapeRegExp(resolvedId)) },
45+
handler(id) {
46+
if (id !== resolvedId) {
47+
return undefined
48+
}
49+
50+
return opts.load.call(this, id)
51+
},
52+
},
53+
}
54+
}

packages/start-plugin-core/src/vite/dev-server-plugin/plugin.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { isRunnableDevEnvironment } from 'vite'
22
import { VIRTUAL_MODULES } from '@tanstack/start-server-core'
33
import { NodeRequest, sendNodeResponse } from 'srvx/node'
44
import { ENTRY_POINTS, VITE_ENVIRONMENT_NAMES } from '../../constants'
5-
import { resolveViteId } from '../../utils'
5+
import { createVirtualModule } from '../createVirtualModule'
66
import { extractHtmlScripts } from './extract-html-scripts'
77
import {
88
CSS_MODULES_REGEX,
@@ -248,27 +248,17 @@ export function devServerPlugin({
248248
}
249249
},
250250
},
251-
{
251+
createVirtualModule({
252252
name: 'tanstack-start-core:dev-server:injected-head-scripts',
253253
sharedDuringBuild: true,
254254
applyToEnvironment: (env) => env.config.consumer === 'server',
255-
resolveId: {
256-
filter: { id: new RegExp(VIRTUAL_MODULES.injectedHeadScripts) },
257-
handler(_id) {
258-
return resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)
259-
},
260-
},
261-
load: {
262-
filter: {
263-
id: new RegExp(resolveViteId(VIRTUAL_MODULES.injectedHeadScripts)),
264-
},
265-
handler() {
266-
const mod = `
255+
moduleId: VIRTUAL_MODULES.injectedHeadScripts,
256+
load() {
257+
const mod = `
267258
export const injectedHeadScripts = ${JSON.stringify(injectedHeadScripts) || 'undefined'}`
268-
return mod
269-
},
259+
return mod
270260
},
271-
},
261+
}),
272262
]
273263
}
274264

packages/start-plugin-core/src/vite/plugin.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
createCaptureClientBuildPlugin,
2626
createDevBaseRewritePlugin,
2727
createPostBuildPlugin,
28+
createVirtualClientEntryPlugin,
2829
} from './plugins'
2930
import { parseStartConfig } from './schema'
3031
import { startManifestPlugin } from './start-manifest-plugin/plugin'
@@ -237,6 +238,9 @@ export function tanStackStartVite(
237238
}),
238239
tanStackStartRouter(normalizedStartPluginOpts, getConfig, corePluginOpts),
239240
loadEnvPlugin(),
241+
createVirtualClientEntryPlugin({
242+
getClientEntry: () => configContext.resolveEntries().entryPaths.client,
243+
}),
240244
startManifestPlugin({
241245
getClientBuild: () => getClientBuild(START_ENVIRONMENT_NAMES.client),
242246
getConfig,

packages/start-plugin-core/src/vite/plugins.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { START_ENVIRONMENT_NAMES } from '../constants'
1+
import { normalizePath } from 'vite'
2+
import { ENTRY_POINTS, START_ENVIRONMENT_NAMES } from '../constants'
3+
import { createVirtualModule } from './createVirtualModule'
24
import { normalizeViteClientBuild } from './start-manifest-plugin/normalized-client-build'
35
import type {
46
GetConfigFn,
@@ -8,6 +10,19 @@ import type {
810
import type { StartEnvironmentName } from '../constants'
911
import type { PluginOption, ViteBuilder } from 'vite'
1012

13+
export function createVirtualClientEntryPlugin(opts: {
14+
getClientEntry: () => string
15+
}): PluginOption {
16+
return createVirtualModule({
17+
name: 'tanstack-start-core:virtual-client-entry',
18+
moduleId: ENTRY_POINTS.client,
19+
enforce: 'pre',
20+
load() {
21+
return `import ${JSON.stringify(normalizePath(opts.getClientEntry()).replaceAll('\\', '/'))}`
22+
},
23+
})
24+
}
25+
1126
export function createPostBuildPlugin(opts: {
1227
getConfig: GetConfigFn
1328
postServerBuild: (opts: {

0 commit comments

Comments
 (0)