@solidjs/start 2.0.0-alpha.2 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/config/constants.d.ts +3 -3
  3. package/dist/config/constants.js +1 -1
  4. package/dist/config/dev-server.js +9 -1
  5. package/dist/config/env.d.ts +19 -0
  6. package/dist/config/env.js +81 -0
  7. package/dist/config/fs-routes/fs-watcher.js +32 -28
  8. package/dist/config/fs-routes/index.js +2 -2
  9. package/dist/config/fs-routes/router.d.ts +8 -3
  10. package/dist/config/fs-routes/router.js +26 -13
  11. package/dist/config/fs-routes/tree-shake.js +0 -43
  12. package/dist/config/index.d.ts +16 -1
  13. package/dist/config/index.js +31 -31
  14. package/dist/config/lazy.js +4 -2
  15. package/dist/config/manifest.js +41 -7
  16. package/dist/config/vite-utils.d.ts +1 -1
  17. package/dist/directives/bubble-function-declaration.d.ts +3 -0
  18. package/dist/directives/bubble-function-declaration.js +23 -0
  19. package/dist/directives/compile.d.ts +9 -0
  20. package/dist/directives/compile.js +40 -0
  21. package/dist/directives/generate-unique-name.d.ts +3 -0
  22. package/dist/directives/generate-unique-name.js +16 -0
  23. package/dist/directives/get-descriptive-name.d.ts +2 -0
  24. package/dist/directives/get-descriptive-name.js +37 -0
  25. package/dist/directives/get-import-identifier.d.ts +4 -0
  26. package/dist/directives/get-import-identifier.js +19 -0
  27. package/dist/directives/get-root-statement-path.d.ts +2 -0
  28. package/dist/directives/get-root-statement-path.js +12 -0
  29. package/dist/directives/index.d.ts +14 -0
  30. package/dist/directives/index.js +170 -0
  31. package/dist/directives/is-statement-top-level.d.ts +3 -0
  32. package/dist/directives/is-statement-top-level.js +9 -0
  33. package/dist/directives/paths.d.ts +9 -0
  34. package/dist/directives/paths.js +36 -0
  35. package/dist/directives/plugin.d.ts +21 -0
  36. package/dist/directives/plugin.js +302 -0
  37. package/dist/directives/remove-unused-variables.d.ts +3 -0
  38. package/dist/directives/remove-unused-variables.js +66 -0
  39. package/dist/directives/types.d.ts +10 -0
  40. package/dist/directives/xxhash32.d.ts +6 -0
  41. package/dist/directives/xxhash32.js +193 -0
  42. package/dist/{server/server-runtime.d.ts → fns/client.d.ts} +1 -1
  43. package/dist/fns/client.js +106 -0
  44. package/dist/fns/handler.d.ts +5 -0
  45. package/dist/{server/server-functions-handler.js → fns/handler.js} +55 -96
  46. package/dist/fns/registration.d.ts +2 -0
  47. package/dist/fns/registration.js +12 -0
  48. package/dist/fns/serialization.d.ts +6 -0
  49. package/dist/fns/serialization.js +208 -0
  50. package/dist/fns/server.d.ts +7 -0
  51. package/dist/{server/server-fns-runtime.js → fns/server.js} +7 -1
  52. package/dist/fns/shared.d.ts +17 -0
  53. package/dist/fns/shared.js +105 -0
  54. package/dist/http/index.d.ts +1 -1
  55. package/dist/middleware/index.d.ts +17 -0
  56. package/dist/middleware/index.js +15 -0
  57. package/dist/server/handler.d.ts +61 -3
  58. package/dist/server/handler.js +18 -16
  59. package/dist/server/spa/handler.d.ts +30 -1
  60. package/dist/server/types.d.ts +1 -1
  61. package/dist/shared/dev-overlay/CodeView.jsx +4 -4
  62. package/dist/shared/dev-overlay/DevOverlayDialog.jsx +34 -34
  63. package/dist/shared/dev-overlay/index.jsx +2 -2
  64. package/dist/shared/dev-overlay/styles.css +40 -40
  65. package/dist/utils/debounce.d.ts +4 -0
  66. package/dist/utils/debounce.js +10 -0
  67. package/package.json +28 -21
  68. package/src/config/constants.ts +3 -3
  69. package/src/config/env.ts +105 -0
  70. package/src/config/fs-routes/fs-watcher.ts +38 -49
  71. package/src/config/fs-routes/index.ts +2 -2
  72. package/src/config/fs-routes/router.ts +29 -13
  73. package/src/config/fs-routes/tree-shake.ts +1 -48
  74. package/src/config/index.ts +45 -41
  75. package/src/config/lazy.ts +3 -2
  76. package/src/config/manifest.ts +45 -10
  77. package/src/config/vite-utils.ts +1 -1
  78. package/src/directives/bubble-function-declaration.ts +33 -0
  79. package/src/directives/compile.ts +54 -0
  80. package/src/directives/generate-unique-name.ts +22 -0
  81. package/src/directives/get-descriptive-name.ts +39 -0
  82. package/src/directives/get-import-identifier.ts +34 -0
  83. package/src/directives/get-root-statement-path.ts +14 -0
  84. package/src/directives/index.ts +233 -0
  85. package/src/directives/is-statement-top-level.ts +13 -0
  86. package/src/directives/paths.ts +63 -0
  87. package/src/directives/plugin.ts +389 -0
  88. package/src/directives/remove-unused-variables.ts +70 -0
  89. package/src/directives/types.ts +12 -0
  90. package/src/directives/xxhash32.ts +214 -0
  91. package/src/env.d.ts +1 -1
  92. package/src/fns/client.ts +141 -0
  93. package/src/fns/handler.spec.ts +85 -0
  94. package/src/{server/server-functions-handler.ts → fns/handler.ts} +65 -117
  95. package/src/fns/registration.ts +19 -0
  96. package/src/fns/serialization.ts +250 -0
  97. package/src/{server/server-fns-runtime.ts → fns/server.ts} +17 -2
  98. package/src/fns/shared.ts +116 -0
  99. package/src/middleware/index.ts +17 -0
  100. package/src/server/handler.ts +19 -17
  101. package/src/server/types.ts +1 -1
  102. package/src/shared/dev-overlay/CodeView.tsx +5 -7
  103. package/src/shared/dev-overlay/DevOverlayDialog.tsx +34 -34
  104. package/src/shared/dev-overlay/get-source-map.ts +1 -1
  105. package/src/shared/dev-overlay/index.tsx +4 -4
  106. package/src/shared/dev-overlay/styles.css +40 -40
  107. package/src/utils/debounce.ts +10 -0
  108. package/tsconfig.json +1 -0
  109. package/dist/server/fetchEvent.spec.js +0 -65
  110. package/dist/server/server-fns-runtime.d.ts +0 -1
  111. package/dist/server/server-functions-handler.d.ts +0 -2
  112. package/dist/server/server-runtime.js +0 -194
  113. package/src/server/server-runtime.ts +0 -240
  114. /package/dist/{server/fetchEvent.spec.d.ts → directives/types.js} +0 -0
@@ -1,9 +1,9 @@
1
1
  import { init, parse } from "es-module-lexer";
2
2
  import esbuild from "esbuild";
3
3
  import fg from "fast-glob";
4
- import fs from "fs";
4
+ import fs from "node:fs";
5
5
  import micromatch from "micromatch";
6
- import { posix } from "path";
6
+ import { posix } from "node:path";
7
7
  import { pathToRegexp } from "path-to-regexp";
8
8
 
9
9
  import { normalizePath } from "vite";
@@ -32,6 +32,8 @@ export function analyzeModule(src: string) {
32
32
  );
33
33
  }
34
34
 
35
+ type RouterEvent = CustomEvent<{ route: string; type: "update" | "remove" | "add" }>;
36
+
35
37
  export class BaseFileSystemRouter extends EventTarget {
36
38
  routes: Route[];
37
39
 
@@ -98,8 +100,11 @@ export class BaseFileSystemRouter extends EventTarget {
98
100
  update = undefined;
99
101
 
100
102
  _addRoute(route: Route) {
101
- this.routes = this.routes.filter(r => r.path !== route.path);
103
+ const idx = this.routes.findIndex(r => r.path === route.path);
104
+ if (idx >= 0) this.routes.splice(idx, 1);
102
105
  this.routes.push(route);
106
+
107
+ return idx >= 0;
103
108
  }
104
109
 
105
110
  async addRoute(src: string) {
@@ -109,7 +114,7 @@ export class BaseFileSystemRouter extends EventTarget {
109
114
  const route = this.toRoute(src);
110
115
  if (route) {
111
116
  this._addRoute(route);
112
- this.reload(route.path);
117
+ this.reload(route.path, "add");
113
118
  }
114
119
  } catch (e) {
115
120
  console.error(e);
@@ -117,25 +122,27 @@ export class BaseFileSystemRouter extends EventTarget {
117
122
  }
118
123
  }
119
124
 
120
- reload(route: string) {
125
+ reload(route: string, type: "update" | "remove" | "add") {
121
126
  this.dispatchEvent(
122
- new Event("reload", {
123
- // @ts-ignore
127
+ new CustomEvent("reload", {
124
128
  detail: {
125
129
  route,
130
+ type,
126
131
  },
127
132
  }),
128
133
  );
129
134
  }
130
135
 
131
- async updateRoute(src: string) {
132
- src = normalizePath(src);
136
+ async updateRoute(src_: string) {
137
+ const src = normalizePath(src_);
133
138
  if (this.isRoute(src)) {
134
139
  try {
135
140
  const route = this.toRoute(src);
136
141
  if (route) {
137
- this._addRoute(route);
138
- this.reload(route.path);
142
+ const updated = this._addRoute(route);
143
+ this.reload(route.path, updated ? "update" : "add");
144
+ } else {
145
+ this.removeRoute(src_);
139
146
  }
140
147
  } catch (e) {
141
148
  console.error(e);
@@ -151,11 +158,20 @@ export class BaseFileSystemRouter extends EventTarget {
151
158
  if (path === undefined) {
152
159
  return;
153
160
  }
154
- this.routes = this.routes.filter(r => r.path !== path);
155
- this.dispatchEvent(new Event("reload", {}));
161
+
162
+ const idx = this.routes.findIndex(r => r.path === path);
163
+ if (idx === -1) return;
164
+
165
+ this.routes.splice(idx, 1);
166
+ this.reload(path, "remove");
156
167
  }
157
168
  }
158
169
 
170
+ on(type: string, cb: (evt: RouterEvent) => void) {
171
+ this.addEventListener(type, cb as any);
172
+ return () => this.removeEventListener(type, cb as any);
173
+ }
174
+
159
175
  buildRoutesPromise?: Promise<any[]>;
160
176
 
161
177
  async getRoutes() {
@@ -6,7 +6,7 @@ import type * as Babel from "@babel/core";
6
6
  import type { NodePath, PluginObj, PluginPass } from "@babel/core";
7
7
  import type { Binding } from "@babel/traverse";
8
8
  import { basename } from "pathe";
9
- import type { Plugin, ResolvedConfig, ViteDevServer } from "vite";
9
+ import type { Plugin } from "vite";
10
10
 
11
11
  type State = Omit<PluginPass, "opts"> & {
12
12
  opts: { pick: string[] };
@@ -280,10 +280,6 @@ function treeShakeTransform({ types: t }: typeof Babel): PluginObj<State> {
280
280
  }
281
281
 
282
282
  export function treeShake(): Plugin {
283
- let config: ResolvedConfig;
284
- const cache: Record<string, any> = {};
285
- let server: ViteDevServer;
286
-
287
283
  async function transform(id: string, code: string) {
288
284
  const [path, queryString] = id.split("?");
289
285
  const query = new URLSearchParams(queryString);
@@ -308,46 +304,6 @@ export function treeShake(): Plugin {
308
304
  return {
309
305
  name: "tree-shake",
310
306
  enforce: "pre",
311
- configResolved(resolvedConfig) {
312
- config = resolvedConfig;
313
- },
314
- configureServer(s) {
315
- server = s;
316
- },
317
- async handleHotUpdate(ctx) {
318
- if (cache[ctx.file]) {
319
- const mods = [];
320
- const newCode = await ctx.read();
321
- for (const [id, code] of Object.entries(cache[ctx.file])) {
322
- const transformed = await transform(id, newCode);
323
- if (!transformed) continue;
324
-
325
- const { code: transformedCode } = transformed;
326
-
327
- if (transformedCode !== code) {
328
- const mod = server.moduleGraph.getModuleById(id);
329
- if (mod) mods.push(mod);
330
- }
331
-
332
- cache[ctx.file] ??= {};
333
- cache[ctx.file][id] = transformedCode;
334
- // server.moduleGraph.setModuleSource(id, code);
335
- }
336
-
337
- return mods;
338
- }
339
- // const mods = [];
340
- // [...server.moduleGraph.urlToModuleMap.entries()].forEach(([url, m]) => {
341
- // if (m.file === ctx.file && m.id.includes("pick=")) {
342
- // if (!m.id.includes("pick=loader")) {
343
- // mods.push(m);
344
- // }
345
- // }
346
- // });
347
- // return mods;
348
- // // this.router.updateRoute(ctx.path);
349
- // }
350
- },
351
307
  async transform(code, id) {
352
308
  const [path, queryString] = id.split("?");
353
309
  if (!path) return;
@@ -358,9 +314,6 @@ export function treeShake(): Plugin {
358
314
  const transformed = await transform(id, code);
359
315
  if (!transformed?.code) return;
360
316
 
361
- cache[path] ??= {};
362
- cache[path][id] = transformed.code;
363
-
364
317
  return {
365
318
  code: transformed.code,
366
319
  map: transformed.map,
@@ -1,13 +1,12 @@
1
- import { TanStackServerFnPlugin } from "@tanstack/server-functions-plugin";
2
1
  import { defu } from "defu";
3
2
  import { globSync } from "node:fs";
4
3
  import { extname, isAbsolute, join } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { normalizePath, type PluginOption } from "vite";
4
+ import type { PluginOption } from "vite";
7
5
  import solid, { type Options as SolidOptions } from "vite-plugin-solid";
8
-
6
+ import { type ServerFunctionsOptions, serverFunctionsPlugin } from "../directives/index.ts";
9
7
  import { DEFAULT_EXTENSIONS, VIRTUAL_MODULES, VITE_ENVIRONMENTS } from "./constants.ts";
10
8
  import { devServer } from "./dev-server.ts";
9
+ import { envPlugin, type EnvPluginOptions } from "./env.ts";
11
10
  import { SolidStartClientFileRouter, SolidStartServerFileRouter } from "./fs-router.ts";
12
11
  import { fsRoutes } from "./fs-routes/index.ts";
13
12
  import type { BaseFileSystemRouter } from "./fs-routes/router.ts";
@@ -21,6 +20,19 @@ export interface SolidStartOptions {
21
20
  routeDir?: string;
22
21
  extensions?: string[];
23
22
  middleware?: string;
23
+ serialization?: {
24
+ /**
25
+ * The serialization mode to use for server functions/actions.
26
+ * The "js" mode uses a custom binary format that is more efficient than JSON, but requires a custom deserializer (with `eval()`) on the client.
27
+ * A strong CSP should block `eval()` executions, which would prevent the "js" mode from working.
28
+ * The "json" mode uses JSON for serialization, which is less efficient but can be deserialized with `JSON.parse` on the client.
29
+ *
30
+ * @default "json"
31
+ */
32
+ mode?: "js" | "json";
33
+ };
34
+ env?: EnvPluginOptions;
35
+ serverFunctions?: Pick<ServerFunctionsOptions, "filter">;
24
36
  }
25
37
 
26
38
  const absolute = (path: string, root: string) =>
@@ -51,6 +63,18 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
51
63
  server: `${start.appRoot}/entry-server${entryExtension}`,
52
64
  };
53
65
  return [
66
+ // TODO (Alexis): check if the comment below is still relevant
67
+ //
68
+ // Must be placed after fsRoutes, as treeShake will remove the
69
+ // server fn exports added in by this plugin
70
+ serverFunctionsPlugin({
71
+ manifest: VIRTUAL_MODULES.serverFnManifest,
72
+ runtime: {
73
+ server: '@solidjs/start/fns/server',
74
+ client: '@solidjs/start/fns/client',
75
+ },
76
+ filter: options?.serverFunctions?.filter,
77
+ }),
54
78
  {
55
79
  name: "solid-start:config",
56
80
  enforce: "pre",
@@ -80,6 +104,16 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
80
104
  return {
81
105
  appType: "custom",
82
106
  build: { assetsDir: "_build/assets" },
107
+ optimizeDeps: {
108
+ // Suppress TS errors from Vite 7 types when configuring Vite 8's Rolldown
109
+ ...({
110
+ rolldownOptions: {
111
+ transform: {
112
+ jsx: "react",
113
+ },
114
+ },
115
+ } as any),
116
+ },
83
117
  environments: {
84
118
  [VITE_ENVIRONMENTS.client]: {
85
119
  consumer: "client",
@@ -122,6 +156,9 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
122
156
  }
123
157
  : {}),
124
158
  },
159
+ // Depending on the package manager and dependency structure Vite externalizes @solidjs/start
160
+ // This makes sure that @solidjs/start goes through the Vite build process
161
+ noExternal: ["@solidjs/start"],
125
162
  },
126
163
  define: {
127
164
  "import.meta.env.MANIFEST": `globalThis.MANIFEST`,
@@ -131,6 +168,7 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
131
168
  "import.meta.env.START_APP_ENTRY": JSON.stringify(appEntryPath),
132
169
  "import.meta.env.START_CLIENT_ENTRY": JSON.stringify(handlers.client),
133
170
  "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
171
+ "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || "json"),
134
172
  },
135
173
  builder: {
136
174
  sharedPlugins: true,
@@ -163,42 +201,7 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
163
201
  },
164
202
  }),
165
203
  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
- },
201
- }),
204
+ envPlugin(options?.env),
202
205
  {
203
206
  name: "solid-start:virtual-modules",
204
207
  async resolveId(id) {
@@ -221,8 +224,9 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
221
224
  {
222
225
  name: "solid-start:capture-client-bundle",
223
226
  enforce: "post",
224
- generateBundle(_options, bundle) {
227
+ generateBundle(options, bundle) {
225
228
  globalThis.START_CLIENT_BUNDLE = bundle;
229
+ (globalThis as any).START_CLIENT_OUT_DIR = options.dir;
226
230
  },
227
231
  },
228
232
  devServer(),
@@ -1,8 +1,8 @@
1
1
  import type { PluginItem } from "@babel/core";
2
2
  import babel from "@babel/core";
3
3
  import * as t from "@babel/types";
4
- import { sep as osSep } from "path";
5
- import { basename, relative, sep } from "path/posix";
4
+ import { sep as osSep } from "node:path";
5
+ import { basename, relative, sep } from "node:path/posix";
6
6
  import type { PluginOption } from "vite";
7
7
  import { VITE_ENVIRONMENTS } from "./constants.ts";
8
8
 
@@ -90,6 +90,7 @@ const lazy = (): PluginOption => {
90
90
  // The transformed files either import "lazy" or css files
91
91
  // Therefore we skip, if the src doesn't have any import
92
92
  if (src.indexOf("import") === -1) return;
93
+ if (id.includes("entry-server")) return;
93
94
 
94
95
  const plugins: PluginItem[] = [];
95
96
 
@@ -1,4 +1,7 @@
1
- import { type PluginOption, type ViteDevServer } from "vite";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { type PluginOption, type ViteDevServer, version as viteVersion } from "vite";
4
+ import { fileURLToPath } from "node:url";
2
5
 
3
6
  import { findStylesInModuleGraph } from "../server/collect-styles.ts";
4
7
  import { VIRTUAL_MODULES } from "./constants.ts";
@@ -18,12 +21,16 @@ export function manifest(start: SolidStartOptions): PluginOption {
18
21
  return `\0${VIRTUAL_MODULES.clientViteManifest}`;
19
22
  if (id === VIRTUAL_MODULES.getClientManifest)
20
23
  return this.resolve(
21
- new URL("../server/manifest/client-manifest", import.meta.url).pathname,
24
+ fileURLToPath(new URL("../server/manifest/client-manifest", import.meta.url)),
22
25
  );
23
26
  if (id === VIRTUAL_MODULES.getManifest) {
24
27
  return this.environment.config.consumer === "client"
25
- ? this.resolve(new URL("../server/manifest/client-manifest", import.meta.url).pathname)
26
- : this.resolve(new URL("../server/manifest/ssr-manifest", import.meta.url).pathname);
28
+ ? this.resolve(
29
+ fileURLToPath(new URL("../server/manifest/client-manifest", import.meta.url)),
30
+ )
31
+ : this.resolve(
32
+ fileURLToPath(new URL("../server/manifest/ssr-manifest", import.meta.url)),
33
+ );
27
34
  }
28
35
  if (id === VIRTUAL_MODULES.middleware) {
29
36
  if (start.middleware) return await this.resolve(start.middleware);
@@ -40,9 +47,37 @@ export function manifest(start: SolidStartOptions): PluginOption {
40
47
  v => "isEntry" in v && v.isEntry,
41
48
  );
42
49
  if (!entry) throw new Error("No client entry found");
43
- clientViteManifest = JSON.parse(
44
- (globalThis.START_CLIENT_BUNDLE[".vite/manifest.json"] as any).source,
45
- );
50
+ let rawManifest: string | undefined;
51
+
52
+ const viteMajor = parseInt(viteVersion.split('.')[0]!, 10);
53
+
54
+ const manifestKey = Object.keys(globalThis.START_CLIENT_BUNDLE).find(k => k.endsWith("manifest.json"));
55
+ if (manifestKey && viteMajor < 8) {
56
+ const manifestAsset = globalThis.START_CLIENT_BUNDLE[manifestKey] as any;
57
+ rawManifest = manifestAsset.source as string;
58
+ } else {
59
+ try {
60
+ const appRoot = (start as any).appRoot || "./src";
61
+ let outDir = ".solid-start/client";
62
+ if (devServer?.environments?.client?.config?.build?.outDir) {
63
+ outDir = devServer.environments.client.config.build.outDir;
64
+ } else if (this.environment?.config?.build?.outDir && this.environment?.config?.consumer === "client") {
65
+ outDir = this.environment.config.build.outDir;
66
+ } else if ((globalThis as any).START_CLIENT_OUT_DIR) {
67
+ outDir = (globalThis as any).START_CLIENT_OUT_DIR;
68
+ }
69
+ const manifestPath = path.resolve(appRoot, "..", outDir, ".vite/manifest.json");
70
+ rawManifest = fs.readFileSync(manifestPath, "utf-8");
71
+ } catch (e) {
72
+ throw new Error(`Manifest asset not found in bundle and could not be read from disk. Keys: ${Object.keys(globalThis.START_CLIENT_BUNDLE).join(", ")}. Error: ${e}`);
73
+ }
74
+ }
75
+
76
+ if (!rawManifest) {
77
+ throw new Error("Failed to extract or read raw manifest.");
78
+ }
79
+
80
+ clientViteManifest = JSON.parse(rawManifest);
46
81
  }
47
82
  return `export const clientViteManifest = ${JSON.stringify(clientViteManifest)};`;
48
83
  } else if (id === `\0${VIRTUAL_MODULES.middleware}`) return "export default {};";
@@ -50,11 +85,11 @@ export function manifest(start: SolidStartOptions): PluginOption {
50
85
  if (this.environment.mode !== "dev")
51
86
  throw new Error("@manifest queries are only allowed in dev");
52
87
 
53
- const [path, query] = id.split("?");
88
+ const [urlPath, query] = id.split("?");
54
89
  const target = id.split("/")[2]!;
55
90
  const params = new URLSearchParams(query);
56
- if (!path || !query) return;
57
- if (path.endsWith("assets")) {
91
+ if (!urlPath || !query) return;
92
+ if (urlPath.endsWith("assets")) {
58
93
  const id = params.get("id");
59
94
  if (!id) {
60
95
  throw new Error("Missing id to get assets.");
@@ -1,4 +1,4 @@
1
- import { DevEnvironment, Rollup } from "vite";
1
+ import type { DevEnvironment, Rollup } from "vite";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
 
@@ -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
+ }
@@ -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
+ }
@@ -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,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
+ }
@@ -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
+ }
@@ -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
+ }