@anaemia/bundler 0.0.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.
Files changed (61) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +3 -0
  3. package/dist/aliases.d.ts +8 -0
  4. package/dist/aliases.d.ts.map +1 -0
  5. package/dist/aliases.js +10 -0
  6. package/dist/index.d.ts +6 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +153 -0
  9. package/dist/optimization.d.ts +41 -0
  10. package/dist/optimization.d.ts.map +1 -0
  11. package/dist/optimization.js +35 -0
  12. package/dist/plugins/babel-hash-injector-server.d.ts +7 -0
  13. package/dist/plugins/babel-hash-injector-server.d.ts.map +1 -0
  14. package/dist/plugins/babel-hash-injector-server.js +17 -0
  15. package/dist/plugins/babel-transform-client.d.ts +6 -0
  16. package/dist/plugins/babel-transform-client.d.ts.map +1 -0
  17. package/dist/plugins/babel-transform-client.js +49 -0
  18. package/dist/plugins/babel-transform-server.d.ts +11 -0
  19. package/dist/plugins/babel-transform-server.d.ts.map +1 -0
  20. package/dist/plugins/babel-transform-server.js +60 -0
  21. package/dist/plugins/rspack-manifest-hydration.d.ts +11 -0
  22. package/dist/plugins/rspack-manifest-hydration.d.ts.map +1 -0
  23. package/dist/plugins/rspack-manifest-hydration.js +38 -0
  24. package/dist/plugins/server-function-id.d.ts +2 -0
  25. package/dist/plugins/server-function-id.d.ts.map +1 -0
  26. package/dist/plugins/server-function-id.js +3 -0
  27. package/dist/router/generate-entry.d.ts +3 -0
  28. package/dist/router/generate-entry.d.ts.map +1 -0
  29. package/dist/router/generate-entry.js +243 -0
  30. package/dist/router/generate-server-routes.d.ts +3 -0
  31. package/dist/router/generate-server-routes.d.ts.map +1 -0
  32. package/dist/router/generate-server-routes.js +30 -0
  33. package/dist/router/manifest.d.ts +12 -0
  34. package/dist/router/manifest.d.ts.map +1 -0
  35. package/dist/router/manifest.js +23 -0
  36. package/dist/router/scan.d.ts +22 -0
  37. package/dist/router/scan.d.ts.map +1 -0
  38. package/dist/router/scan.js +164 -0
  39. package/dist/rules.d.ts +47 -0
  40. package/dist/rules.d.ts.map +1 -0
  41. package/dist/rules.js +42 -0
  42. package/dist/server-function-id.d.ts +2 -0
  43. package/dist/server-function-id.d.ts.map +1 -0
  44. package/dist/server-function-id.js +3 -0
  45. package/package.json +36 -0
  46. package/src/aliases.ts +11 -0
  47. package/src/index.ts +170 -0
  48. package/src/optimization.ts +37 -0
  49. package/src/plugins/babel-hash-injector-server.ts +19 -0
  50. package/src/plugins/babel-transform-server.ts +144 -0
  51. package/src/plugins/rspack-manifest-hydration.ts +56 -0
  52. package/src/router/generate-entry.ts +306 -0
  53. package/src/router/generate-server-routes.ts +36 -0
  54. package/src/router/manifest.ts +42 -0
  55. package/src/router/scan.ts +228 -0
  56. package/src/rules.ts +58 -0
  57. package/src/runtime/empty-module.cjs +10 -0
  58. package/src/server-function-id.ts +3 -0
  59. package/test/dev-config.test.mjs +30 -0
  60. package/test/server-functions.test.mjs +77 -0
  61. package/tsconfig.json +13 -0
package/dist/rules.js ADDED
@@ -0,0 +1,42 @@
1
+ import { createRequire } from "node:module";
2
+ const require = createRequire(import.meta.url);
3
+ export function createStyleRules(config) {
4
+ const useSass = config.styles?.sass !== false;
5
+ const useModules = config.styles?.modules ?? true;
6
+ const baseLoaders = useSass ? [{ loader: require.resolve("sass-loader"), options: { api: "modern" } }] : [];
7
+ return {
8
+ client: {
9
+ test: /\.(c|sc|sa)ss$/,
10
+ type: useModules ? "css/auto" : "css",
11
+ use: baseLoaders,
12
+ },
13
+ server: {
14
+ test: /\.(c|sc|sa)ss$/,
15
+ type: useModules ? "css/auto" : "css",
16
+ generator: {
17
+ css: {
18
+ exportOnlyLocals: true,
19
+ },
20
+ },
21
+ use: baseLoaders,
22
+ },
23
+ };
24
+ }
25
+ export function createBabelRule({ isServer, isDev, plugins = [], }) {
26
+ const generateMode = isServer ? "ssr" : "dom";
27
+ return {
28
+ test: /\.[jt]sx?$/,
29
+ use: [
30
+ {
31
+ loader: require.resolve("babel-loader"),
32
+ options: {
33
+ presets: [
34
+ [require.resolve("babel-preset-solid"), { generate: generateMode, hydratable: true, dev: isDev }],
35
+ require.resolve("@babel/preset-typescript"),
36
+ ],
37
+ plugins: plugins,
38
+ },
39
+ },
40
+ ],
41
+ };
42
+ }
@@ -0,0 +1,2 @@
1
+ export declare function createServerFunctionId(filename: string, start: number | null | undefined): string;
2
+ //# sourceMappingURL=server-function-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server-function-id.d.ts","sourceRoot":"","sources":["../src/server-function-id.ts"],"names":[],"mappings":"AAAA,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAExF"}
@@ -0,0 +1,3 @@
1
+ export function createServerFunctionId(filename, start) {
2
+ return Buffer.from(`${filename}:${start ?? 0}`).toString("base64url");
3
+ }
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@anaemia/bundler",
3
+ "version": "0.0.1",
4
+ "main": "./dist/index.js",
5
+ "types": "./dist/index.d.ts",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "dependencies": {
14
+ "@babel/types": "^7.29.7",
15
+ "jiti": "^2.7.0",
16
+ "@anaemia/core": "0.0.1"
17
+ },
18
+ "devDependencies": {
19
+ "@babel/core": "^7.29.7",
20
+ "@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",
25
+ "babel-loader": "^10.1.1",
26
+ "babel-preset-solid": "^1.9.12",
27
+ "glob": "^13.0.6",
28
+ "sass": "^1.100.0",
29
+ "sass-loader": "^17.0.0",
30
+ "solid-refresh": "^0.7.8"
31
+ },
32
+ "scripts": {
33
+ "build": "tsc",
34
+ "test": "pnpm run build && node --test test/*.test.mjs"
35
+ }
36
+ }
package/src/aliases.ts ADDED
@@ -0,0 +1,11 @@
1
+ import path from "path";
2
+
3
+ export function getAliases(appRoot: string) {
4
+ return {
5
+ "~": path.resolve(appRoot, "./src"),
6
+ "@core": path.resolve(appRoot, "./src/core"),
7
+ "@shared": path.resolve(appRoot, "./src/shared"),
8
+ "@features": path.resolve(appRoot, "./src/features"),
9
+ "@routes": path.resolve(appRoot, "./src/routes"),
10
+ };
11
+ }
package/src/index.ts ADDED
@@ -0,0 +1,170 @@
1
+ import { Configuration, rspack } from "@rspack/core";
2
+ import path from "path";
3
+ import fs from "node:fs";
4
+ import type { AnaemiaConfig } from "@anaemia/core/config";
5
+ import { createRequire } from "node:module";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import clientServerFnTransform from "./plugins/babel-transform-server.js";
9
+ import serverHashInjector from "./plugins/babel-hash-injector-server.js";
10
+ import { AnaemiaManifestHydrationPlugin } from "./plugins/rspack-manifest-hydration.js";
11
+
12
+ import { scanRoutes, scanServerRoutes } from "./router/scan.js";
13
+ import { writeManifest } from "./router/manifest.js";
14
+ import { generateRouterEntry } from "./router/generate-entry.js";
15
+ import { generateServerRoutes } from "./router/generate-server-routes.js";
16
+ import { getAliases } from "./aliases.js";
17
+
18
+ import { createStyleRules, createBabelRule } from "./rules.js";
19
+ import { getClientOptimization, getPerformanceProfile } from "./optimization.js";
20
+
21
+ const require = createRequire(import.meta.url);
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+
25
+ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {}): Promise<[Configuration, Configuration]> {
26
+ const isDev = process.env.NODE_ENV !== "production";
27
+ const coreRuntimeDir = path.dirname(require.resolve("@anaemia/core/package.json"));
28
+
29
+ const routes = await scanRoutes(appRoot);
30
+ const serverRoutes = scanServerRoutes(appRoot);
31
+ writeManifest(appRoot, routes);
32
+
33
+ const frameworkInternalDir = path.resolve(appRoot, "./.anaemia");
34
+ if (!fs.existsSync(frameworkInternalDir)) {
35
+ fs.mkdirSync(frameworkInternalDir, { recursive: true });
36
+ }
37
+
38
+ const entryFile = generateRouterEntry(appRoot, routes);
39
+ const serverRoutesFile = generateServerRoutes(appRoot, serverRoutes);
40
+
41
+ const styleRules = createStyleRules(config);
42
+ const extraClientBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.client ?? []) ?? [];
43
+ const extraServerBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.server ?? []) ?? [];
44
+ const solidRefreshPlugin = [require.resolve("solid-refresh/babel"), { bundler: "rspack-esm", jsx: false }];
45
+
46
+ const sharedResolve = {
47
+ extensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".scss", ".css"],
48
+ extensionAlias: { ".js": [".ts", ".js"], ".jsx": [".tsx", ".jsx"] },
49
+ alias: { "anaemia-user-app": entryFile, ...getAliases(appRoot) },
50
+ };
51
+
52
+ let clientConfig: Configuration = {
53
+ name: "client",
54
+ context: appRoot,
55
+ target: "web",
56
+ devtool: isDev ? "eval-cheap-module-source-map" : false,
57
+ cache: isDev,
58
+ entry: {
59
+ client: [...(isDev ? [require.resolve("solid-refresh")] : []), path.resolve(coreRuntimeDir, "./src/runtime/entry-client.tsx")],
60
+ },
61
+ output: {
62
+ path: path.resolve(appRoot, "./dist/client"),
63
+ filename: isDev ? "assets/[name].js" : "assets/[name].[contenthash:8].js",
64
+ chunkFilename: isDev ? "assets/[name].chunk.js" : "assets/[name].[contenthash:8].chunk.js",
65
+ cssFilename: isDev ? "assets/[name].css" : "assets/[name].[contenthash:8].css",
66
+ publicPath: "/",
67
+ },
68
+ performance: getPerformanceProfile(isDev),
69
+ optimization: getClientOptimization(isDev),
70
+ resolve: {
71
+ ...sharedResolve,
72
+ conditionNames: ["solid", "browser", ...(isDev ? ["development"] : []), "import", "..."],
73
+ alias: {
74
+ ...sharedResolve.alias,
75
+ "solid-refresh": require.resolve("solid-refresh"),
76
+ [path.resolve(coreRuntimeDir, "./src/runtime/context.ts")]: path.resolve(coreRuntimeDir, "./src/runtime/context.browser.ts"),
77
+ [path.resolve(coreRuntimeDir, "./dist/runtime/context.js")]: path.resolve(coreRuntimeDir, "./src/runtime/context.browser.ts"),
78
+ },
79
+ fallback: { async_hooks: false, "node:async_hooks": false, fs: false, "node:fs": false, path: false, "node:path": false },
80
+ },
81
+ devServer: isDev
82
+ ? {
83
+ hot: true,
84
+ liveReload: false,
85
+ port: (config.port ?? 3000) + 1,
86
+ allowedHosts: "all",
87
+ headers: { "Access-Control-Allow-Origin": "*" },
88
+ client: { webSocketURL: `ws://localhost:${(config.port ?? 3000) + 2}` },
89
+ }
90
+ : undefined,
91
+ plugins: [
92
+ new rspack.HtmlRspackPlugin({ template: path.resolve(appRoot, "./index.html"), filename: "index.html", inject: false }),
93
+ new rspack.DefinePlugin({
94
+ __ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({ port: config.port, assets: config.assets, styles: config.styles }),
95
+ ...config.define?.client,
96
+ }),
97
+ new rspack.NormalModuleReplacementPlugin(/^node:/, (resource) => { resource.request = resource.request.replace(/^node:/, ""); }),
98
+ new rspack.NormalModuleReplacementPlugin(
99
+ /\.server\.(ts|tsx|js|jsx)$/,
100
+ (() => {
101
+ const srcPath = path.resolve(__dirname, "./runtime/empty-module.cjs");
102
+ if (fs.existsSync(srcPath)) return srcPath;
103
+
104
+ return path.resolve(__dirname, "../src/runtime/empty-module.cjs");
105
+ })()
106
+ ),
107
+ new AnaemiaManifestHydrationPlugin({ appRoot }),
108
+ ],
109
+ module: {
110
+ parser: { "css/auto": { namedExports: false } },
111
+ rules: [
112
+ styleRules.client,
113
+ {
114
+ ...createBabelRule({ isServer: false, isDev, plugins: [clientServerFnTransform, ...(isDev ? [solidRefreshPlugin] : []), ...extraClientBabelPlugins] }),
115
+ exclude: /[\\/]node_modules[\\/]/,
116
+ },
117
+ {
118
+ ...createBabelRule({ isServer: false, isDev, plugins: [clientServerFnTransform, ...(isDev ? [solidRefreshPlugin] : []), ...extraClientBabelPlugins] }),
119
+ include: /[\\/]node_modules[\\/]@solidjs[\\/]router/,
120
+ }
121
+ ],
122
+ },
123
+ };
124
+
125
+ let serverConfig: Configuration = {
126
+ name: "server",
127
+ context: appRoot,
128
+ target: "node",
129
+ entry: { server: path.resolve(coreRuntimeDir, "./src/runtime/entry-server.tsx") },
130
+ output: { path: path.resolve(appRoot, "./dist/server"), filename: "index.js", module: true, chunkFormat: "module", chunkLoading: "import" },
131
+ optimization: { nodeEnv: false },
132
+ resolve: {
133
+ ...sharedResolve,
134
+ conditionNames: ["node", "solid", ...(isDev ? ["development"] : []), "import", "..."],
135
+ alias: {
136
+ ...sharedResolve.alias,
137
+ "solid-refresh": require.resolve("solid-refresh"),
138
+ "@anaemia/core/config": path.resolve(coreRuntimeDir, "./src/config.ts"),
139
+ "@anaemia/core": path.resolve(coreRuntimeDir, "./src/index.ts"),
140
+ __anaemia_user_config__: path.resolve(appRoot, "./anaemia.config.ts"),
141
+ __anaemia_server_routes__: serverRoutesFile,
142
+ },
143
+ },
144
+ plugins: [new rspack.DefinePlugin({ ...config.define?.server })],
145
+ module: {
146
+ parser: { "css/auto": { namedExports: false } },
147
+ rules: [
148
+ styleRules.server,
149
+ {
150
+ ...createBabelRule({ isServer: true, isDev, plugins: [serverHashInjector, ...extraServerBabelPlugins] }),
151
+ exclude: /[\\/]node_modules[\\/]/,
152
+ },
153
+ {
154
+ ...createBabelRule({ isServer: true, isDev, plugins: [...extraServerBabelPlugins] }),
155
+ include: /[\\/]node_modules[\\/]@solidjs[\\/]router/,
156
+ }
157
+ ],
158
+ },
159
+ };
160
+
161
+ for (const plugin of config.plugins ?? []) {
162
+ if (plugin.clientRspackConfig) clientConfig = plugin.clientRspackConfig(clientConfig);
163
+ if (plugin.serverRspackConfig) serverConfig = plugin.serverRspackConfig(serverConfig);
164
+ }
165
+
166
+ return [clientConfig, serverConfig];
167
+ }
168
+
169
+ export { scanRoutes } from "./router/scan.js";
170
+ export { writeManifest } from "./router/manifest.js";
@@ -0,0 +1,37 @@
1
+ import { rspack } from "@rspack/core";
2
+
3
+ export function getClientOptimization(isDev: boolean) {
4
+ return {
5
+ sideEffects: true,
6
+ usedExports: true,
7
+ splitChunks: isDev
8
+ ? (false as const)
9
+ : ({
10
+ chunks: "all" as const,
11
+ maxInitialRequests: 25,
12
+ minSize: 20000,
13
+ cacheGroups: {
14
+ framework: {
15
+ chunks: "all" as const,
16
+ name: "framework",
17
+ test: /[\\/]node_modules[\\/](solid-js|@solidjs[\\/]router)[\\/]/,
18
+ priority: 40,
19
+ enforce: true,
20
+ },
21
+ vendor: {
22
+ chunks: "all" as const,
23
+ name: "vendor",
24
+ test: /[\\/]node_modules[\\/]/,
25
+ priority: 30,
26
+ },
27
+ },
28
+ } as const),
29
+ minimizer: isDev ? [] : [new rspack.SwcJsMinimizerRspackPlugin()],
30
+ };
31
+ }
32
+
33
+ export function getPerformanceProfile(isDev: boolean) {
34
+ return isDev
35
+ ? { hints: false as const, maxAssetSize: 1000000, maxEntrypointSize: 1000000 }
36
+ : { hints: "warning" as const, maxAssetSize: 307200, maxEntrypointSize: 512000 };
37
+ }
@@ -0,0 +1,19 @@
1
+ import { createServerFunctionId } from "../server-function-id.js";
2
+
3
+ export default function serverHashInjector({ types: t }: any) {
4
+ return {
5
+ name: "anaemia-server-hash-injector",
6
+ visitor: {
7
+ CallExpression(path: any, state: any) {
8
+ if (path.node.callee.name === "runOnServer") {
9
+ const filename = state.file.opts.filename || "unknown";
10
+ const functionHash = createServerFunctionId(filename, path.node.start);
11
+
12
+ if (path.node.arguments.length === 1) {
13
+ path.node.arguments.push(t.stringLiteral(functionHash));
14
+ }
15
+ }
16
+ }
17
+ }
18
+ };
19
+ }
@@ -0,0 +1,144 @@
1
+ import { createServerFunctionId } from "../server-function-id.js";
2
+
3
+ export default function clientServerFnTransform({ types: t }: any) {
4
+ return {
5
+ name: "anaemia-client-server-fn-transform",
6
+ visitor: {
7
+ Program: {
8
+ enter(path: any, state: any) {
9
+ state.hasRunOnServer = false;
10
+ },
11
+ exit(path: any, state: any) {
12
+ if (state.hasRunOnServer) {
13
+ const importDeclaration = t.importDeclaration(
14
+ [t.importSpecifier(t.identifier("$$executeClientRpc"), t.identifier("$$executeClientRpc"))],
15
+ t.stringLiteral("@anaemia/core")
16
+ );
17
+ path.node.body.unshift(importDeclaration);
18
+ }
19
+ }
20
+ },
21
+ CallExpression(path: any, state: any) {
22
+ if (path.node.callee.name === "runOnServer") {
23
+ state.hasRunOnServer = true;
24
+ const filename = state.file.opts.filename || "unknown";
25
+
26
+ const serverFunctionCallback = path.node.arguments[0];
27
+ const explicitId = path.node.arguments[1];
28
+
29
+ const functionHash = t.isStringLiteral(explicitId)
30
+ ? explicitId.value
31
+ : createServerFunctionId(filename, path.node.start);
32
+
33
+ if (serverFunctionCallback) {
34
+ path.get('arguments.0').remove();
35
+ }
36
+
37
+ // this whole thing is just magic bro
38
+ path.replaceWith(
39
+ t.callExpression(
40
+ t.arrowFunctionExpression(
41
+ [],
42
+ t.blockStatement([
43
+ t.variableDeclaration("const", [
44
+ t.variableDeclarator(
45
+ t.identifier("_rpc"),
46
+ t.arrowFunctionExpression(
47
+ [t.restElement(t.identifier("args"))],
48
+ t.callExpression(
49
+ t.callExpression(t.identifier("$$executeClientRpc"), [
50
+ t.stringLiteral(functionHash)
51
+ ]),
52
+ [t.spreadElement(t.identifier("args"))]
53
+ )
54
+ )
55
+ )
56
+ ]),
57
+ // ✅ Inside the blockStatement array, not a 3rd arg to arrowFunctionExpression
58
+ t.expressionStatement(
59
+ t.assignmentExpression(
60
+ "=",
61
+ t.memberExpression(t.identifier("_rpc"), t.identifier("id")),
62
+ t.stringLiteral(functionHash)
63
+ )
64
+ ),
65
+ t.expressionStatement(
66
+ t.assignmentExpression(
67
+ "=",
68
+ t.memberExpression(t.identifier("_rpc"), t.identifier("readHydrationCache")),
69
+ t.arrowFunctionExpression(
70
+ [t.identifier("sourceArg")],
71
+ t.blockStatement([
72
+ t.variableDeclaration("const", [
73
+ t.variableDeclarator(
74
+ t.identifier("__el"),
75
+ t.callExpression(
76
+ t.memberExpression(t.identifier("document"), t.identifier("getElementById")),
77
+ [t.stringLiteral("__ANAEMIA_DATA__")]
78
+ )
79
+ )
80
+ ]),
81
+ t.ifStatement(
82
+ t.unaryExpression("!", t.identifier("__el")),
83
+ t.returnStatement(t.identifier("undefined"))
84
+ ),
85
+ t.tryStatement(
86
+ t.blockStatement([
87
+ t.variableDeclaration("const", [
88
+ t.variableDeclarator(
89
+ t.identifier("__data"),
90
+ t.callExpression(
91
+ t.memberExpression(t.identifier("JSON"), t.identifier("parse")),
92
+ [t.memberExpression(t.identifier("__el"), t.identifier("textContent"))]
93
+ )
94
+ )
95
+ ]),
96
+ t.variableDeclaration("const", [
97
+ t.variableDeclarator(
98
+ t.identifier("__cache"),
99
+ t.optionalMemberExpression(
100
+ t.memberExpression(
101
+ t.identifier("__data"),
102
+ t.identifier("__SERVER_FUNCTION_DATA__")
103
+ ),
104
+ t.stringLiteral(functionHash),
105
+ true,
106
+ true
107
+ )
108
+ )
109
+ ]),
110
+ t.ifStatement(
111
+ t.unaryExpression("!", t.identifier("__cache")),
112
+ t.returnStatement(t.identifier("undefined"))
113
+ ),
114
+ t.returnStatement(
115
+ t.memberExpression(
116
+ t.identifier("__cache"),
117
+ t.callExpression(
118
+ t.memberExpression(t.identifier("JSON"), t.identifier("stringify")),
119
+ [t.arrayExpression([t.identifier("sourceArg")])]
120
+ ),
121
+ true
122
+ )
123
+ )
124
+ ]),
125
+ t.catchClause(
126
+ t.identifier("_"),
127
+ t.blockStatement([t.returnStatement(t.identifier("undefined"))])
128
+ )
129
+ )
130
+ ])
131
+ )
132
+ )
133
+ ),
134
+ t.returnStatement(t.identifier("_rpc"))
135
+ ])
136
+ ),
137
+ []
138
+ )
139
+ );
140
+ }
141
+ }
142
+ },
143
+ };
144
+ }
@@ -0,0 +1,56 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import type { RspackPluginInstance, Compiler } from "@rspack/core";
4
+
5
+ interface HydrationPluginOptions {
6
+ appRoot: string;
7
+ }
8
+
9
+ export class AnaemiaManifestHydrationPlugin implements RspackPluginInstance {
10
+ private appRoot: string;
11
+
12
+ constructor(options: HydrationPluginOptions) {
13
+ this.appRoot = options.appRoot;
14
+ }
15
+
16
+ apply(compiler: Compiler) {
17
+ compiler.hooks.emit.tap("AnaemiaManifestHydrationPlugin", (compilation) => {
18
+ const manifestPath = path.resolve(this.appRoot, "./dist/route-manifest.json");
19
+
20
+ if (!fs.existsSync(manifestPath)) return;
21
+
22
+ try {
23
+ const currentManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
24
+
25
+ if (!currentManifest.chunks) {
26
+ currentManifest.chunks = {};
27
+ }
28
+
29
+ for (const chunk of compilation.chunks) {
30
+ if (!chunk.name) continue;
31
+
32
+ const files = Array.from(chunk.files);
33
+
34
+ const jsFiles = files.filter(
35
+ (f) => f.endsWith(".js") && !f.includes(".hot-update.") && !f.endsWith(".js.map")
36
+ );
37
+
38
+ const cssFiles = files.filter(
39
+ (f) => f.endsWith(".css") && !f.includes(".hot-update.") && !f.endsWith(".css.map")
40
+ );
41
+
42
+ if (jsFiles.length > 0 || cssFiles.length > 0) {
43
+ currentManifest.chunks[chunk.name] = {
44
+ js: jsFiles.map((f) => `/${f}`),
45
+ css: cssFiles.map((f) => `/${f}`),
46
+ };
47
+ }
48
+ }
49
+
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);
53
+ }
54
+ });
55
+ }
56
+ }