diff --git a/.changeset/calm-types-vanish.md b/.changeset/calm-types-vanish.md new file mode 100644 index 000000000..84bdb53e2 --- /dev/null +++ b/.changeset/calm-types-vanish.md @@ -0,0 +1,5 @@ +--- +"@solidjs/start": patch +--- + +Remove type-only import remnants from client server-function transforms so they do not retain server-only dependency chains. diff --git a/packages/start/src/directives/compile.spec.ts b/packages/start/src/directives/compile.spec.ts new file mode 100644 index 000000000..876a0c7b6 --- /dev/null +++ b/packages/start/src/directives/compile.spec.ts @@ -0,0 +1,62 @@ +import { describe, expect, it } from "vitest"; +import { compile, type CompileOptions } from "./compile.ts"; + +const clientOptions: CompileOptions = { + env: "development", + mode: "client", + directive: "use server", + definitions: { + register: { + kind: "named", + name: "createServerReference", + source: "virtual:server-runtime", + }, + clone: { + kind: "named", + name: "cloneServerReference", + source: "virtual:server-runtime", + }, + }, +}; + +describe("compile", () => { + it("removes an import when only type specifiers remain", async () => { + const result = await compile( + "/src/server-action.ts", + ` + import { type Session, verify } from "./server-module.ts"; + + export const serverAction = async (): Promise => { + "use server"; + return verify(); + }; + `, + clientOptions, + ); + + expect(result.valid).toBe(true); + expect(result.code).not.toContain("./server-module.ts"); + }); + + it("preserves live value specifiers from a mixed import", async () => { + const result = await compile( + "/src/server-action.ts", + ` + import { type Session, clientValue, verify } from "./server-module.ts"; + + export const value = clientValue; + export const serverAction = async (): Promise => { + "use server"; + return verify(); + }; + `, + clientOptions, + ); + + expect(result.valid).toBe(true); + expect(result.code).toContain( + 'import { type Session, clientValue } from "./server-module.ts";', + ); + expect(result.code).not.toMatch(/\bverify\b/); + }); +}); diff --git a/packages/start/src/directives/remove-unused-variables.ts b/packages/start/src/directives/remove-unused-variables.ts index 312901d53..9af4346fb 100644 --- a/packages/start/src/directives/remove-unused-variables.ts +++ b/packages/start/src/directives/remove-unused-variables.ts @@ -16,6 +16,22 @@ function isInvalidForRemoval(path: babel.NodePath) { return isPathValid(target, t.isObjectPattern) || isPathValid(target, t.isArrayPattern); } +function countValidImport(node: t.ImportDeclaration): number { + if (node.importKind === "type") { + return 0; + } + + let count = 0; + + for (const specifier of node.specifiers) { + if (specifier.type !== "ImportSpecifier" || specifier.importKind === "value") { + count += 1; + } + } + + return count; +} + export function removeUnusedVariables(program: babel.NodePath) { // TODO(Alexis): // This implementation is simple but slow @@ -40,7 +56,7 @@ export function removeUnusedVariables(program: babel.NodePath) { if (binding.references === 0 && !binding.path.removed) { const parent = binding.path.parentPath; if (isPathValid(parent, t.isImportDeclaration)) { - if (parent.node.specifiers.length === 1) { + if (countValidImport(parent.node) <= 1) { parent.remove(); } else { binding.path.remove();