hadars 0.1.14 → 0.1.16
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 +38 -13
- package/dist/index.d.ts +18 -0
- package/dist/ssr-watch.js +1 -1
- package/package.json +2 -1
- package/src/build.ts +56 -11
- package/src/types/hadars.ts +18 -0
- package/src/utils/rspack.ts +5 -1
package/dist/cli.js
CHANGED
|
@@ -512,7 +512,7 @@ var buildCompilerConfig = (entry, opts, includeHotPlugin) => {
|
|
|
512
512
|
plugins: [
|
|
513
513
|
new rspack.HtmlRspackPlugin({
|
|
514
514
|
publicPath: base || "/",
|
|
515
|
-
template: clientScriptPath,
|
|
515
|
+
template: opts.htmlTemplate ? pathMod2.resolve(process.cwd(), opts.htmlTemplate) : clientScriptPath,
|
|
516
516
|
scriptLoading: "module",
|
|
517
517
|
filename: "out.html",
|
|
518
518
|
inject: "body"
|
|
@@ -784,6 +784,37 @@ import os from "node:os";
|
|
|
784
784
|
import { spawn } from "node:child_process";
|
|
785
785
|
import cluster from "node:cluster";
|
|
786
786
|
var encoder = new TextEncoder();
|
|
787
|
+
async function processHtmlTemplate(templatePath) {
|
|
788
|
+
const html = await fs.readFile(templatePath, "utf-8");
|
|
789
|
+
const styleRegex = /<style([^>]*)>([\s\S]*?)<\/style>/gi;
|
|
790
|
+
const matches = [];
|
|
791
|
+
let m;
|
|
792
|
+
while ((m = styleRegex.exec(html)) !== null) {
|
|
793
|
+
matches.push({ full: m[0], attrs: m[1] ?? "", css: m[2] ?? "" });
|
|
794
|
+
}
|
|
795
|
+
if (matches.length === 0)
|
|
796
|
+
return templatePath;
|
|
797
|
+
const { default: postcss } = await import("postcss");
|
|
798
|
+
let plugins = [];
|
|
799
|
+
try {
|
|
800
|
+
const { default: loadConfig2 } = await import("postcss-load-config");
|
|
801
|
+
const config = await loadConfig2({}, process.cwd());
|
|
802
|
+
plugins = config.plugins ?? [];
|
|
803
|
+
} catch {
|
|
804
|
+
}
|
|
805
|
+
let processedHtml = html;
|
|
806
|
+
for (const { full, attrs, css } of matches) {
|
|
807
|
+
try {
|
|
808
|
+
const result = await postcss(plugins).process(css, { from: templatePath });
|
|
809
|
+
processedHtml = processedHtml.replace(full, `<style${attrs}>${result.css}</style>`);
|
|
810
|
+
} catch (err) {
|
|
811
|
+
console.warn("[hadars] PostCSS error processing <style> block in HTML template:", err);
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
const tmpPath = pathMod3.join(os.tmpdir(), `hadars-template-${Date.now()}.html`);
|
|
815
|
+
await fs.writeFile(tmpPath, processedHtml);
|
|
816
|
+
return tmpPath;
|
|
817
|
+
}
|
|
787
818
|
var HEAD_MARKER = '<meta name="HADARS_HEAD">';
|
|
788
819
|
var BODY_MARKER = '<meta name="HADARS_BODY">';
|
|
789
820
|
var _renderToString = null;
|
|
@@ -1099,6 +1130,7 @@ var dev = async (options) => {
|
|
|
1099
1130
|
const tmpFilePath = pathMod3.join(os.tmpdir(), `hadars-client-${Date.now()}.tsx`);
|
|
1100
1131
|
await fs.writeFile(tmpFilePath, clientScript);
|
|
1101
1132
|
let ssrBuildId = crypto.randomBytes(4).toString("hex");
|
|
1133
|
+
const resolvedHtmlTemplate = options.htmlTemplate ? await processHtmlTemplate(pathMod3.resolve(__dirname2, options.htmlTemplate)) : void 0;
|
|
1102
1134
|
const clientCompiler = createClientCompiler(tmpFilePath, {
|
|
1103
1135
|
target: "web",
|
|
1104
1136
|
output: {
|
|
@@ -1108,7 +1140,8 @@ var dev = async (options) => {
|
|
|
1108
1140
|
base: baseURL,
|
|
1109
1141
|
mode: "development",
|
|
1110
1142
|
swcPlugins: options.swcPlugins,
|
|
1111
|
-
define: options.define
|
|
1143
|
+
define: options.define,
|
|
1144
|
+
htmlTemplate: resolvedHtmlTemplate
|
|
1112
1145
|
});
|
|
1113
1146
|
const devServer = new RspackDevServer({
|
|
1114
1147
|
port: hmrPort,
|
|
@@ -1260,11 +1293,6 @@ var dev = async (options) => {
|
|
|
1260
1293
|
if (staticRes)
|
|
1261
1294
|
return staticRes;
|
|
1262
1295
|
const projectStaticPath = pathMod3.resolve(process.cwd(), "static");
|
|
1263
|
-
if (path2 === "/" || path2 === "") {
|
|
1264
|
-
const indexRes = await tryServeFile(pathMod3.join(projectStaticPath, "index.html"));
|
|
1265
|
-
if (indexRes)
|
|
1266
|
-
return indexRes;
|
|
1267
|
-
}
|
|
1268
1296
|
const projectRes = await tryServeFile(pathMod3.join(projectStaticPath, path2));
|
|
1269
1297
|
if (projectRes)
|
|
1270
1298
|
return projectRes;
|
|
@@ -1312,6 +1340,7 @@ var build = async (options) => {
|
|
|
1312
1340
|
}
|
|
1313
1341
|
const tmpFilePath = pathMod3.join(os.tmpdir(), `hadars-client-${Date.now()}.tsx`);
|
|
1314
1342
|
await fs.writeFile(tmpFilePath, clientScript);
|
|
1343
|
+
const resolvedHtmlTemplate = options.htmlTemplate ? await processHtmlTemplate(pathMod3.resolve(__dirname2, options.htmlTemplate)) : void 0;
|
|
1315
1344
|
console.log("Building client and server bundles in parallel...");
|
|
1316
1345
|
await Promise.all([
|
|
1317
1346
|
compileEntry(tmpFilePath, {
|
|
@@ -1325,7 +1354,8 @@ var build = async (options) => {
|
|
|
1325
1354
|
mode: "production",
|
|
1326
1355
|
swcPlugins: options.swcPlugins,
|
|
1327
1356
|
define: options.define,
|
|
1328
|
-
optimization: options.optimization
|
|
1357
|
+
optimization: options.optimization,
|
|
1358
|
+
htmlTemplate: resolvedHtmlTemplate
|
|
1329
1359
|
}),
|
|
1330
1360
|
compileEntry(pathMod3.resolve(__dirname2, options.entry), {
|
|
1331
1361
|
output: {
|
|
@@ -1395,11 +1425,6 @@ var run = async (options) => {
|
|
|
1395
1425
|
const staticRes = await tryServeFile(pathMod3.join(__dirname2, StaticPath, path2));
|
|
1396
1426
|
if (staticRes)
|
|
1397
1427
|
return staticRes;
|
|
1398
|
-
if (path2 === "/" || path2 === "") {
|
|
1399
|
-
const indexRes = await tryServeFile(pathMod3.join(__dirname2, StaticPath, "index.html"));
|
|
1400
|
-
if (indexRes)
|
|
1401
|
-
return indexRes;
|
|
1402
|
-
}
|
|
1403
1428
|
const projectStaticPath = pathMod3.resolve(process.cwd(), "static");
|
|
1404
1429
|
const projectRes = await tryServeFile(pathMod3.join(projectStaticPath, path2));
|
|
1405
1430
|
if (projectRes)
|
package/dist/index.d.ts
CHANGED
|
@@ -92,6 +92,24 @@ interface HadarsOptions {
|
|
|
92
92
|
* Has no effect on the SSR bundle or dev mode.
|
|
93
93
|
*/
|
|
94
94
|
optimization?: Record<string, unknown>;
|
|
95
|
+
/**
|
|
96
|
+
* Path to a custom HTML template file (relative to the project root).
|
|
97
|
+
* Replaces the built-in minimal template used to generate the HTML shell.
|
|
98
|
+
*
|
|
99
|
+
* The file must include two marker elements so hadars can inject the
|
|
100
|
+
* per-request head tags and the server-rendered body:
|
|
101
|
+
*
|
|
102
|
+
* ```html
|
|
103
|
+
* <meta name="HADARS_HEAD"> <!-- replaced with <title>, <meta>, <link>, <style> tags -->
|
|
104
|
+
* <meta name="HADARS_BODY"> <!-- replaced with the SSR-rendered React tree -->
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* Any `<style>` blocks in the template are automatically processed through
|
|
108
|
+
* PostCSS (using the project's `postcss.config.js`) at build/dev startup time,
|
|
109
|
+
* so `@import "tailwindcss"` and other PostCSS directives work as expected.
|
|
110
|
+
* Note: inline styles are processed once at startup and are not live-reloaded.
|
|
111
|
+
*/
|
|
112
|
+
htmlTemplate?: string;
|
|
95
113
|
/**
|
|
96
114
|
* SSR response cache for `run()` mode. Has no effect in `dev()` mode.
|
|
97
115
|
*
|
package/dist/ssr-watch.js
CHANGED
|
@@ -238,7 +238,7 @@ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
|
|
|
238
238
|
plugins: [
|
|
239
239
|
new rspack.HtmlRspackPlugin({
|
|
240
240
|
publicPath: base2 || "/",
|
|
241
|
-
template: clientScriptPath,
|
|
241
|
+
template: opts.htmlTemplate ? pathMod.resolve(process.cwd(), opts.htmlTemplate) : clientScriptPath,
|
|
242
242
|
scriptLoading: "module",
|
|
243
243
|
filename: "out.html",
|
|
244
244
|
inject: "body"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hadars",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -80,6 +80,7 @@
|
|
|
80
80
|
"@mdx-js/loader": "^3.1.1",
|
|
81
81
|
"@svgr/webpack": "^8.1.0",
|
|
82
82
|
"postcss": "^8.5.8",
|
|
83
|
+
"postcss-load-config": "^6.0.1",
|
|
83
84
|
"postcss-loader": "^8.2.1"
|
|
84
85
|
},
|
|
85
86
|
"license": "MIT",
|
package/src/build.ts
CHANGED
|
@@ -19,6 +19,48 @@ import cluster from 'node:cluster';
|
|
|
19
19
|
import type { HadarsEntryModule, HadarsOptions, HadarsProps } from "./types/hadars";
|
|
20
20
|
const encoder = new TextEncoder();
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Reads an HTML template, processes any `<style>` blocks through PostCSS
|
|
24
|
+
* (using the project's postcss.config.js), writes the result to a temp file,
|
|
25
|
+
* and returns the temp file path. If there are no `<style>` blocks the
|
|
26
|
+
* original path is returned unchanged.
|
|
27
|
+
*/
|
|
28
|
+
async function processHtmlTemplate(templatePath: string): Promise<string> {
|
|
29
|
+
const html = await fs.readFile(templatePath, 'utf-8');
|
|
30
|
+
|
|
31
|
+
const styleRegex = /<style([^>]*)>([\s\S]*?)<\/style>/gi;
|
|
32
|
+
const matches: Array<{ full: string; attrs: string; css: string }> = [];
|
|
33
|
+
let m: RegExpExecArray | null;
|
|
34
|
+
while ((m = styleRegex.exec(html)) !== null) {
|
|
35
|
+
matches.push({ full: m[0]!, attrs: m[1] ?? '', css: m[2] ?? '' });
|
|
36
|
+
}
|
|
37
|
+
if (matches.length === 0) return templatePath;
|
|
38
|
+
|
|
39
|
+
const { default: postcss } = await import('postcss');
|
|
40
|
+
let plugins: any[] = [];
|
|
41
|
+
try {
|
|
42
|
+
const { default: loadConfig } = await import('postcss-load-config' as any);
|
|
43
|
+
const config = await loadConfig({}, process.cwd());
|
|
44
|
+
plugins = (config as any).plugins ?? [];
|
|
45
|
+
} catch {
|
|
46
|
+
// No postcss config found — process without plugins (passthrough)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let processedHtml = html;
|
|
50
|
+
for (const { full, attrs, css } of matches) {
|
|
51
|
+
try {
|
|
52
|
+
const result = await postcss(plugins).process(css, { from: templatePath });
|
|
53
|
+
processedHtml = processedHtml.replace(full, `<style${attrs}>${result.css}</style>`);
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.warn('[hadars] PostCSS error processing <style> block in HTML template:', err);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const tmpPath = pathMod.join(os.tmpdir(), `hadars-template-${Date.now()}.html`);
|
|
60
|
+
await fs.writeFile(tmpPath, processedHtml);
|
|
61
|
+
return tmpPath;
|
|
62
|
+
}
|
|
63
|
+
|
|
22
64
|
const HEAD_MARKER = '<meta name="HADARS_HEAD">';
|
|
23
65
|
const BODY_MARKER = '<meta name="HADARS_BODY">';
|
|
24
66
|
|
|
@@ -445,6 +487,11 @@ export const dev = async (options: HadarsRuntimeOptions) => {
|
|
|
445
487
|
// SSR live-reload id to force re-import
|
|
446
488
|
let ssrBuildId = crypto.randomBytes(4).toString('hex');
|
|
447
489
|
|
|
490
|
+
// Pre-process the HTML template's <style> blocks through PostCSS (e.g. Tailwind).
|
|
491
|
+
const resolvedHtmlTemplate = options.htmlTemplate
|
|
492
|
+
? await processHtmlTemplate(pathMod.resolve(__dirname, options.htmlTemplate))
|
|
493
|
+
: undefined;
|
|
494
|
+
|
|
448
495
|
// Start rspack-dev-server for the client bundle. It provides true React
|
|
449
496
|
// Fast Refresh HMR: the browser's HMR runtime connects directly to the
|
|
450
497
|
// dev server's WebSocket on hmrPort and receives module-level patches
|
|
@@ -460,6 +507,7 @@ export const dev = async (options: HadarsRuntimeOptions) => {
|
|
|
460
507
|
mode: 'development',
|
|
461
508
|
swcPlugins: options.swcPlugins,
|
|
462
509
|
define: options.define,
|
|
510
|
+
htmlTemplate: resolvedHtmlTemplate,
|
|
463
511
|
});
|
|
464
512
|
|
|
465
513
|
const devServer = new RspackDevServer({
|
|
@@ -609,12 +657,8 @@ export const dev = async (options: HadarsRuntimeOptions) => {
|
|
|
609
657
|
const staticRes = await tryServeFile(pathMod.join(__dirname, StaticPath, path));
|
|
610
658
|
if (staticRes) return staticRes;
|
|
611
659
|
|
|
612
|
-
// project-level static/ directory
|
|
660
|
+
// project-level static/ directory (explicit paths only — never intercept root)
|
|
613
661
|
const projectStaticPath = pathMod.resolve(process.cwd(), 'static');
|
|
614
|
-
if (path === '/' || path === '') {
|
|
615
|
-
const indexRes = await tryServeFile(pathMod.join(projectStaticPath, 'index.html'));
|
|
616
|
-
if (indexRes) return indexRes;
|
|
617
|
-
}
|
|
618
662
|
const projectRes = await tryServeFile(pathMod.join(projectStaticPath, path));
|
|
619
663
|
if (projectRes) return projectRes;
|
|
620
664
|
|
|
@@ -675,6 +719,11 @@ export const build = async (options: HadarsRuntimeOptions) => {
|
|
|
675
719
|
const tmpFilePath = pathMod.join(os.tmpdir(), `hadars-client-${Date.now()}.tsx`);
|
|
676
720
|
await fs.writeFile(tmpFilePath, clientScript);
|
|
677
721
|
|
|
722
|
+
// Pre-process the HTML template's <style> blocks through PostCSS (e.g. Tailwind).
|
|
723
|
+
const resolvedHtmlTemplate = options.htmlTemplate
|
|
724
|
+
? await processHtmlTemplate(pathMod.resolve(__dirname, options.htmlTemplate))
|
|
725
|
+
: undefined;
|
|
726
|
+
|
|
678
727
|
// Compile client and SSR bundles in parallel — they write to different
|
|
679
728
|
// output directories and use different entry files, so they are fully
|
|
680
729
|
// independent and safe to run concurrently.
|
|
@@ -692,6 +741,7 @@ export const build = async (options: HadarsRuntimeOptions) => {
|
|
|
692
741
|
swcPlugins: options.swcPlugins,
|
|
693
742
|
define: options.define,
|
|
694
743
|
optimization: options.optimization,
|
|
744
|
+
htmlTemplate: resolvedHtmlTemplate,
|
|
695
745
|
}),
|
|
696
746
|
compileEntry(pathMod.resolve(__dirname, options.entry), {
|
|
697
747
|
output: {
|
|
@@ -775,12 +825,7 @@ export const run = async (options: HadarsRuntimeOptions) => {
|
|
|
775
825
|
const staticRes = await tryServeFile(pathMod.join(__dirname, StaticPath, path));
|
|
776
826
|
if (staticRes) return staticRes;
|
|
777
827
|
|
|
778
|
-
|
|
779
|
-
const indexRes = await tryServeFile(pathMod.join(__dirname, StaticPath, 'index.html'));
|
|
780
|
-
if (indexRes) return indexRes;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
// project-level static/ directory
|
|
828
|
+
// project-level static/ directory (explicit paths only — never intercept root)
|
|
784
829
|
const projectStaticPath = pathMod.resolve(process.cwd(), 'static');
|
|
785
830
|
const projectRes = await tryServeFile(pathMod.join(projectStaticPath, path));
|
|
786
831
|
if (projectRes) return projectRes;
|
package/src/types/hadars.ts
CHANGED
|
@@ -93,6 +93,24 @@ export interface HadarsOptions {
|
|
|
93
93
|
* Has no effect on the SSR bundle or dev mode.
|
|
94
94
|
*/
|
|
95
95
|
optimization?: Record<string, unknown>;
|
|
96
|
+
/**
|
|
97
|
+
* Path to a custom HTML template file (relative to the project root).
|
|
98
|
+
* Replaces the built-in minimal template used to generate the HTML shell.
|
|
99
|
+
*
|
|
100
|
+
* The file must include two marker elements so hadars can inject the
|
|
101
|
+
* per-request head tags and the server-rendered body:
|
|
102
|
+
*
|
|
103
|
+
* ```html
|
|
104
|
+
* <meta name="HADARS_HEAD"> <!-- replaced with <title>, <meta>, <link>, <style> tags -->
|
|
105
|
+
* <meta name="HADARS_BODY"> <!-- replaced with the SSR-rendered React tree -->
|
|
106
|
+
* ```
|
|
107
|
+
*
|
|
108
|
+
* Any `<style>` blocks in the template are automatically processed through
|
|
109
|
+
* PostCSS (using the project's `postcss.config.js`) at build/dev startup time,
|
|
110
|
+
* so `@import "tailwindcss"` and other PostCSS directives work as expected.
|
|
111
|
+
* Note: inline styles are processed once at startup and are not live-reloaded.
|
|
112
|
+
*/
|
|
113
|
+
htmlTemplate?: string;
|
|
96
114
|
/**
|
|
97
115
|
* SSR response cache for `run()` mode. Has no effect in `dev()` mode.
|
|
98
116
|
*
|
package/src/utils/rspack.ts
CHANGED
|
@@ -135,6 +135,8 @@ interface EntryOptions {
|
|
|
135
135
|
mode: "development" | "production",
|
|
136
136
|
// optional swc plugins to pass to swc-loader
|
|
137
137
|
swcPlugins?: SwcPluginList,
|
|
138
|
+
// optional path to a custom HTML template (resolved relative to cwd)
|
|
139
|
+
htmlTemplate?: string,
|
|
138
140
|
// optional compile-time defines (e.g. { 'process.env.NODE_ENV': '"development"' })
|
|
139
141
|
define?: Record<string, string>;
|
|
140
142
|
base?: string;
|
|
@@ -291,7 +293,9 @@ const buildCompilerConfig = (
|
|
|
291
293
|
plugins: [
|
|
292
294
|
new rspack.HtmlRspackPlugin({
|
|
293
295
|
publicPath: base || '/',
|
|
294
|
-
template:
|
|
296
|
+
template: opts.htmlTemplate
|
|
297
|
+
? pathMod.resolve(process.cwd(), opts.htmlTemplate)
|
|
298
|
+
: clientScriptPath,
|
|
295
299
|
scriptLoading: 'module',
|
|
296
300
|
filename: 'out.html',
|
|
297
301
|
inject: 'body',
|