hadars 0.1.26 → 0.1.27

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/cli.js CHANGED
@@ -1138,13 +1138,33 @@ var buildCompilerConfig = (entry, opts, includeHotPlugin) => {
1138
1138
  // and does not call React hooks, so it is safe to leave as external.
1139
1139
  "@emotion/server"
1140
1140
  ] : void 0;
1141
+ const effectiveReactDev = isServerBuild ? false : opts.reactMode === "development" ? true : opts.reactMode === "production" ? false : isDev;
1142
+ if (!isServerBuild && opts.reactMode !== void 0) {
1143
+ const rules = localConfig.module?.rules ?? [];
1144
+ for (const rule of rules) {
1145
+ if (!rule?.use || !Array.isArray(rule.use))
1146
+ continue;
1147
+ for (const entry2 of rule.use) {
1148
+ if (entry2?.loader?.includes("swc-loader")) {
1149
+ entry2.options = entry2.options ?? {};
1150
+ entry2.options.jsc = entry2.options.jsc ?? {};
1151
+ entry2.options.jsc.transform = entry2.options.jsc.transform ?? {};
1152
+ entry2.options.jsc.transform.react = entry2.options.jsc.transform.react ?? {};
1153
+ entry2.options.jsc.transform.react.development = effectiveReactDev;
1154
+ entry2.options.jsc.transform.react.refresh = effectiveReactDev && isDev;
1155
+ }
1156
+ }
1157
+ }
1158
+ }
1141
1159
  const extraPlugins = [];
1142
- if (opts.define && typeof opts.define === "object") {
1160
+ const defineValues = { ...opts.define ?? {} };
1161
+ if (!isServerBuild && opts.reactMode !== void 0) {
1162
+ defineValues["process.env.NODE_ENV"] = JSON.stringify(opts.reactMode);
1163
+ }
1164
+ if (Object.keys(defineValues).length > 0) {
1143
1165
  const DefinePlugin = rspack.DefinePlugin || rspack.plugins?.DefinePlugin;
1144
1166
  if (DefinePlugin) {
1145
- extraPlugins.push(new DefinePlugin(opts.define));
1146
- } else {
1147
- extraPlugins.push({ name: "DefinePlugin", value: opts.define });
1167
+ extraPlugins.push(new DefinePlugin(defineValues));
1148
1168
  }
1149
1169
  }
1150
1170
  const resolveConfig = {
@@ -1875,6 +1895,7 @@ var dev = async (options) => {
1875
1895
  swcPlugins: options.swcPlugins,
1876
1896
  define: options.define,
1877
1897
  moduleRules: options.moduleRules,
1898
+ reactMode: options.reactMode,
1878
1899
  htmlTemplate: resolvedHtmlTemplate
1879
1900
  });
1880
1901
  const devServer = new RspackDevServer({
@@ -1912,7 +1933,7 @@ var dev = async (options) => {
1912
1933
  `--base=${baseURL}`,
1913
1934
  ...options.swcPlugins ? [`--swcPlugins=${JSON.stringify(options.swcPlugins)}`] : [],
1914
1935
  ...options.define ? [`--define=${JSON.stringify(options.define)}`] : [],
1915
- ...options.moduleRules ? [`--moduleRules=${JSON.stringify(options.moduleRules)}`] : []
1936
+ ...options.moduleRules ? [`--moduleRules=${JSON.stringify(options.moduleRules, (_k, v) => v instanceof RegExp ? { __re: v.source, __flags: v.flags } : v)}`] : []
1916
1937
  ], { stdio: "pipe" });
1917
1938
  child.stdin?.end();
1918
1939
  const cleanupChild = () => {
@@ -2094,6 +2115,7 @@ var build = async (options) => {
2094
2115
  define: options.define,
2095
2116
  moduleRules: options.moduleRules,
2096
2117
  optimization: options.optimization,
2118
+ reactMode: options.reactMode,
2097
2119
  htmlTemplate: resolvedHtmlTemplate
2098
2120
  }),
2099
2121
  compileEntry(pathMod2.resolve(__dirname2, options.entry), {
package/dist/index.d.ts CHANGED
@@ -111,6 +111,24 @@ interface HadarsOptions {
111
111
  * Note: inline styles are processed once at startup and are not live-reloaded.
112
112
  */
113
113
  htmlTemplate?: string;
114
+ /**
115
+ * Force the React runtime mode independently of the build mode.
116
+ * Useful when you need production build optimizations (minification, tree-shaking)
117
+ * but want React's development build for debugging hydration mismatches or
118
+ * component stack traces.
119
+ *
120
+ * - `'development'` — forces `process.env.NODE_ENV = "development"` and enables
121
+ * JSX source info even in `hadars build`. React prints detailed hydration error
122
+ * messages and component stacks.
123
+ * - `'production'` — the default; React uses the optimised production bundle.
124
+ *
125
+ * Only affects the **client** bundle. The SSR bundle always uses slim-react.
126
+ *
127
+ * @example
128
+ * // hadars.config.ts — debug hydration errors in a production build
129
+ * reactMode: 'development'
130
+ */
131
+ reactMode?: 'development' | 'production';
114
132
  /**
115
133
  * Additional rspack module rules appended to the built-in rule set.
116
134
  * Applied to both the client and the SSR bundle.
package/dist/ssr-watch.js CHANGED
@@ -188,13 +188,33 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
188
188
  // and does not call React hooks, so it is safe to leave as external.
189
189
  "@emotion/server"
190
190
  ] : void 0;
191
+ const effectiveReactDev = isServerBuild ? false : opts.reactMode === "development" ? true : opts.reactMode === "production" ? false : isDev;
192
+ if (!isServerBuild && opts.reactMode !== void 0) {
193
+ const rules = localConfig.module?.rules ?? [];
194
+ for (const rule of rules) {
195
+ if (!rule?.use || !Array.isArray(rule.use))
196
+ continue;
197
+ for (const entry3 of rule.use) {
198
+ if (entry3?.loader?.includes("swc-loader")) {
199
+ entry3.options = entry3.options ?? {};
200
+ entry3.options.jsc = entry3.options.jsc ?? {};
201
+ entry3.options.jsc.transform = entry3.options.jsc.transform ?? {};
202
+ entry3.options.jsc.transform.react = entry3.options.jsc.transform.react ?? {};
203
+ entry3.options.jsc.transform.react.development = effectiveReactDev;
204
+ entry3.options.jsc.transform.react.refresh = effectiveReactDev && isDev;
205
+ }
206
+ }
207
+ }
208
+ }
191
209
  const extraPlugins = [];
192
- if (opts.define && typeof opts.define === "object") {
210
+ const defineValues = { ...opts.define ?? {} };
211
+ if (!isServerBuild && opts.reactMode !== void 0) {
212
+ defineValues["process.env.NODE_ENV"] = JSON.stringify(opts.reactMode);
213
+ }
214
+ if (Object.keys(defineValues).length > 0) {
193
215
  const DefinePlugin = rspack.DefinePlugin || rspack.plugins?.DefinePlugin;
194
216
  if (DefinePlugin) {
195
- extraPlugins.push(new DefinePlugin(opts.define));
196
- } else {
197
- extraPlugins.push({ name: "DefinePlugin", value: opts.define });
217
+ extraPlugins.push(new DefinePlugin(defineValues));
198
218
  }
199
219
  }
200
220
  const resolveConfig = {
@@ -354,7 +374,7 @@ var outFile = argv["outFile"] || "index.ssr.js";
354
374
  var base = argv["base"] || "";
355
375
  var swcPlugins = argv["swcPlugins"] ? JSON.parse(argv["swcPlugins"]) : void 0;
356
376
  var define = argv["define"] ? JSON.parse(argv["define"]) : void 0;
357
- var moduleRules = argv["moduleRules"] ? JSON.parse(argv["moduleRules"]) : void 0;
377
+ var moduleRules = argv["moduleRules"] ? JSON.parse(argv["moduleRules"], (_k, v) => v && typeof v === "object" && "__re" in v ? new RegExp(v.__re, v.__flags) : v) : void 0;
358
378
  if (!entry) {
359
379
  console.error("ssr-watch: missing --entry argument");
360
380
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hadars",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
5
5
  "module": "./dist/index.js",
6
6
  "type": "module",
package/src/build.ts CHANGED
@@ -508,6 +508,7 @@ export const dev = async (options: HadarsRuntimeOptions) => {
508
508
  swcPlugins: options.swcPlugins,
509
509
  define: options.define,
510
510
  moduleRules: options.moduleRules,
511
+ reactMode: options.reactMode,
511
512
  htmlTemplate: resolvedHtmlTemplate,
512
513
  });
513
514
 
@@ -555,7 +556,7 @@ export const dev = async (options: HadarsRuntimeOptions) => {
555
556
  `--base=${baseURL}`,
556
557
  ...(options.swcPlugins ? [`--swcPlugins=${JSON.stringify(options.swcPlugins)}`] : []),
557
558
  ...(options.define ? [`--define=${JSON.stringify(options.define)}`] : []),
558
- ...(options.moduleRules ? [`--moduleRules=${JSON.stringify(options.moduleRules)}`] : []),
559
+ ...(options.moduleRules ? [`--moduleRules=${JSON.stringify(options.moduleRules, (_k, v) => v instanceof RegExp ? { __re: v.source, __flags: v.flags } : v)}`] : []),
559
560
  ], { stdio: 'pipe' });
560
561
  child.stdin?.end();
561
562
 
@@ -751,6 +752,7 @@ export const build = async (options: HadarsRuntimeOptions) => {
751
752
  define: options.define,
752
753
  moduleRules: options.moduleRules,
753
754
  optimization: options.optimization,
755
+ reactMode: options.reactMode,
754
756
  htmlTemplate: resolvedHtmlTemplate,
755
757
  }),
756
758
  compileEntry(pathMod.resolve(__dirname, options.entry), {
package/src/ssr-watch.ts CHANGED
@@ -21,7 +21,7 @@ const outFile = argv['outFile'] || 'index.ssr.js';
21
21
  const base = argv['base'] || '';
22
22
  const swcPlugins = argv['swcPlugins'] ? JSON.parse(argv['swcPlugins']) : undefined;
23
23
  const define = argv['define'] ? JSON.parse(argv['define']) : undefined;
24
- const moduleRules = argv['moduleRules'] ? JSON.parse(argv['moduleRules']) : undefined;
24
+ const moduleRules = argv['moduleRules'] ? JSON.parse(argv['moduleRules'], (_k, v) => (v && typeof v === 'object' && '__re' in v) ? new RegExp(v.__re, v.__flags) : v) : undefined;
25
25
 
26
26
  if (!entry) {
27
27
  console.error('ssr-watch: missing --entry argument');
@@ -111,6 +111,24 @@ export interface HadarsOptions {
111
111
  * Note: inline styles are processed once at startup and are not live-reloaded.
112
112
  */
113
113
  htmlTemplate?: string;
114
+ /**
115
+ * Force the React runtime mode independently of the build mode.
116
+ * Useful when you need production build optimizations (minification, tree-shaking)
117
+ * but want React's development build for debugging hydration mismatches or
118
+ * component stack traces.
119
+ *
120
+ * - `'development'` — forces `process.env.NODE_ENV = "development"` and enables
121
+ * JSX source info even in `hadars build`. React prints detailed hydration error
122
+ * messages and component stacks.
123
+ * - `'production'` — the default; React uses the optimised production bundle.
124
+ *
125
+ * Only affects the **client** bundle. The SSR bundle always uses slim-react.
126
+ *
127
+ * @example
128
+ * // hadars.config.ts — debug hydration errors in a production build
129
+ * reactMode: 'development'
130
+ */
131
+ reactMode?: 'development' | 'production';
114
132
  /**
115
133
  * Additional rspack module rules appended to the built-in rule set.
116
134
  * Applied to both the client and the SSR bundle.
@@ -134,6 +134,8 @@ interface EntryOptions {
134
134
  optimization?: Record<string, unknown>;
135
135
  // additional module rules appended after the built-in rules
136
136
  moduleRules?: Record<string, any>[];
137
+ // force React runtime mode independently of build mode (client only)
138
+ reactMode?: 'development' | 'production';
137
139
  }
138
140
 
139
141
  const buildCompilerConfig = (
@@ -232,15 +234,44 @@ const buildCompilerConfig = (
232
234
  '@emotion/server',
233
235
  ] : undefined;
234
236
 
237
+ // reactMode lets the caller force React's dev/prod runtime independently of
238
+ // the webpack build mode. Only applies to the client bundle (SSR uses slim-react).
239
+ // 'development' → process.env.NODE_ENV = "development" + JSX dev transform.
240
+ const effectiveReactDev = isServerBuild
241
+ ? false // slim-react doesn't use NODE_ENV
242
+ : opts.reactMode === 'development' ? true
243
+ : opts.reactMode === 'production' ? false
244
+ : isDev; // default: follow build mode
245
+
246
+ if (!isServerBuild && opts.reactMode !== undefined) {
247
+ // Override the SWC JSX development flag for all js/ts rules already built
248
+ const rules = localConfig.module?.rules ?? [];
249
+ for (const rule of rules) {
250
+ if (!rule?.use || !Array.isArray(rule.use)) continue;
251
+ for (const entry of rule.use) {
252
+ if (entry?.loader?.includes('swc-loader')) {
253
+ entry.options = entry.options ?? {};
254
+ entry.options.jsc = entry.options.jsc ?? {};
255
+ entry.options.jsc.transform = entry.options.jsc.transform ?? {};
256
+ entry.options.jsc.transform.react = entry.options.jsc.transform.react ?? {};
257
+ entry.options.jsc.transform.react.development = effectiveReactDev;
258
+ entry.options.jsc.transform.react.refresh = effectiveReactDev && isDev;
259
+ }
260
+ }
261
+ }
262
+ }
263
+
235
264
  const extraPlugins: any[] = [];
236
- if (opts.define && typeof opts.define === 'object') {
237
- // rspack's DefinePlugin shape mirrors webpack's DefinePlugin
265
+ const defineValues: Record<string, string> = { ...(opts.define ?? {}) };
266
+ // When reactMode overrides the React runtime we must also set process.env.NODE_ENV
267
+ // so React picks its dev/prod bundle, independently of the rspack build mode.
268
+ if (!isServerBuild && opts.reactMode !== undefined) {
269
+ defineValues['process.env.NODE_ENV'] = JSON.stringify(opts.reactMode);
270
+ }
271
+ if (Object.keys(defineValues).length > 0) {
238
272
  const DefinePlugin = (rspack as any).DefinePlugin || (rspack as any).plugins?.DefinePlugin;
239
273
  if (DefinePlugin) {
240
- extraPlugins.push(new DefinePlugin(opts.define));
241
- } else {
242
- // fallback: try to inject via plugin API name
243
- extraPlugins.push({ name: 'DefinePlugin', value: opts.define });
274
+ extraPlugins.push(new DefinePlugin(defineValues));
244
275
  }
245
276
  }
246
277