@anaemia/bundler 0.0.1 → 0.1.1

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.
@@ -1,7 +1,5 @@
1
- export default function serverHashInjector({ types: t }: any): {
2
- name: string;
3
- visitor: {
4
- CallExpression(path: any, state: any): void;
5
- };
6
- };
1
+ import type { PluginPass, types as BabelTypes, PluginObj } from "@babel/core";
2
+ export default function serverHashInjector({ types: t }: {
3
+ types: typeof BabelTypes;
4
+ }): PluginObj<PluginPass>;
7
5
  //# sourceMappingURL=babel-hash-injector-server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"babel-hash-injector-server.d.ts","sourceRoot":"","sources":["../../src/plugins/babel-hash-injector-server.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG;;;6BAIjC,GAAG,SAAS,GAAG;;EAYzC"}
1
+ {"version":3,"file":"babel-hash-injector-server.d.ts","sourceRoot":"","sources":["../../src/plugins/babel-hash-injector-server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAY,UAAU,EAAE,KAAK,IAAI,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExF,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAAE,KAAK,EAAE,OAAO,UAAU,CAAA;CAAE,GAAG,SAAS,CAAC,UAAU,CAAC,CAgB5G"}
@@ -4,14 +4,14 @@ export default function serverHashInjector({ types: t }) {
4
4
  name: "anaemia-server-hash-injector",
5
5
  visitor: {
6
6
  CallExpression(path, state) {
7
- if (path.node.callee.name === "runOnServer") {
7
+ if (t.isIdentifier(path.node.callee) && path.node.callee.name === "runOnServer") {
8
8
  const filename = state.file.opts.filename || "unknown";
9
9
  const functionHash = createServerFunctionId(filename, path.node.start);
10
10
  if (path.node.arguments.length === 1) {
11
11
  path.node.arguments.push(t.stringLiteral(functionHash));
12
12
  }
13
13
  }
14
- }
15
- }
14
+ },
15
+ },
16
16
  };
17
17
  }
@@ -1,11 +1,9 @@
1
- export default function clientServerFnTransform({ types: t }: any): {
2
- name: string;
3
- visitor: {
4
- Program: {
5
- enter(path: any, state: any): void;
6
- exit(path: any, state: any): void;
7
- };
8
- CallExpression(path: any, state: any): void;
9
- };
10
- };
1
+ import type { PluginObj, PluginPass, types as BabelTypes } from "@babel/core";
2
+ interface PluginState extends PluginPass {
3
+ hasRunOnServer: boolean;
4
+ }
5
+ export default function clientServerFnTransform({ types: t }: {
6
+ types: typeof BabelTypes;
7
+ }): PluginObj<PluginState>;
8
+ export {};
11
9
  //# sourceMappingURL=babel-transform-server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"babel-transform-server.d.ts","sourceRoot":"","sources":["../../src/plugins/babel-transform-server.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE,GAAG;;;;wBAK7C,GAAG,SAAS,GAAG;uBAGhB,GAAG,SAAS,GAAG;;6BAUP,GAAG,SAAS,GAAG;;EA2HzC"}
1
+ {"version":3,"file":"babel-transform-server.d.ts","sourceRoot":"","sources":["../../src/plugins/babel-transform-server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAY,SAAS,EAAE,UAAU,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAExF,UAAU,WAAY,SAAQ,UAAU;IACtC,cAAc,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,CAAC,OAAO,UAAU,uBAAuB,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;IAAE,KAAK,EAAE,OAAO,UAAU,CAAA;CAAE,GAAG,SAAS,CAAC,WAAW,CAAC,CA6IlH"}
@@ -1,4 +1,5 @@
1
1
  import { createServerFunctionId } from "../server-function-id.js";
2
+ ;
2
3
  export default function clientServerFnTransform({ types: t }) {
3
4
  return {
4
5
  name: "anaemia-client-server-fn-transform",
@@ -15,7 +16,7 @@ export default function clientServerFnTransform({ types: t }) {
15
16
  }
16
17
  },
17
18
  CallExpression(path, state) {
18
- if (path.node.callee.name === "runOnServer") {
19
+ if (t.isIdentifier(path.node.callee) && path.node.callee.name === "runOnServer") {
19
20
  state.hasRunOnServer = true;
20
21
  const filename = state.file.opts.filename || "unknown";
21
22
  const serverFunctionCallback = path.node.arguments[0];
@@ -27,13 +28,13 @@ export default function clientServerFnTransform({ types: t }) {
27
28
  path.get('arguments.0').remove();
28
29
  }
29
30
  // this whole thing is just magic bro
31
+ // wtf is this ast manipulation
30
32
  path.replaceWith(t.callExpression(t.arrowFunctionExpression([], t.blockStatement([
31
33
  t.variableDeclaration("const", [
32
34
  t.variableDeclarator(t.identifier("_rpc"), t.arrowFunctionExpression([t.restElement(t.identifier("args"))], t.callExpression(t.callExpression(t.identifier("$$executeClientRpc"), [
33
35
  t.stringLiteral(functionHash)
34
36
  ]), [t.spreadElement(t.identifier("args"))])))
35
37
  ]),
36
- // ✅ Inside the blockStatement array, not a 3rd arg to arrowFunctionExpression
37
38
  t.expressionStatement(t.assignmentExpression("=", t.memberExpression(t.identifier("_rpc"), t.identifier("id")), t.stringLiteral(functionHash))),
38
39
  t.expressionStatement(t.assignmentExpression("=", t.memberExpression(t.identifier("_rpc"), t.identifier("readHydrationCache")), t.arrowFunctionExpression([t.identifier("sourceArg")], t.blockStatement([
39
40
  t.variableDeclaration("const", [
@@ -1 +1 @@
1
- {"version":3,"file":"rspack-manifest-hydration.d.ts","sourceRoot":"","sources":["../../src/plugins/rspack-manifest-hydration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEnE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,8BAA+B,YAAW,oBAAoB;IACzE,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,sBAAsB;IAI3C,KAAK,CAAC,QAAQ,EAAE,QAAQ;CAwCzB"}
1
+ {"version":3,"file":"rspack-manifest-hydration.d.ts","sourceRoot":"","sources":["../../src/plugins/rspack-manifest-hydration.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAEnE,UAAU,sBAAsB;IAC9B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,8BAA+B,YAAW,oBAAoB;IACzE,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,sBAAsB;IAI3C,KAAK,CAAC,QAAQ,EAAE,QAAQ;CAyCzB"}
@@ -31,7 +31,8 @@ export class AnaemiaManifestHydrationPlugin {
31
31
  fs.writeFileSync(manifestPath, JSON.stringify(currentManifest, null, 2));
32
32
  }
33
33
  catch (e) {
34
- console.error("[anaemia compiler] failed updating route-manifest with assets:", e.message);
34
+ const message = e instanceof Error ? e.message : String(e);
35
+ console.error("[anaemia compiler] failed updating route-manifest with assets:", message);
35
36
  }
36
37
  });
37
38
  }
@@ -1 +1 @@
1
- {"version":3,"file":"generate-entry.d.ts","sourceRoot":"","sources":["../../src/router/generate-entry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAgHpD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,CA+LzF"}
1
+ {"version":3,"file":"generate-entry.d.ts","sourceRoot":"","sources":["../../src/router/generate-entry.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAgHpD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,CA2LzF"}
@@ -100,7 +100,7 @@ export function generateRouterEntry(appRoot, routes) {
100
100
  const relativeToRoutes = path.relative(routesDir, r.filePath);
101
101
  const chunkName = relativeToRoutes
102
102
  .replace(/\.[jt]sx?$/, "")
103
- .replace(/[^a-zA-Z0-9-_\[\]]/g, "-")
103
+ .replace(/[^a-zA-Z0-9-_[\]]/g, "-")
104
104
  .toLowerCase();
105
105
  const guardSources = [...r.layouts.map((l) => l.filePath), r.filePath]
106
106
  .map((fp) => {
@@ -141,8 +141,6 @@ const Route${i}Wrapped = (props) => (
141
141
  .join("\n");
142
142
  const layoutImports = [...allLayouts.entries()]
143
143
  .map(([file, i]) => {
144
- const relativeToRoutes = path.relative(routesDir, file);
145
- const chunkName = ("layout-" + relativeToRoutes.replace(/\.[jt]sx?$/, "").replace(/[^a-zA-Z0-9-_\[\]]/g, "-")).toLowerCase();
146
144
  return `import Layout${i} from "${file.replace(/\\/g, "/")}";`;
147
145
  })
148
146
  .join("\n");
@@ -191,7 +189,6 @@ ${routeJsx}
191
189
  const chunkPreloadRegistryCode = buildPreloadMapString(conventionalRoutes, allLayouts);
192
190
  const preloadFnCode = `
193
191
  export async function preloadActiveClientRoute(pathname: string) {
194
- // Simple match logic against parameters or route patterns
195
192
  const pattern = Object.keys(chunkPreloadRegistry).find(p => {
196
193
  if (p === pathname) return true;
197
194
  const regexStr = p.replace(/:([a-zA-Z0-9_-]+)/g, "([^/]+)").replace(/\\*([a-zA-Z0-9_-]*)/g, "(.*)");
@@ -1,15 +1,14 @@
1
- export declare function createScanJiti(appRoot: string): import("jiti").Jiti;
2
1
  export type RouteType = "page" | "layout" | "catch-all";
3
2
  export interface LayoutManifestEntry {
4
3
  filePath: string;
5
- guards: any[];
4
+ guards: RouteGuard[];
6
5
  }
7
6
  export interface RouteManifestEntry {
8
7
  urlPattern: string;
9
8
  filePath: string;
10
9
  chunkName: string;
11
10
  layouts: LayoutManifestEntry[];
12
- guards: any[];
11
+ guards: RouteGuard[];
13
12
  type: RouteType;
14
13
  params: string[];
15
14
  }
@@ -17,6 +16,7 @@ export interface ServerRouteEntry {
17
16
  urlPattern: string;
18
17
  filePath: string;
19
18
  }
19
+ export type RouteGuard = (...args: unknown[]) => unknown;
20
20
  export declare function scanServerRoutes(appRoot: string): ServerRouteEntry[];
21
21
  export declare function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]>;
22
22
  //# sourceMappingURL=scan.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/router/scan.ts"],"names":[],"mappings":"AAMA,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,uBAK7C;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,GAAG,EAAE,CAAC;CACf;AAED,MAAM,WAAW,kBAAkB;IAGjC,UAAU,EAAE,MAAM,CAAC;IAGnB,QAAQ,EAAE,MAAM,CAAC;IAGjB,SAAS,EAAE,MAAM,CAAC;IAIlB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAG/B,MAAM,EAAE,GAAG,EAAE,CAAC;IAEd,IAAI,EAAE,SAAS,CAAC;IAIhB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAMD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAYpE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAgF/E"}
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/router/scan.ts"],"names":[],"mappings":"AAaA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC;AAExD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAAG,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC;AAezD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAYpE;AAUD,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAiE/E"}
@@ -3,7 +3,7 @@ import path from "path";
3
3
  import { createJiti } from "jiti";
4
4
  import fs from "node:fs";
5
5
  import { getAliases } from "../aliases.js";
6
- export function createScanJiti(appRoot) {
6
+ function createScanJiti(appRoot) {
7
7
  return createJiti(import.meta.url, {
8
8
  interopDefault: true,
9
9
  alias: getAliases(appRoot),
@@ -24,6 +24,13 @@ export function scanServerRoutes(appRoot) {
24
24
  };
25
25
  });
26
26
  }
27
+ function resolveConfigPath(absolutePath) {
28
+ const configPath = absolutePath
29
+ .replace(/\.(tsx|jsx)$/, ".config.$1")
30
+ .replace(".config.tsx", ".config.ts")
31
+ .replace(".config.jsx", ".config.js");
32
+ return fs.existsSync(configPath) ? configPath : absolutePath;
33
+ }
27
34
  export async function scanRoutes(appRoot) {
28
35
  const jiti = createScanJiti(appRoot);
29
36
  const routesDir = path.resolve(appRoot, "./src/routes");
@@ -35,18 +42,13 @@ export async function scanRoutes(appRoot) {
35
42
  const dir = path.dirname(file);
36
43
  const absolutePath = path.resolve(routesDir, file);
37
44
  let layoutGuards = [];
38
- const configPath = absolutePath
39
- .replace(/\.(tsx|jsx)$/, ".config.$1")
40
- .replace(".config.tsx", ".config.ts")
41
- .replace(".config.jsx", ".config.js");
42
- const moduleToScan = fs.existsSync(configPath) ? configPath : absolutePath;
43
45
  try {
44
- const layoutModule = jiti.import(moduleToScan);
46
+ const layoutModule = (await jiti.import(resolveConfigPath(absolutePath)));
45
47
  if (layoutModule?.config?.guards) {
46
48
  layoutGuards = layoutModule.config.guards;
47
49
  }
48
50
  }
49
- catch (err) {
51
+ catch {
50
52
  console.warn(`[anaemia bundler warning]: Failed parsing config flags on layout: ${file}`);
51
53
  }
52
54
  layoutMap.set(dir, {
@@ -66,26 +68,20 @@ export async function scanRoutes(appRoot) {
66
68
  const absolutePagePath = path.resolve(routesDir, file);
67
69
  const { urlPattern, chunkName, params, type } = parseFilePath(file);
68
70
  let pageGuards = [];
69
- const pageConfigPath = absolutePagePath
70
- .replace(/\.(tsx|jsx)$/, ".config.$1")
71
- .replace(".config.tsx", ".config.ts")
72
- .replace(".config.jsx", ".config.js");
73
- const pageModuleToScan = fs.existsSync(pageConfigPath) ? pageConfigPath : absolutePagePath;
74
71
  try {
75
- const pageModule = (await jiti.import(pageModuleToScan));
72
+ const pageModule = (await jiti.import(resolveConfigPath(absolutePagePath)));
76
73
  if (pageModule?.config?.guards) {
77
74
  pageGuards = pageModule.config.guards;
78
75
  }
79
76
  }
80
- catch (err) {
77
+ catch {
81
78
  // quietly ignore parsing problems for pure components lacking a config wrapper
82
79
  }
83
- const layouts = resolveLayoutChain(dir, layoutMap);
84
80
  entries.push({
85
81
  urlPattern,
86
82
  filePath: absolutePagePath,
87
83
  chunkName,
88
- layouts,
84
+ layouts: resolveLayoutChain(dir, layoutMap),
89
85
  guards: pageGuards,
90
86
  type,
91
87
  params,
@@ -136,10 +132,7 @@ function parseFilePath(file) {
136
132
  }
137
133
  }
138
134
  const filteredParts = urlParts.filter((part) => part !== "" && part !== ".");
139
- let urlPattern = "/" + filteredParts.join("/");
140
- if (urlPattern === "") {
141
- urlPattern = "/";
142
- }
135
+ const urlPattern = filteredParts.length === 0 ? "/" : "/" + filteredParts.join("/");
143
136
  const chunkName = file
144
137
  .replace(/\.(tsx|jsx)$/, "")
145
138
  .replace(/\[\.\.\.(.+?)\]/g, "catchall-$1")
package/dist/rules.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { AnaemiaConfig } from "@anaemia/core";
2
+ import { PluginItem } from "@babel/core";
2
3
  export declare function createStyleRules(config: AnaemiaConfig): {
3
4
  client: {
4
5
  test: RegExp;
@@ -29,7 +30,7 @@ export declare function createStyleRules(config: AnaemiaConfig): {
29
30
  export declare function createBabelRule({ isServer, isDev, plugins, }: {
30
31
  isServer: boolean;
31
32
  isDev: boolean;
32
- plugins: any[];
33
+ plugins?: PluginItem[];
33
34
  }): {
34
35
  test: RegExp;
35
36
  use: {
@@ -40,7 +41,7 @@ export declare function createBabelRule({ isServer, isDev, plugins, }: {
40
41
  hydratable: boolean;
41
42
  dev: boolean;
42
43
  })[])[];
43
- plugins: any[];
44
+ plugins: PluginItem[];
44
45
  };
45
46
  }[];
46
47
  };
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAInD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBrD;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,KAAK,EACL,OAAY,GACb,EAAE;IACD,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,GAAG,EAAE,CAAC;CAChB;;;;;;;;;;;;;EAkBA"}
1
+ {"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAIzC,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;EAuBrD;AAED,wBAAgB,eAAe,CAAC,EAC9B,QAAQ,EACR,KAAK,EACL,OAAY,GACb,EAAE;IACD,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,UAAU,EAAE,CAAC;CACxB;;;;;;;;;;;;;EAkBA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@anaemia/bundler",
3
- "version": "0.0.1",
3
+ "version": "0.1.1",
4
4
  "main": "./dist/index.js",
5
5
  "types": "./dist/index.d.ts",
6
6
  "type": "module",
@@ -11,23 +11,18 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@babel/types": "^7.29.7",
15
- "jiti": "^2.7.0",
16
- "@anaemia/core": "0.0.1"
17
- },
18
- "devDependencies": {
19
14
  "@babel/core": "^7.29.7",
20
15
  "@babel/preset-typescript": "^7.29.7",
21
- "@rspack/cli": "^2.0.4",
22
- "@rspack/core": "^2.0.4",
23
- "@types/babel__core": "^7.20.5",
24
- "@types/node": "^25.9.1",
16
+ "@babel/types": "^7.29.7",
17
+ "@rspack/core": "^2.0.5",
25
18
  "babel-loader": "^10.1.1",
26
19
  "babel-preset-solid": "^1.9.12",
27
20
  "glob": "^13.0.6",
21
+ "jiti": "^2.7.0",
28
22
  "sass": "^1.100.0",
29
23
  "sass-loader": "^17.0.0",
30
- "solid-refresh": "^0.7.8"
24
+ "solid-refresh": "^0.7.8",
25
+ "@anaemia/core": "0.1.0"
31
26
  },
32
27
  "scripts": {
33
28
  "build": "tsc",
@@ -1,11 +1,12 @@
1
1
  import { createServerFunctionId } from "../server-function-id.js";
2
+ import type { NodePath, PluginPass, types as BabelTypes, PluginObj } from "@babel/core";
2
3
 
3
- export default function serverHashInjector({ types: t }: any) {
4
+ export default function serverHashInjector({ types: t }: { types: typeof BabelTypes }): PluginObj<PluginPass> {
4
5
  return {
5
6
  name: "anaemia-server-hash-injector",
6
7
  visitor: {
7
- CallExpression(path: any, state: any) {
8
- if (path.node.callee.name === "runOnServer") {
8
+ CallExpression(path: NodePath<BabelTypes.CallExpression>, state: PluginPass) {
9
+ if (t.isIdentifier(path.node.callee) && path.node.callee.name === "runOnServer") {
9
10
  const filename = state.file.opts.filename || "unknown";
10
11
  const functionHash = createServerFunctionId(filename, path.node.start);
11
12
 
@@ -13,7 +14,7 @@ export default function serverHashInjector({ types: t }: any) {
13
14
  path.node.arguments.push(t.stringLiteral(functionHash));
14
15
  }
15
16
  }
16
- }
17
- }
17
+ },
18
+ },
18
19
  };
19
20
  }
@@ -1,14 +1,19 @@
1
1
  import { createServerFunctionId } from "../server-function-id.js";
2
+ import type { NodePath, PluginObj, PluginPass, types as BabelTypes } from "@babel/core";
2
3
 
3
- export default function clientServerFnTransform({ types: t }: any) {
4
+ interface PluginState extends PluginPass {
5
+ hasRunOnServer: boolean;
6
+ };
7
+
8
+ export default function clientServerFnTransform({ types: t }: { types: typeof BabelTypes }): PluginObj<PluginState> {
4
9
  return {
5
10
  name: "anaemia-client-server-fn-transform",
6
11
  visitor: {
7
12
  Program: {
8
- enter(path: any, state: any) {
13
+ enter(path: NodePath<BabelTypes.Program>, state: PluginState) {
9
14
  state.hasRunOnServer = false;
10
15
  },
11
- exit(path: any, state: any) {
16
+ exit(path: NodePath<BabelTypes.Program>, state: PluginState) {
12
17
  if (state.hasRunOnServer) {
13
18
  const importDeclaration = t.importDeclaration(
14
19
  [t.importSpecifier(t.identifier("$$executeClientRpc"), t.identifier("$$executeClientRpc"))],
@@ -18,8 +23,8 @@ export default function clientServerFnTransform({ types: t }: any) {
18
23
  }
19
24
  }
20
25
  },
21
- CallExpression(path: any, state: any) {
22
- if (path.node.callee.name === "runOnServer") {
26
+ CallExpression(path: NodePath<BabelTypes.CallExpression>, state: PluginState) {
27
+ if (t.isIdentifier(path.node.callee) && path.node.callee.name === "runOnServer") {
23
28
  state.hasRunOnServer = true;
24
29
  const filename = state.file.opts.filename || "unknown";
25
30
 
@@ -35,6 +40,7 @@ export default function clientServerFnTransform({ types: t }: any) {
35
40
  }
36
41
 
37
42
  // this whole thing is just magic bro
43
+ // wtf is this ast manipulation
38
44
  path.replaceWith(
39
45
  t.callExpression(
40
46
  t.arrowFunctionExpression(
@@ -54,7 +60,6 @@ export default function clientServerFnTransform({ types: t }: any) {
54
60
  )
55
61
  )
56
62
  ]),
57
- // ✅ Inside the blockStatement array, not a 3rd arg to arrowFunctionExpression
58
63
  t.expressionStatement(
59
64
  t.assignmentExpression(
60
65
  "=",
@@ -48,8 +48,9 @@ export class AnaemiaManifestHydrationPlugin implements RspackPluginInstance {
48
48
  }
49
49
 
50
50
  fs.writeFileSync(manifestPath, JSON.stringify(currentManifest, null, 2));
51
- } catch (e: any) {
52
- console.error("[anaemia compiler] failed updating route-manifest with assets:", e.message);
51
+ } catch (e) {
52
+ const message = e instanceof Error ? e.message : String(e);
53
+ console.error("[anaemia compiler] failed updating route-manifest with assets:", message);
53
54
  }
54
55
  });
55
56
  }
@@ -93,7 +93,7 @@ function renderTree(nodes: TreeNode[], indent = 6): string {
93
93
  return `${pad}<Route path="${routePath}" component={Route${node.routeIdx}Wrapped} />`;
94
94
  }
95
95
 
96
- let layoutPath = (node as any).relativePath;
96
+ let layoutPath = node.relativePath;
97
97
  const inner = renderTree(node.children, indent + 2);
98
98
 
99
99
  return [`${pad}<Route path="${layoutPath}" component={Layout${node.layoutIdx}}>`, inner, `${pad}</Route>`].join("\n");
@@ -136,7 +136,7 @@ export function generateRouterEntry(appRoot: string, routes: RouteManifestEntry[
136
136
  const relativeToRoutes = path.relative(routesDir, r.filePath);
137
137
  const chunkName = relativeToRoutes
138
138
  .replace(/\.[jt]sx?$/, "")
139
- .replace(/[^a-zA-Z0-9-_\[\]]/g, "-")
139
+ .replace(/[^a-zA-Z0-9-_[\]]/g, "-")
140
140
  .toLowerCase();
141
141
 
142
142
  const guardSources = [...r.layouts.map((l) => l.filePath), r.filePath]
@@ -180,9 +180,6 @@ const Route${i}Wrapped = (props) => (
180
180
 
181
181
  const layoutImports = [...allLayouts.entries()]
182
182
  .map(([file, i]) => {
183
- const relativeToRoutes = path.relative(routesDir, file);
184
- const chunkName = ("layout-" + relativeToRoutes.replace(/\.[jt]sx?$/, "").replace(/[^a-zA-Z0-9-_\[\]]/g, "-")).toLowerCase();
185
-
186
183
  return `import Layout${i} from "${file.replace(/\\/g, "/")}";`;
187
184
  })
188
185
  .join("\n");
@@ -251,7 +248,6 @@ ${routeJsx}
251
248
 
252
249
  const preloadFnCode = `
253
250
  export async function preloadActiveClientRoute(pathname: string) {
254
- // Simple match logic against parameters or route patterns
255
251
  const pattern = Object.keys(chunkPreloadRegistry).find(p => {
256
252
  if (p === pathname) return true;
257
253
  const regexStr = p.replace(/:([a-zA-Z0-9_-]+)/g, "([^/]+)").replace(/\\*([a-zA-Z0-9_-]*)/g, "(.*)");
@@ -4,7 +4,7 @@ import { createJiti } from "jiti";
4
4
  import fs from "node:fs";
5
5
  import { getAliases } from "../aliases.js";
6
6
 
7
- export function createScanJiti(appRoot: string) {
7
+ function createScanJiti(appRoot: string) {
8
8
  return createJiti(import.meta.url, {
9
9
  interopDefault: true,
10
10
  alias: getAliases(appRoot),
@@ -15,31 +15,16 @@ export type RouteType = "page" | "layout" | "catch-all";
15
15
 
16
16
  export interface LayoutManifestEntry {
17
17
  filePath: string;
18
- guards: any[];
18
+ guards: RouteGuard[];
19
19
  }
20
20
 
21
21
  export interface RouteManifestEntry {
22
- // the URL pattern this route matches
23
- // e.g. /blog/:slug, /dashboard, /
24
22
  urlPattern: string;
25
-
26
- // absolute path to the route file
27
23
  filePath: string;
28
-
29
- // rspack chunk name derived from the path
30
24
  chunkName: string;
31
-
32
- // ordered list of layout files that wrap this route
33
- // e.g. [root layout, dashboard layout]
34
25
  layouts: LayoutManifestEntry[];
35
-
36
- // holds page level guards
37
- guards: any[];
38
- // what type of file this is
26
+ guards: RouteGuard[];
39
27
  type: RouteType;
40
-
41
- // the dynamic params this route exposes
42
- // e.g. /blog/[slug] -> ["slug"]
43
28
  params: string[];
44
29
  }
45
30
 
@@ -48,6 +33,17 @@ export interface ServerRouteEntry {
48
33
  filePath: string;
49
34
  }
50
35
 
36
+ export type RouteGuard = (...args: unknown[]) => unknown;
37
+
38
+ interface RouteConfigShape {
39
+ guards?: RouteGuard[];
40
+ }
41
+
42
+ interface RouteModule {
43
+ config?: RouteConfigShape;
44
+ default?: unknown;
45
+ }
46
+
51
47
  const LAYOUT_FILE = /^_layout\.(tsx|jsx)$/;
52
48
  const CATCH_ALL_FILE = /^\[\.\.\.(.+?)\]\.(tsx|jsx)$/;
53
49
  const DYNAMIC_SEGMENT = /^\[(.+?)\]\.(tsx|jsx)$/;
@@ -66,6 +62,14 @@ export function scanServerRoutes(appRoot: string): ServerRouteEntry[] {
66
62
  });
67
63
  }
68
64
 
65
+ function resolveConfigPath(absolutePath: string): string {
66
+ const configPath = absolutePath
67
+ .replace(/\.(tsx|jsx)$/, ".config.$1")
68
+ .replace(".config.tsx", ".config.ts")
69
+ .replace(".config.jsx", ".config.js");
70
+ return fs.existsSync(configPath) ? configPath : absolutePath;
71
+ }
72
+
69
73
  export async function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]> {
70
74
  const jiti = createScanJiti(appRoot);
71
75
  const routesDir = path.resolve(appRoot, "./src/routes");
@@ -78,20 +82,14 @@ export async function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]>
78
82
  const dir = path.dirname(file);
79
83
  const absolutePath = path.resolve(routesDir, file);
80
84
 
81
- let layoutGuards: any[] = [];
82
- const configPath = absolutePath
83
- .replace(/\.(tsx|jsx)$/, ".config.$1")
84
- .replace(".config.tsx", ".config.ts")
85
- .replace(".config.jsx", ".config.js");
86
-
87
- const moduleToScan = fs.existsSync(configPath) ? configPath : absolutePath;
85
+ let layoutGuards: RouteGuard[] = [];
88
86
 
89
87
  try {
90
- const layoutModule = jiti.import(moduleToScan) as any;
88
+ const layoutModule = (await jiti.import(resolveConfigPath(absolutePath))) as RouteModule;
91
89
  if (layoutModule?.config?.guards) {
92
90
  layoutGuards = layoutModule.config.guards;
93
91
  }
94
- } catch (err) {
92
+ } catch {
95
93
  console.warn(`[anaemia bundler warning]: Failed parsing config flags on layout: ${file}`);
96
94
  }
97
95
 
@@ -114,31 +112,22 @@ export async function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]>
114
112
  const absolutePagePath = path.resolve(routesDir, file);
115
113
  const { urlPattern, chunkName, params, type } = parseFilePath(file);
116
114
 
117
- let pageGuards: any[] = [];
118
-
119
- const pageConfigPath = absolutePagePath
120
- .replace(/\.(tsx|jsx)$/, ".config.$1")
121
- .replace(".config.tsx", ".config.ts")
122
- .replace(".config.jsx", ".config.js");
123
-
124
- const pageModuleToScan = fs.existsSync(pageConfigPath) ? pageConfigPath : absolutePagePath;
115
+ let pageGuards: RouteGuard[] = [];
125
116
 
126
117
  try {
127
- const pageModule = (await jiti.import(pageModuleToScan)) as any;
118
+ const pageModule = (await jiti.import(resolveConfigPath(absolutePagePath))) as RouteModule;
128
119
  if (pageModule?.config?.guards) {
129
120
  pageGuards = pageModule.config.guards;
130
121
  }
131
- } catch (err) {
122
+ } catch {
132
123
  // quietly ignore parsing problems for pure components lacking a config wrapper
133
124
  }
134
125
 
135
- const layouts = resolveLayoutChain(dir, layoutMap);
136
-
137
126
  entries.push({
138
127
  urlPattern,
139
128
  filePath: absolutePagePath,
140
129
  chunkName,
141
- layouts,
130
+ layouts: resolveLayoutChain(dir, layoutMap),
142
131
  guards: pageGuards,
143
132
  type,
144
133
  params,
@@ -195,11 +184,7 @@ function parseFilePath(file: string): {
195
184
  }
196
185
 
197
186
  const filteredParts = urlParts.filter((part) => part !== "" && part !== ".");
198
- let urlPattern = "/" + filteredParts.join("/");
199
-
200
- if (urlPattern === "") {
201
- urlPattern = "/";
202
- }
187
+ const urlPattern = filteredParts.length === 0 ? "/" : "/" + filteredParts.join("/");
203
188
 
204
189
  const chunkName = file
205
190
  .replace(/\.(tsx|jsx)$/, "")
@@ -219,10 +204,9 @@ function resolveLayoutChain(dir: string, layoutMap: Map<string, LayoutManifestEn
219
204
  while (true) {
220
205
  const layoutEntry = layoutMap.get(current);
221
206
  if (layoutEntry) layouts.unshift(layoutEntry);
222
-
223
207
  if (current === "." || current === "") break;
224
208
  current = path.dirname(current);
225
209
  }
226
210
 
227
211
  return layouts;
228
- }
212
+ }
package/src/rules.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
- import path from "node:path";
3
2
  import type { AnaemiaConfig } from "@anaemia/core";
3
+ import { PluginItem } from "@babel/core";
4
4
 
5
5
  const require = createRequire(import.meta.url);
6
6
 
@@ -36,7 +36,7 @@ export function createBabelRule({
36
36
  }: {
37
37
  isServer: boolean;
38
38
  isDev: boolean;
39
- plugins: any[];
39
+ plugins?: PluginItem[];
40
40
  }) {
41
41
  const generateMode = isServer ? "ssr" : "dom";
42
42
 
@@ -1,6 +0,0 @@
1
- import type { PluginObj } from "@babel/core";
2
- import * as BabelTypes from "@babel/types";
3
- export default function anaemiaClientTransformer({ types: t }: {
4
- types: typeof BabelTypes;
5
- }): PluginObj;
6
- //# sourceMappingURL=babel-transform-client.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"babel-transform-client.d.ts","sourceRoot":"","sources":["../../src/plugins/babel-transform-client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,KAAK,UAAU,MAAM,cAAc,CAAC;AAE3C,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,EAC/C,KAAK,EAAE,CAAC,EACT,EAAE;IACD,KAAK,EAAE,OAAO,UAAU,CAAA;CACzB,GAAG,SAAS,CA4EZ"}
@@ -1,49 +0,0 @@
1
- export default function anaemiaClientTransformer({ types: t }) {
2
- const serverFunctionIdentifiers = new Set();
3
- return {
4
- name: "anaemia-client-transformer",
5
- visitor: {
6
- Program: {
7
- enter() {
8
- serverFunctionIdentifiers.clear();
9
- }
10
- },
11
- VariableDeclarator(path) {
12
- const init = path.node.init;
13
- if (init &&
14
- t.isCallExpression(init) &&
15
- t.isIdentifier(init.callee) &&
16
- init.callee.name === "runOnServer") {
17
- if (t.isIdentifier(path.node.id)) {
18
- serverFunctionIdentifiers.add(path.node.id.name);
19
- }
20
- }
21
- },
22
- CallExpression(path) {
23
- const { callee, arguments: args } = path.node;
24
- if (t.isIdentifier(callee) && callee.name === "createResource") {
25
- if (args.length === 2) {
26
- const fetcherArg = args[1];
27
- if (t.isIdentifier(fetcherArg) && serverFunctionIdentifiers.has(fetcherArg.name)) {
28
- const fetcherName = fetcherArg.name;
29
- const sourceArg = args[0];
30
- let cacheCallExpression;
31
- if (t.isArrowFunctionExpression(sourceArg) || t.isFunctionExpression(sourceArg)) {
32
- const body = sourceArg.body;
33
- const paramValue = t.isBlockStatement(body) ? null : (body);
34
- cacheCallExpression = t.conditionalExpression(t.binaryExpression("===", t.unaryExpression("typeof", t.memberExpression(t.identifier(fetcherName), t.identifier("readHydrationCache"))), t.stringLiteral("function")), t.callExpression(t.memberExpression(t.identifier(fetcherName), t.identifier("readHydrationCache")), paramValue ? [paramValue] : []), t.identifier("undefined"));
35
- }
36
- else {
37
- cacheCallExpression = t.callExpression(t.memberExpression(t.identifier(fetcherName), t.identifier("readHydrationCache")), [sourceArg]);
38
- }
39
- const optionsObject = t.objectExpression([
40
- t.objectProperty(t.identifier("initialValue"), cacheCallExpression)
41
- ]);
42
- path.node.arguments.push(optionsObject);
43
- }
44
- }
45
- }
46
- }
47
- }
48
- };
49
- }
@@ -1,2 +0,0 @@
1
- export declare function createServerFunctionId(filename: string, start: number | null | undefined): string;
2
- //# sourceMappingURL=server-function-id.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"server-function-id.d.ts","sourceRoot":"","sources":["../../src/plugins/server-function-id.ts"],"names":[],"mappings":"AAAA,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAExF"}
@@ -1,3 +0,0 @@
1
- export function createServerFunctionId(filename, start) {
2
- return Buffer.from(`${filename}:${start ?? 0}`).toString("base64url");
3
- }