Skip to content

Commit d080046

Browse files
committed
feat: add new directives plugin
1 parent 9c1c936 commit d080046

18 files changed

Lines changed: 1137 additions & 41 deletions

.vscode/settings.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,8 @@
44
},
55
"typescript.tsdk": "node_modules/typescript/lib",
66
"editor.formatOnSave": true,
7-
"editor.defaultFormatter": "oxc.oxc-vscode"
7+
"editor.defaultFormatter": "oxc.oxc-vscode",
8+
"[typescript]": {
9+
"editor.defaultFormatter": "oxc.oxc-vscode"
10+
}
811
}

packages/start/src/config/index.ts

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { BaseFileSystemRouter } from "./fs-routes/router.ts";
1414
import lazy from "./lazy.ts";
1515
import { manifest } from "./manifest.ts";
1616
import { parseIdQuery } from "./utils.ts";
17+
import { serverFunctionsPlugin } from "../directives/index.ts";
1718

1819
export interface SolidStartOptions {
1920
solid?: Partial<SolidOptions>;
@@ -163,42 +164,45 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
163164
},
164165
}),
165166
lazy(),
166-
// Must be placed after fsRoutes, as treeShake will remove the
167-
// server fn exports added in by this plugin
168-
TanStackServerFnPlugin({
169-
// This is the ID that will be available to look up and import
170-
// our server function manifest and resolve its module
171-
manifestVirtualImportId: VIRTUAL_MODULES.serverFnManifest,
172-
directive: "use server",
173-
callers: [
174-
{
175-
envConsumer: "client",
176-
envName: VITE_ENVIRONMENTS.client,
177-
getRuntimeCode: () =>
178-
`import { createServerReference } from "${normalizePath(
179-
fileURLToPath(new URL("../server/server-runtime", import.meta.url))
180-
)}"`,
181-
replacer: opts => `createServerReference('${opts.functionId}')`,
182-
},
183-
{
184-
envConsumer: "server",
185-
envName: VITE_ENVIRONMENTS.server,
186-
getRuntimeCode: () =>
187-
`import { createServerReference } from '${normalizePath(
188-
fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url))
189-
)}'`,
190-
replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
191-
},
192-
],
193-
provider: {
194-
envName: VITE_ENVIRONMENTS.server,
195-
getRuntimeCode: () =>
196-
`import { createServerReference } from '${normalizePath(
197-
fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url))
198-
)}'`,
199-
replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
200-
},
167+
serverFunctionsPlugin({
168+
manifest: VIRTUAL_MODULES.serverFnManifest,
201169
}),
170+
// // Must be placed after fsRoutes, as treeShake will remove the
171+
// // server fn exports added in by this plugin
172+
// TanStackServerFnPlugin({
173+
// // This is the ID that will be available to look up and import
174+
// // our server function manifest and resolve its module
175+
// manifestVirtualImportId: VIRTUAL_MODULES.serverFnManifest,
176+
// directive: "use server",
177+
// callers: [
178+
// {
179+
// envConsumer: "client",
180+
// envName: VITE_ENVIRONMENTS.client,
181+
// getRuntimeCode: () =>
182+
// `import { createServerReference } from "${normalizePath(
183+
// fileURLToPath(new URL("../server/server-runtime", import.meta.url))
184+
// )}"`,
185+
// replacer: opts => `createServerReference('${opts.functionId}')`,
186+
// },
187+
// {
188+
// envConsumer: "server",
189+
// envName: VITE_ENVIRONMENTS.server,
190+
// getRuntimeCode: () =>
191+
// `import { createServerReference } from '${normalizePath(
192+
// fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url))
193+
// )}'`,
194+
// replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
195+
// },
196+
// ],
197+
// provider: {
198+
// envName: VITE_ENVIRONMENTS.server,
199+
// getRuntimeCode: () =>
200+
// `import { createServerReference } from '${normalizePath(
201+
// fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url))
202+
// )}'`,
203+
// replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
204+
// },
205+
// }),
202206
{
203207
name: "solid-start:virtual-modules",
204208
async resolveId(id) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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+
const [tmp] = block.unshiftContainer(
12+
"body",
13+
t.variableDeclaration("const", [
14+
t.variableDeclarator(
15+
decl.id,
16+
t.functionExpression(decl.id, decl.params, decl.body, decl.generator, decl.async),
17+
),
18+
]),
19+
);
20+
path.scope.registerDeclaration(tmp);
21+
tmp.skip();
22+
if (path.parentPath.isExportNamedDeclaration()) {
23+
path.parentPath.replaceWith(
24+
t.exportNamedDeclaration(undefined, [t.exportSpecifier(decl.id, decl.id)]),
25+
);
26+
} else if (path.parentPath.isExportDefaultDeclaration()) {
27+
path.replaceWith(decl.id);
28+
} else {
29+
path.remove();
30+
}
31+
}
32+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import path from "node:path";
2+
import * as babel from "@babel/core";
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">;
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+
hash: xxHash32(id).toString(16),
22+
count: 0,
23+
imports: new Map(),
24+
}
25+
const pluginOption = [
26+
directivesPlugin,
27+
context,
28+
];
29+
const plugins: NonNullable<NonNullable<babel.TransformOptions["parserOpts"]>["plugins"]> = [
30+
"jsx",
31+
];
32+
if (/\.[mc]?tsx?$/i.test(id)) {
33+
plugins.push("typescript");
34+
}
35+
const result = await babel.transformAsync(code, {
36+
plugins: [pluginOption],
37+
parserOpts: {
38+
plugins,
39+
},
40+
filename: path.basename(id),
41+
ast: false,
42+
sourceMaps: true,
43+
configFile: false,
44+
babelrc: false,
45+
sourceFileName: id,
46+
});
47+
48+
if (result) {
49+
return {
50+
valid: context.count > 0,
51+
code: result.code || "",
52+
map: result.map,
53+
};
54+
}
55+
throw new Error("invariant");
56+
}
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+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { NodePath } from "@babel/core";
2+
3+
export function getDescriptiveName(path: NodePath, defaultName: string): string {
4+
let current: NodePath | null = path;
5+
while (current) {
6+
switch (current.node.type) {
7+
case "FunctionDeclaration":
8+
case "FunctionExpression": {
9+
if (current.node.id) {
10+
return current.node.id.name;
11+
}
12+
break;
13+
}
14+
case "VariableDeclarator": {
15+
if (current.node.id.type === "Identifier") {
16+
return current.node.id.name;
17+
}
18+
break;
19+
}
20+
case "ClassPrivateMethod":
21+
case "ClassMethod":
22+
case "ObjectMethod": {
23+
switch (current.node.key.type) {
24+
case "Identifier":
25+
return current.node.key.name;
26+
case "PrivateName":
27+
return current.node.key.id.name;
28+
default:
29+
break;
30+
}
31+
break;
32+
}
33+
default:
34+
break;
35+
}
36+
current = current.parentPath;
37+
}
38+
return defaultName;
39+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import type * as babel from "@babel/core";
2+
import * as t from "@babel/types";
3+
import { generateUniqueName } from "./generate-unique-name.ts";
4+
import type { ImportDefinition } from "./types.ts";
5+
6+
export function getImportIdentifier(
7+
imports: Map<string, t.Identifier>,
8+
path: babel.NodePath,
9+
registration: ImportDefinition,
10+
): t.Identifier {
11+
const name = registration.kind === "named" ? registration.name : "default";
12+
const target = `${registration.source}[${name}]`;
13+
const current = imports.get(target);
14+
if (current) {
15+
return current;
16+
}
17+
const programParent = path.scope.getProgramParent();
18+
const uid = generateUniqueName(programParent.path, name);
19+
programParent.registerDeclaration(
20+
(programParent.path as babel.NodePath<t.Program>).unshiftContainer(
21+
"body",
22+
t.importDeclaration(
23+
[
24+
registration.kind === "named"
25+
? t.importSpecifier(uid, t.identifier(registration.name))
26+
: t.importDefaultSpecifier(uid),
27+
],
28+
t.stringLiteral(registration.source),
29+
),
30+
)[0],
31+
);
32+
imports.set(target, uid);
33+
return uid;
34+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type * as babel from "@babel/core";
2+
import * as t from "@babel/types";
3+
4+
export function getRootStatementPath(path: babel.NodePath): babel.NodePath {
5+
let current = path.parentPath;
6+
while (current) {
7+
const next = current.parentPath;
8+
if (next && t.isProgram(next.node)) {
9+
return current;
10+
}
11+
current = next;
12+
}
13+
return path;
14+
}

0 commit comments

Comments
 (0)