@anaemia/bundler 0.3.7 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/dist/aliases.js +1 -1
- package/dist/env-loader.d.ts +2 -0
- package/dist/env-loader.d.ts.map +1 -0
- package/dist/env-loader.js +10 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +60 -14
- package/dist/optimization.d.ts.map +1 -1
- package/dist/optimization.js +17 -4
- package/dist/plugins/babel-transform-server.d.ts.map +1 -1
- package/dist/plugins/babel-transform-server.js +1 -4
- package/dist/router/generate-entry.d.ts.map +1 -1
- package/dist/router/generate-entry.js +3 -3
- package/dist/router/generate-server-routes.d.ts.map +1 -1
- package/dist/router/generate-server-routes.js +15 -5
- package/dist/router/manifest.d.ts.map +1 -1
- package/dist/router/manifest.js +2 -2
- package/dist/router/scan.d.ts.map +1 -1
- package/dist/router/scan.js +5 -6
- package/dist/rules.d.ts +16 -1
- package/dist/rules.d.ts.map +1 -1
- package/dist/rules.js +37 -5
- package/package.json +5 -3
- package/src/aliases.ts +2 -2
- package/src/env-loader.ts +13 -0
- package/src/index.ts +76 -18
- package/src/optimization.ts +18 -5
- package/src/plugins/babel-transform-server.ts +1 -4
- package/src/plugins/rspack-manifest-hydration.ts +3 -3
- package/src/router/generate-entry.ts +15 -6
- package/src/router/generate-server-routes.ts +16 -5
- package/src/router/manifest.ts +5 -10
- package/src/router/scan.ts +9 -10
- package/src/rules.ts +48 -8
- package/test/rspack-config.test.mjs +5 -2
- package/test/server-functions.test.mjs +25 -22
- package/tsconfig.json +1 -1
package/LICENSE
CHANGED
|
@@ -186,7 +186,7 @@
|
|
|
186
186
|
same "printed page" as the copyright notice for easier
|
|
187
187
|
identification within third-party archives.
|
|
188
188
|
|
|
189
|
-
Copyright [
|
|
189
|
+
Copyright [2026] [colourlabs]
|
|
190
190
|
|
|
191
191
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
192
|
you may not use this file except in compliance with the License.
|
package/README.md
CHANGED
package/dist/aliases.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-loader.d.ts","sourceRoot":"","sources":["../src/env-loader.ts"],"names":[],"mappings":"AAKA,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,QAOjE"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { config as loadDotenv } from "dotenv";
|
|
2
|
+
import { expand as expandDotenv } from "dotenv-expand";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export default function loadEnvFiles(appRoot, mode) {
|
|
5
|
+
const files = [`.env`, `.env.local`, `.env.${mode}`, `.env.${mode}.local`];
|
|
6
|
+
for (const file of files) {
|
|
7
|
+
const result = loadDotenv({ path: path.resolve(appRoot, file), override: true });
|
|
8
|
+
expandDotenv(result);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Configuration } from "@rspack/core";
|
|
1
|
+
import type { Configuration } from "@rspack/core";
|
|
2
2
|
import type { AnaemiaConfig } from "@anaemia/core/config";
|
|
3
3
|
export declare function getRspackConfig(appRoot: string, config?: AnaemiaConfig): Promise<[Configuration, Configuration]>;
|
|
4
4
|
export { scanRoutes } from "./router/scan.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAIlD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAsB1D,wBAAsB,eAAe,CACnC,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,aAAkB,GACzB,OAAO,CAAC,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC,CA4NzC;AAED,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { rspack } from "@rspack/core";
|
|
2
|
-
import path from "path";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -11,14 +11,15 @@ import { writeManifest } from "./router/manifest.js";
|
|
|
11
11
|
import { generateRouterEntry } from "./router/generate-entry.js";
|
|
12
12
|
import { generateServerRoutes } from "./router/generate-server-routes.js";
|
|
13
13
|
import { getAliases } from "./aliases.js";
|
|
14
|
-
import { createStyleRules, createBabelRule } from "./rules.js";
|
|
14
|
+
import { createStyleRules, createBabelRule, createAssetRules } from "./rules.js";
|
|
15
15
|
import { getClientOptimization, getPerformanceProfile } from "./optimization.js";
|
|
16
|
+
import loadEnvFiles from "./env-loader.js";
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
17
18
|
const __filename = fileURLToPath(import.meta.url);
|
|
18
19
|
const __dirname = path.dirname(__filename);
|
|
19
20
|
export async function getRspackConfig(appRoot, config = {}) {
|
|
20
21
|
const isDev = process.env.NODE_ENV !== "production";
|
|
21
|
-
|
|
22
|
+
loadEnvFiles(appRoot, process.env.NODE_ENV || "development");
|
|
22
23
|
const coreRuntimeDir = path.dirname(require.resolve("@anaemia/core/package.json"));
|
|
23
24
|
const runtimeDir = path.resolve(coreRuntimeDir, "./dist/runtime");
|
|
24
25
|
const routes = await scanRoutes(appRoot);
|
|
@@ -28,36 +29,49 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
28
29
|
if (!fs.existsSync(frameworkInternalDir)) {
|
|
29
30
|
fs.mkdirSync(frameworkInternalDir, { recursive: true });
|
|
30
31
|
}
|
|
32
|
+
// bootstrap config entries and generate necessary files for the router based on the scanned routes
|
|
31
33
|
const entryFile = generateRouterEntry(appRoot, routes);
|
|
32
34
|
const serverRoutesFile = generateServerRoutes(appRoot, serverRoutes);
|
|
33
35
|
const styleRules = createStyleRules(config);
|
|
36
|
+
const assetRules = createAssetRules(isDev);
|
|
37
|
+
// allow users to inject additional babel plugins via the config, which is useful for things like macros or other code transforms that need to run at compile time
|
|
34
38
|
const extraClientBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.client ?? []) ?? [];
|
|
35
39
|
const extraServerBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.server ?? []) ?? [];
|
|
36
40
|
const solidRefreshPlugin = [require.resolve("solid-refresh/babel"), { bundler: "rspack-esm", jsx: false }];
|
|
37
|
-
|
|
41
|
+
/**
|
|
42
|
+
* env processing:
|
|
43
|
+
*
|
|
44
|
+
* - we inject some default env vars like MODE, DEV, and PROD for convenience
|
|
45
|
+
* - for the server, we expose all env vars
|
|
46
|
+
* - for the client, we only expose vars that start with PUBLIC_, as well as the same defaults
|
|
47
|
+
* - users can also define additional compile-time constants via config.define.client and config.define.server, which are merged into the rspack DefinePlugin config
|
|
48
|
+
*/
|
|
38
49
|
const serverEnv = {
|
|
39
50
|
MODE: JSON.stringify(process.env.NODE_ENV || "development"),
|
|
40
51
|
DEV: JSON.stringify(isDev),
|
|
41
52
|
PROD: JSON.stringify(!isDev),
|
|
42
53
|
};
|
|
43
|
-
for (const key in
|
|
44
|
-
serverEnv[key] = JSON.stringify(
|
|
54
|
+
for (const key in process.env) {
|
|
55
|
+
serverEnv[key] = JSON.stringify(process.env[key]);
|
|
45
56
|
}
|
|
46
57
|
const clientEnv = {
|
|
47
58
|
MODE: JSON.stringify(process.env.NODE_ENV || "development"),
|
|
48
59
|
DEV: JSON.stringify(isDev),
|
|
49
60
|
PROD: JSON.stringify(!isDev),
|
|
50
61
|
};
|
|
51
|
-
for (const key in
|
|
62
|
+
for (const key in process.env) {
|
|
52
63
|
if (key.startsWith("PUBLIC_")) {
|
|
53
|
-
clientEnv[key] = JSON.stringify(
|
|
64
|
+
clientEnv[key] = JSON.stringify(process.env[key]);
|
|
54
65
|
}
|
|
55
66
|
}
|
|
67
|
+
// shared resolve config
|
|
68
|
+
// we set up some shared resolve config between client and server here, like extension aliases and path aliases
|
|
56
69
|
const sharedResolve = {
|
|
57
70
|
extensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".scss", ".css"],
|
|
58
71
|
extensionAlias: { ".js": [".ts", ".js"], ".jsx": [".tsx", ".jsx"] },
|
|
59
72
|
alias: { "anaemia-user-app": entryFile, ...getAliases(appRoot) },
|
|
60
73
|
};
|
|
74
|
+
// client and server configurations
|
|
61
75
|
let clientConfig = {
|
|
62
76
|
name: "client",
|
|
63
77
|
context: appRoot,
|
|
@@ -84,7 +98,14 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
84
98
|
"solid-refresh": require.resolve("solid-refresh"),
|
|
85
99
|
[path.resolve(coreRuntimeDir, "./dist/runtime/context.js")]: path.resolve(coreRuntimeDir, "./dist/runtime/context.browser.js"),
|
|
86
100
|
},
|
|
87
|
-
fallback: {
|
|
101
|
+
fallback: {
|
|
102
|
+
async_hooks: false,
|
|
103
|
+
"node:async_hooks": false,
|
|
104
|
+
fs: false,
|
|
105
|
+
"node:fs": false,
|
|
106
|
+
path: false,
|
|
107
|
+
"node:path": false,
|
|
108
|
+
},
|
|
88
109
|
},
|
|
89
110
|
devServer: isDev
|
|
90
111
|
? {
|
|
@@ -97,9 +118,17 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
97
118
|
}
|
|
98
119
|
: undefined,
|
|
99
120
|
plugins: [
|
|
100
|
-
new rspack.HtmlRspackPlugin({
|
|
121
|
+
new rspack.HtmlRspackPlugin({
|
|
122
|
+
template: path.resolve(appRoot, "./index.html"),
|
|
123
|
+
filename: "index.html",
|
|
124
|
+
inject: false,
|
|
125
|
+
}),
|
|
101
126
|
new rspack.DefinePlugin({
|
|
102
|
-
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
127
|
+
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
128
|
+
port: config.port,
|
|
129
|
+
assets: config.assets,
|
|
130
|
+
styles: config.styles,
|
|
131
|
+
}),
|
|
103
132
|
...config.define?.client,
|
|
104
133
|
"import.meta.env": clientEnv,
|
|
105
134
|
}),
|
|
@@ -118,8 +147,13 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
118
147
|
parser: { "css/auto": { namedExports: false } },
|
|
119
148
|
rules: [
|
|
120
149
|
styleRules.client,
|
|
150
|
+
...assetRules.client,
|
|
121
151
|
{
|
|
122
|
-
...createBabelRule({
|
|
152
|
+
...createBabelRule({
|
|
153
|
+
isServer: false,
|
|
154
|
+
isDev,
|
|
155
|
+
plugins: [clientServerFnTransform, ...(isDev ? [solidRefreshPlugin] : []), ...extraClientBabelPlugins],
|
|
156
|
+
}),
|
|
123
157
|
exclude: (modulePath) => {
|
|
124
158
|
if (modulePath.includes("@anaemia") && modulePath.includes("core"))
|
|
125
159
|
return false;
|
|
@@ -137,7 +171,13 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
137
171
|
context: appRoot,
|
|
138
172
|
target: "node",
|
|
139
173
|
entry: { server: path.resolve(runtimeDir, "entry-server.jsx") },
|
|
140
|
-
output: {
|
|
174
|
+
output: {
|
|
175
|
+
path: path.resolve(appRoot, "./dist/server"),
|
|
176
|
+
filename: "index.js",
|
|
177
|
+
module: true,
|
|
178
|
+
chunkFormat: "module",
|
|
179
|
+
chunkLoading: "import",
|
|
180
|
+
},
|
|
141
181
|
optimization: { nodeEnv: false },
|
|
142
182
|
resolve: {
|
|
143
183
|
...sharedResolve,
|
|
@@ -156,8 +196,13 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
156
196
|
parser: { "css/auto": { namedExports: false } },
|
|
157
197
|
rules: [
|
|
158
198
|
styleRules.server,
|
|
199
|
+
...assetRules.server,
|
|
159
200
|
{
|
|
160
|
-
...createBabelRule({
|
|
201
|
+
...createBabelRule({
|
|
202
|
+
isServer: true,
|
|
203
|
+
isDev,
|
|
204
|
+
plugins: [serverHashInjector, ...extraServerBabelPlugins],
|
|
205
|
+
}),
|
|
161
206
|
exclude: (modulePath) => {
|
|
162
207
|
if (modulePath.includes("@anaemia") && modulePath.includes("core"))
|
|
163
208
|
return false;
|
|
@@ -169,6 +214,7 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
169
214
|
],
|
|
170
215
|
},
|
|
171
216
|
};
|
|
217
|
+
// allow plugins to modify the client and server configurations before they are returned
|
|
172
218
|
for (const plugin of config.plugins ?? []) {
|
|
173
219
|
if (plugin.clientRspackConfig)
|
|
174
220
|
clientConfig = plugin.clientRspackConfig(clientConfig);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimization.d.ts","sourceRoot":"","sources":["../src/optimization.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"optimization.d.ts","sourceRoot":"","sources":["../src/optimization.ts"],"names":[],"mappings":"AAEA,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;uBAgDwhI,CAAC;;;;;EAP5kI;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO;;;;;;;;EAInD"}
|
package/dist/optimization.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { rspack } from "@rspack/core";
|
|
2
2
|
export function getClientOptimization(isDev) {
|
|
3
3
|
return {
|
|
4
|
-
sideEffects:
|
|
5
|
-
usedExports:
|
|
4
|
+
sideEffects: !isDev,
|
|
5
|
+
usedExports: !isDev,
|
|
6
6
|
splitChunks: isDev
|
|
7
7
|
? false
|
|
8
8
|
: {
|
|
@@ -13,7 +13,7 @@ export function getClientOptimization(isDev) {
|
|
|
13
13
|
framework: {
|
|
14
14
|
chunks: "all",
|
|
15
15
|
name: "framework",
|
|
16
|
-
test: /[\\/]node_modules[\\/](solid-js|@solidjs
|
|
16
|
+
test: /[\\/]node_modules[\\/](solid-js|@solidjs)[\\/]/,
|
|
17
17
|
priority: 40,
|
|
18
18
|
enforce: true,
|
|
19
19
|
},
|
|
@@ -25,7 +25,20 @@ export function getClientOptimization(isDev) {
|
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
|
-
minimizer: isDev
|
|
28
|
+
minimizer: isDev
|
|
29
|
+
? []
|
|
30
|
+
: [
|
|
31
|
+
new rspack.SwcJsMinimizerRspackPlugin({
|
|
32
|
+
minimizerOptions: {
|
|
33
|
+
mangle: {
|
|
34
|
+
keep_fnames: true,
|
|
35
|
+
},
|
|
36
|
+
compress: {
|
|
37
|
+
keep_fnames: true,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
],
|
|
29
42
|
};
|
|
30
43
|
}
|
|
31
44
|
export function getPerformanceProfile(isDev) {
|
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CA0IlH"}
|
|
@@ -19,14 +19,11 @@ export default function clientServerFnTransform({ types: t }) {
|
|
|
19
19
|
if (t.isIdentifier(path.node.callee) && path.node.callee.name === "runOnServer") {
|
|
20
20
|
state.hasRunOnServer = true;
|
|
21
21
|
const filename = state.file.opts.filename || "unknown";
|
|
22
|
-
const serverFunctionCallback = path.node.arguments[0];
|
|
23
22
|
const explicitId = path.node.arguments[1];
|
|
24
23
|
const functionHash = t.isStringLiteral(explicitId)
|
|
25
24
|
? explicitId.value
|
|
26
25
|
: createServerFunctionId(filename, path.node.start);
|
|
27
|
-
|
|
28
|
-
path.get('arguments.0').remove();
|
|
29
|
-
}
|
|
26
|
+
path.get('arguments.0').remove();
|
|
30
27
|
// this whole thing is just magic bro
|
|
31
28
|
// wtf is this ast manipulation
|
|
32
29
|
path.replaceWith(t.callExpression(t.arrowFunctionExpression([], t.blockStatement([
|
|
@@ -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;
|
|
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;AA0HpD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAqMzF"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import { transform } from "sucrase";
|
|
4
4
|
function buildTree(routes, strippedLayouts, routeIndices, routesDir, allLayouts, parentPrefix) {
|
|
5
5
|
const nodes = [];
|
|
@@ -66,7 +66,7 @@ function renderTree(nodes, indent = 6) {
|
|
|
66
66
|
}
|
|
67
67
|
return `${pad}<Route path="${routePath}" component={Route${node.routeIdx}Wrapped} />`;
|
|
68
68
|
}
|
|
69
|
-
|
|
69
|
+
const layoutPath = node.relativePath;
|
|
70
70
|
const inner = renderTree(node.children, indent + 2);
|
|
71
71
|
return [`${pad}<Route path="${layoutPath}" component={Layout${node.layoutIdx}}>`, inner, `${pad}</Route>`].join("\n");
|
|
72
72
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-server-routes.d.ts","sourceRoot":"","sources":["../../src/router/generate-server-routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"generate-server-routes.d.ts","sourceRoot":"","sources":["../../src/router/generate-server-routes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAGlD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,gBAAgB,EAAE,GAAG,MAAM,CAyCxF"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { transform } from "sucrase";
|
|
3
4
|
export function generateServerRoutes(appRoot, routes) {
|
|
4
5
|
const outDir = path.resolve(appRoot, "./.anaemia");
|
|
5
|
-
const
|
|
6
|
+
const isTs = fs.existsSync(path.resolve(appRoot, "tsconfig.json"));
|
|
7
|
+
const ext = isTs ? "ts" : "js";
|
|
8
|
+
const outPath = path.resolve(outDir, "./__anaemia_server_routes__." + ext);
|
|
6
9
|
const imports = routes.map((r, i) => `import * as ServerRoute${i} from "${r.filePath}";`).join("\n");
|
|
7
10
|
const registrations = routes.map((r, i) => ` registerRoute(app, "${r.urlPattern}", ServerRoute${i});`).join("\n");
|
|
8
|
-
const
|
|
11
|
+
const rawCode = `
|
|
9
12
|
// @ts-nocheck
|
|
10
13
|
// auto-generated by anaemia - do not edit!!
|
|
11
14
|
import type { Hono } from "hono";
|
|
@@ -25,6 +28,13 @@ export function registerServerRoutes(app: Hono) {
|
|
|
25
28
|
${registrations}
|
|
26
29
|
}
|
|
27
30
|
`.trimStart();
|
|
28
|
-
|
|
31
|
+
const finalCode = isTs
|
|
32
|
+
? rawCode
|
|
33
|
+
: transform(rawCode.replace("// @ts-nocheck\n", ""), {
|
|
34
|
+
transforms: ["typescript", "jsx"],
|
|
35
|
+
jsxRuntime: "preserve",
|
|
36
|
+
production: true,
|
|
37
|
+
}).code;
|
|
38
|
+
fs.writeFileSync(outPath, finalCode);
|
|
29
39
|
return outPath;
|
|
30
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/router/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAUpD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/router/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAUpD,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE,GAAG,IAAI,CAwBjF"}
|
package/dist/router/manifest.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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,
|
|
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,CAgBpE;AAUD,wBAAsB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAiE/E"}
|
package/dist/router/scan.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
|
-
import path from "path";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import { createJiti } from "jiti";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { getAliases } from "../aliases.js";
|
|
@@ -17,9 +17,7 @@ export function scanServerRoutes(appRoot) {
|
|
|
17
17
|
const files = glob.sync("**/_route.{ts,tsx,js,jsx}", { cwd: routesDir, posix: true });
|
|
18
18
|
return files.map((file) => {
|
|
19
19
|
const dir = path.dirname(file);
|
|
20
|
-
const normalizedDir = dir
|
|
21
|
-
.replace(/\[\.\.\.(.+?)\]/g, "*")
|
|
22
|
-
.replace(/\[(.+?)\]/g, ":$1");
|
|
20
|
+
const normalizedDir = dir.replace(/\[\.\.\.(.+?)\]/g, "*").replace(/\[(.+?)\]/g, ":$1");
|
|
23
21
|
const urlPattern = normalizedDir === "." ? "/" : `/${normalizedDir}`;
|
|
24
22
|
return {
|
|
25
23
|
urlPattern,
|
|
@@ -47,7 +45,7 @@ export async function scanRoutes(appRoot) {
|
|
|
47
45
|
let layoutGuards = [];
|
|
48
46
|
try {
|
|
49
47
|
const layoutModule = (await jiti.import(resolveConfigPath(absolutePath)));
|
|
50
|
-
if (layoutModule
|
|
48
|
+
if (layoutModule.config?.guards) {
|
|
51
49
|
layoutGuards = layoutModule.config.guards;
|
|
52
50
|
}
|
|
53
51
|
}
|
|
@@ -73,7 +71,7 @@ export async function scanRoutes(appRoot) {
|
|
|
73
71
|
let pageGuards = [];
|
|
74
72
|
try {
|
|
75
73
|
const pageModule = (await jiti.import(resolveConfigPath(absolutePagePath)));
|
|
76
|
-
if (pageModule
|
|
74
|
+
if (pageModule.config?.guards) {
|
|
77
75
|
pageGuards = pageModule.config.guards;
|
|
78
76
|
}
|
|
79
77
|
}
|
|
@@ -148,6 +146,7 @@ function parseFilePath(file) {
|
|
|
148
146
|
function resolveLayoutChain(dir, layoutMap) {
|
|
149
147
|
const layouts = [];
|
|
150
148
|
let current = dir;
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
151
150
|
while (true) {
|
|
152
151
|
const layoutEntry = layoutMap.get(current);
|
|
153
152
|
if (layoutEntry)
|
package/dist/rules.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnaemiaConfig } from "@anaemia/core";
|
|
2
|
-
import { PluginItem } from "@babel/core";
|
|
2
|
+
import type { PluginItem } from "@babel/core";
|
|
3
|
+
import type { RuleSetRule } from "@rspack/core";
|
|
3
4
|
export declare function createStyleRules(config: AnaemiaConfig): {
|
|
4
5
|
client: {
|
|
5
6
|
test: RegExp;
|
|
@@ -10,6 +11,11 @@ export declare function createStyleRules(config: AnaemiaConfig): {
|
|
|
10
11
|
api: string;
|
|
11
12
|
};
|
|
12
13
|
}[];
|
|
14
|
+
parser: {
|
|
15
|
+
cssModules: {
|
|
16
|
+
localIdentName: string;
|
|
17
|
+
};
|
|
18
|
+
} | undefined;
|
|
13
19
|
};
|
|
14
20
|
server: {
|
|
15
21
|
test: RegExp;
|
|
@@ -25,6 +31,11 @@ export declare function createStyleRules(config: AnaemiaConfig): {
|
|
|
25
31
|
api: string;
|
|
26
32
|
};
|
|
27
33
|
}[];
|
|
34
|
+
parser: {
|
|
35
|
+
cssModules: {
|
|
36
|
+
localIdentName: string;
|
|
37
|
+
};
|
|
38
|
+
} | undefined;
|
|
28
39
|
};
|
|
29
40
|
};
|
|
30
41
|
export declare function createBabelRule({ isServer, isDev, plugins, }: {
|
|
@@ -45,4 +56,8 @@ export declare function createBabelRule({ isServer, isDev, plugins, }: {
|
|
|
45
56
|
};
|
|
46
57
|
}[];
|
|
47
58
|
};
|
|
59
|
+
export declare function createAssetRules(isDev: boolean): {
|
|
60
|
+
client: RuleSetRule[];
|
|
61
|
+
server: RuleSetRule[];
|
|
62
|
+
};
|
|
48
63
|
//# sourceMappingURL=rules.d.ts.map
|
package/dist/rules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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;
|
|
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,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAIhD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAsBrD;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;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO;;;EAsC9C"}
|
package/dist/rules.js
CHANGED
|
@@ -4,21 +4,21 @@ export function createStyleRules(config) {
|
|
|
4
4
|
const useSass = config.styles?.sass !== false;
|
|
5
5
|
const useModules = config.styles?.modules ?? true;
|
|
6
6
|
const baseLoaders = useSass ? [{ loader: require.resolve("sass-loader"), options: { api: "modern" } }] : [];
|
|
7
|
+
const localIdentName = config.styles?.modulesLocalIdentName ?? "[name]__[local]__[hash:base64:5]";
|
|
8
|
+
const cssParser = useModules ? { cssModules: { localIdentName } } : undefined;
|
|
7
9
|
return {
|
|
8
10
|
client: {
|
|
9
11
|
test: /\.(c|sc|sa)ss$/,
|
|
10
12
|
type: useModules ? "css/auto" : "css",
|
|
11
13
|
use: baseLoaders,
|
|
14
|
+
parser: cssParser,
|
|
12
15
|
},
|
|
13
16
|
server: {
|
|
14
17
|
test: /\.(c|sc|sa)ss$/,
|
|
15
18
|
type: useModules ? "css/auto" : "css",
|
|
16
|
-
generator: {
|
|
17
|
-
css: {
|
|
18
|
-
exportOnlyLocals: true,
|
|
19
|
-
},
|
|
20
|
-
},
|
|
19
|
+
generator: { css: { exportOnlyLocals: true } },
|
|
21
20
|
use: baseLoaders,
|
|
21
|
+
parser: cssParser,
|
|
22
22
|
},
|
|
23
23
|
};
|
|
24
24
|
}
|
|
@@ -40,3 +40,35 @@ export function createBabelRule({ isServer, isDev, plugins = [], }) {
|
|
|
40
40
|
],
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
+
export function createAssetRules(isDev) {
|
|
44
|
+
const filename = isDev ? "assets/[name][ext]" : "assets/[name].[contenthash:8][ext]";
|
|
45
|
+
const sharedRawRule = {
|
|
46
|
+
test: /\.(png|jpe?g|gif|webp|avif|ico|svg|json)$/i,
|
|
47
|
+
resourceQuery: /raw/,
|
|
48
|
+
type: "asset/source",
|
|
49
|
+
};
|
|
50
|
+
const sharedUrlRule = {
|
|
51
|
+
test: /\.(png|jpe?g|gif|webp|avif|ico|svg)$/i,
|
|
52
|
+
resourceQuery: /url/,
|
|
53
|
+
type: "asset/resource",
|
|
54
|
+
generator: { filename },
|
|
55
|
+
};
|
|
56
|
+
const sharedInlineRule = {
|
|
57
|
+
test: /\.(png|jpe?g|gif|webp|avif|ico|svg)$/i,
|
|
58
|
+
resourceQuery: /inline/,
|
|
59
|
+
type: "asset/inline",
|
|
60
|
+
};
|
|
61
|
+
const sharedAssetRules = [sharedRawRule, sharedUrlRule, sharedInlineRule];
|
|
62
|
+
const clientRules = [
|
|
63
|
+
...sharedAssetRules,
|
|
64
|
+
{ test: /\.(png|jpe?g|gif|webp|avif|ico)$/i, type: "asset/resource", generator: { filename } },
|
|
65
|
+
{ test: /\.svg$/i, type: "asset", parser: { dataUrlCondition: { maxSize: 8192 } }, generator: { filename } },
|
|
66
|
+
{ test: /\.json$/i, type: "json" },
|
|
67
|
+
];
|
|
68
|
+
const serverRules = [
|
|
69
|
+
...sharedAssetRules,
|
|
70
|
+
{ test: /\.(png|jpe?g|gif|webp|avif|ico|svg)$/i, type: "asset/source" },
|
|
71
|
+
{ test: /\.json$/i, type: "json" },
|
|
72
|
+
];
|
|
73
|
+
return { client: clientRules, server: serverRules };
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anaemia/bundler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"main": "./dist/index.js",
|
|
5
6
|
"types": "./dist/index.d.ts",
|
|
6
|
-
"type": "module",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@anaemia/core": "^0.
|
|
14
|
+
"@anaemia/core": "^0.4.0",
|
|
15
15
|
"@babel/core": "^7.29.7",
|
|
16
16
|
"@babel/preset-typescript": "^7.29.7",
|
|
17
17
|
"@rspack/core": "^2.0.5",
|
|
18
18
|
"babel-loader": "^10.1.1",
|
|
19
19
|
"babel-preset-solid": "^1.9.12",
|
|
20
|
+
"dotenv": "^17.4.2",
|
|
21
|
+
"dotenv-expand": "^13.0.0",
|
|
20
22
|
"glob": "^13.0.6",
|
|
21
23
|
"jiti": "^2.7.0",
|
|
22
24
|
"sass": "^1.100.0",
|
package/src/aliases.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import path from "path";
|
|
1
|
+
import path from "node:path";
|
|
2
2
|
|
|
3
3
|
export function getAliases(appRoot: string) {
|
|
4
4
|
return {
|
|
@@ -8,4 +8,4 @@ export function getAliases(appRoot: string) {
|
|
|
8
8
|
"@features": path.resolve(appRoot, "./src/features"),
|
|
9
9
|
"@routes": path.resolve(appRoot, "./src/routes"),
|
|
10
10
|
};
|
|
11
|
-
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { config as loadDotenv } from "dotenv";
|
|
2
|
+
import { expand as expandDotenv } from "dotenv-expand";
|
|
3
|
+
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
export default function loadEnvFiles(appRoot: string, mode: string) {
|
|
7
|
+
const files = [`.env`, `.env.local`, `.env.${mode}`, `.env.${mode}.local`];
|
|
8
|
+
|
|
9
|
+
for (const file of files) {
|
|
10
|
+
const result = loadDotenv({ path: path.resolve(appRoot, file), override: true });
|
|
11
|
+
expandDotenv(result);
|
|
12
|
+
}
|
|
13
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Configuration
|
|
2
|
-
import
|
|
1
|
+
import type { Configuration } from "@rspack/core";
|
|
2
|
+
import { rspack } from "@rspack/core";
|
|
3
|
+
import path from "node:path";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import type { AnaemiaConfig } from "@anaemia/core/config";
|
|
5
6
|
import { createRequire } from "node:module";
|
|
@@ -15,16 +16,21 @@ import { generateRouterEntry } from "./router/generate-entry.js";
|
|
|
15
16
|
import { generateServerRoutes } from "./router/generate-server-routes.js";
|
|
16
17
|
import { getAliases } from "./aliases.js";
|
|
17
18
|
|
|
18
|
-
import { createStyleRules, createBabelRule } from "./rules.js";
|
|
19
|
+
import { createStyleRules, createBabelRule, createAssetRules } from "./rules.js";
|
|
19
20
|
import { getClientOptimization, getPerformanceProfile } from "./optimization.js";
|
|
21
|
+
import loadEnvFiles from "./env-loader.js";
|
|
20
22
|
|
|
21
23
|
const require = createRequire(import.meta.url);
|
|
22
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
23
25
|
const __dirname = path.dirname(__filename);
|
|
24
26
|
|
|
25
|
-
export async function getRspackConfig(
|
|
27
|
+
export async function getRspackConfig(
|
|
28
|
+
appRoot: string,
|
|
29
|
+
config: AnaemiaConfig = {},
|
|
30
|
+
): Promise<[Configuration, Configuration]> {
|
|
26
31
|
const isDev = process.env.NODE_ENV !== "production";
|
|
27
|
-
|
|
32
|
+
loadEnvFiles(appRoot, process.env.NODE_ENV || "development");
|
|
33
|
+
|
|
28
34
|
const coreRuntimeDir = path.dirname(require.resolve("@anaemia/core/package.json"));
|
|
29
35
|
const runtimeDir = path.resolve(coreRuntimeDir, "./dist/runtime");
|
|
30
36
|
|
|
@@ -37,21 +43,34 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
37
43
|
fs.mkdirSync(frameworkInternalDir, { recursive: true });
|
|
38
44
|
}
|
|
39
45
|
|
|
46
|
+
// bootstrap config entries and generate necessary files for the router based on the scanned routes
|
|
40
47
|
const entryFile = generateRouterEntry(appRoot, routes);
|
|
41
48
|
const serverRoutesFile = generateServerRoutes(appRoot, serverRoutes);
|
|
49
|
+
|
|
42
50
|
const styleRules = createStyleRules(config);
|
|
51
|
+
const assetRules = createAssetRules(isDev);
|
|
52
|
+
|
|
53
|
+
// allow users to inject additional babel plugins via the config, which is useful for things like macros or other code transforms that need to run at compile time
|
|
43
54
|
const extraClientBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.client ?? []) ?? [];
|
|
44
55
|
const extraServerBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.server ?? []) ?? [];
|
|
45
56
|
const solidRefreshPlugin = [require.resolve("solid-refresh/babel"), { bundler: "rspack-esm", jsx: false }];
|
|
46
57
|
|
|
47
|
-
|
|
58
|
+
/**
|
|
59
|
+
* env processing:
|
|
60
|
+
*
|
|
61
|
+
* - we inject some default env vars like MODE, DEV, and PROD for convenience
|
|
62
|
+
* - for the server, we expose all env vars
|
|
63
|
+
* - for the client, we only expose vars that start with PUBLIC_, as well as the same defaults
|
|
64
|
+
* - users can also define additional compile-time constants via config.define.client and config.define.server, which are merged into the rspack DefinePlugin config
|
|
65
|
+
*/
|
|
48
66
|
const serverEnv: Record<string, string> = {
|
|
49
67
|
MODE: JSON.stringify(process.env.NODE_ENV || "development"),
|
|
50
68
|
DEV: JSON.stringify(isDev),
|
|
51
69
|
PROD: JSON.stringify(!isDev),
|
|
52
70
|
};
|
|
53
|
-
|
|
54
|
-
|
|
71
|
+
|
|
72
|
+
for (const key in process.env) {
|
|
73
|
+
serverEnv[key] = JSON.stringify(process.env[key]);
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
const clientEnv: Record<string, string> = {
|
|
@@ -59,18 +78,22 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
59
78
|
DEV: JSON.stringify(isDev),
|
|
60
79
|
PROD: JSON.stringify(!isDev),
|
|
61
80
|
};
|
|
62
|
-
|
|
81
|
+
|
|
82
|
+
for (const key in process.env) {
|
|
63
83
|
if (key.startsWith("PUBLIC_")) {
|
|
64
|
-
clientEnv[key] = JSON.stringify(
|
|
84
|
+
clientEnv[key] = JSON.stringify(process.env[key]);
|
|
65
85
|
}
|
|
66
86
|
}
|
|
67
87
|
|
|
88
|
+
// shared resolve config
|
|
89
|
+
// we set up some shared resolve config between client and server here, like extension aliases and path aliases
|
|
68
90
|
const sharedResolve = {
|
|
69
91
|
extensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".scss", ".css"],
|
|
70
92
|
extensionAlias: { ".js": [".ts", ".js"], ".jsx": [".tsx", ".jsx"] },
|
|
71
93
|
alias: { "anaemia-user-app": entryFile, ...getAliases(appRoot) },
|
|
72
94
|
};
|
|
73
95
|
|
|
96
|
+
// client and server configurations
|
|
74
97
|
let clientConfig: Configuration = {
|
|
75
98
|
name: "client",
|
|
76
99
|
context: appRoot,
|
|
@@ -95,9 +118,19 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
95
118
|
alias: {
|
|
96
119
|
...sharedResolve.alias,
|
|
97
120
|
"solid-refresh": require.resolve("solid-refresh"),
|
|
98
|
-
[path.resolve(coreRuntimeDir, "./dist/runtime/context.js")]: path.resolve(
|
|
121
|
+
[path.resolve(coreRuntimeDir, "./dist/runtime/context.js")]: path.resolve(
|
|
122
|
+
coreRuntimeDir,
|
|
123
|
+
"./dist/runtime/context.browser.js",
|
|
124
|
+
),
|
|
125
|
+
},
|
|
126
|
+
fallback: {
|
|
127
|
+
async_hooks: false,
|
|
128
|
+
"node:async_hooks": false,
|
|
129
|
+
fs: false,
|
|
130
|
+
"node:fs": false,
|
|
131
|
+
path: false,
|
|
132
|
+
"node:path": false,
|
|
99
133
|
},
|
|
100
|
-
fallback: { async_hooks: false, "node:async_hooks": false, fs: false, "node:fs": false, path: false, "node:path": false },
|
|
101
134
|
},
|
|
102
135
|
devServer: isDev
|
|
103
136
|
? {
|
|
@@ -110,9 +143,17 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
110
143
|
}
|
|
111
144
|
: undefined,
|
|
112
145
|
plugins: [
|
|
113
|
-
new rspack.HtmlRspackPlugin({
|
|
146
|
+
new rspack.HtmlRspackPlugin({
|
|
147
|
+
template: path.resolve(appRoot, "./index.html"),
|
|
148
|
+
filename: "index.html",
|
|
149
|
+
inject: false,
|
|
150
|
+
}),
|
|
114
151
|
new rspack.DefinePlugin({
|
|
115
|
-
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
152
|
+
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
153
|
+
port: config.port,
|
|
154
|
+
assets: config.assets,
|
|
155
|
+
styles: config.styles,
|
|
156
|
+
}),
|
|
116
157
|
...config.define?.client,
|
|
117
158
|
"import.meta.env": clientEnv,
|
|
118
159
|
}),
|
|
@@ -126,7 +167,7 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
126
167
|
if (fs.existsSync(srcPath)) return srcPath;
|
|
127
168
|
|
|
128
169
|
return path.resolve(__dirname, "../src/runtime/empty-module.cjs");
|
|
129
|
-
})()
|
|
170
|
+
})(),
|
|
130
171
|
),
|
|
131
172
|
new AnaemiaManifestHydrationPlugin({ appRoot }),
|
|
132
173
|
],
|
|
@@ -134,8 +175,13 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
134
175
|
parser: { "css/auto": { namedExports: false } },
|
|
135
176
|
rules: [
|
|
136
177
|
styleRules.client,
|
|
178
|
+
...assetRules.client,
|
|
137
179
|
{
|
|
138
|
-
...createBabelRule({
|
|
180
|
+
...createBabelRule({
|
|
181
|
+
isServer: false,
|
|
182
|
+
isDev,
|
|
183
|
+
plugins: [clientServerFnTransform, ...(isDev ? [solidRefreshPlugin] : []), ...extraClientBabelPlugins],
|
|
184
|
+
}),
|
|
139
185
|
exclude: (modulePath: string) => {
|
|
140
186
|
if (modulePath.includes("@anaemia") && modulePath.includes("core")) return false;
|
|
141
187
|
if (modulePath.includes("@solidjs") && modulePath.includes("router")) return false;
|
|
@@ -152,7 +198,13 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
152
198
|
context: appRoot,
|
|
153
199
|
target: "node",
|
|
154
200
|
entry: { server: path.resolve(runtimeDir, "entry-server.jsx") },
|
|
155
|
-
output: {
|
|
201
|
+
output: {
|
|
202
|
+
path: path.resolve(appRoot, "./dist/server"),
|
|
203
|
+
filename: "index.js",
|
|
204
|
+
module: true,
|
|
205
|
+
chunkFormat: "module",
|
|
206
|
+
chunkLoading: "import",
|
|
207
|
+
},
|
|
156
208
|
optimization: { nodeEnv: false },
|
|
157
209
|
resolve: {
|
|
158
210
|
...sharedResolve,
|
|
@@ -171,8 +223,13 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
171
223
|
parser: { "css/auto": { namedExports: false } },
|
|
172
224
|
rules: [
|
|
173
225
|
styleRules.server,
|
|
226
|
+
...assetRules.server,
|
|
174
227
|
{
|
|
175
|
-
...createBabelRule({
|
|
228
|
+
...createBabelRule({
|
|
229
|
+
isServer: true,
|
|
230
|
+
isDev,
|
|
231
|
+
plugins: [serverHashInjector, ...extraServerBabelPlugins],
|
|
232
|
+
}),
|
|
176
233
|
exclude: (modulePath: string) => {
|
|
177
234
|
if (modulePath.includes("@anaemia") && modulePath.includes("core")) return false;
|
|
178
235
|
if (modulePath.includes("@solidjs") && modulePath.includes("router")) return false;
|
|
@@ -183,6 +240,7 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
183
240
|
},
|
|
184
241
|
};
|
|
185
242
|
|
|
243
|
+
// allow plugins to modify the client and server configurations before they are returned
|
|
186
244
|
for (const plugin of config.plugins ?? []) {
|
|
187
245
|
if (plugin.clientRspackConfig) clientConfig = plugin.clientRspackConfig(clientConfig);
|
|
188
246
|
if (plugin.serverRspackConfig) serverConfig = plugin.serverRspackConfig(serverConfig);
|
package/src/optimization.ts
CHANGED
|
@@ -2,8 +2,8 @@ import { rspack } from "@rspack/core";
|
|
|
2
2
|
|
|
3
3
|
export function getClientOptimization(isDev: boolean) {
|
|
4
4
|
return {
|
|
5
|
-
sideEffects:
|
|
6
|
-
usedExports:
|
|
5
|
+
sideEffects: !isDev,
|
|
6
|
+
usedExports: !isDev,
|
|
7
7
|
splitChunks: isDev
|
|
8
8
|
? (false as const)
|
|
9
9
|
: ({
|
|
@@ -14,7 +14,7 @@ export function getClientOptimization(isDev: boolean) {
|
|
|
14
14
|
framework: {
|
|
15
15
|
chunks: "all" as const,
|
|
16
16
|
name: "framework",
|
|
17
|
-
test: /[\\/]node_modules[\\/](solid-js|@solidjs
|
|
17
|
+
test: /[\\/]node_modules[\\/](solid-js|@solidjs)[\\/]/,
|
|
18
18
|
priority: 40,
|
|
19
19
|
enforce: true,
|
|
20
20
|
},
|
|
@@ -26,7 +26,20 @@ export function getClientOptimization(isDev: boolean) {
|
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
28
|
} as const),
|
|
29
|
-
minimizer: isDev
|
|
29
|
+
minimizer: isDev
|
|
30
|
+
? []
|
|
31
|
+
: [
|
|
32
|
+
new rspack.SwcJsMinimizerRspackPlugin({
|
|
33
|
+
minimizerOptions: {
|
|
34
|
+
mangle: {
|
|
35
|
+
keep_fnames: true,
|
|
36
|
+
},
|
|
37
|
+
compress: {
|
|
38
|
+
keep_fnames: true,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
}),
|
|
42
|
+
],
|
|
30
43
|
};
|
|
31
44
|
}
|
|
32
45
|
|
|
@@ -34,4 +47,4 @@ export function getPerformanceProfile(isDev: boolean) {
|
|
|
34
47
|
return isDev
|
|
35
48
|
? { hints: false as const, maxAssetSize: 1000000, maxEntrypointSize: 1000000 }
|
|
36
49
|
: { hints: "warning" as const, maxAssetSize: 307200, maxEntrypointSize: 512000 };
|
|
37
|
-
}
|
|
50
|
+
}
|
|
@@ -28,16 +28,13 @@ export default function clientServerFnTransform({ types: t }: { types: typeof Ba
|
|
|
28
28
|
state.hasRunOnServer = true;
|
|
29
29
|
const filename = state.file.opts.filename || "unknown";
|
|
30
30
|
|
|
31
|
-
const serverFunctionCallback = path.node.arguments[0];
|
|
32
31
|
const explicitId = path.node.arguments[1];
|
|
33
32
|
|
|
34
33
|
const functionHash = t.isStringLiteral(explicitId)
|
|
35
34
|
? explicitId.value
|
|
36
35
|
: createServerFunctionId(filename, path.node.start);
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
path.get('arguments.0').remove();
|
|
40
|
-
}
|
|
37
|
+
path.get('arguments.0').remove();
|
|
41
38
|
|
|
42
39
|
// this whole thing is just magic bro
|
|
43
40
|
// wtf is this ast manipulation
|
|
@@ -32,11 +32,11 @@ export class AnaemiaManifestHydrationPlugin implements RspackPluginInstance {
|
|
|
32
32
|
const files = Array.from(chunk.files);
|
|
33
33
|
|
|
34
34
|
const jsFiles = files.filter(
|
|
35
|
-
(f) => f.endsWith(".js") && !f.includes(".hot-update.") && !f.endsWith(".js.map")
|
|
35
|
+
(f) => f.endsWith(".js") && !f.includes(".hot-update.") && !f.endsWith(".js.map"),
|
|
36
36
|
);
|
|
37
37
|
|
|
38
38
|
const cssFiles = files.filter(
|
|
39
|
-
(f) => f.endsWith(".css") && !f.includes(".hot-update.") && !f.endsWith(".css.map")
|
|
39
|
+
(f) => f.endsWith(".css") && !f.includes(".hot-update.") && !f.endsWith(".css.map"),
|
|
40
40
|
);
|
|
41
41
|
|
|
42
42
|
if (jsFiles.length > 0 || cssFiles.length > 0) {
|
|
@@ -54,4 +54,4 @@ export class AnaemiaManifestHydrationPlugin implements RspackPluginInstance {
|
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
-
}
|
|
57
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import type { RouteManifestEntry } from "./scan.js";
|
|
4
4
|
import { transform } from "sucrase";
|
|
5
5
|
|
|
@@ -21,7 +21,14 @@ type PageNode = {
|
|
|
21
21
|
|
|
22
22
|
type TreeNode = LayoutNode | PageNode;
|
|
23
23
|
|
|
24
|
-
function buildTree(
|
|
24
|
+
function buildTree(
|
|
25
|
+
routes: RouteManifestEntry[],
|
|
26
|
+
strippedLayouts: string[][],
|
|
27
|
+
routeIndices: number[],
|
|
28
|
+
routesDir: string,
|
|
29
|
+
allLayouts: Map<string, number>,
|
|
30
|
+
parentPrefix: string,
|
|
31
|
+
): TreeNode[] {
|
|
25
32
|
const nodes: TreeNode[] = [];
|
|
26
33
|
const leafIndices = strippedLayouts.map((l, i) => (l.length === 0 ? i : -1)).filter((i) => i !== -1);
|
|
27
34
|
|
|
@@ -94,10 +101,12 @@ function renderTree(nodes: TreeNode[], indent = 6): string {
|
|
|
94
101
|
return `${pad}<Route path="${routePath}" component={Route${node.routeIdx}Wrapped} />`;
|
|
95
102
|
}
|
|
96
103
|
|
|
97
|
-
|
|
104
|
+
const layoutPath = node.relativePath;
|
|
98
105
|
const inner = renderTree(node.children, indent + 2);
|
|
99
106
|
|
|
100
|
-
return [`${pad}<Route path="${layoutPath}" component={Layout${node.layoutIdx}}>`, inner, `${pad}</Route>`].join(
|
|
107
|
+
return [`${pad}<Route path="${layoutPath}" component={Layout${node.layoutIdx}}>`, inner, `${pad}</Route>`].join(
|
|
108
|
+
"\n",
|
|
109
|
+
);
|
|
101
110
|
})
|
|
102
111
|
.join("\n");
|
|
103
112
|
}
|
|
@@ -193,7 +202,7 @@ const Route${i}Wrapped = (props) => (
|
|
|
193
202
|
conventionalRoutes.map((_, i) => i),
|
|
194
203
|
routesDir,
|
|
195
204
|
allLayouts,
|
|
196
|
-
"/"
|
|
205
|
+
"/",
|
|
197
206
|
);
|
|
198
207
|
|
|
199
208
|
let routeJsx = renderTree(tree, 6);
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import type { ServerRouteEntry } from "./scan.js";
|
|
4
|
+
import { transform } from "sucrase";
|
|
4
5
|
|
|
5
6
|
export function generateServerRoutes(appRoot: string, routes: ServerRouteEntry[]): string {
|
|
6
7
|
const outDir = path.resolve(appRoot, "./.anaemia");
|
|
7
|
-
const
|
|
8
|
+
const isTs = fs.existsSync(path.resolve(appRoot, "tsconfig.json"));
|
|
9
|
+
const ext = isTs ? "ts" : "js";
|
|
10
|
+
const outPath = path.resolve(outDir, "./__anaemia_server_routes__." + ext);
|
|
8
11
|
|
|
9
12
|
const imports = routes.map((r, i) => `import * as ServerRoute${i} from "${r.filePath}";`).join("\n");
|
|
10
13
|
|
|
11
14
|
const registrations = routes.map((r, i) => ` registerRoute(app, "${r.urlPattern}", ServerRoute${i});`).join("\n");
|
|
12
15
|
|
|
13
|
-
const
|
|
16
|
+
const rawCode = `
|
|
14
17
|
// @ts-nocheck
|
|
15
18
|
// auto-generated by anaemia - do not edit!!
|
|
16
19
|
import type { Hono } from "hono";
|
|
@@ -31,6 +34,14 @@ ${registrations}
|
|
|
31
34
|
}
|
|
32
35
|
`.trimStart();
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
const finalCode = isTs
|
|
38
|
+
? rawCode
|
|
39
|
+
: transform(rawCode.replace("// @ts-nocheck\n", ""), {
|
|
40
|
+
transforms: ["typescript", "jsx"],
|
|
41
|
+
jsxRuntime: "preserve",
|
|
42
|
+
production: true,
|
|
43
|
+
}).code;
|
|
44
|
+
|
|
45
|
+
fs.writeFileSync(outPath, finalCode);
|
|
35
46
|
return outPath;
|
|
36
47
|
}
|
package/src/router/manifest.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import type { RouteManifestEntry } from "./scan.js";
|
|
4
4
|
|
|
5
5
|
interface BuildManifest {
|
|
@@ -22,9 +22,7 @@ export function writeManifest(appRoot: string, routes: RouteManifestEntry[]): vo
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const conventionalRoutes = routes.filter(
|
|
26
|
-
(r) => !r.filePath.endsWith("404.tsx") && !r.filePath.endsWith("500.tsx")
|
|
27
|
-
);
|
|
25
|
+
const conventionalRoutes = routes.filter((r) => !r.filePath.endsWith("404.tsx") && !r.filePath.endsWith("500.tsx"));
|
|
28
26
|
|
|
29
27
|
const manifest: BuildManifest = {
|
|
30
28
|
routes: conventionalRoutes,
|
|
@@ -35,8 +33,5 @@ export function writeManifest(appRoot: string, routes: RouteManifestEntry[]): vo
|
|
|
35
33
|
|
|
36
34
|
const outDir = path.resolve(appRoot, "./dist");
|
|
37
35
|
fs.mkdirSync(outDir, { recursive: true });
|
|
38
|
-
fs.writeFileSync(
|
|
39
|
-
|
|
40
|
-
JSON.stringify(manifest, null, 2)
|
|
41
|
-
);
|
|
42
|
-
}
|
|
36
|
+
fs.writeFileSync(path.resolve(outDir, "route-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
37
|
+
}
|
package/src/router/scan.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { glob } from "glob";
|
|
2
|
-
import path from "path";
|
|
2
|
+
import path from "node:path";
|
|
3
3
|
import { createJiti } from "jiti";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import { getAliases } from "../aliases.js";
|
|
@@ -51,16 +51,14 @@ const DYNAMIC_SEGMENT = /^\[(.+?)\]\.(tsx|jsx)$/;
|
|
|
51
51
|
export function scanServerRoutes(appRoot: string): ServerRouteEntry[] {
|
|
52
52
|
const routesDir = path.resolve(appRoot, "./src/routes");
|
|
53
53
|
const files = glob.sync("**/_route.{ts,tsx,js,jsx}", { cwd: routesDir, posix: true });
|
|
54
|
-
|
|
54
|
+
|
|
55
55
|
return files.map((file) => {
|
|
56
56
|
const dir = path.dirname(file);
|
|
57
|
-
|
|
58
|
-
const normalizedDir = dir
|
|
59
|
-
.replace(/\[\.\.\.(.+?)\]/g, "*")
|
|
60
|
-
.replace(/\[(.+?)\]/g, ":$1");
|
|
57
|
+
|
|
58
|
+
const normalizedDir = dir.replace(/\[\.\.\.(.+?)\]/g, "*").replace(/\[(.+?)\]/g, ":$1");
|
|
61
59
|
|
|
62
60
|
const urlPattern = normalizedDir === "." ? "/" : `/${normalizedDir}`;
|
|
63
|
-
|
|
61
|
+
|
|
64
62
|
return {
|
|
65
63
|
urlPattern,
|
|
66
64
|
filePath: path.resolve(routesDir, file),
|
|
@@ -92,7 +90,7 @@ export async function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]>
|
|
|
92
90
|
|
|
93
91
|
try {
|
|
94
92
|
const layoutModule = (await jiti.import(resolveConfigPath(absolutePath))) as RouteModule;
|
|
95
|
-
if (layoutModule
|
|
93
|
+
if (layoutModule.config?.guards) {
|
|
96
94
|
layoutGuards = layoutModule.config.guards;
|
|
97
95
|
}
|
|
98
96
|
} catch {
|
|
@@ -122,7 +120,7 @@ export async function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]>
|
|
|
122
120
|
|
|
123
121
|
try {
|
|
124
122
|
const pageModule = (await jiti.import(resolveConfigPath(absolutePagePath))) as RouteModule;
|
|
125
|
-
if (pageModule
|
|
123
|
+
if (pageModule.config?.guards) {
|
|
126
124
|
pageGuards = pageModule.config.guards;
|
|
127
125
|
}
|
|
128
126
|
} catch {
|
|
@@ -207,6 +205,7 @@ function resolveLayoutChain(dir: string, layoutMap: Map<string, LayoutManifestEn
|
|
|
207
205
|
const layouts: LayoutManifestEntry[] = [];
|
|
208
206
|
let current = dir;
|
|
209
207
|
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
210
209
|
while (true) {
|
|
211
210
|
const layoutEntry = layoutMap.get(current);
|
|
212
211
|
if (layoutEntry) layouts.unshift(layoutEntry);
|
|
@@ -215,4 +214,4 @@ function resolveLayoutChain(dir: string, layoutMap: Map<string, LayoutManifestEn
|
|
|
215
214
|
}
|
|
216
215
|
|
|
217
216
|
return layouts;
|
|
218
|
-
}
|
|
217
|
+
}
|
package/src/rules.ts
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import type { AnaemiaConfig } from "@anaemia/core";
|
|
3
|
-
import { PluginItem } from "@babel/core";
|
|
3
|
+
import type { PluginItem } from "@babel/core";
|
|
4
|
+
import type { RuleSetRule } from "@rspack/core";
|
|
4
5
|
|
|
5
6
|
const require = createRequire(import.meta.url);
|
|
6
7
|
|
|
7
8
|
export function createStyleRules(config: AnaemiaConfig) {
|
|
8
9
|
const useSass = config.styles?.sass !== false;
|
|
9
10
|
const useModules = config.styles?.modules ?? true;
|
|
10
|
-
|
|
11
11
|
const baseLoaders = useSass ? [{ loader: require.resolve("sass-loader"), options: { api: "modern" } }] : [];
|
|
12
|
+
const localIdentName = config.styles?.modulesLocalIdentName ?? "[name]__[local]__[hash:base64:5]";
|
|
13
|
+
const cssParser = useModules ? { cssModules: { localIdentName } } : undefined;
|
|
12
14
|
|
|
13
15
|
return {
|
|
14
16
|
client: {
|
|
15
17
|
test: /\.(c|sc|sa)ss$/,
|
|
16
18
|
type: useModules ? "css/auto" : ("css" as const),
|
|
17
19
|
use: baseLoaders,
|
|
20
|
+
parser: cssParser,
|
|
18
21
|
},
|
|
19
22
|
server: {
|
|
20
23
|
test: /\.(c|sc|sa)ss$/,
|
|
21
24
|
type: useModules ? "css/auto" : ("css" as const),
|
|
22
|
-
generator: {
|
|
23
|
-
css: {
|
|
24
|
-
exportOnlyLocals: true,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
25
|
+
generator: { css: { exportOnlyLocals: true } },
|
|
27
26
|
use: baseLoaders,
|
|
27
|
+
parser: cssParser,
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
30
|
}
|
|
@@ -55,4 +55,44 @@ export function createBabelRule({
|
|
|
55
55
|
},
|
|
56
56
|
],
|
|
57
57
|
};
|
|
58
|
-
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createAssetRules(isDev: boolean) {
|
|
61
|
+
const filename = isDev ? "assets/[name][ext]" : "assets/[name].[contenthash:8][ext]";
|
|
62
|
+
|
|
63
|
+
const sharedRawRule: RuleSetRule = {
|
|
64
|
+
test: /\.(png|jpe?g|gif|webp|avif|ico|svg|json)$/i,
|
|
65
|
+
resourceQuery: /raw/,
|
|
66
|
+
type: "asset/source",
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const sharedUrlRule: RuleSetRule = {
|
|
70
|
+
test: /\.(png|jpe?g|gif|webp|avif|ico|svg)$/i,
|
|
71
|
+
resourceQuery: /url/,
|
|
72
|
+
type: "asset/resource",
|
|
73
|
+
generator: { filename },
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const sharedInlineRule: RuleSetRule = {
|
|
77
|
+
test: /\.(png|jpe?g|gif|webp|avif|ico|svg)$/i,
|
|
78
|
+
resourceQuery: /inline/,
|
|
79
|
+
type: "asset/inline",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const sharedAssetRules: RuleSetRule[] = [sharedRawRule, sharedUrlRule, sharedInlineRule];
|
|
83
|
+
|
|
84
|
+
const clientRules: RuleSetRule[] = [
|
|
85
|
+
...sharedAssetRules,
|
|
86
|
+
{ test: /\.(png|jpe?g|gif|webp|avif|ico)$/i, type: "asset/resource", generator: { filename } },
|
|
87
|
+
{ test: /\.svg$/i, type: "asset", parser: { dataUrlCondition: { maxSize: 8192 } }, generator: { filename } },
|
|
88
|
+
{ test: /\.json$/i, type: "json" },
|
|
89
|
+
];
|
|
90
|
+
|
|
91
|
+
const serverRules: RuleSetRule[] = [
|
|
92
|
+
...sharedAssetRules,
|
|
93
|
+
{ test: /\.(png|jpe?g|gif|webp|avif|ico|svg)$/i, type: "asset/source" },
|
|
94
|
+
{ test: /\.json$/i, type: "json" },
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
return { client: clientRules, server: serverRules };
|
|
98
|
+
}
|
|
@@ -9,7 +9,10 @@ async function createTmpProject(isTs = true) {
|
|
|
9
9
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "anaemia-bundler-test-"));
|
|
10
10
|
|
|
11
11
|
fs.mkdirSync(path.join(dir, "src/routes"), { recursive: true });
|
|
12
|
-
fs.writeFileSync(
|
|
12
|
+
fs.writeFileSync(
|
|
13
|
+
path.join(dir, "src/routes/index.tsx"),
|
|
14
|
+
`export default function Index() { return <div>hello</div>; }`,
|
|
15
|
+
);
|
|
13
16
|
fs.writeFileSync(path.join(dir, "index.html"), `<html><body><div anaemia-entry></div></body></html>`);
|
|
14
17
|
|
|
15
18
|
if (isTs) {
|
|
@@ -106,4 +109,4 @@ test("server config aliases point to dist not src", async () => {
|
|
|
106
109
|
} finally {
|
|
107
110
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
108
111
|
}
|
|
109
|
-
});
|
|
112
|
+
});
|
|
@@ -15,12 +15,14 @@ const source = `
|
|
|
15
15
|
`;
|
|
16
16
|
|
|
17
17
|
function transform(plugin) {
|
|
18
|
-
return
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
return (
|
|
19
|
+
transformSync(source, {
|
|
20
|
+
filename,
|
|
21
|
+
plugins: [plugin],
|
|
22
|
+
configFile: false,
|
|
23
|
+
babelrc: false,
|
|
24
|
+
})?.code ?? ""
|
|
25
|
+
);
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
test("client and server transforms generate the same server function id", () => {
|
|
@@ -42,36 +44,37 @@ test("client transform forwards call arguments to the RPC wrapper", () => {
|
|
|
42
44
|
});
|
|
43
45
|
|
|
44
46
|
test("client transform preserves explicit server function ids", () => {
|
|
45
|
-
const code =
|
|
46
|
-
|
|
47
|
+
const code =
|
|
48
|
+
transformSync(
|
|
49
|
+
`
|
|
47
50
|
import { runOnServer } from "@anaemia/core";
|
|
48
51
|
export const ping = runOnServer(async () => "pong", "custom-id");
|
|
49
52
|
`,
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
{
|
|
54
|
+
filename,
|
|
55
|
+
plugins: [clientServerFnTransform],
|
|
56
|
+
configFile: false,
|
|
57
|
+
babelrc: false,
|
|
58
|
+
},
|
|
59
|
+
)?.code ?? "";
|
|
57
60
|
|
|
58
61
|
assert.match(code, /\$\$executeClientRpc\("custom-id"\)/);
|
|
59
62
|
});
|
|
60
63
|
|
|
61
64
|
test("should guarantee server-side logic never leaks to client assets", async () => {
|
|
62
65
|
const clientAssetDir = path.resolve(process.cwd(), "dist/client/assets");
|
|
63
|
-
|
|
66
|
+
|
|
64
67
|
if (!fs.existsSync(clientAssetDir)) return;
|
|
65
68
|
|
|
66
|
-
const files = fs.readdirSync(clientAssetDir).filter(f => f.endsWith(".js"));
|
|
69
|
+
const files = fs.readdirSync(clientAssetDir).filter((f) => f.endsWith(".js"));
|
|
67
70
|
|
|
68
71
|
for (const file of files) {
|
|
69
72
|
const content = fs.readFileSync(path.join(clientAssetDir, file), "utf-8");
|
|
70
|
-
|
|
73
|
+
|
|
71
74
|
assert.equal(
|
|
72
|
-
content.includes("SELECT * FROM users"),
|
|
73
|
-
false,
|
|
74
|
-
`CRITICAL SECURITY LEAK: server logic found inside client asset: ${file}
|
|
75
|
+
content.includes("SELECT * FROM users"),
|
|
76
|
+
false,
|
|
77
|
+
`CRITICAL SECURITY LEAK: server logic found inside client asset: ${file}`,
|
|
75
78
|
);
|
|
76
79
|
}
|
|
77
|
-
});
|
|
80
|
+
});
|
package/tsconfig.json
CHANGED