@solidjs/start 2.0.0-alpha.1 → 2.0.0-alpha.3

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 (119) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/config/constants.d.ts +1 -1
  3. package/dist/config/constants.js +1 -1
  4. package/dist/config/dev-server.js +18 -0
  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 +14 -20
  8. package/dist/config/fs-routes/index.js +2 -2
  9. package/dist/config/fs-routes/router.js +7 -8
  10. package/dist/config/index.d.ts +16 -1
  11. package/dist/config/index.js +30 -29
  12. package/dist/config/lazy.js +35 -5
  13. package/dist/config/manifest.js +40 -6
  14. package/dist/config/vite-utils.d.ts +6 -1
  15. package/dist/config/vite-utils.js +7 -3
  16. package/dist/directives/bubble-function-declaration.d.ts +3 -0
  17. package/dist/directives/bubble-function-declaration.js +23 -0
  18. package/dist/directives/compile.d.ts +9 -0
  19. package/dist/directives/compile.js +40 -0
  20. package/dist/directives/generate-unique-name.d.ts +3 -0
  21. package/dist/directives/generate-unique-name.js +16 -0
  22. package/dist/directives/get-descriptive-name.d.ts +2 -0
  23. package/dist/directives/get-descriptive-name.js +37 -0
  24. package/dist/directives/get-import-identifier.d.ts +4 -0
  25. package/dist/directives/get-import-identifier.js +19 -0
  26. package/dist/directives/get-root-statement-path.d.ts +2 -0
  27. package/dist/directives/get-root-statement-path.js +12 -0
  28. package/dist/directives/index.d.ts +14 -0
  29. package/dist/directives/index.js +169 -0
  30. package/dist/directives/is-statement-top-level.d.ts +3 -0
  31. package/dist/directives/is-statement-top-level.js +9 -0
  32. package/dist/directives/paths.d.ts +9 -0
  33. package/dist/directives/paths.js +36 -0
  34. package/dist/directives/plugin.d.ts +21 -0
  35. package/dist/directives/plugin.js +302 -0
  36. package/dist/directives/remove-unused-variables.d.ts +3 -0
  37. package/dist/directives/remove-unused-variables.js +53 -0
  38. package/dist/directives/types.d.ts +10 -0
  39. package/dist/directives/xxhash32.d.ts +6 -0
  40. package/dist/directives/xxhash32.js +193 -0
  41. package/dist/{server/server-runtime.d.ts → fns/client.d.ts} +1 -1
  42. package/dist/fns/client.js +106 -0
  43. package/dist/fns/handler.d.ts +5 -0
  44. package/dist/{server/server-functions-handler.js → fns/handler.js} +55 -96
  45. package/dist/fns/registration.d.ts +2 -0
  46. package/dist/fns/registration.js +12 -0
  47. package/dist/fns/serialization.d.ts +6 -0
  48. package/dist/fns/serialization.js +208 -0
  49. package/dist/fns/server.d.ts +7 -0
  50. package/dist/{server/server-fns-runtime.js → fns/server.js} +7 -1
  51. package/dist/fns/shared.d.ts +17 -0
  52. package/dist/fns/shared.js +105 -0
  53. package/dist/http/index.d.ts +1 -1
  54. package/dist/middleware/index.d.ts +20 -0
  55. package/dist/middleware/index.js +21 -1
  56. package/dist/server/StartServer.jsx +2 -0
  57. package/dist/server/assets/PatchVirtualDevStyles.d.ts +4 -0
  58. package/dist/server/assets/PatchVirtualDevStyles.jsx +28 -0
  59. package/dist/server/collect-styles.js +9 -21
  60. package/dist/server/handler.d.ts +61 -3
  61. package/dist/server/handler.js +18 -15
  62. package/dist/server/spa/StartServer.jsx +2 -0
  63. package/dist/server/spa/handler.d.ts +30 -1
  64. package/dist/server/types.d.ts +1 -1
  65. package/dist/shared/dev-overlay/CodeView.jsx +4 -4
  66. package/dist/shared/dev-overlay/DevOverlayDialog.jsx +34 -34
  67. package/dist/shared/dev-overlay/index.jsx +2 -2
  68. package/dist/shared/dev-overlay/styles.css +40 -40
  69. package/package.json +27 -20
  70. package/src/config/constants.ts +1 -1
  71. package/src/config/dev-server.ts +12 -0
  72. package/src/config/env.ts +105 -0
  73. package/src/config/fs-routes/fs-watcher.ts +17 -24
  74. package/src/config/fs-routes/index.ts +2 -2
  75. package/src/config/fs-routes/router.ts +7 -8
  76. package/src/config/index.ts +44 -40
  77. package/src/config/lazy.ts +36 -5
  78. package/src/config/manifest.ts +40 -9
  79. package/src/config/vite-utils.ts +8 -4
  80. package/src/directives/bubble-function-declaration.ts +33 -0
  81. package/src/directives/compile.ts +54 -0
  82. package/src/directives/generate-unique-name.ts +22 -0
  83. package/src/directives/get-descriptive-name.ts +39 -0
  84. package/src/directives/get-import-identifier.ts +34 -0
  85. package/src/directives/get-root-statement-path.ts +14 -0
  86. package/src/directives/index.ts +232 -0
  87. package/src/directives/is-statement-top-level.ts +13 -0
  88. package/src/directives/paths.ts +63 -0
  89. package/src/directives/plugin.ts +389 -0
  90. package/src/directives/remove-unused-variables.ts +55 -0
  91. package/src/directives/types.ts +12 -0
  92. package/src/directives/xxhash32.ts +214 -0
  93. package/src/env.d.ts +1 -1
  94. package/src/fns/client.ts +141 -0
  95. package/src/fns/handler.spec.ts +85 -0
  96. package/src/{server/server-functions-handler.ts → fns/handler.ts} +65 -117
  97. package/src/fns/registration.ts +19 -0
  98. package/src/fns/serialization.ts +250 -0
  99. package/src/{server/server-fns-runtime.ts → fns/server.ts} +17 -2
  100. package/src/fns/shared.ts +116 -0
  101. package/src/middleware/index.ts +21 -1
  102. package/src/server/StartServer.tsx +2 -0
  103. package/src/server/assets/PatchVirtualDevStyles.tsx +31 -0
  104. package/src/server/collect-styles.ts +9 -26
  105. package/src/server/handler.ts +20 -16
  106. package/src/server/spa/StartServer.tsx +2 -0
  107. package/src/server/types.ts +1 -1
  108. package/src/shared/dev-overlay/CodeView.tsx +5 -7
  109. package/src/shared/dev-overlay/DevOverlayDialog.tsx +34 -34
  110. package/src/shared/dev-overlay/get-source-map.ts +1 -1
  111. package/src/shared/dev-overlay/index.tsx +4 -4
  112. package/src/shared/dev-overlay/styles.css +40 -40
  113. package/tsconfig.json +1 -0
  114. package/dist/server/fetchEvent.spec.js +0 -65
  115. package/dist/server/server-fns-runtime.d.ts +0 -1
  116. package/dist/server/server-functions-handler.d.ts +0 -2
  117. package/dist/server/server-runtime.js +0 -194
  118. package/src/server/server-runtime.ts +0 -240
  119. /package/dist/{server/fetchEvent.spec.d.ts → directives/types.js} +0 -0
@@ -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
- import { extname, isAbsolute, join, normalize } from "node:path";
5
- import { fileURLToPath } from "node:url";
6
- import { type PluginOption } from "vite";
3
+ import { extname, isAbsolute, join } from "node:path";
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) =>
@@ -80,6 +92,16 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
80
92
  return {
81
93
  appType: "custom",
82
94
  build: { assetsDir: "_build/assets" },
95
+ optimizeDeps: {
96
+ // Suppress TS errors from Vite 7 types when configuring Vite 8's Rolldown
97
+ ...({
98
+ rolldownOptions: {
99
+ transform: {
100
+ jsx: "react",
101
+ },
102
+ },
103
+ } as any),
104
+ },
83
105
  environments: {
84
106
  [VITE_ENVIRONMENTS.client]: {
85
107
  consumer: "client",
@@ -122,13 +144,19 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
122
144
  }
123
145
  : {}),
124
146
  },
147
+ // Depending on the package manager and dependency structure Vite externalizes @solidjs/start
148
+ // This makes sure that @solidjs/start goes through the Vite build process
149
+ noExternal: ["@solidjs/start"],
125
150
  },
126
151
  define: {
127
152
  "import.meta.env.MANIFEST": `globalThis.MANIFEST`,
128
153
  "import.meta.env.START_SSR": JSON.stringify(start.ssr),
129
- "import.meta.env.START_APP_ENTRY": `"${appEntryPath}"`,
130
- "import.meta.env.START_CLIENT_ENTRY": `"${handlers.client}"`,
154
+ // Use JSON.stringify so backslashes on Windows are escaped and
155
+ // esbuild receives a valid JS string literal for the define value
156
+ "import.meta.env.START_APP_ENTRY": JSON.stringify(appEntryPath),
157
+ "import.meta.env.START_CLIENT_ENTRY": JSON.stringify(handlers.client),
131
158
  "import.meta.env.START_DEV_OVERLAY": JSON.stringify(start.devOverlay),
159
+ "import.meta.env.SEROVAL_MODE": JSON.stringify(start.serialization?.mode || "json"),
132
160
  },
133
161
  builder: {
134
162
  sharedPlugins: true,
@@ -161,41 +189,16 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
161
189
  },
162
190
  }),
163
191
  lazy(),
192
+ envPlugin(options?.env),
164
193
  // Must be placed after fsRoutes, as treeShake will remove the
165
194
  // server fn exports added in by this plugin
166
- TanStackServerFnPlugin({
167
- // This is the ID that will be available to look up and import
168
- // our server function manifest and resolve its module
169
- manifestVirtualImportId: VIRTUAL_MODULES.serverFnManifest,
170
- directive: "use server",
171
- callers: [
172
- {
173
- envConsumer: "client",
174
- envName: VITE_ENVIRONMENTS.client,
175
- getRuntimeCode: () =>
176
- `import { createServerReference } from "${normalize(
177
- fileURLToPath(new URL("../server/server-runtime", import.meta.url)),
178
- )}"`,
179
- replacer: opts => `createServerReference('${opts.functionId}')`,
180
- },
181
- {
182
- envConsumer: "server",
183
- envName: VITE_ENVIRONMENTS.server,
184
- getRuntimeCode: () =>
185
- `import { createServerReference } from '${normalize(
186
- fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)),
187
- )}'`,
188
- replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
189
- },
190
- ],
191
- provider: {
192
- envName: VITE_ENVIRONMENTS.server,
193
- getRuntimeCode: () =>
194
- `import { createServerReference } from '${normalize(
195
- fileURLToPath(new URL("../server/server-fns-runtime", import.meta.url)),
196
- )}'`,
197
- replacer: opts => `createServerReference(${opts.fn}, '${opts.functionId}')`,
195
+ serverFunctionsPlugin({
196
+ manifest: VIRTUAL_MODULES.serverFnManifest,
197
+ runtime: {
198
+ server: '@solidjs/start/fns/server',
199
+ client: '@solidjs/start/fns/client',
198
200
  },
201
+ filter: options?.serverFunctions?.filter,
199
202
  }),
200
203
  {
201
204
  name: "solid-start:virtual-modules",
@@ -219,8 +222,9 @@ export function solidStart(options?: SolidStartOptions): Array<PluginOption> {
219
222
  {
220
223
  name: "solid-start:capture-client-bundle",
221
224
  enforce: "post",
222
- generateBundle(_options, bundle) {
225
+ generateBundle(options, bundle) {
223
226
  globalThis.START_CLIENT_BUNDLE = bundle;
227
+ (globalThis as any).START_CLIENT_OUT_DIR = options.dir;
224
228
  },
225
229
  },
226
230
  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
 
@@ -48,13 +48,43 @@ const fileEndingRegex = /(ts|js)x(\?.*)?$/;
48
48
 
49
49
  const lazy = (): PluginOption => {
50
50
  const cwd = process.cwd().replaceAll(osSep, sep);
51
+
52
+ /**
53
+ * Maps module ids to their client-specific shared chunk names.
54
+ * Modules in shared chunks need to find their assets via the chunk name, instead of their module id.
55
+ *
56
+ * Vite includes assets of such modules in the manifest via the chunk name:
57
+ * https://github.com/vitejs/vite/blob/4be37a8389c67873880f826b01fe40137e1c29a7/packages/vite/src/node/plugins/manifest.ts#L179
58
+ * https://github.com/vitejs/vite/blob/4be37a8389c67873880f826b01fe40137e1c29a7/packages/vite/src/node/plugins/manifest.ts#L319
59
+ *
60
+ * Rollup occassionally creates shared chunks automatically,
61
+ * but they can also be manually created by the user via:
62
+ * https://rollupjs.org/configuration-options/#output-manualchunks
63
+ *
64
+ * More infos on Rollup's logic:
65
+ * https://github.com/rollup/rollup/issues/3772#issuecomment-689955168
66
+ */
67
+ const sharedChunkNames: Record<string, string> = {};
68
+
51
69
  return {
52
70
  name: "solid-lazy-css",
53
71
  enforce: "pre",
54
- applyToEnvironment(env) {
55
- return env.name === VITE_ENVIRONMENTS.server;
72
+ generateBundle(_, bundle) {
73
+ if (this.environment.name !== VITE_ENVIRONMENTS.client) return;
74
+
75
+ for (const chunk of Object.values(bundle)) {
76
+ if (chunk.type !== "chunk" || !chunk.isDynamicEntry || chunk.facadeModuleId) continue;
77
+
78
+ // Has to follow Vites implementation:
79
+ // https://github.com/vitejs/vite/blob/4be37a8389c67873880f826b01fe40137e1c29a7/packages/vite/src/node/plugins/manifest.ts#L179
80
+ const chunkName = `_${basename(chunk.fileName)}`;
81
+ for (const id of chunk.moduleIds) {
82
+ sharedChunkNames[id] = chunkName;
83
+ }
84
+ }
56
85
  },
57
86
  async transform(src, id) {
87
+ if (this.environment.name !== VITE_ENVIRONMENTS.server) return;
58
88
  if (!id.match(fileEndingRegex)) return;
59
89
 
60
90
  // The transformed files either import "lazy" or css files
@@ -66,7 +96,8 @@ const lazy = (): PluginOption => {
66
96
  const hasDefaultExport = src.indexOf("export default") !== -1;
67
97
  if (hasDefaultExport) {
68
98
  const localId = relative(cwd, id);
69
- plugins.push(idTransform(localId));
99
+ const chunkName = sharedChunkNames[id];
100
+ plugins.push(idTransform(chunkName ?? localId));
70
101
  }
71
102
 
72
103
  const hasLazy = src.indexOf("lazy(") !== -1;
@@ -1,8 +1,11 @@
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";
2
4
 
3
5
  import { findStylesInModuleGraph } from "../server/collect-styles.ts";
4
6
  import { VIRTUAL_MODULES } from "./constants.ts";
5
7
  import { type SolidStartOptions } from "./index.ts";
8
+ import { wrapId } from "./vite-utils.ts";
6
9
 
7
10
  export function manifest(start: SolidStartOptions): PluginOption {
8
11
  let devServer: ViteDevServer = undefined!;
@@ -39,9 +42,37 @@ export function manifest(start: SolidStartOptions): PluginOption {
39
42
  v => "isEntry" in v && v.isEntry,
40
43
  );
41
44
  if (!entry) throw new Error("No client entry found");
42
- clientViteManifest = JSON.parse(
43
- (globalThis.START_CLIENT_BUNDLE[".vite/manifest.json"] as any).source,
44
- );
45
+ let rawManifest: string | undefined;
46
+
47
+ const viteMajor = parseInt(viteVersion.split('.')[0]!, 10);
48
+
49
+ const manifestKey = Object.keys(globalThis.START_CLIENT_BUNDLE).find(k => k.endsWith("manifest.json"));
50
+ if (manifestKey && viteMajor < 8) {
51
+ const manifestAsset = globalThis.START_CLIENT_BUNDLE[manifestKey] as any;
52
+ rawManifest = manifestAsset.source as string;
53
+ } else {
54
+ try {
55
+ const appRoot = (start as any).appRoot || "./src";
56
+ let outDir = ".solid-start/client";
57
+ if (devServer?.environments?.client?.config?.build?.outDir) {
58
+ outDir = devServer.environments.client.config.build.outDir;
59
+ } else if (this.environment?.config?.build?.outDir && this.environment?.config?.consumer === "client") {
60
+ outDir = this.environment.config.build.outDir;
61
+ } else if ((globalThis as any).START_CLIENT_OUT_DIR) {
62
+ outDir = (globalThis as any).START_CLIENT_OUT_DIR;
63
+ }
64
+ const manifestPath = path.resolve(appRoot, "..", outDir, ".vite/manifest.json");
65
+ rawManifest = fs.readFileSync(manifestPath, "utf-8");
66
+ } catch (e) {
67
+ 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}`);
68
+ }
69
+ }
70
+
71
+ if (!rawManifest) {
72
+ throw new Error("Failed to extract or read raw manifest.");
73
+ }
74
+
75
+ clientViteManifest = JSON.parse(rawManifest);
45
76
  }
46
77
  return `export const clientViteManifest = ${JSON.stringify(clientViteManifest)};`;
47
78
  } else if (id === `\0${VIRTUAL_MODULES.middleware}`) return "export default {};";
@@ -49,11 +80,11 @@ export function manifest(start: SolidStartOptions): PluginOption {
49
80
  if (this.environment.mode !== "dev")
50
81
  throw new Error("@manifest queries are only allowed in dev");
51
82
 
52
- const [path, query] = id.split("?");
83
+ const [urlPath, query] = id.split("?");
53
84
  const target = id.split("/")[2]!;
54
85
  const params = new URLSearchParams(query);
55
- if (!path || !query) return;
56
- if (path.endsWith("assets")) {
86
+ if (!urlPath || !query) return;
87
+ if (urlPath.endsWith("assets")) {
57
88
  const id = params.get("id");
58
89
  if (!id) {
59
90
  throw new Error("Missing id to get assets.");
@@ -69,10 +100,10 @@ export function manifest(start: SolidStartOptions): PluginOption {
69
100
  tag: "style",
70
101
  attrs: {
71
102
  type: "text/css",
72
- "data-vite-dev-id": "${key}",
103
+ "data-vite-dev-id": "${wrapId(key)}",
73
104
  "data-vite-ref": "0",
74
105
  },
75
- children: () => import("${value}?inline").then(mod => mod.default),
106
+ children: () => import("${wrapId(value)}?inline").then(mod => mod.default),
76
107
  }`,
77
108
  );
78
109
 
@@ -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
 
@@ -12,6 +12,7 @@ export const FS_PREFIX = `/@fs/`;
12
12
  export const VALID_ID_PREFIX = `/@id/`;
13
13
 
14
14
  export const NULL_BYTE_PLACEHOLDER = `__x00__`;
15
+ const NULL_BYTE_REGEX = /^\0/;
15
16
 
16
17
  export function normalizeResolvedIdToUrl(
17
18
  environment: DevEnvironment,
@@ -52,10 +53,13 @@ export function normalizeResolvedIdToUrl(
52
53
  return url;
53
54
  }
54
55
 
56
+ /**
57
+ * Inspired by:
58
+ * https://github.com/withastro/astro/blob/fddde5fad81007795eb263c7fd0cea096b8e2cba/packages/astro/src/core/util.ts#L115
59
+ * https://github.com/vitejs/vite/blob/130e7181a55c524383c63bbfb1749d0ff7185cad/packages/vite/src/shared/utils.ts#L11
60
+ */
55
61
  export function wrapId(id: string): string {
56
- return id.startsWith(VALID_ID_PREFIX)
57
- ? id
58
- : VALID_ID_PREFIX + id.replace("\0", NULL_BYTE_PLACEHOLDER);
62
+ return id.replace(NULL_BYTE_REGEX, `${VALID_ID_PREFIX}${NULL_BYTE_PLACEHOLDER}`);
59
63
  }
60
64
 
61
65
  export function unwrapId(id: string): string {
@@ -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
+ }