@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.
- package/dist/plugins/babel-hash-injector-server.d.ts +4 -6
- package/dist/plugins/babel-hash-injector-server.d.ts.map +1 -1
- package/dist/plugins/babel-hash-injector-server.js +3 -3
- package/dist/plugins/babel-transform-server.d.ts +8 -10
- package/dist/plugins/babel-transform-server.d.ts.map +1 -1
- package/dist/plugins/babel-transform-server.js +3 -2
- package/dist/plugins/rspack-manifest-hydration.d.ts.map +1 -1
- package/dist/plugins/rspack-manifest-hydration.js +2 -1
- package/dist/router/generate-entry.d.ts.map +1 -1
- package/dist/router/generate-entry.js +1 -4
- package/dist/router/scan.d.ts +3 -3
- package/dist/router/scan.d.ts.map +1 -1
- package/dist/router/scan.js +14 -21
- package/dist/rules.d.ts +3 -2
- package/dist/rules.d.ts.map +1 -1
- package/package.json +6 -11
- package/src/plugins/babel-hash-injector-server.ts +6 -5
- package/src/plugins/babel-transform-server.ts +11 -6
- package/src/plugins/rspack-manifest-hydration.ts +3 -2
- package/src/router/generate-entry.ts +2 -6
- package/src/router/scan.ts +31 -47
- package/src/rules.ts +2 -2
- package/dist/plugins/babel-transform-client.d.ts +0 -6
- package/dist/plugins/babel-transform-client.d.ts.map +0 -1
- package/dist/plugins/babel-transform-client.js +0 -49
- package/dist/plugins/server-function-id.d.ts +0 -2
- package/dist/plugins/server-function-id.d.ts.map +0 -1
- package/dist/plugins/server-function-id.js +0 -3
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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":"
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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":"
|
|
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;
|
|
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
|
-
|
|
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,
|
|
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-_
|
|
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, "(.*)");
|
package/dist/router/scan.d.ts
CHANGED
|
@@ -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:
|
|
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:
|
|
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":"
|
|
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"}
|
package/dist/router/scan.js
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
44
|
+
plugins: PluginItem[];
|
|
44
45
|
};
|
|
45
46
|
}[];
|
|
46
47
|
};
|
package/dist/rules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"
|
|
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.
|
|
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
|
-
"@
|
|
22
|
-
"@rspack/core": "^2.0.
|
|
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 }:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
13
|
+
enter(path: NodePath<BabelTypes.Program>, state: PluginState) {
|
|
9
14
|
state.hasRunOnServer = false;
|
|
10
15
|
},
|
|
11
|
-
exit(path:
|
|
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:
|
|
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
|
|
52
|
-
|
|
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 =
|
|
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-_
|
|
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, "(.*)");
|
package/src/router/scan.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
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
|
-
|
|
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
|
|
39
|
+
plugins?: PluginItem[];
|
|
40
40
|
}) {
|
|
41
41
|
const generateMode = isServer ? "ssr" : "dom";
|
|
42
42
|
|
|
@@ -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 +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"}
|