Current behavior
When a module uses function-level 'use server' and imports a symbol alongside a TypeScript inline type (import { type Session, verify }), after the transform makes verify unreferenced, removeUnusedVariables removes verify but leaves the type Session specifier. The import declaration survives, Vite follows the module chain, and if that module contains import 'server-only', the boundary-modules guard fires:
Error: Attempt to import 'server-only' in a client module: .../modules/user/auth/session.ts
This does not happen with import { verify } (no type specifier) — the single-specifier path hits parent.remove() and deletes the whole import cleanly.
Steps to reproduce
// modules/user/auth/session.ts
import 'server-only';
export interface Session { ... }
export async function verify(token: Buffer): Promise<Session | null> { ... }
// functions/user/auth/session.ts
import { type Session, verify } from '~/modules/user/auth/session';
export const verifyCookieToken = query(async (): Promise<VerifyResult> => {
'use server';
const res = await verify(token);
// ...
}, 'key');
Expected behavior
After the 'use server' transform replaces the inner function with an RPC stub, all imports only used by that function should be fully removed — including those whose only remaining specifiers are type-only.
Root cause
packages/start/src/directives/remove-unused-variables.ts uses a BindingIdentifier visitor to find zero-reference bindings. compile.ts registers TypeScript only as a parser plugin (parserOpts.plugins), not as a transform plugin, so type annotations are parsed but not stripped. TypeScript type import specifiers (import { type Session }) create no scope bindings in Babel, so the visitor never visits or removes them.
When verify is removed from a two-specifier import:
// remove-unused-variables.ts:42-47
if (isPathValid(parent, t.isImportDeclaration)) {
if (parent.node.specifiers.length === 1) {
parent.remove(); // ← NOT reached (was 2→1, triggers the else branch)
} else {
binding.path.remove(); // only `verify` specifier removed
}
}
The surviving import { type Session } from '~/modules/user/auth/session' keeps the import alive, and the entire module chain (including server-only) is pulled into the client bundle.
Suggested fix
After removing the last value specifier from an import, check if all remaining specifiers have importKind === 'type'; if so, remove the entire ImportDeclaration.
Environment
- OS: Windows 11
- Node: v26.2.0
- pnpm: 11.8.0
- @solidjs/start: 2.0.0-rc.7
Some of the content was generated by AI.
Current behavior
When a module uses function-level
'use server'and imports a symbol alongside a TypeScript inline type (import { type Session, verify }), after the transform makesverifyunreferenced,removeUnusedVariablesremovesverifybut leaves thetype Sessionspecifier. The import declaration survives, Vite follows the module chain, and if that module containsimport 'server-only', theboundary-modulesguard fires:This does not happen with
import { verify }(no type specifier) — the single-specifier path hitsparent.remove()and deletes the whole import cleanly.Steps to reproduce
Expected behavior
After the
'use server'transform replaces the inner function with an RPC stub, all imports only used by that function should be fully removed — including those whose only remaining specifiers are type-only.Root cause
packages/start/src/directives/remove-unused-variables.tsuses aBindingIdentifiervisitor to find zero-reference bindings.compile.tsregisters TypeScript only as a parser plugin (parserOpts.plugins), not as a transform plugin, so type annotations are parsed but not stripped. TypeScript type import specifiers (import { type Session }) create no scope bindings in Babel, so the visitor never visits or removes them.When
verifyis removed from a two-specifier import:The surviving
import { type Session } from '~/modules/user/auth/session'keeps the import alive, and the entire module chain (includingserver-only) is pulled into the client bundle.Suggested fix
After removing the last value specifier from an import, check if all remaining specifiers have
importKind === 'type'; if so, remove the entireImportDeclaration.Environment
Some of the content was generated by AI.