hadars 0.1.1

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.
@@ -0,0 +1,345 @@
1
+ // src/ssr-watch.ts
2
+ import pathMod2 from "node:path";
3
+
4
+ // src/utils/rspack.ts
5
+ import rspack from "@rspack/core";
6
+ import ReactRefreshPlugin from "@rspack/plugin-react-refresh";
7
+ import path from "node:path";
8
+ import { fileURLToPath } from "node:url";
9
+ import pathMod from "node:path";
10
+ import { existsSync } from "node:fs";
11
+ var __dirname = process.cwd();
12
+ var packageDir = pathMod.dirname(fileURLToPath(import.meta.url));
13
+ var clientScriptPath = pathMod.resolve(packageDir, "template.html");
14
+ var loaderPath = existsSync(pathMod.resolve(packageDir, "loader.cjs")) ? pathMod.resolve(packageDir, "loader.cjs") : pathMod.resolve(packageDir, "loader.ts");
15
+ var getConfigBase = (mode) => {
16
+ const isDev = mode === "development";
17
+ return {
18
+ experiments: {
19
+ css: true,
20
+ outputModule: true
21
+ },
22
+ resolve: {
23
+ modules: [
24
+ path.resolve(__dirname, "node_modules"),
25
+ // 'node_modules' (relative) enables the standard upward-traversal
26
+ // resolution so rspack can find transitive deps (e.g. webpack-dev-server)
27
+ // that live in a parent node_modules when running from a sub-project.
28
+ "node_modules"
29
+ ],
30
+ tsConfig: path.resolve(__dirname, "tsconfig.json"),
31
+ extensions: [".tsx", ".ts", ".js", ".jsx"]
32
+ },
33
+ module: {
34
+ rules: [
35
+ {
36
+ test: /\.mdx?$/,
37
+ use: [
38
+ {
39
+ loader: "@mdx-js/loader",
40
+ options: {}
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ test: /\.css$/,
46
+ use: ["postcss-loader"],
47
+ type: "css"
48
+ },
49
+ {
50
+ test: /\.svg$/i,
51
+ issuer: /\.[jt]sx?$/,
52
+ use: ["@svgr/webpack"]
53
+ },
54
+ {
55
+ test: /\.m?jsx?$/,
56
+ resolve: {
57
+ fullySpecified: false
58
+ },
59
+ exclude: [loaderPath],
60
+ use: [
61
+ // Transforms loadModule('./path') based on build target.
62
+ // Runs before swc-loader (loaders execute right-to-left).
63
+ {
64
+ loader: loaderPath
65
+ },
66
+ {
67
+ loader: "builtin:swc-loader",
68
+ options: {
69
+ jsc: {
70
+ parser: {
71
+ syntax: "ecmascript",
72
+ jsx: true
73
+ },
74
+ transform: {
75
+ react: {
76
+ runtime: "automatic",
77
+ development: isDev,
78
+ refresh: isDev
79
+ }
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ],
85
+ type: "javascript/auto"
86
+ },
87
+ {
88
+ test: /\.tsx?$/,
89
+ resolve: {
90
+ fullySpecified: false
91
+ },
92
+ exclude: [loaderPath],
93
+ use: [
94
+ {
95
+ loader: loaderPath
96
+ },
97
+ {
98
+ loader: "builtin:swc-loader",
99
+ options: {
100
+ jsc: {
101
+ parser: {
102
+ syntax: "typescript",
103
+ tsx: true
104
+ },
105
+ transform: {
106
+ react: {
107
+ runtime: "automatic",
108
+ development: isDev,
109
+ refresh: isDev
110
+ }
111
+ }
112
+ }
113
+ }
114
+ }
115
+ ],
116
+ type: "javascript/auto"
117
+ }
118
+ ]
119
+ }
120
+ };
121
+ };
122
+ var buildCompilerConfig = (entry2, opts, includeHotPlugin) => {
123
+ const Config = getConfigBase(opts.mode);
124
+ const { base: base2 } = opts;
125
+ const isDev = opts.mode === "development";
126
+ const localConfig = {
127
+ ...Config,
128
+ module: {
129
+ ...Config.module,
130
+ rules: (Config.module && Array.isArray(Config.module.rules) ? Config.module.rules : []).map((r) => {
131
+ const nr = { ...r };
132
+ if (r && Array.isArray(r.use)) {
133
+ nr.use = r.use.map((u) => ({ ...typeof u === "object" ? u : { loader: u } }));
134
+ }
135
+ return nr;
136
+ })
137
+ }
138
+ };
139
+ if (opts.swcPlugins && Array.isArray(opts.swcPlugins) && opts.swcPlugins.length > 0) {
140
+ const rules = localConfig.module && localConfig.module.rules;
141
+ if (Array.isArray(rules)) {
142
+ for (const rule of rules) {
143
+ const ruleUse = rule;
144
+ if (ruleUse.use && Array.isArray(ruleUse.use)) {
145
+ for (const entry3 of ruleUse.use) {
146
+ const useEntry = entry3;
147
+ if (useEntry && useEntry.loader && typeof useEntry.loader === "string" && useEntry.loader.includes("swc-loader")) {
148
+ const options = useEntry.options || {};
149
+ useEntry.options = options;
150
+ useEntry.options.jsc = useEntry.options.jsc || {};
151
+ useEntry.options.jsc.experimental = useEntry.options.jsc.experimental || {};
152
+ useEntry.options.jsc.experimental.runPluginFirst = true;
153
+ const existingPlugins = Array.isArray(useEntry.options.jsc.experimental.plugins) ? useEntry.options.jsc.experimental.plugins : [];
154
+ const incomingPlugins = Array.isArray(opts.swcPlugins) ? opts.swcPlugins : [];
155
+ const seen = /* @__PURE__ */ new Set();
156
+ const merged = [];
157
+ for (const p of existingPlugins.concat(incomingPlugins)) {
158
+ const name = Array.isArray(p) && p.length > 0 ? String(p[0]) : String(p);
159
+ if (!seen.has(name)) {
160
+ seen.add(name);
161
+ merged.push(p);
162
+ }
163
+ }
164
+ useEntry.options.jsc.experimental.plugins = merged;
165
+ }
166
+ }
167
+ }
168
+ }
169
+ }
170
+ }
171
+ const isServerBuild = Boolean(
172
+ opts.output && typeof opts.output === "object" && (opts.output.library || String(opts.output.filename || "").includes("ssr"))
173
+ );
174
+ const resolveAliases = isServerBuild ? {
175
+ // force all react imports to resolve to this project's react
176
+ react: path.resolve(process.cwd(), "node_modules", "react"),
177
+ "react-dom": path.resolve(process.cwd(), "node_modules", "react-dom"),
178
+ // also map react/jsx-runtime to avoid duplicates when automatic runtime is used
179
+ "react/jsx-runtime": path.resolve(process.cwd(), "node_modules", "react", "jsx-runtime.js"),
180
+ "react/jsx-dev-runtime": path.resolve(process.cwd(), "node_modules", "react", "jsx-dev-runtime.js"),
181
+ // ensure emotion packages resolve to the project's node_modules so we don't pick up a browser-specific entry
182
+ "@emotion/react": path.resolve(process.cwd(), "node_modules", "@emotion", "react"),
183
+ "@emotion/server": path.resolve(process.cwd(), "node_modules", "@emotion", "server"),
184
+ "@emotion/cache": path.resolve(process.cwd(), "node_modules", "@emotion", "cache"),
185
+ "@emotion/styled": path.resolve(process.cwd(), "node_modules", "@emotion", "styled")
186
+ } : void 0;
187
+ const externals = isServerBuild ? [
188
+ "react",
189
+ "react-dom",
190
+ // keep common aliases external as well
191
+ "react/jsx-runtime",
192
+ "react/jsx-dev-runtime",
193
+ // emotion should be external on server builds to avoid client/browser code
194
+ "@emotion/react",
195
+ "@emotion/server",
196
+ "@emotion/cache",
197
+ "@emotion/styled"
198
+ ] : void 0;
199
+ const extraPlugins = [];
200
+ if (opts.define && typeof opts.define === "object") {
201
+ const DefinePlugin = rspack.DefinePlugin || rspack.plugins?.DefinePlugin;
202
+ if (DefinePlugin) {
203
+ extraPlugins.push(new DefinePlugin(opts.define));
204
+ } else {
205
+ extraPlugins.push({ name: "DefinePlugin", value: opts.define });
206
+ }
207
+ }
208
+ const resolveConfig = {
209
+ extensions: [".tsx", ".ts", ".js", ".jsx"],
210
+ alias: resolveAliases,
211
+ // for server builds prefer the package "main"/"module" fields and avoid "browser" so we don't pick browser-specific entrypoints
212
+ mainFields: isServerBuild ? ["main", "module"] : ["browser", "module", "main"]
213
+ };
214
+ return {
215
+ entry: entry2,
216
+ resolve: resolveConfig,
217
+ output: {
218
+ ...opts.output,
219
+ clean: false
220
+ },
221
+ mode: opts.mode,
222
+ externals,
223
+ plugins: [
224
+ new rspack.HtmlRspackPlugin({
225
+ publicPath: base2 || "/",
226
+ template: clientScriptPath,
227
+ scriptLoading: "module",
228
+ filename: "out.html",
229
+ inject: "body"
230
+ }),
231
+ isDev && new ReactRefreshPlugin(),
232
+ includeHotPlugin && isDev && new rspack.HotModuleReplacementPlugin(),
233
+ ...extraPlugins
234
+ ],
235
+ ...localConfig,
236
+ // HMR is not implemented for module chunk format, so disable outputModule
237
+ // for client builds. SSR builds still need it for dynamic import() of exports.
238
+ experiments: {
239
+ ...localConfig.experiments || {},
240
+ outputModule: isServerBuild
241
+ }
242
+ };
243
+ };
244
+ var compileEntry = async (entry2, opts) => {
245
+ const compiler = rspack(buildCompilerConfig(entry2, opts, true));
246
+ if (opts.watch) {
247
+ await new Promise((resolve, reject) => {
248
+ let first = true;
249
+ compiler.watch({}, (err, stats) => {
250
+ if (err) {
251
+ if (first) {
252
+ first = false;
253
+ reject(err);
254
+ } else {
255
+ console.error("rspack watch error", err);
256
+ }
257
+ return;
258
+ }
259
+ console.log(stats?.toString({
260
+ colors: true,
261
+ modules: true,
262
+ children: true,
263
+ chunks: true,
264
+ chunkModules: true
265
+ }));
266
+ if (first) {
267
+ first = false;
268
+ resolve(stats);
269
+ } else {
270
+ try {
271
+ opts.onChange && opts.onChange(stats);
272
+ } catch (e) {
273
+ console.error("onChange handler error", e);
274
+ }
275
+ }
276
+ });
277
+ });
278
+ return;
279
+ }
280
+ await new Promise((resolve, reject) => {
281
+ compiler.run((err, stats) => {
282
+ if (err) {
283
+ reject(err);
284
+ return;
285
+ }
286
+ console.log(stats?.toString({
287
+ colors: true,
288
+ modules: true,
289
+ children: true,
290
+ chunks: true,
291
+ chunkModules: true
292
+ }));
293
+ resolve(stats);
294
+ });
295
+ });
296
+ };
297
+
298
+ // src/ssr-watch.ts
299
+ var args = process.argv.slice(2);
300
+ var argv = {};
301
+ for (const a of args) {
302
+ if (!a.startsWith("--"))
303
+ continue;
304
+ const idx = a.indexOf("=");
305
+ if (idx === -1)
306
+ continue;
307
+ const key = a.slice(2, idx);
308
+ const val = a.slice(idx + 1);
309
+ argv[key] = val;
310
+ }
311
+ var entry = argv["entry"];
312
+ var outDir = argv["outDir"] || ".hadars";
313
+ var outFile = argv["outFile"] || "index.ssr.js";
314
+ var base = argv["base"] || "";
315
+ if (!entry) {
316
+ console.error("ssr-watch: missing --entry argument");
317
+ process.exit(1);
318
+ }
319
+ (async () => {
320
+ try {
321
+ console.log("ssr-watch: starting SSR watcher for", entry);
322
+ await compileEntry(entry, {
323
+ target: "node",
324
+ output: {
325
+ iife: false,
326
+ filename: outFile,
327
+ path: pathMod2.resolve(process.cwd(), outDir),
328
+ publicPath: "",
329
+ library: { type: "module" }
330
+ },
331
+ base,
332
+ mode: "development",
333
+ watch: true,
334
+ onChange: () => {
335
+ console.log("ssr-watch: SSR rebuilt");
336
+ }
337
+ });
338
+ console.log("ssr-watch: initial-build-complete");
339
+ await new Promise(() => {
340
+ });
341
+ } catch (err) {
342
+ console.error("ssr-watch: error", err);
343
+ process.exit(1);
344
+ }
345
+ })();
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <meta name="NINETY_HEAD">
7
+ </head>
8
+ <body>
9
+ <meta name="NINETY_BODY">
10
+ </body>
11
+ </html>
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { hydrateRoot, createRoot } from 'react-dom/client';
3
+ import type { HadarsEntryModule } from '../types/ninety';
4
+ import { initServerDataCache } from '$_HEAD_PATH$';
5
+ import * as _appMod from '$_MOD_PATH$';
6
+
7
+ const appMod = _appMod as HadarsEntryModule<{}>;
8
+
9
+ const getProps = () => {
10
+ const script = document.getElementById('hadars');
11
+ if (script) {
12
+ try {
13
+ const data = JSON.parse(script.textContent || '{}');
14
+ return data.hadars?.props || {};
15
+ } catch (e) {
16
+ return {};
17
+ }
18
+ }
19
+ return {};
20
+ }
21
+
22
+ const main = async () => {
23
+ let props = getProps();
24
+
25
+ // Seed the useServerData client cache from server-resolved values before
26
+ // hydration so that hooks return the same data on the first render.
27
+ if (props.__serverData && typeof props.__serverData === 'object') {
28
+ initServerDataCache(props.__serverData as Record<string, unknown>);
29
+ const { __serverData: _, ...rest } = props;
30
+ props = rest;
31
+ }
32
+
33
+ const { location } = props;
34
+
35
+ if ( appMod.getClientProps ) {
36
+ props = await appMod.getClientProps(props);
37
+ }
38
+
39
+ props = {
40
+ ...props,
41
+ location,
42
+ }
43
+
44
+ const Component = appMod.default;
45
+
46
+ const appEl = document.getElementById("app");
47
+ if (appEl) {
48
+ // In HMR mode the client component may have already changed since SSR,
49
+ // so skip hydration to avoid mismatch warnings and do a fresh render.
50
+ if ((module as any).hot) {
51
+ createRoot(appEl).render(<Component {...props} />);
52
+ } else {
53
+ hydrateRoot(appEl, <Component {...props} />);
54
+ }
55
+ }
56
+ }
57
+
58
+ main();
package/index.ts ADDED
@@ -0,0 +1,15 @@
1
+ // Bun entry point — re-exports the full public API from source so that
2
+ // `ninety-bun` (which runs TypeScript directly) gets the same exports as
3
+ // the compiled `dist/index.js` used by Node.js / Deno.
4
+ export type {
5
+ NinetyOptions,
6
+ NinetyProps,
7
+ NinetyRequest,
8
+ NinetyGetAfterRenderProps,
9
+ NinetyGetFinalProps,
10
+ NinetyGetInitialProps,
11
+ NinetyGetClientProps,
12
+ NinetyEntryModule,
13
+ NinetyApp,
14
+ } from "./src/types/ninety";
15
+ export { NinetyHead, NinetyContext, loadModule } from "./src/index";
package/package.json ADDED
@@ -0,0 +1,99 @@
1
+ {
2
+ "name": "hadars",
3
+ "version": "0.1.1",
4
+ "description": "Minimal SSR framework for React — rspack, HMR, TypeScript, Bun/Node/Deno",
5
+ "module": "./dist/index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "hadars": "dist/cli.js",
9
+ "hadars-bun": "cli-bun.ts"
10
+ },
11
+ "main": "dist/index.js",
12
+ "types": "dist/index.d.ts",
13
+ "files": [
14
+ "dist",
15
+ "src",
16
+ "cli-lib.ts",
17
+ "cli.ts",
18
+ "cli-bun.ts",
19
+ "index.ts",
20
+ "LICENSE"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.js",
25
+ "require": "./dist/index.cjs",
26
+ "types": "./dist/index.d.ts"
27
+ },
28
+ "./cli": {
29
+ "import": "./dist/cli.js",
30
+ "require": "./dist/cli.js"
31
+ }
32
+ },
33
+ "scripts": {
34
+ "build:lib": "tsup src/index.tsx --format esm,cjs --dts --out-dir dist --clean --external '@rspack/*' --external '@rspack/binding'",
35
+ "build:cli": "node build-scripts/build-cli.mjs",
36
+ "build:all": "npm run build:lib && npm run build:cli",
37
+ "prepare": "npm run build:all",
38
+ "prepublishOnly": "npm run build:all"
39
+ },
40
+ "keywords": [
41
+ "react",
42
+ "ssr",
43
+ "server-side-rendering",
44
+ "rspack",
45
+ "typescript",
46
+ "bun",
47
+ "deno",
48
+ "hmr",
49
+ "framework",
50
+ "streaming"
51
+ ],
52
+ "engines": {
53
+ "node": ">=18.0.0"
54
+ },
55
+ "peerDependencies": {
56
+ "react": "^19.0.0",
57
+ "react-dom": "^19.0.0",
58
+ "typescript": "^5"
59
+ },
60
+ "peerDependenciesMeta": {
61
+ "typescript": {
62
+ "optional": true
63
+ }
64
+ },
65
+ "optionalDependencies": {
66
+ "@rspack/core": "1.4.9",
67
+ "@rspack/dev-server": "^1.1.4",
68
+ "@rspack/plugin-react-refresh": "^1.5.0",
69
+ "@types/bun": "latest",
70
+ "@types/react-dom": "^19.1.9",
71
+ "react-refresh": "^0.17.0",
72
+ "typescript": "^5"
73
+ },
74
+ "devDependencies": {
75
+ "@types/react": "^19.0.0",
76
+ "@types/react-dom": "^19.0.0",
77
+ "esbuild": "^0.19.0",
78
+ "tsup": "^6.6.0",
79
+ "@swc/core": "^1.4.0"
80
+ },
81
+ "dependencies": {
82
+ "@mdx-js/loader": "^3.1.1",
83
+ "@mdx-js/react": "^3.1.1",
84
+ "@svgr/webpack": "^8.1.0",
85
+ "@tailwindcss/postcss": "^4.1.12",
86
+ "postcss": "^8.5.6",
87
+ "postcss-loader": "^8.2.0",
88
+ "tailwindcss": "^4.1.12"
89
+ },
90
+ "license": "MIT",
91
+ "repository": {
92
+ "type": "git",
93
+ "url": "git+https://github.com/dumistoklus/ninety.git"
94
+ },
95
+ "bugs": {
96
+ "url": "https://github.com/dumistoklus/ninety/issues"
97
+ },
98
+ "homepage": "https://github.com/dumistoklus/ninety#readme"
99
+ }