hadars 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-TV37IMRB.js → chunk-2TMQUXFL.js} +10 -10
- package/dist/{chunk-2J2L2H3H.js → chunk-NYLXE7T7.js} +6 -6
- package/dist/{chunk-OS3V4CPN.js → chunk-OZUZS2PD.js} +4 -4
- package/dist/cli.js +462 -496
- package/dist/cloudflare.cjs +11 -11
- package/dist/cloudflare.js +3 -3
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/lambda.cjs +11 -11
- package/dist/lambda.js +7 -7
- package/dist/loader.cjs +90 -54
- package/dist/slim-react/index.cjs +13 -13
- package/dist/slim-react/index.js +2 -2
- package/dist/slim-react/jsx-runtime.cjs +2 -4
- package/dist/slim-react/jsx-runtime.js +1 -1
- package/dist/ssr-render-worker.js +174 -161
- package/dist/ssr-watch.js +40 -74
- package/package.json +8 -10
- package/cli-lib.ts +0 -676
- package/cli.ts +0 -36
- package/index.ts +0 -17
- package/src/build.ts +0 -805
- package/src/cloudflare.ts +0 -140
- package/src/index.tsx +0 -41
- package/src/lambda.ts +0 -287
- package/src/slim-react/context.ts +0 -55
- package/src/slim-react/dispatcher.ts +0 -87
- package/src/slim-react/hooks.ts +0 -137
- package/src/slim-react/index.ts +0 -232
- package/src/slim-react/jsx-runtime.ts +0 -7
- package/src/slim-react/jsx.ts +0 -53
- package/src/slim-react/render.ts +0 -1101
- package/src/slim-react/renderContext.ts +0 -294
- package/src/slim-react/types.ts +0 -33
- package/src/source/context.ts +0 -113
- package/src/source/graphiql.ts +0 -101
- package/src/source/inference.ts +0 -260
- package/src/source/runner.ts +0 -138
- package/src/source/store.ts +0 -50
- package/src/ssr-render-worker.ts +0 -116
- package/src/ssr-watch.ts +0 -62
- package/src/static.ts +0 -109
- package/src/types/global.d.ts +0 -5
- package/src/types/hadars.ts +0 -350
- package/src/utils/Head.tsx +0 -462
- package/src/utils/clientScript.tsx +0 -71
- package/src/utils/cookies.ts +0 -16
- package/src/utils/loader.ts +0 -335
- package/src/utils/proxyHandler.tsx +0 -104
- package/src/utils/request.tsx +0 -9
- package/src/utils/response.tsx +0 -141
- package/src/utils/rspack.ts +0 -467
- package/src/utils/runtime.ts +0 -19
- package/src/utils/serve.ts +0 -155
- package/src/utils/ssrHandler.ts +0 -239
- package/src/utils/staticFile.ts +0 -43
- package/src/utils/template.html +0 -11
- package/src/utils/upgradeRequest.tsx +0 -19
package/src/utils/rspack.ts
DELETED
|
@@ -1,467 +0,0 @@
|
|
|
1
|
-
import rspack from "@rspack/core";
|
|
2
|
-
import type { Configuration, RuleSetLoaderWithOptions, RuleSetRule } from "@rspack/core";
|
|
3
|
-
import ReactRefreshPlugin from '@rspack/plugin-react-refresh';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import type { SwcPluginList } from '../types/hadars';
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
import pathMod from "node:path";
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
|
-
|
|
10
|
-
const __dirname = process.cwd();
|
|
11
|
-
const packageDir = pathMod.dirname(fileURLToPath(import.meta.url));
|
|
12
|
-
const clientScriptPath = pathMod.resolve(packageDir, 'template.html');
|
|
13
|
-
|
|
14
|
-
// When running from compiled dist/cli.js the loader is pre-built as loader.cjs.
|
|
15
|
-
// The .cjs extension forces CommonJS regardless of the package "type": "module".
|
|
16
|
-
// When running from source (bun/tsx) it falls back to loader.ts.
|
|
17
|
-
const loaderPath = existsSync(pathMod.resolve(packageDir, 'loader.cjs'))
|
|
18
|
-
? pathMod.resolve(packageDir, 'loader.cjs')
|
|
19
|
-
: pathMod.resolve(packageDir, 'loader.ts');
|
|
20
|
-
|
|
21
|
-
const getConfigBase = (mode: "development" | "production", isServerBuild = false): Omit<Configuration, "entry" | "output" | "plugins"> => {
|
|
22
|
-
const isDev = mode === 'development';
|
|
23
|
-
return {
|
|
24
|
-
experiments: {
|
|
25
|
-
css: true,
|
|
26
|
-
outputModule: true,
|
|
27
|
-
},
|
|
28
|
-
resolve: {
|
|
29
|
-
modules: [
|
|
30
|
-
path.resolve(__dirname, 'node_modules'),
|
|
31
|
-
// 'node_modules' (relative) enables the standard upward-traversal
|
|
32
|
-
// resolution so rspack can find transitive deps (e.g. webpack-dev-server)
|
|
33
|
-
// that live in a parent node_modules when running from a sub-project.
|
|
34
|
-
'node_modules',
|
|
35
|
-
],
|
|
36
|
-
tsConfig: path.resolve(__dirname, 'tsconfig.json'),
|
|
37
|
-
extensions: ['.tsx', '.ts', '.js', '.jsx'],
|
|
38
|
-
},
|
|
39
|
-
module: {
|
|
40
|
-
rules: [
|
|
41
|
-
{
|
|
42
|
-
test: /\.css$/,
|
|
43
|
-
use: [{ loader: "builtin:lightningcss-loader" }],
|
|
44
|
-
type: "css",
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
test: /\.svg$/i,
|
|
48
|
-
issuer: /\.[jt]sx?$/,
|
|
49
|
-
use: ['@svgr/webpack'],
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
test: /\.m?jsx?$/,
|
|
53
|
-
resolve: {
|
|
54
|
-
fullySpecified: false,
|
|
55
|
-
},
|
|
56
|
-
exclude: [loaderPath],
|
|
57
|
-
use: [
|
|
58
|
-
// Transforms loadModule('./path') based on build target.
|
|
59
|
-
// Runs before swc-loader (loaders execute right-to-left).
|
|
60
|
-
{
|
|
61
|
-
loader: loaderPath,
|
|
62
|
-
options: { server: isServerBuild },
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
loader: 'builtin:swc-loader',
|
|
66
|
-
options: {
|
|
67
|
-
jsc: {
|
|
68
|
-
parser: {
|
|
69
|
-
syntax: 'ecmascript',
|
|
70
|
-
jsx: true,
|
|
71
|
-
},
|
|
72
|
-
transform: {
|
|
73
|
-
react: {
|
|
74
|
-
runtime: "automatic",
|
|
75
|
-
development: isDev,
|
|
76
|
-
refresh: isDev && !isServerBuild,
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
],
|
|
83
|
-
type: 'javascript/auto',
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
test: /\.tsx?$/,
|
|
87
|
-
resolve: {
|
|
88
|
-
fullySpecified: false,
|
|
89
|
-
},
|
|
90
|
-
exclude: [loaderPath],
|
|
91
|
-
use: [
|
|
92
|
-
{
|
|
93
|
-
loader: loaderPath,
|
|
94
|
-
options: { server: isServerBuild },
|
|
95
|
-
},
|
|
96
|
-
{
|
|
97
|
-
loader: 'builtin:swc-loader',
|
|
98
|
-
options: {
|
|
99
|
-
jsc: {
|
|
100
|
-
parser: {
|
|
101
|
-
syntax: 'typescript',
|
|
102
|
-
tsx: true,
|
|
103
|
-
},
|
|
104
|
-
transform: {
|
|
105
|
-
react: {
|
|
106
|
-
runtime: "automatic",
|
|
107
|
-
development: isDev,
|
|
108
|
-
refresh: isDev && !isServerBuild,
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
type: 'javascript/auto',
|
|
116
|
-
},
|
|
117
|
-
],
|
|
118
|
-
},
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
type EntryOutput = Configuration["output"];
|
|
123
|
-
|
|
124
|
-
interface EntryOptions {
|
|
125
|
-
target: Configuration["target"],
|
|
126
|
-
output: EntryOutput,
|
|
127
|
-
mode: "development" | "production",
|
|
128
|
-
// optional swc plugins to pass to swc-loader
|
|
129
|
-
swcPlugins?: SwcPluginList,
|
|
130
|
-
// optional path to a custom HTML template (resolved relative to cwd)
|
|
131
|
-
htmlTemplate?: string,
|
|
132
|
-
// optional compile-time defines (e.g. { 'process.env.NODE_ENV': '"development"' })
|
|
133
|
-
define?: Record<string, string>;
|
|
134
|
-
base?: string;
|
|
135
|
-
// optional rspack optimization overrides (production client builds only)
|
|
136
|
-
optimization?: Record<string, unknown>;
|
|
137
|
-
// additional module rules appended after the built-in rules
|
|
138
|
-
moduleRules?: Record<string, any>[];
|
|
139
|
-
// additional rspack/webpack-compatible plugins (applied after built-in plugins)
|
|
140
|
-
plugins?: Array<{ apply(compiler: any): void }>;
|
|
141
|
-
// PostCSS plugins to pass to postcss-loader (replaces the default builtin:lightningcss-loader).
|
|
142
|
-
// Use this when you need PostCSS transforms such as Tailwind CSS v4 (@tailwindcss/postcss).
|
|
143
|
-
postcssPlugins?: any[];
|
|
144
|
-
// force React runtime mode independently of build mode (client only)
|
|
145
|
-
reactMode?: 'development' | 'production';
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const buildCompilerConfig = (
|
|
149
|
-
entry: string,
|
|
150
|
-
opts: EntryOptions,
|
|
151
|
-
includeHotPlugin: boolean,
|
|
152
|
-
): Configuration => {
|
|
153
|
-
const { base } = opts;
|
|
154
|
-
const isDev = opts.mode === 'development';
|
|
155
|
-
const isServerBuild = Boolean(
|
|
156
|
-
(opts.output && typeof opts.output === 'object' && (opts.output.library || String(opts.output.filename || '').includes('ssr')))
|
|
157
|
-
);
|
|
158
|
-
const Config = getConfigBase(opts.mode, isServerBuild);
|
|
159
|
-
|
|
160
|
-
// shallow-clone base config to avoid mutating shared Config while preserving RegExp and plugin instances
|
|
161
|
-
const localConfig: any = {
|
|
162
|
-
...Config,
|
|
163
|
-
module: {
|
|
164
|
-
...Config.module,
|
|
165
|
-
rules: (Config.module && Array.isArray(Config.module.rules) ? Config.module.rules : []).map((r: any) => {
|
|
166
|
-
// shallow copy each rule and its 'use' array/entries so we can mutate safely
|
|
167
|
-
const nr: any = { ...r };
|
|
168
|
-
if (r && Array.isArray(r.use)) {
|
|
169
|
-
nr.use = r.use.map((u: any) => ({ ...(typeof u === 'object' ? u : { loader: u }) }));
|
|
170
|
-
}
|
|
171
|
-
return nr;
|
|
172
|
-
}),
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
// if swc plugins are provided, inject them into swc-loader options for js/jsx and ts/tsx rules
|
|
177
|
-
if (opts.swcPlugins && Array.isArray(opts.swcPlugins) && opts.swcPlugins.length > 0) {
|
|
178
|
-
const rules = localConfig.module && localConfig.module.rules;
|
|
179
|
-
if (Array.isArray(rules)) {
|
|
180
|
-
for (const rule of rules) {
|
|
181
|
-
const ruleUse = rule as RuleSetRule;
|
|
182
|
-
if (ruleUse.use && Array.isArray(ruleUse.use)) {
|
|
183
|
-
for (const entry of ruleUse.use ) {
|
|
184
|
-
const useEntry = entry as RuleSetLoaderWithOptions;
|
|
185
|
-
if (useEntry && useEntry.loader && typeof useEntry.loader === 'string' && useEntry.loader.includes('swc-loader')) {
|
|
186
|
-
const options = ( useEntry.options || {} ) as Record<string, any>;
|
|
187
|
-
useEntry.options = options;
|
|
188
|
-
useEntry.options.jsc = useEntry.options.jsc || {};
|
|
189
|
-
useEntry.options.jsc.experimental = useEntry.options.jsc.experimental || {};
|
|
190
|
-
// ensure plugins run before other transforms (important for Relay plugin)
|
|
191
|
-
useEntry.options.jsc.experimental.runPluginFirst = true;
|
|
192
|
-
// existing plugins may be present under jsc.experimental.plugins; merge them with provided ones
|
|
193
|
-
const existingPlugins = Array.isArray(useEntry.options.jsc.experimental.plugins) ? useEntry.options.jsc.experimental.plugins : [];
|
|
194
|
-
const incomingPlugins = Array.isArray(opts.swcPlugins) ? opts.swcPlugins : [];
|
|
195
|
-
// simple dedupe by plugin name (first element of tuple) to avoid duplicates
|
|
196
|
-
const seen = new Set<string>();
|
|
197
|
-
const merged: any[] = [];
|
|
198
|
-
for (const p of existingPlugins.concat(incomingPlugins)) {
|
|
199
|
-
// plugin can be [name, options] or string; normalize
|
|
200
|
-
const name = Array.isArray(p) && p.length > 0 ? String(p[0]) : String(p);
|
|
201
|
-
if (!seen.has(name)) {
|
|
202
|
-
seen.add(name);
|
|
203
|
-
merged.push(p);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
useEntry.options.jsc.experimental.plugins = merged;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// If postcssPlugins are provided, swap the default lightningcss-loader CSS rule
|
|
215
|
-
// for postcss-loader with the given plugins.
|
|
216
|
-
if (opts.postcssPlugins && opts.postcssPlugins.length > 0) {
|
|
217
|
-
const rules: any[] = localConfig.module?.rules ?? [];
|
|
218
|
-
for (const rule of rules) {
|
|
219
|
-
if (rule?.test instanceof RegExp && rule.test.source === '\\.css$') {
|
|
220
|
-
rule.use = [{
|
|
221
|
-
loader: 'postcss-loader',
|
|
222
|
-
options: { postcssOptions: { plugins: opts.postcssPlugins } },
|
|
223
|
-
}];
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (opts.moduleRules && opts.moduleRules.length > 0) {
|
|
230
|
-
localConfig.module.rules.push(...opts.moduleRules);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// slim-react: the SSR-only React-compatible renderer bundled with hadars.
|
|
234
|
-
// On server builds we replace the real React with slim-react so that hooks
|
|
235
|
-
// get safe SSR stubs, context works, and renderToStream / Suspense are
|
|
236
|
-
// natively supported. The client build is untouched and uses real React.
|
|
237
|
-
const slimReactIndex = pathMod.resolve(packageDir, 'slim-react', 'index.js');
|
|
238
|
-
const slimReactJsx = pathMod.resolve(packageDir, 'slim-react', 'jsx-runtime.js');
|
|
239
|
-
|
|
240
|
-
const resolveAliases: Record<string, string> | undefined = isServerBuild ? {
|
|
241
|
-
// Route all React imports to slim-react for SSR.
|
|
242
|
-
react: slimReactIndex,
|
|
243
|
-
'react/jsx-runtime': slimReactJsx,
|
|
244
|
-
'react/jsx-dev-runtime': slimReactJsx,
|
|
245
|
-
// @emotion/* is bundled (not external) so that its `react` imports are
|
|
246
|
-
// resolved through the alias above to slim-react. If left external,
|
|
247
|
-
// emotion loads real React from node_modules and calls
|
|
248
|
-
// ReactSharedInternals.H.useContext which requires React's dispatcher.
|
|
249
|
-
} : undefined;
|
|
250
|
-
|
|
251
|
-
const externals = isServerBuild ? [
|
|
252
|
-
// Node.js built-ins — must not be bundled; resolved by the runtime.
|
|
253
|
-
'node:fs', 'node:path', 'node:os', 'node:stream', 'node:util',
|
|
254
|
-
// @emotion/server is only used outside component rendering (CSS extraction)
|
|
255
|
-
// and does not call React hooks, so it is safe to leave as external.
|
|
256
|
-
'@emotion/server',
|
|
257
|
-
] : undefined;
|
|
258
|
-
|
|
259
|
-
// reactMode lets the caller force React's dev/prod runtime independently of
|
|
260
|
-
// the webpack build mode. Only applies to the client bundle (SSR uses slim-react).
|
|
261
|
-
// 'development' → process.env.NODE_ENV = "development" + JSX dev transform.
|
|
262
|
-
const effectiveReactDev = isServerBuild
|
|
263
|
-
? false // slim-react doesn't use NODE_ENV
|
|
264
|
-
: opts.reactMode === 'development' ? true
|
|
265
|
-
: opts.reactMode === 'production' ? false
|
|
266
|
-
: isDev; // default: follow build mode
|
|
267
|
-
|
|
268
|
-
if (!isServerBuild && opts.reactMode !== undefined) {
|
|
269
|
-
// Override the SWC JSX development flag for all js/ts rules already built
|
|
270
|
-
const rules = localConfig.module?.rules ?? [];
|
|
271
|
-
for (const rule of rules) {
|
|
272
|
-
if (!rule?.use || !Array.isArray(rule.use)) continue;
|
|
273
|
-
for (const entry of rule.use) {
|
|
274
|
-
if (entry?.loader?.includes('swc-loader')) {
|
|
275
|
-
entry.options = entry.options ?? {};
|
|
276
|
-
entry.options.jsc = entry.options.jsc ?? {};
|
|
277
|
-
entry.options.jsc.transform = entry.options.jsc.transform ?? {};
|
|
278
|
-
entry.options.jsc.transform.react = entry.options.jsc.transform.react ?? {};
|
|
279
|
-
entry.options.jsc.transform.react.development = effectiveReactDev;
|
|
280
|
-
entry.options.jsc.transform.react.refresh = effectiveReactDev && isDev;
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
const extraPlugins: any[] = [];
|
|
287
|
-
|
|
288
|
-
const defineValues: Record<string, string> = { ...(opts.define ?? {}) };
|
|
289
|
-
// When reactMode overrides the React runtime we must also set process.env.NODE_ENV
|
|
290
|
-
// so React picks its dev/prod bundle, independently of the rspack build mode.
|
|
291
|
-
if (!isServerBuild && opts.reactMode !== undefined) {
|
|
292
|
-
defineValues['process.env.NODE_ENV'] = JSON.stringify(opts.reactMode);
|
|
293
|
-
}
|
|
294
|
-
if (Object.keys(defineValues).length > 0) {
|
|
295
|
-
const DefinePlugin = (rspack as any).DefinePlugin || (rspack as any).plugins?.DefinePlugin;
|
|
296
|
-
if (DefinePlugin) {
|
|
297
|
-
extraPlugins.push(new DefinePlugin(defineValues));
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
const resolveConfig: any = {
|
|
302
|
-
extensions: ['.tsx', '.ts', '.js', '.jsx'],
|
|
303
|
-
alias: resolveAliases,
|
|
304
|
-
// for server builds prefer the package "main"/"module" fields and avoid "browser" so we don't pick browser-specific entrypoints
|
|
305
|
-
mainFields: isServerBuild ? ['main', 'module'] : ['browser', 'module', 'main'],
|
|
306
|
-
// for server builds exclude the "browser" condition so packages with package.json
|
|
307
|
-
// "exports" conditions (e.g. @emotion/*) resolve their Node/CJS entry, not the browser build
|
|
308
|
-
...(isServerBuild ? { conditionNames: ['node', 'require', 'default'] } : {}),
|
|
309
|
-
};
|
|
310
|
-
|
|
311
|
-
// Production client builds get vendor splitting and deterministic module IDs.
|
|
312
|
-
// User-supplied optimization is merged on top so it can extend or override defaults.
|
|
313
|
-
// Dev and SSR builds skip this — splitChunks slows HMR, SSR uses externals instead.
|
|
314
|
-
const optimization: any = (!isServerBuild && !isDev) ? {
|
|
315
|
-
moduleIds: 'deterministic',
|
|
316
|
-
splitChunks: {
|
|
317
|
-
chunks: 'all',
|
|
318
|
-
cacheGroups: {
|
|
319
|
-
react: {
|
|
320
|
-
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
|
|
321
|
-
name: 'vendor-react',
|
|
322
|
-
chunks: 'all' as const,
|
|
323
|
-
priority: 20,
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
},
|
|
327
|
-
...(opts.optimization ?? {}),
|
|
328
|
-
} : (opts.optimization ? { ...opts.optimization } : undefined);
|
|
329
|
-
|
|
330
|
-
return {
|
|
331
|
-
entry,
|
|
332
|
-
output: {
|
|
333
|
-
...opts.output,
|
|
334
|
-
clean: false,
|
|
335
|
-
},
|
|
336
|
-
mode: opts.mode,
|
|
337
|
-
// Persist transformed modules to disk — subsequent starts only recompile
|
|
338
|
-
// changed files, making repeat dev starts significantly faster.
|
|
339
|
-
cache: true,
|
|
340
|
-
externals,
|
|
341
|
-
// externalsPresets.node externalises ALL Node.js built-ins (bare names
|
|
342
|
-
// and the node: prefix) for both static and dynamic imports. This
|
|
343
|
-
// complements the explicit `externals` array: the preset handles the
|
|
344
|
-
// node: URI scheme that rspack cannot resolve as a file, while the
|
|
345
|
-
// array keeps '@emotion/server' as an explicit external.
|
|
346
|
-
...(isServerBuild ? { externalsPresets: { node: true } } : {}),
|
|
347
|
-
...(optimization !== undefined ? { optimization } : {}),
|
|
348
|
-
plugins: [
|
|
349
|
-
!isServerBuild && new rspack.HtmlRspackPlugin({
|
|
350
|
-
publicPath: base || '/',
|
|
351
|
-
template: opts.htmlTemplate
|
|
352
|
-
? pathMod.resolve(process.cwd(), opts.htmlTemplate)
|
|
353
|
-
: clientScriptPath,
|
|
354
|
-
scriptLoading: 'module',
|
|
355
|
-
filename: 'out.html',
|
|
356
|
-
inject: 'head',
|
|
357
|
-
minify: opts.mode === 'production',
|
|
358
|
-
}),
|
|
359
|
-
!isServerBuild && {
|
|
360
|
-
apply(compiler: any) {
|
|
361
|
-
compiler.hooks.emit.tapAsync('HadarsAsyncModuleScript', (compilation: any, cb: () => void) => {
|
|
362
|
-
const asset = compilation.assets['out.html'];
|
|
363
|
-
if (asset) {
|
|
364
|
-
const html: string = asset.source();
|
|
365
|
-
const updated = html.replace(
|
|
366
|
-
/(<script\b[^>]*\btype="module"[^>]*)(>)/g,
|
|
367
|
-
(match, before: string, end: string) =>
|
|
368
|
-
before.includes('async') ? match : `${before} async${end}`,
|
|
369
|
-
);
|
|
370
|
-
compilation.assets['out.html'] = {
|
|
371
|
-
source: () => updated,
|
|
372
|
-
size: () => Buffer.byteLength(updated),
|
|
373
|
-
};
|
|
374
|
-
}
|
|
375
|
-
cb();
|
|
376
|
-
});
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
isDev && !isServerBuild && new ReactRefreshPlugin(),
|
|
380
|
-
includeHotPlugin && isDev && !isServerBuild && new rspack.HotModuleReplacementPlugin(),
|
|
381
|
-
...extraPlugins,
|
|
382
|
-
...(opts.plugins ?? []),
|
|
383
|
-
],
|
|
384
|
-
...localConfig,
|
|
385
|
-
// Merge base resolve (modules, tsConfig, extensions) with per-build resolve
|
|
386
|
-
// (alias, mainFields). The spread order matters: resolveConfig wins for keys
|
|
387
|
-
// it defines, localConfig.resolve wins for keys it defines exclusively.
|
|
388
|
-
resolve: {
|
|
389
|
-
...localConfig.resolve,
|
|
390
|
-
...resolveConfig,
|
|
391
|
-
},
|
|
392
|
-
// HMR is not implemented for module chunk format, so disable outputModule
|
|
393
|
-
// for client builds. SSR builds still need it for dynamic import() of exports.
|
|
394
|
-
experiments: {
|
|
395
|
-
...(localConfig.experiments || {}),
|
|
396
|
-
outputModule: isServerBuild,
|
|
397
|
-
},
|
|
398
|
-
// Prevent rspack from watching its own build output — without this the
|
|
399
|
-
// SSR watcher writing .hadars/index.ssr.js triggers the client compiler
|
|
400
|
-
// and vice versa, causing an infinite rebuild loop.
|
|
401
|
-
watchOptions: {
|
|
402
|
-
ignored: ['**/node_modules/**', '**/.hadars/**', '/tmp/**'],
|
|
403
|
-
},
|
|
404
|
-
};
|
|
405
|
-
};
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Creates a configured rspack compiler for the client bundle without running it.
|
|
409
|
-
* Intended for use with RspackDevServer for proper HMR support.
|
|
410
|
-
* HotModuleReplacementPlugin is intentionally omitted — RspackDevServer adds it automatically.
|
|
411
|
-
*/
|
|
412
|
-
export const createClientCompiler = (entry: string, opts: EntryOptions) => {
|
|
413
|
-
return rspack(buildCompilerConfig(entry, opts, false));
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
export const compileEntry = async (entry: string, opts: EntryOptions & { watch?: boolean, onChange?: (stats:any)=>void }) => {
|
|
417
|
-
const compiler = rspack(buildCompilerConfig(entry, opts, true));
|
|
418
|
-
|
|
419
|
-
// If watch mode is requested, start watching and invoke onChange for each rebuild.
|
|
420
|
-
// The returned promise resolves once the first build completes so callers can
|
|
421
|
-
// await initial build completion before starting their own server.
|
|
422
|
-
if (opts.watch) {
|
|
423
|
-
await new Promise((resolve, reject) => {
|
|
424
|
-
let first = true;
|
|
425
|
-
// Pass ignored patterns directly — compiler.watch(watchOptions) replaces
|
|
426
|
-
// the config-level watchOptions, so we must repeat them here.
|
|
427
|
-
compiler.watch({ ignored: ['**/node_modules/**', '**/.hadars/**', '/tmp/**'] }, (err: any, stats: any) => {
|
|
428
|
-
if (err) {
|
|
429
|
-
if (first) { first = false; reject(err); }
|
|
430
|
-
else { console.error('rspack watch error', err); }
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
console.log(stats?.toString({ colors: true }));
|
|
435
|
-
|
|
436
|
-
if (first) {
|
|
437
|
-
first = false;
|
|
438
|
-
resolve(stats);
|
|
439
|
-
} else {
|
|
440
|
-
try {
|
|
441
|
-
opts.onChange && opts.onChange(stats);
|
|
442
|
-
} catch (e) {
|
|
443
|
-
console.error('onChange handler error', e);
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// non-watch: do a single run and resolve when complete
|
|
452
|
-
await new Promise((resolve, reject) => {
|
|
453
|
-
compiler.run((err: any, stats: any) => {
|
|
454
|
-
if (err) {
|
|
455
|
-
reject(err);
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
console.log(stats?.toString({
|
|
460
|
-
colors: true,
|
|
461
|
-
preset: 'minimal',
|
|
462
|
-
}));
|
|
463
|
-
|
|
464
|
-
resolve(stats);
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
}
|
package/src/utils/runtime.ts
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/** True when running inside Bun. */
|
|
2
|
-
export const isBun = typeof (globalThis as any).Bun !== 'undefined';
|
|
3
|
-
|
|
4
|
-
/** True when running inside Deno. */
|
|
5
|
-
export const isDeno = typeof (globalThis as any).Deno !== 'undefined';
|
|
6
|
-
|
|
7
|
-
/** True when running inside Node.js (not Bun, not Deno). */
|
|
8
|
-
export const isNode = !isBun && !isDeno;
|
|
9
|
-
|
|
10
|
-
/** Returns a human-readable runtime identifier. */
|
|
11
|
-
export const getRuntimeName = (): 'bun' | 'deno' | 'node' =>
|
|
12
|
-
isBun ? 'bun' : isDeno ? 'deno' : 'node';
|
|
13
|
-
|
|
14
|
-
/** Returns a version string for the current runtime, e.g. "Bun 1.1.0". */
|
|
15
|
-
export const getRuntimeVersion = (): string => {
|
|
16
|
-
if (isBun) return `Bun ${(globalThis as any).Bun.version}`;
|
|
17
|
-
if (isDeno) return `Deno ${(globalThis as any).Deno.version.deno}`;
|
|
18
|
-
return `Node.js ${process.version}`;
|
|
19
|
-
};
|
package/src/utils/serve.ts
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
import { isBun, isDeno } from './runtime';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Minimal server context passed to every fetch handler invocation.
|
|
5
|
-
* On Bun, `upgrade` performs a real WebSocket upgrade.
|
|
6
|
-
* On all other runtimes it is a no-op that always returns false.
|
|
7
|
-
*/
|
|
8
|
-
export interface ServerContext {
|
|
9
|
-
upgrade(req: Request): boolean;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
type FetchHandler = (
|
|
13
|
-
req: Request,
|
|
14
|
-
ctx: ServerContext,
|
|
15
|
-
) => Promise<Response | undefined> | Response | undefined;
|
|
16
|
-
|
|
17
|
-
/** Converts a Node.js Readable stream to a Web ReadableStream<Uint8Array>. */
|
|
18
|
-
function nodeReadableToWebStream(readable: NodeJS.ReadableStream): ReadableStream<Uint8Array> {
|
|
19
|
-
const enc = new TextEncoder();
|
|
20
|
-
return new ReadableStream<Uint8Array>({
|
|
21
|
-
start(controller) {
|
|
22
|
-
readable.on('data', (chunk: Buffer | string) => {
|
|
23
|
-
controller.enqueue(
|
|
24
|
-
typeof chunk === 'string'
|
|
25
|
-
? enc.encode(chunk)
|
|
26
|
-
: new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength),
|
|
27
|
-
);
|
|
28
|
-
});
|
|
29
|
-
readable.on('end', () => controller.close());
|
|
30
|
-
readable.on('error', (err) => controller.error(err));
|
|
31
|
-
},
|
|
32
|
-
cancel() {
|
|
33
|
-
(readable as any).destroy?.();
|
|
34
|
-
},
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const noopCtx: ServerContext = { upgrade: () => false };
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Starts an HTTP server on the given port using the best available runtime API:
|
|
42
|
-
* - **Bun**: `Bun.serve()` — full WebSocket support via the `websocket` option
|
|
43
|
-
* - **Deno**: `Deno.serve()`
|
|
44
|
-
* - **Node.js**: `node:http` `createServer()` with a Web-Fetch bridge
|
|
45
|
-
*
|
|
46
|
-
* The `fetchHandler` may return `undefined` to signal that the response was
|
|
47
|
-
* handled out-of-band (e.g. a Bun WebSocket upgrade).
|
|
48
|
-
*/
|
|
49
|
-
function withRequestLogging(handler: FetchHandler): FetchHandler {
|
|
50
|
-
return async (req, ctx) => {
|
|
51
|
-
const start = performance.now();
|
|
52
|
-
const res = await handler(req, ctx);
|
|
53
|
-
const ms = Math.round(performance.now() - start);
|
|
54
|
-
const status = res?.status ?? 404;
|
|
55
|
-
const path = new URL(req.url).pathname;
|
|
56
|
-
console.log(`[hadars] ${req.method} ${path} ${status} ${ms}ms`);
|
|
57
|
-
return res;
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
export async function serve(
|
|
63
|
-
port: number,
|
|
64
|
-
fetchHandler: FetchHandler,
|
|
65
|
-
/** Bun WebSocketHandler — ignored on Deno and Node.js. */
|
|
66
|
-
websocket?: unknown,
|
|
67
|
-
): Promise<void> {
|
|
68
|
-
fetchHandler = withRequestLogging(fetchHandler);
|
|
69
|
-
|
|
70
|
-
// ── Bun ────────────────────────────────────────────────────────────────
|
|
71
|
-
if (isBun) {
|
|
72
|
-
(globalThis as any).Bun.serve({
|
|
73
|
-
port,
|
|
74
|
-
websocket,
|
|
75
|
-
async fetch(req: Request, server: any) {
|
|
76
|
-
const ctx: ServerContext = { upgrade: (r) => server.upgrade(r) };
|
|
77
|
-
// Returning undefined from a Bun fetch handler means the
|
|
78
|
-
// request was handled as a WebSocket upgrade.
|
|
79
|
-
return (await fetchHandler(req, ctx)) ?? undefined;
|
|
80
|
-
},
|
|
81
|
-
});
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// ── Deno ───────────────────────────────────────────────────────────────
|
|
86
|
-
// Deno 2.x changed the signature to options-first. Use the single-object
|
|
87
|
-
// form { port, handler } which is stable across Deno 1.x and 2.x.
|
|
88
|
-
if (isDeno) {
|
|
89
|
-
(globalThis as any).Deno.serve({
|
|
90
|
-
port,
|
|
91
|
-
handler: async (req: Request) => {
|
|
92
|
-
const res = await fetchHandler(req, noopCtx);
|
|
93
|
-
return res ?? new Response('Not Found', { status: 404 });
|
|
94
|
-
},
|
|
95
|
-
});
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// ── Node.js ────────────────────────────────────────────────────────────
|
|
100
|
-
const { createServer } = await import('node:http');
|
|
101
|
-
|
|
102
|
-
const server = createServer(async (nodeReq, nodeRes) => {
|
|
103
|
-
try {
|
|
104
|
-
// Collect body for non-GET/HEAD requests
|
|
105
|
-
const chunks: Buffer[] = [];
|
|
106
|
-
if (!['GET', 'HEAD'].includes(nodeReq.method ?? 'GET')) {
|
|
107
|
-
for await (const chunk of nodeReq) {
|
|
108
|
-
chunks.push(chunk as Buffer);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined;
|
|
112
|
-
|
|
113
|
-
const url = `http://localhost:${port}${nodeReq.url ?? '/'}`;
|
|
114
|
-
const reqInit: RequestInit & { duplex?: string } = {
|
|
115
|
-
method: nodeReq.method ?? 'GET',
|
|
116
|
-
headers: new Headers(nodeReq.headers as Record<string, string>),
|
|
117
|
-
};
|
|
118
|
-
if (body) {
|
|
119
|
-
reqInit.body = body;
|
|
120
|
-
reqInit.duplex = 'half';
|
|
121
|
-
}
|
|
122
|
-
const req = new Request(url, reqInit);
|
|
123
|
-
|
|
124
|
-
const res = await fetchHandler(req, noopCtx);
|
|
125
|
-
const response = res ?? new Response('Not Found', { status: 404 });
|
|
126
|
-
|
|
127
|
-
const headers: Record<string, string> = {};
|
|
128
|
-
response.headers.forEach((v, k) => { headers[k] = v; });
|
|
129
|
-
nodeRes.writeHead(response.status, headers);
|
|
130
|
-
|
|
131
|
-
if (response.body) {
|
|
132
|
-
const reader = response.body.getReader();
|
|
133
|
-
while (true) {
|
|
134
|
-
const { done, value } = await reader.read();
|
|
135
|
-
if (done) break;
|
|
136
|
-
await new Promise<void>((resolve, reject) =>
|
|
137
|
-
nodeRes.write(value, (err) => (err ? reject(err) : resolve())),
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
} catch (err) {
|
|
142
|
-
console.error('[hadars] request error', err);
|
|
143
|
-
if (!nodeRes.headersSent) nodeRes.writeHead(500);
|
|
144
|
-
} finally {
|
|
145
|
-
nodeRes.end();
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
await new Promise<void>((resolve, reject) => {
|
|
150
|
-
server.listen(port, () => resolve());
|
|
151
|
-
server.once('error', reject);
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export { nodeReadableToWebStream };
|