Skip to content

Commit d4be1b6

Browse files
feat: add new directives plugin (#2070)
Co-authored-by: Atila Fassina <atila@fassina.eu>
1 parent b9c4ade commit d4be1b6

25 files changed

+1256
-274
lines changed

.changeset/rare-canyons-travel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@solidjs/start": minor
3+
---
4+
5+
Add new directives plugin with shorter function IDs and inner declaration support

apps/tests/src/e2e/server-function.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ test.describe("server-function", () => {
8181
await page.goto("http://localhost:3000/server-function-blob");
8282
await expect(page.locator("#server-fn-test")).toContainText('{"result":true}');
8383
});
84+
test("should remove exports for non-function values when top-level use server is used", async ({ page }) => {
85+
await page.goto("http://localhost:3000/server-function-query-toplevel");
86+
await expect(page.locator("#server-fn-test")).toContainText('false');
87+
});
8488

8589
// TODO not sure if this is the correct place
8690
test("should build with a env:server", async ({ page }) => {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use server";
2+
3+
import { query } from "@solidjs/router";
4+
import { isServer } from "solid-js/web";
5+
6+
export const testQuery = query(() => isServer, 'testQuery');
7+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createEffect, createSignal } from "solid-js";
2+
import * as testModule from "~/functions/solid-router-query";
3+
4+
export default function App() {
5+
const [output, setOutput] = createSignal<boolean | null>();
6+
7+
createEffect(() => {
8+
setOutput('testQuery' in testModule);
9+
});
10+
11+
return (
12+
<main>
13+
<span id="server-fn-test">{JSON.stringify(output())}</span>
14+
</main>
15+
);
16+
}

apps/tests/test-results/.last-run.json

Lines changed: 0 additions & 4 deletions
This file was deleted.

packages/start/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"@babel/traverse": "^7.29.0",
4242
"@babel/types": "^7.29.0",
4343
"@solidjs/meta": "^0.29.4",
44-
"@tanstack/server-functions-plugin": "1.134.5",
4544
"@types/babel__traverse": "^7.28.0",
4645
"@types/micromatch": "^4.0.10",
4746
"cookie-es": "^2.0.0",

packages/start/src/config/index.ts

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import { TanStackServerFnPlugin } from "@tanstack/server-functions-plugin";
21
import { defu } from "defu";
32
import { globSync } from "node:fs";
43
import { extname, isAbsolute, join } from "node:path";
54
import { fileURLToPath } from "node:url";
65
import { normalizePath, type PluginOption } from "vite";
76
import solid, { type Options as SolidOptions } from "vite-plugin-solid";
8-
7+
import { serverFunctionsPlugin } from "../directives/index.ts";
98
import { DEFAULT_EXTENSIONS, VIRTUAL_MODULES, VITE_ENVIRONMENTS } from "./constants.ts";
109
import { devServer } from "./dev-server.ts";
1110
import { type EnvPluginOptions, envPlugin } from "./env.ts";
@@ -180,38 +179,15 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
180179
envPlugin(options?.env),
181180
// Must be placed after fsRoutes, as treeShake will remove the
182181
// server fn exports added in by this plugin
183-
TanStackServerFnPlugin({
184-
// This is the ID that will be available to look up and import
185-
// our server function manifest and resolve its module
186-
manifestVirtualImportId: VIRTUAL_MODULES.serverFnManifest,
187-
directive: "use server",
188-
callers: [
189-
{
190-
envConsumer: "client",
191-
envName: VITE_ENVIRONMENTS.client,
192-
getRuntimeCode: () =>
193-
`import { createServerReference } from "${normalizePath(
194-
fileURLToPath(new URL("../server/server-runtime", import.meta.url)),
195-
)}"`,
196-
replacer: opts => `createServerReference('${opts.functionId}')`,
197-
},
198-
{
199-
envConsumer: "server",
200-
envName: VITE_ENVIRONMENTS.server,
201-
getRuntimeCode: () =>
202-
`import { createServerReference } from '${normalizePath(
203-
fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)),
204-
)}'`,
205-
replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
206-
},
207-
],
208-
provider: {
209-
envName: VITE_ENVIRONMENTS.server,
210-
getRuntimeCode: () =>
211-
`import { createServerReference } from '${normalizePath(
212-
fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)),
213-
)}'`,
214-
replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
182+
serverFunctionsPlugin({
183+
manifest: VIRTUAL_MODULES.serverFnManifest,
184+
runtime: {
185+
server: normalizePath(
186+
fileURLToPath(new URL("../server/server-fns-runtime.ts", import.meta.url)),
187+
),
188+
client: normalizePath(
189+
fileURLToPath(new URL("../server/server-runtime.ts", import.meta.url)),
190+
),
215191
},
216192
}),
217193
{
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type * as babel from "@babel/core";
2+
import * as t from "@babel/types";
3+
4+
export function bubbleFunctionDeclaration(path: babel.NodePath<t.FunctionDeclaration>): void {
5+
const decl = path.node;
6+
// Check if declaration is FunctionDeclaration
7+
if (decl.id) {
8+
const block = (path.findParent(current => current.isBlockStatement()) ||
9+
path.scope.getProgramParent().path) as babel.NodePath<t.BlockStatement>;
10+
11+
if (path.parentPath.isExportNamedDeclaration()) {
12+
path.parentPath.replaceWith(
13+
t.exportNamedDeclaration(undefined, [t.exportSpecifier(decl.id, decl.id)]),
14+
);
15+
} else if (path.parentPath.isExportDefaultDeclaration()) {
16+
path.replaceWith(decl.id);
17+
} else {
18+
path.remove();
19+
}
20+
21+
const [tmp] = block.unshiftContainer(
22+
"body",
23+
t.variableDeclaration("const", [
24+
t.variableDeclarator(
25+
decl.id,
26+
t.functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async),
27+
),
28+
]),
29+
);
30+
block.scope.registerDeclaration(tmp);
31+
tmp.skip();
32+
}
33+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as babel from "@babel/core";
2+
import path from "node:path";
3+
import { directivesPlugin, type StateContext } from "./plugin.ts";
4+
import xxHash32 from "./xxhash32.ts";
5+
6+
export interface CompileResult {
7+
valid: boolean;
8+
code: string;
9+
map: babel.BabelFileResult["map"];
10+
}
11+
12+
export type CompileOptions = Omit<StateContext, "count" | "hash" | "imports" | "valid">;
13+
14+
export async function compile(
15+
id: string,
16+
code: string,
17+
options: CompileOptions,
18+
): Promise<CompileResult> {
19+
const context: StateContext = {
20+
...options,
21+
valid: false,
22+
hash: xxHash32(id).toString(16),
23+
count: 0,
24+
imports: new Map(),
25+
};
26+
const pluginOption = [directivesPlugin, context];
27+
const plugins: NonNullable<NonNullable<babel.TransformOptions["parserOpts"]>["plugins"]> = [
28+
"jsx",
29+
];
30+
if (/\.[mc]?tsx?$/i.test(id)) {
31+
plugins.push("typescript");
32+
}
33+
const result = await babel.transformAsync(code, {
34+
plugins: [pluginOption],
35+
parserOpts: {
36+
plugins,
37+
},
38+
filename: path.basename(id),
39+
ast: false,
40+
sourceMaps: true,
41+
configFile: false,
42+
babelrc: false,
43+
sourceFileName: id,
44+
});
45+
46+
if (result) {
47+
return {
48+
valid: context.valid,
49+
code: result.code || "",
50+
map: result.map,
51+
};
52+
}
53+
throw new Error("invariant");
54+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type * as babel from "@babel/core";
2+
import * as t from "@babel/types";
3+
4+
export function generateUniqueName(path: babel.NodePath, name: string): t.Identifier {
5+
let uid: string;
6+
let i = 1;
7+
do {
8+
uid = name + "_" + i;
9+
i++;
10+
} while (
11+
path.scope.hasLabel(uid) ||
12+
path.scope.hasBinding(uid) ||
13+
path.scope.hasGlobal(uid) ||
14+
path.scope.hasReference(uid)
15+
);
16+
17+
const program = path.scope.getProgramParent();
18+
program.references[uid] = true;
19+
program.uids[uid] = true;
20+
21+
return t.identifier(uid);
22+
}

0 commit comments

Comments
 (0)