@anaemia/bundler 0.3.6 → 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 +76 -9
- 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 +7 -5
- 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 +94 -13
- 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 +11 -6
- 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,13 +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";
|
|
22
|
+
loadEnvFiles(appRoot, process.env.NODE_ENV || "development");
|
|
21
23
|
const coreRuntimeDir = path.dirname(require.resolve("@anaemia/core/package.json"));
|
|
22
24
|
const runtimeDir = path.resolve(coreRuntimeDir, "./dist/runtime");
|
|
23
25
|
const routes = await scanRoutes(appRoot);
|
|
@@ -27,17 +29,49 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
27
29
|
if (!fs.existsSync(frameworkInternalDir)) {
|
|
28
30
|
fs.mkdirSync(frameworkInternalDir, { recursive: true });
|
|
29
31
|
}
|
|
32
|
+
// bootstrap config entries and generate necessary files for the router based on the scanned routes
|
|
30
33
|
const entryFile = generateRouterEntry(appRoot, routes);
|
|
31
34
|
const serverRoutesFile = generateServerRoutes(appRoot, serverRoutes);
|
|
32
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
|
|
33
38
|
const extraClientBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.client ?? []) ?? [];
|
|
34
39
|
const extraServerBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.server ?? []) ?? [];
|
|
35
40
|
const solidRefreshPlugin = [require.resolve("solid-refresh/babel"), { bundler: "rspack-esm", jsx: false }];
|
|
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
|
+
*/
|
|
49
|
+
const serverEnv = {
|
|
50
|
+
MODE: JSON.stringify(process.env.NODE_ENV || "development"),
|
|
51
|
+
DEV: JSON.stringify(isDev),
|
|
52
|
+
PROD: JSON.stringify(!isDev),
|
|
53
|
+
};
|
|
54
|
+
for (const key in process.env) {
|
|
55
|
+
serverEnv[key] = JSON.stringify(process.env[key]);
|
|
56
|
+
}
|
|
57
|
+
const clientEnv = {
|
|
58
|
+
MODE: JSON.stringify(process.env.NODE_ENV || "development"),
|
|
59
|
+
DEV: JSON.stringify(isDev),
|
|
60
|
+
PROD: JSON.stringify(!isDev),
|
|
61
|
+
};
|
|
62
|
+
for (const key in process.env) {
|
|
63
|
+
if (key.startsWith("PUBLIC_")) {
|
|
64
|
+
clientEnv[key] = JSON.stringify(process.env[key]);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// shared resolve config
|
|
68
|
+
// we set up some shared resolve config between client and server here, like extension aliases and path aliases
|
|
36
69
|
const sharedResolve = {
|
|
37
70
|
extensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".scss", ".css"],
|
|
38
71
|
extensionAlias: { ".js": [".ts", ".js"], ".jsx": [".tsx", ".jsx"] },
|
|
39
72
|
alias: { "anaemia-user-app": entryFile, ...getAliases(appRoot) },
|
|
40
73
|
};
|
|
74
|
+
// client and server configurations
|
|
41
75
|
let clientConfig = {
|
|
42
76
|
name: "client",
|
|
43
77
|
context: appRoot,
|
|
@@ -64,7 +98,14 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
64
98
|
"solid-refresh": require.resolve("solid-refresh"),
|
|
65
99
|
[path.resolve(coreRuntimeDir, "./dist/runtime/context.js")]: path.resolve(coreRuntimeDir, "./dist/runtime/context.browser.js"),
|
|
66
100
|
},
|
|
67
|
-
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
|
+
},
|
|
68
109
|
},
|
|
69
110
|
devServer: isDev
|
|
70
111
|
? {
|
|
@@ -77,10 +118,19 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
77
118
|
}
|
|
78
119
|
: undefined,
|
|
79
120
|
plugins: [
|
|
80
|
-
new rspack.HtmlRspackPlugin({
|
|
121
|
+
new rspack.HtmlRspackPlugin({
|
|
122
|
+
template: path.resolve(appRoot, "./index.html"),
|
|
123
|
+
filename: "index.html",
|
|
124
|
+
inject: false,
|
|
125
|
+
}),
|
|
81
126
|
new rspack.DefinePlugin({
|
|
82
|
-
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
127
|
+
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
128
|
+
port: config.port,
|
|
129
|
+
assets: config.assets,
|
|
130
|
+
styles: config.styles,
|
|
131
|
+
}),
|
|
83
132
|
...config.define?.client,
|
|
133
|
+
"import.meta.env": clientEnv,
|
|
84
134
|
}),
|
|
85
135
|
new rspack.NormalModuleReplacementPlugin(/^node:/, (resource) => {
|
|
86
136
|
resource.request = resource.request.replace(/^node:/, "");
|
|
@@ -97,8 +147,13 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
97
147
|
parser: { "css/auto": { namedExports: false } },
|
|
98
148
|
rules: [
|
|
99
149
|
styleRules.client,
|
|
150
|
+
...assetRules.client,
|
|
100
151
|
{
|
|
101
|
-
...createBabelRule({
|
|
152
|
+
...createBabelRule({
|
|
153
|
+
isServer: false,
|
|
154
|
+
isDev,
|
|
155
|
+
plugins: [clientServerFnTransform, ...(isDev ? [solidRefreshPlugin] : []), ...extraClientBabelPlugins],
|
|
156
|
+
}),
|
|
102
157
|
exclude: (modulePath) => {
|
|
103
158
|
if (modulePath.includes("@anaemia") && modulePath.includes("core"))
|
|
104
159
|
return false;
|
|
@@ -116,7 +171,13 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
116
171
|
context: appRoot,
|
|
117
172
|
target: "node",
|
|
118
173
|
entry: { server: path.resolve(runtimeDir, "entry-server.jsx") },
|
|
119
|
-
output: {
|
|
174
|
+
output: {
|
|
175
|
+
path: path.resolve(appRoot, "./dist/server"),
|
|
176
|
+
filename: "index.js",
|
|
177
|
+
module: true,
|
|
178
|
+
chunkFormat: "module",
|
|
179
|
+
chunkLoading: "import",
|
|
180
|
+
},
|
|
120
181
|
optimization: { nodeEnv: false },
|
|
121
182
|
resolve: {
|
|
122
183
|
...sharedResolve,
|
|
@@ -130,13 +191,18 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
130
191
|
__anaemia_server_routes__: serverRoutesFile,
|
|
131
192
|
},
|
|
132
193
|
},
|
|
133
|
-
plugins: [new rspack.DefinePlugin({ ...config.define?.server })],
|
|
194
|
+
plugins: [new rspack.DefinePlugin({ ...config.define?.server, "import.meta.env": serverEnv })],
|
|
134
195
|
module: {
|
|
135
196
|
parser: { "css/auto": { namedExports: false } },
|
|
136
197
|
rules: [
|
|
137
198
|
styleRules.server,
|
|
199
|
+
...assetRules.server,
|
|
138
200
|
{
|
|
139
|
-
...createBabelRule({
|
|
201
|
+
...createBabelRule({
|
|
202
|
+
isServer: true,
|
|
203
|
+
isDev,
|
|
204
|
+
plugins: [serverHashInjector, ...extraServerBabelPlugins],
|
|
205
|
+
}),
|
|
140
206
|
exclude: (modulePath) => {
|
|
141
207
|
if (modulePath.includes("@anaemia") && modulePath.includes("core"))
|
|
142
208
|
return false;
|
|
@@ -148,6 +214,7 @@ export async function getRspackConfig(appRoot, config = {}) {
|
|
|
148
214
|
],
|
|
149
215
|
},
|
|
150
216
|
};
|
|
217
|
+
// allow plugins to modify the client and server configurations before they are returned
|
|
151
218
|
for (const plugin of config.plugins ?? []) {
|
|
152
219
|
if (plugin.clientRspackConfig)
|
|
153
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";
|
|
@@ -14,10 +14,11 @@ const CATCH_ALL_FILE = /^\[\.\.\.(.+?)\]\.(tsx|jsx)$/;
|
|
|
14
14
|
const DYNAMIC_SEGMENT = /^\[(.+?)\]\.(tsx|jsx)$/;
|
|
15
15
|
export function scanServerRoutes(appRoot) {
|
|
16
16
|
const routesDir = path.resolve(appRoot, "./src/routes");
|
|
17
|
-
const files = glob.sync("**/_route.{ts,tsx}", { cwd: routesDir, posix: true });
|
|
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
|
|
20
|
+
const normalizedDir = dir.replace(/\[\.\.\.(.+?)\]/g, "*").replace(/\[(.+?)\]/g, ":$1");
|
|
21
|
+
const urlPattern = normalizedDir === "." ? "/" : `/${normalizedDir}`;
|
|
21
22
|
return {
|
|
22
23
|
urlPattern,
|
|
23
24
|
filePath: path.resolve(routesDir, file),
|
|
@@ -44,7 +45,7 @@ export async function scanRoutes(appRoot) {
|
|
|
44
45
|
let layoutGuards = [];
|
|
45
46
|
try {
|
|
46
47
|
const layoutModule = (await jiti.import(resolveConfigPath(absolutePath)));
|
|
47
|
-
if (layoutModule
|
|
48
|
+
if (layoutModule.config?.guards) {
|
|
48
49
|
layoutGuards = layoutModule.config.guards;
|
|
49
50
|
}
|
|
50
51
|
}
|
|
@@ -70,7 +71,7 @@ export async function scanRoutes(appRoot) {
|
|
|
70
71
|
let pageGuards = [];
|
|
71
72
|
try {
|
|
72
73
|
const pageModule = (await jiti.import(resolveConfigPath(absolutePagePath)));
|
|
73
|
-
if (pageModule
|
|
74
|
+
if (pageModule.config?.guards) {
|
|
74
75
|
pageGuards = pageModule.config.guards;
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -145,6 +146,7 @@ function parseFilePath(file) {
|
|
|
145
146
|
function resolveLayoutChain(dir, layoutMap) {
|
|
146
147
|
const layouts = [];
|
|
147
148
|
let current = dir;
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
148
150
|
while (true) {
|
|
149
151
|
const layoutEntry = layoutMap.get(current);
|
|
150
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,15 +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";
|
|
32
|
+
loadEnvFiles(appRoot, process.env.NODE_ENV || "development");
|
|
33
|
+
|
|
27
34
|
const coreRuntimeDir = path.dirname(require.resolve("@anaemia/core/package.json"));
|
|
28
35
|
const runtimeDir = path.resolve(coreRuntimeDir, "./dist/runtime");
|
|
29
36
|
|
|
@@ -36,19 +43,57 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
36
43
|
fs.mkdirSync(frameworkInternalDir, { recursive: true });
|
|
37
44
|
}
|
|
38
45
|
|
|
46
|
+
// bootstrap config entries and generate necessary files for the router based on the scanned routes
|
|
39
47
|
const entryFile = generateRouterEntry(appRoot, routes);
|
|
40
48
|
const serverRoutesFile = generateServerRoutes(appRoot, serverRoutes);
|
|
49
|
+
|
|
41
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
|
|
42
54
|
const extraClientBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.client ?? []) ?? [];
|
|
43
55
|
const extraServerBabelPlugins = config.plugins?.flatMap((p) => p.babelPlugins?.server ?? []) ?? [];
|
|
44
56
|
const solidRefreshPlugin = [require.resolve("solid-refresh/babel"), { bundler: "rspack-esm", jsx: false }];
|
|
45
57
|
|
|
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
|
+
*/
|
|
66
|
+
const serverEnv: Record<string, string> = {
|
|
67
|
+
MODE: JSON.stringify(process.env.NODE_ENV || "development"),
|
|
68
|
+
DEV: JSON.stringify(isDev),
|
|
69
|
+
PROD: JSON.stringify(!isDev),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
for (const key in process.env) {
|
|
73
|
+
serverEnv[key] = JSON.stringify(process.env[key]);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const clientEnv: Record<string, string> = {
|
|
77
|
+
MODE: JSON.stringify(process.env.NODE_ENV || "development"),
|
|
78
|
+
DEV: JSON.stringify(isDev),
|
|
79
|
+
PROD: JSON.stringify(!isDev),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
for (const key in process.env) {
|
|
83
|
+
if (key.startsWith("PUBLIC_")) {
|
|
84
|
+
clientEnv[key] = JSON.stringify(process.env[key]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// shared resolve config
|
|
89
|
+
// we set up some shared resolve config between client and server here, like extension aliases and path aliases
|
|
46
90
|
const sharedResolve = {
|
|
47
91
|
extensions: [".tsx", ".ts", ".jsx", ".js", ".json", ".scss", ".css"],
|
|
48
92
|
extensionAlias: { ".js": [".ts", ".js"], ".jsx": [".tsx", ".jsx"] },
|
|
49
93
|
alias: { "anaemia-user-app": entryFile, ...getAliases(appRoot) },
|
|
50
94
|
};
|
|
51
95
|
|
|
96
|
+
// client and server configurations
|
|
52
97
|
let clientConfig: Configuration = {
|
|
53
98
|
name: "client",
|
|
54
99
|
context: appRoot,
|
|
@@ -73,9 +118,19 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
73
118
|
alias: {
|
|
74
119
|
...sharedResolve.alias,
|
|
75
120
|
"solid-refresh": require.resolve("solid-refresh"),
|
|
76
|
-
[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,
|
|
77
133
|
},
|
|
78
|
-
fallback: { async_hooks: false, "node:async_hooks": false, fs: false, "node:fs": false, path: false, "node:path": false },
|
|
79
134
|
},
|
|
80
135
|
devServer: isDev
|
|
81
136
|
? {
|
|
@@ -88,10 +143,19 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
88
143
|
}
|
|
89
144
|
: undefined,
|
|
90
145
|
plugins: [
|
|
91
|
-
new rspack.HtmlRspackPlugin({
|
|
146
|
+
new rspack.HtmlRspackPlugin({
|
|
147
|
+
template: path.resolve(appRoot, "./index.html"),
|
|
148
|
+
filename: "index.html",
|
|
149
|
+
inject: false,
|
|
150
|
+
}),
|
|
92
151
|
new rspack.DefinePlugin({
|
|
93
|
-
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
152
|
+
__ANAEMIA_RUNTIME_CONFIG__: JSON.stringify({
|
|
153
|
+
port: config.port,
|
|
154
|
+
assets: config.assets,
|
|
155
|
+
styles: config.styles,
|
|
156
|
+
}),
|
|
94
157
|
...config.define?.client,
|
|
158
|
+
"import.meta.env": clientEnv,
|
|
95
159
|
}),
|
|
96
160
|
new rspack.NormalModuleReplacementPlugin(/^node:/, (resource) => {
|
|
97
161
|
resource.request = resource.request.replace(/^node:/, "");
|
|
@@ -103,7 +167,7 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
103
167
|
if (fs.existsSync(srcPath)) return srcPath;
|
|
104
168
|
|
|
105
169
|
return path.resolve(__dirname, "../src/runtime/empty-module.cjs");
|
|
106
|
-
})()
|
|
170
|
+
})(),
|
|
107
171
|
),
|
|
108
172
|
new AnaemiaManifestHydrationPlugin({ appRoot }),
|
|
109
173
|
],
|
|
@@ -111,8 +175,13 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
111
175
|
parser: { "css/auto": { namedExports: false } },
|
|
112
176
|
rules: [
|
|
113
177
|
styleRules.client,
|
|
178
|
+
...assetRules.client,
|
|
114
179
|
{
|
|
115
|
-
...createBabelRule({
|
|
180
|
+
...createBabelRule({
|
|
181
|
+
isServer: false,
|
|
182
|
+
isDev,
|
|
183
|
+
plugins: [clientServerFnTransform, ...(isDev ? [solidRefreshPlugin] : []), ...extraClientBabelPlugins],
|
|
184
|
+
}),
|
|
116
185
|
exclude: (modulePath: string) => {
|
|
117
186
|
if (modulePath.includes("@anaemia") && modulePath.includes("core")) return false;
|
|
118
187
|
if (modulePath.includes("@solidjs") && modulePath.includes("router")) return false;
|
|
@@ -129,7 +198,13 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
129
198
|
context: appRoot,
|
|
130
199
|
target: "node",
|
|
131
200
|
entry: { server: path.resolve(runtimeDir, "entry-server.jsx") },
|
|
132
|
-
output: {
|
|
201
|
+
output: {
|
|
202
|
+
path: path.resolve(appRoot, "./dist/server"),
|
|
203
|
+
filename: "index.js",
|
|
204
|
+
module: true,
|
|
205
|
+
chunkFormat: "module",
|
|
206
|
+
chunkLoading: "import",
|
|
207
|
+
},
|
|
133
208
|
optimization: { nodeEnv: false },
|
|
134
209
|
resolve: {
|
|
135
210
|
...sharedResolve,
|
|
@@ -143,13 +218,18 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
143
218
|
__anaemia_server_routes__: serverRoutesFile,
|
|
144
219
|
},
|
|
145
220
|
},
|
|
146
|
-
plugins: [new rspack.DefinePlugin({ ...config.define?.server })],
|
|
221
|
+
plugins: [new rspack.DefinePlugin({ ...config.define?.server, "import.meta.env": serverEnv })],
|
|
147
222
|
module: {
|
|
148
223
|
parser: { "css/auto": { namedExports: false } },
|
|
149
224
|
rules: [
|
|
150
225
|
styleRules.server,
|
|
226
|
+
...assetRules.server,
|
|
151
227
|
{
|
|
152
|
-
...createBabelRule({
|
|
228
|
+
...createBabelRule({
|
|
229
|
+
isServer: true,
|
|
230
|
+
isDev,
|
|
231
|
+
plugins: [serverHashInjector, ...extraServerBabelPlugins],
|
|
232
|
+
}),
|
|
153
233
|
exclude: (modulePath: string) => {
|
|
154
234
|
if (modulePath.includes("@anaemia") && modulePath.includes("core")) return false;
|
|
155
235
|
if (modulePath.includes("@solidjs") && modulePath.includes("router")) return false;
|
|
@@ -160,6 +240,7 @@ export async function getRspackConfig(appRoot: string, config: AnaemiaConfig = {
|
|
|
160
240
|
},
|
|
161
241
|
};
|
|
162
242
|
|
|
243
|
+
// allow plugins to modify the client and server configurations before they are returned
|
|
163
244
|
for (const plugin of config.plugins ?? []) {
|
|
164
245
|
if (plugin.clientRspackConfig) clientConfig = plugin.clientRspackConfig(clientConfig);
|
|
165
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";
|
|
@@ -50,11 +50,15 @@ const DYNAMIC_SEGMENT = /^\[(.+?)\]\.(tsx|jsx)$/;
|
|
|
50
50
|
|
|
51
51
|
export function scanServerRoutes(appRoot: string): ServerRouteEntry[] {
|
|
52
52
|
const routesDir = path.resolve(appRoot, "./src/routes");
|
|
53
|
-
const files = glob.sync("**/_route.{ts,tsx}", { cwd: routesDir, posix: true });
|
|
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
|
-
|
|
57
|
+
|
|
58
|
+
const normalizedDir = dir.replace(/\[\.\.\.(.+?)\]/g, "*").replace(/\[(.+?)\]/g, ":$1");
|
|
59
|
+
|
|
60
|
+
const urlPattern = normalizedDir === "." ? "/" : `/${normalizedDir}`;
|
|
61
|
+
|
|
58
62
|
return {
|
|
59
63
|
urlPattern,
|
|
60
64
|
filePath: path.resolve(routesDir, file),
|
|
@@ -86,7 +90,7 @@ export async function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]>
|
|
|
86
90
|
|
|
87
91
|
try {
|
|
88
92
|
const layoutModule = (await jiti.import(resolveConfigPath(absolutePath))) as RouteModule;
|
|
89
|
-
if (layoutModule
|
|
93
|
+
if (layoutModule.config?.guards) {
|
|
90
94
|
layoutGuards = layoutModule.config.guards;
|
|
91
95
|
}
|
|
92
96
|
} catch {
|
|
@@ -116,7 +120,7 @@ export async function scanRoutes(appRoot: string): Promise<RouteManifestEntry[]>
|
|
|
116
120
|
|
|
117
121
|
try {
|
|
118
122
|
const pageModule = (await jiti.import(resolveConfigPath(absolutePagePath))) as RouteModule;
|
|
119
|
-
if (pageModule
|
|
123
|
+
if (pageModule.config?.guards) {
|
|
120
124
|
pageGuards = pageModule.config.guards;
|
|
121
125
|
}
|
|
122
126
|
} catch {
|
|
@@ -201,6 +205,7 @@ function resolveLayoutChain(dir: string, layoutMap: Map<string, LayoutManifestEn
|
|
|
201
205
|
const layouts: LayoutManifestEntry[] = [];
|
|
202
206
|
let current = dir;
|
|
203
207
|
|
|
208
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
204
209
|
while (true) {
|
|
205
210
|
const layoutEntry = layoutMap.get(current);
|
|
206
211
|
if (layoutEntry) layouts.unshift(layoutEntry);
|
|
@@ -209,4 +214,4 @@ function resolveLayoutChain(dir: string, layoutMap: Map<string, LayoutManifestEn
|
|
|
209
214
|
}
|
|
210
215
|
|
|
211
216
|
return layouts;
|
|
212
|
-
}
|
|
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