Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/vite-server-protocol-imports.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'zyzz': patch
---

Fixed Vite analysis of server-only protocol imports such as `cloudflare:workers` before TanStack Start removed them from client code. Resolvable protocol imports remained available for static style analysis, and unresolved ordinary file and package imports continued to report errors.
84 changes: 84 additions & 0 deletions src/vite/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2722,6 +2722,90 @@ Object.assign(globalThis, { fixture: { card: shared.card().className, label, pro
}
})

test.each(['cloudflare:workers', './missing', 'missing-package'])(
'analyzes server-only imports before TanStack removes %s',
async (specifier) => {
const { config, root } = await create({
...TanStack.files,
'declarations.ts': `export const declarations = { padding: '17px' } as const;`,
'main.tsx': `export { getClient, styles } from './rpc';`,
'rpc.ts': `import { createIsomorphicFn } from '@tanstack/react-start'
import { env } from '${specifier}'
import { declarations } from 'styles:declarations'
import { style } from 'zyzz'

export const getClient = createIsomorphicFn()
.server(() => env)
.client(() => 'browser')

export namespace styles {
export const card = style(declarations)
}`,
})
await Fs.mkdir(Path.join(root, 'routes'))
for (const name of ['__root.tsx', 'settings.tsx'])
await Fs.rename(Path.join(root, name), Path.join(root, 'routes', name))

const server = await Vite.createServer({
...config,
plugins: [
zyzz({ include: ['.'], script: false }),
tanstackStart({
router: {
generatedRouteTree: Path.join(root, 'routeTree.gen.ts'),
routesDirectory: Path.join(root, 'routes'),
},
srcDirectory: '.',
}),
react(),
],
resolve: {
alias: {
...(config.resolve!.alias as Record<string, string>),
// Resolvable protocols still supply statically evaluated declarations.
'styles:declarations': Path.join(root, 'declarations.ts'),
},
},
})

try {
await server.listen()

// The importer walks rpc.ts before Start transforms its server branch.
const output = server.environments.client.transformRequest('/main.tsx')

if (specifier !== 'cloudflare:workers') {
const error = await output.catch((error: unknown) =>
String(error).replace(root, '<root>'),
)

if (specifier === './missing')
expect(error).toMatchInlineSnapshot(
`"Source.ExtractError: <root>/rpc.ts:59: Unable to resolve "./missing""`,
)
else
expect(error).toMatchInlineSnapshot(
`"Source.ExtractError: <root>/rpc.ts:59: Unable to resolve "missing-package""`,
)

return
}

expect((await output) !== null).toMatchInlineSnapshot(`true`)

const rpc = await server.environments.client.transformRequest('/rpc.ts')

expect(rpc?.code.includes('cloudflare:workers')).toMatchInlineSnapshot(
`false`,
)
expect(rpc?.code.includes('browser')).toMatchInlineSnapshot(`true`)
} finally {
await server.close()
await Fs.rm(root, { recursive: true, force: true })
}
},
)

test('keeps unreachable analysis imports out of dependency optimization', async () => {
const { config, root } = await create({
...Fixture.files,
Expand Down
13 changes: 12 additions & 1 deletion src/vite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -948,7 +948,17 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
}

const resolved = await resolve(specifier, file)
if (!resolved)
if (!resolved) {
// Framework transforms can remove server-only protocols from client code.
// Resolve first so aliases and plugins can still supply analyzable source.
if (
!Path.isAbsolute(specifier) &&
/^[a-z][a-z\d+.-]*:/i.test(specifier)
) {
resolutions[specifier] = null
continue
}

throw new Source.ExtractError([
{
code: 'unsupported_syntax',
Expand All @@ -958,6 +968,7 @@ export function zyzz(options: zyzz.Options = {}): Plugin {
start: node.start,
},
])
}

if (resource(specifier) || resource(resolved.id)) {
resolutions[specifier] = null
Expand Down
Loading