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.
Files changed (58) hide show
  1. package/dist/{chunk-TV37IMRB.js → chunk-2TMQUXFL.js} +10 -10
  2. package/dist/{chunk-2J2L2H3H.js → chunk-NYLXE7T7.js} +6 -6
  3. package/dist/{chunk-OS3V4CPN.js → chunk-OZUZS2PD.js} +4 -4
  4. package/dist/cli.js +462 -496
  5. package/dist/cloudflare.cjs +11 -11
  6. package/dist/cloudflare.js +3 -3
  7. package/dist/index.d.cts +8 -4
  8. package/dist/index.d.ts +8 -4
  9. package/dist/lambda.cjs +11 -11
  10. package/dist/lambda.js +7 -7
  11. package/dist/loader.cjs +90 -54
  12. package/dist/slim-react/index.cjs +13 -13
  13. package/dist/slim-react/index.js +2 -2
  14. package/dist/slim-react/jsx-runtime.cjs +2 -4
  15. package/dist/slim-react/jsx-runtime.js +1 -1
  16. package/dist/ssr-render-worker.js +174 -161
  17. package/dist/ssr-watch.js +40 -74
  18. package/package.json +8 -10
  19. package/cli-lib.ts +0 -676
  20. package/cli.ts +0 -36
  21. package/index.ts +0 -17
  22. package/src/build.ts +0 -805
  23. package/src/cloudflare.ts +0 -140
  24. package/src/index.tsx +0 -41
  25. package/src/lambda.ts +0 -287
  26. package/src/slim-react/context.ts +0 -55
  27. package/src/slim-react/dispatcher.ts +0 -87
  28. package/src/slim-react/hooks.ts +0 -137
  29. package/src/slim-react/index.ts +0 -232
  30. package/src/slim-react/jsx-runtime.ts +0 -7
  31. package/src/slim-react/jsx.ts +0 -53
  32. package/src/slim-react/render.ts +0 -1101
  33. package/src/slim-react/renderContext.ts +0 -294
  34. package/src/slim-react/types.ts +0 -33
  35. package/src/source/context.ts +0 -113
  36. package/src/source/graphiql.ts +0 -101
  37. package/src/source/inference.ts +0 -260
  38. package/src/source/runner.ts +0 -138
  39. package/src/source/store.ts +0 -50
  40. package/src/ssr-render-worker.ts +0 -116
  41. package/src/ssr-watch.ts +0 -62
  42. package/src/static.ts +0 -109
  43. package/src/types/global.d.ts +0 -5
  44. package/src/types/hadars.ts +0 -350
  45. package/src/utils/Head.tsx +0 -462
  46. package/src/utils/clientScript.tsx +0 -71
  47. package/src/utils/cookies.ts +0 -16
  48. package/src/utils/loader.ts +0 -335
  49. package/src/utils/proxyHandler.tsx +0 -104
  50. package/src/utils/request.tsx +0 -9
  51. package/src/utils/response.tsx +0 -141
  52. package/src/utils/rspack.ts +0 -467
  53. package/src/utils/runtime.ts +0 -19
  54. package/src/utils/serve.ts +0 -155
  55. package/src/utils/ssrHandler.ts +0 -239
  56. package/src/utils/staticFile.ts +0 -43
  57. package/src/utils/template.html +0 -11
  58. package/src/utils/upgradeRequest.tsx +0 -19
@@ -1,260 +0,0 @@
1
- /**
2
- * Schema inference — builds a GraphQL schema from the node store automatically,
3
- * just like Gatsby does. Uses graphql-js (must be installed in the user's project).
4
- *
5
- * Dynamically imports graphql so hadars itself does not depend on it.
6
- */
7
-
8
- import type { NodeStore } from './store';
9
- import type { GraphQLExecutor } from '../types/hadars';
10
-
11
- // ── Primitive inference ────────────────────────────────────────────────────────
12
-
13
- type ScalarName = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID';
14
-
15
- function inferScalar(value: unknown): ScalarName {
16
- if (typeof value === 'boolean') return 'Boolean';
17
- if (typeof value === 'number') return Number.isInteger(value) ? 'Int' : 'Float';
18
- return 'String';
19
- }
20
-
21
- interface FieldShape {
22
- type: string; // e.g. "String", "Int", "Boolean", "[String]", "InternalType"
23
- nullable: boolean;
24
- }
25
-
26
- function inferFieldShape(value: unknown, seenTypes: Set<string>): FieldShape {
27
- if (value === null || value === undefined) {
28
- return { type: 'String', nullable: true };
29
- }
30
- if (Array.isArray(value)) {
31
- const inner = value.length > 0
32
- ? inferFieldShape(value[0], seenTypes)
33
- : { type: 'String', nullable: true };
34
- return { type: `[${inner.type}]`, nullable: true };
35
- }
36
- if (typeof value === 'object') {
37
- // nested object — we don't recurse deeply for now; use JSON string
38
- return { type: 'String', nullable: true };
39
- }
40
- return { type: inferScalar(value), nullable: true };
41
- }
42
-
43
- // ── Schema string builder ──────────────────────────────────────────────────────
44
-
45
- const INTERNAL_FIELDS = new Set(['id', 'internal', '__typename', 'parent', 'children']);
46
- /** GraphQL spec reserves all names beginning with __ for introspection. */
47
- const isReservedFieldName = (name: string) => name.startsWith('__');
48
-
49
- /** Scalar GraphQL types that are safe to use as lookup filter arguments. */
50
- const FILTERABLE_SCALARS = new Set(['String', 'Int', 'Float', 'Boolean', 'ID']);
51
-
52
- interface InferredField {
53
- name: string;
54
- type: string;
55
- /** True when the base type is a plain scalar (not a list/object). */
56
- filterable: boolean;
57
- }
58
-
59
- function buildTypeFields(nodes: readonly Record<string, unknown>[]): InferredField[] {
60
- const fieldMap = new Map<string, InferredField>();
61
-
62
- for (const node of nodes) {
63
- for (const [key, val] of Object.entries(node)) {
64
- if (INTERNAL_FIELDS.has(key) || isReservedFieldName(key)) continue;
65
- if (fieldMap.has(key)) continue;
66
- const { type } = inferFieldShape(val, new Set());
67
- fieldMap.set(key, {
68
- name: key,
69
- type,
70
- filterable: FILTERABLE_SCALARS.has(type),
71
- });
72
- }
73
- }
74
-
75
- return Array.from(fieldMap.values());
76
- }
77
-
78
- function buildTypeSDL(typeName: string, fields: InferredField[]): string {
79
- const lines = [
80
- ' id: ID!',
81
- ...fields.map(f => ` ${f.name}: ${f.type}`),
82
- ];
83
- return `type ${typeName} {\n${lines.join('\n')}\n}`;
84
- }
85
-
86
- // ── Query builder ──────────────────────────────────────────────────────────────
87
-
88
- /** Build allXxx / xxx query names from a type name, matching Gatsby's convention. */
89
- function queryNames(typeName: string) {
90
- const lower = typeName.charAt(0).toLowerCase() + typeName.slice(1);
91
- return { single: lower, all: `all${typeName}` };
92
- }
93
-
94
- /**
95
- * Build the SDL argument list for the single-item query.
96
- * Includes `id` plus every filterable scalar field so callers can look up
97
- * nodes by any natural key (e.g. slug, email) without knowing the hashed id.
98
- * The resolver returns the first node where ALL supplied arguments match.
99
- */
100
- function buildSingleArgs(fields: InferredField[]): string {
101
- const args = [
102
- 'id: ID',
103
- ...fields.filter(f => f.filterable && f.name !== 'id').map(f => `${f.name}: ${f.type}`),
104
- ];
105
- return args.join(', ');
106
- }
107
-
108
- // ── Public API ─────────────────────────────────────────────────────────────────
109
-
110
- /**
111
- * Load graphql-js from the user's project and build a schema + SDL from the
112
- * node store. Returns null if graphql-js is not installed.
113
- */
114
- async function loadAndBuildSchema(store: NodeStore): Promise<{
115
- schema: any;
116
- sdl: string;
117
- gql: any;
118
- } | null> {
119
- let gql: any;
120
- try {
121
- const { createRequire } = await import('node:module');
122
- const projectRequire = createRequire(process.cwd() + '/package.json');
123
- const graphqlPath = projectRequire.resolve('graphql');
124
- gql = await import(graphqlPath);
125
- } catch {
126
- return null;
127
- }
128
-
129
- const { buildSchema, printSchema, print } = gql;
130
- const types = store.getTypes();
131
-
132
- if (types.length === 0) {
133
- const rawSdl = 'type Query { _empty: String }';
134
- return { schema: buildSchema(rawSdl), sdl: rawSdl, gql };
135
- }
136
-
137
- // Infer field shapes once per type — reused for SDL, resolvers, and args.
138
- const typeFields = new Map(
139
- types.map(typeName => {
140
- const nodes = store.getNodesByType(typeName) as Record<string, unknown>[];
141
- return [typeName, buildTypeFields(nodes)] as const;
142
- })
143
- );
144
-
145
- const typeSDLs = types.map(typeName =>
146
- buildTypeSDL(typeName, typeFields.get(typeName)!)
147
- );
148
-
149
- const queryFields = types.map(typeName => {
150
- const { single, all } = queryNames(typeName);
151
- const args = buildSingleArgs(typeFields.get(typeName)!);
152
- return [
153
- ` ${single}(${args}): ${typeName}`,
154
- ` ${all}: [${typeName}!]!`,
155
- ].join('\n');
156
- });
157
-
158
- const rawSdl = [
159
- ...typeSDLs,
160
- `type Query {\n${queryFields.join('\n')}\n}`,
161
- ].join('\n\n');
162
-
163
- let schema: any;
164
- try {
165
- schema = buildSchema(rawSdl);
166
- } catch (err) {
167
- throw new Error(`[hadars] Failed to build GraphQL schema from node store: ${(err as Error).message}`);
168
- }
169
-
170
- return { schema, sdl: printSchema(schema), gql };
171
- }
172
-
173
- /**
174
- * Build a GraphQL executor backed by the node store.
175
- *
176
- * Returns null if graphql-js is not installed — in that case the caller should
177
- * surface a clear error message asking the user to install `graphql`.
178
- */
179
- /**
180
- * Normalise a query argument to a string.
181
- * Accepts either a plain query string or a TypedDocumentNode / codegen document object.
182
- */
183
- function toQueryString(query: unknown, print: (doc: any) => string): string {
184
- return typeof query === 'string' ? query : print(query);
185
- }
186
-
187
- export async function buildSchemaExecutor(
188
- store: NodeStore,
189
- ): Promise<GraphQLExecutor | null> {
190
- const built = await loadAndBuildSchema(store);
191
- if (!built) return null;
192
-
193
- const { schema, gql } = built;
194
- const { graphql, print } = gql;
195
- const types = store.getTypes();
196
-
197
- if (types.length === 0) {
198
- return (query, variables) =>
199
- graphql({ schema, source: toQueryString(query, print), variableValues: variables });
200
- }
201
-
202
- // Build root resolver map
203
- const rootValue: Record<string, unknown> = {};
204
- for (const typeName of types) {
205
- const { single, all } = queryNames(typeName);
206
- rootValue[all] = () => store.getNodesByType(typeName);
207
- // Single-item resolver: return the first node matching ALL supplied args.
208
- rootValue[single] = (args: Record<string, unknown>) => {
209
- const nodes = store.getNodesByType(typeName) as Record<string, unknown>[];
210
- return nodes.find(node =>
211
- Object.entries(args).every(([k, v]) => v === undefined || node[k] === v)
212
- ) ?? null;
213
- };
214
- }
215
-
216
- return (query, variables) =>
217
- graphql({ schema, rootValue, source: toQueryString(query, print), variableValues: variables }) as any;
218
- }
219
-
220
- /**
221
- * Return the inferred GraphQL schema as a SDL string suitable for writing to a
222
- * `schema.graphql` file and consuming with graphql-codegen or gql.tada.
223
- *
224
- * Returns null if graphql-js is not installed in the user's project.
225
- */
226
- export async function buildSchemaSDL(store: NodeStore): Promise<string | null> {
227
- const built = await loadAndBuildSchema(store);
228
- return built?.sdl ?? null;
229
- }
230
-
231
- /**
232
- * Introspect a custom GraphQL executor and return its schema as SDL.
233
- * Uses the standard introspection query so the executor doesn't need to know
234
- * about hadars internals.
235
- *
236
- * Returns null if graphql-js is not installed in the user's project.
237
- */
238
- export async function introspectExecutorSDL(
239
- executor: GraphQLExecutor,
240
- ): Promise<string | null> {
241
- let gql: any;
242
- try {
243
- const { createRequire } = await import('node:module');
244
- const projectRequire = createRequire(process.cwd() + '/package.json');
245
- const graphqlPath = projectRequire.resolve('graphql');
246
- gql = await import(graphqlPath);
247
- } catch {
248
- return null;
249
- }
250
-
251
- const { getIntrospectionQuery, buildClientSchema, printSchema } = gql;
252
- const result = await executor(getIntrospectionQuery());
253
- if (result.errors?.length) {
254
- throw new Error(`[hadars] Introspection failed: ${result.errors[0].message}`);
255
- }
256
- if (!result.data) {
257
- throw new Error('[hadars] Introspection returned no data');
258
- }
259
- return printSchema(buildClientSchema(result.data));
260
- }
@@ -1,138 +0,0 @@
1
- /**
2
- * Source plugin runner — calls each plugin's `sourceNodes` with a Gatsby-compatible
3
- * context object, then returns a populated node store ready for schema inference.
4
- *
5
- * Handles async plugins (e.g. gatsby-source-filesystem) that return immediately
6
- * and create nodes later via file-system events:
7
- * 1. Wait for nodes to stop arriving ("settle").
8
- * 2. Emit BOOTSTRAP_FINISHED on the plugin's emitter.
9
- * 3. Wait for any post-bootstrap nodes to settle.
10
- */
11
-
12
- import { EventEmitter } from 'node:events';
13
- import { createRequire } from 'node:module';
14
- import { NodeStore } from './store';
15
- import { makeGatsbyContext } from './context';
16
- import type { HadarsSourceEntry } from '../types/hadars';
17
-
18
- /** ms without a new createNode call before we consider the plugin settled. */
19
- const SETTLE_IDLE_MS = 300;
20
- /** Hard timeout — give up waiting after this many ms. */
21
- const SETTLE_TIMEOUT_MS = 10_000;
22
- /** Timeout for a single sourceNodes() call before we give up waiting. */
23
- const SOURCE_NODES_TIMEOUT_MS = 30_000;
24
-
25
- /**
26
- * Wait until no createNode call has been received for SETTLE_IDLE_MS,
27
- * or until SETTLE_TIMEOUT_MS elapses, whichever comes first.
28
- * Timers are unref()ed so they don't keep the process alive artificially.
29
- */
30
- function waitForSettle(getLastNodeTime: () => number): Promise<void> {
31
- return new Promise(resolve => {
32
- const deadline = Date.now() + SETTLE_TIMEOUT_MS;
33
- const check = () => {
34
- const idle = Date.now() - getLastNodeTime();
35
- if (idle >= SETTLE_IDLE_MS || Date.now() >= deadline) {
36
- resolve();
37
- } else {
38
- const t = setTimeout(check, 50);
39
- t.unref?.();
40
- }
41
- };
42
- const t = setTimeout(check, SETTLE_IDLE_MS);
43
- t.unref?.();
44
- });
45
- }
46
-
47
- /**
48
- * Run a promise with a hard timeout. Rejects with a clear message if the
49
- * deadline is exceeded, but does not cancel the original promise.
50
- */
51
- function withTimeout<T>(promise: Promise<T>, ms: number, label: string): Promise<T> {
52
- return new Promise((resolve, reject) => {
53
- const t = setTimeout(() => reject(new Error(`[hadars] "${label}" timed out after ${ms}ms`)), ms);
54
- t.unref?.();
55
- promise.then(resolve, reject).finally(() => clearTimeout(t));
56
- });
57
- }
58
-
59
- export async function runSources(
60
- sources: HadarsSourceEntry[],
61
- ): Promise<NodeStore> {
62
- const store = new NodeStore();
63
-
64
- for (const entry of sources) {
65
- const { resolve, options = {} } = entry;
66
-
67
- let mod: { sourceNodes?: (ctx: any, opts?: any) => Promise<void> | void };
68
- if (typeof resolve === 'string') {
69
- // Resolve from the user's project directory so the package is found
70
- // in their node_modules, not in the CLI's own node_modules.
71
- const projectRequire = createRequire(process.cwd() + '/package.json');
72
-
73
- // Gatsby plugins export sourceNodes from gatsby-node.js, not their
74
- // package main. Try gatsby-node.js first, fall back to main entry.
75
- let pkgPath: string;
76
- try {
77
- pkgPath = projectRequire.resolve(`${resolve}/gatsby-node`);
78
- } catch {
79
- pkgPath = projectRequire.resolve(resolve);
80
- }
81
- mod = await import(pkgPath);
82
- } else {
83
- mod = resolve as any;
84
- }
85
-
86
- if (typeof mod.sourceNodes !== 'function') {
87
- const name = typeof resolve === 'string' ? resolve : '(module)';
88
- console.warn(`[hadars] source plugin ${name} does not export sourceNodes — skipping`);
89
- continue;
90
- }
91
-
92
- const pluginName = typeof resolve === 'string' ? resolve : 'hadars-source';
93
-
94
- // Track the last time createNode was called so we can detect settling.
95
- let lastNodeTime = Date.now();
96
- const trackingStore = new Proxy(store, {
97
- get(target, prop) {
98
- if (prop === 'createNode') {
99
- return (node: any) => {
100
- lastNodeTime = Date.now();
101
- return target.createNode(node);
102
- };
103
- }
104
- return (target as any)[prop];
105
- },
106
- });
107
-
108
- // Real EventEmitter — plugins subscribe to BOOTSTRAP_FINISHED on this.
109
- const emitter = new EventEmitter();
110
- const ctx = makeGatsbyContext(trackingStore, pluginName, options, emitter);
111
-
112
- try {
113
- await withTimeout(
114
- Promise.resolve(mod.sourceNodes(ctx, options)),
115
- SOURCE_NODES_TIMEOUT_MS,
116
- `${pluginName} sourceNodes`,
117
- );
118
- } catch (err) {
119
- const cause = err instanceof Error ? err : new Error(String(err));
120
- const wrapped = new Error(
121
- `[hadars] source plugin "${pluginName}" threw during sourceNodes: ${cause.message}`,
122
- );
123
- wrapped.cause = cause;
124
- throw wrapped;
125
- }
126
-
127
- // Phase 1: wait for async initial-scan nodes (e.g. chokidar ready → createFileNode).
128
- await waitForSettle(() => lastNodeTime);
129
-
130
- // Signal Gatsby lifecycle end — some plugins create additional nodes here.
131
- emitter.emit('BOOTSTRAP_FINISHED');
132
-
133
- // Phase 2: wait for any post-bootstrap nodes to settle.
134
- await waitForSettle(() => lastNodeTime);
135
- }
136
-
137
- return store;
138
- }
@@ -1,50 +0,0 @@
1
- /**
2
- * In-memory node store — the equivalent of Gatsby's internal node database.
3
- * Source plugins write nodes here via the context shim; the schema inferencer
4
- * reads them to build the GraphQL schema.
5
- */
6
-
7
- export interface HadarsNode {
8
- id: string;
9
- /** Gatsby convention: the node type string, e.g. "MarkdownRemark", "ContentfulBlogPost". */
10
- internal: {
11
- type: string;
12
- contentDigest: string;
13
- content?: string;
14
- mediaType?: string;
15
- description?: string;
16
- };
17
- [key: string]: unknown;
18
- }
19
-
20
- export class NodeStore {
21
- private byId = new Map<string, HadarsNode>();
22
- private byType = new Map<string, HadarsNode[]>();
23
-
24
- createNode(node: HadarsNode): void {
25
- if (!node.id) throw new Error('[hadars] createNode: node.id must be a non-empty string');
26
- if (!node.internal?.type) throw new Error('[hadars] createNode: node.internal.type must be a non-empty string');
27
- this.byId.set(node.id, node);
28
- const list = this.byType.get(node.internal.type) ?? [];
29
- // Replace existing node with same id if present
30
- const idx = list.findIndex(n => n.id === node.id);
31
- if (idx >= 0) list[idx] = node; else list.push(node);
32
- this.byType.set(node.internal.type, list);
33
- }
34
-
35
- getNode(id: string): HadarsNode | undefined {
36
- return this.byId.get(id);
37
- }
38
-
39
- getNodes(): HadarsNode[] {
40
- return Array.from(this.byId.values());
41
- }
42
-
43
- getNodesByType(type: string): HadarsNode[] {
44
- return this.byType.get(type) ?? [];
45
- }
46
-
47
- getTypes(): string[] {
48
- return Array.from(this.byType.keys());
49
- }
50
- }
@@ -1,116 +0,0 @@
1
- /**
2
- * SSR render worker — runs in a node:worker_threads thread.
3
- *
4
- * Uses slim-react (bundled with hadars) for rendering instead of react-dom/server.
5
- * The SSR bundle is compiled with `react` aliased to slim-react, so both the
6
- * worker and the bundle share the same slim-react instance (and its globalThis
7
- * render state) without any extra coordination.
8
- *
9
- * Message: { type: 'renderFull', id, request: SerializableRequest }
10
- * Reply: { id, html, headHtml, status } | { id, error }
11
- */
12
-
13
- import { workerData, parentPort } from 'node:worker_threads';
14
- import { pathToFileURL } from 'node:url';
15
- import { renderToString, createElement } from './slim-react/index';
16
- import { buildHeadHtml } from './utils/response';
17
-
18
- const { ssrBundlePath } = workerData as { ssrBundlePath: string };
19
-
20
- let _ssrMod: any = null;
21
-
22
- async function init() {
23
- if (_ssrMod) return;
24
- _ssrMod = await import(pathToFileURL(ssrBundlePath).href);
25
- }
26
-
27
- export type SerializableRequest = {
28
- url: string;
29
- method: string;
30
- headers: Record<string, string>;
31
- body: Uint8Array | null;
32
- pathname: string;
33
- search: string;
34
- location: string;
35
- cookies: Record<string, string>;
36
- };
37
-
38
- function deserializeRequest(s: SerializableRequest): any {
39
- const init: RequestInit = { method: s.method, headers: new Headers(s.headers) };
40
- if (s.body) init.body = s.body.buffer as ArrayBuffer;
41
- const req = new Request(s.url, init);
42
- Object.assign(req, { pathname: s.pathname, search: s.search, location: s.location, cookies: s.cookies });
43
- return req;
44
- }
45
-
46
- // ── Full lifecycle ─────────────────────────────────────────────────────────
47
-
48
- async function runFullLifecycle(serialReq: SerializableRequest) {
49
- const Component = _ssrMod.default;
50
- const { getInitProps, getFinalProps } = _ssrMod;
51
-
52
- const parsedReq = deserializeRequest(serialReq);
53
-
54
- const context: any = {
55
- head: { title: 'Hadars App', meta: {}, link: {}, style: {}, script: {}, status: 200 },
56
- };
57
-
58
- let props: any = {
59
- ...(getInitProps ? await getInitProps(parsedReq) : {}),
60
- location: serialReq.location,
61
- };
62
-
63
- // Create per-request cache for useServerData, active for all renders.
64
- const unsuspend = { cache: new Map<string, any>() };
65
- (globalThis as any).__hadarsUnsuspend = unsuspend;
66
- // Expose the head context so HadarsHead can write into it without needing
67
- // the user to manually wrap their App with HadarsContext.
68
- (globalThis as any).__hadarsContext = context;
69
-
70
- // Single pass — component-level self-retry resolves all useServerData inline.
71
- // context.head is fully populated by the time renderToString returns.
72
- let appHtml: string;
73
- try {
74
- appHtml = await renderToString(createElement(Component, props));
75
- } finally {
76
- (globalThis as any).__hadarsUnsuspend = null;
77
- (globalThis as any).__hadarsContext = null;
78
- }
79
- // Head is captured after the render — all components have run.
80
- const headHtml = buildHeadHtml(context.head);
81
- const status = context.head.status ?? 200;
82
- const { context: _ctx, ...restProps } = getFinalProps ? await getFinalProps(props) : props;
83
-
84
- // Collect fulfilled useServerData values for client-side hydration.
85
- const serverData: Record<string, unknown> = {};
86
- let hasServerData = false;
87
- for (const [key, entry] of unsuspend.cache) {
88
- if (entry.status === 'fulfilled') { serverData[key] = entry.value; hasServerData = true; }
89
- }
90
- const clientProps = {
91
- ...restProps,
92
- location: serialReq.location,
93
- ...(hasServerData ? { __serverData: serverData } : {}),
94
- };
95
-
96
- const scriptContent = JSON.stringify({ hadars: { props: clientProps } }).replace(/</g, '\\u003c');
97
- const html = `<div id="app">${appHtml}</div><script id="hadars" type="application/json">${scriptContent}</script>`;
98
-
99
- return { html, headHtml, status };
100
- }
101
-
102
- // ── Message handler ────────────────────────────────────────────────────────
103
-
104
- parentPort!.on('message', async (msg: any) => {
105
- const { id, type, request } = msg;
106
- try {
107
- await init();
108
- if (type !== 'renderFull') return;
109
-
110
- const { html, headHtml, status } = await runFullLifecycle(request as SerializableRequest);
111
- parentPort!.postMessage({ id, html, headHtml, status });
112
-
113
- } catch (err: any) {
114
- parentPort!.postMessage({ id, error: err?.message ?? String(err) });
115
- }
116
- });
package/src/ssr-watch.ts DELETED
@@ -1,62 +0,0 @@
1
- import pathMod from 'node:path';
2
- import { compileEntry } from './utils/rspack';
3
-
4
- // Simple worker to run an SSR watch in a separate process to avoid multiple
5
- // rspack instances in the same process.
6
-
7
- const args = process.argv.slice(2);
8
- const argv: Record<string, string> = {};
9
- for (const a of args) {
10
- if (!a.startsWith('--')) continue;
11
- const idx = a.indexOf('=');
12
- if (idx === -1) continue;
13
- const key = a.slice(2, idx);
14
- const val = a.slice(idx + 1);
15
- argv[key] = val;
16
- }
17
-
18
- const entry = argv['entry'];
19
- const outDir = argv['outDir'] || '.hadars';
20
- const outFile = argv['outFile'] || 'index.ssr.js';
21
- const base = argv['base'] || '';
22
- const swcPlugins = argv['swcPlugins'] ? JSON.parse(argv['swcPlugins']) : undefined;
23
- const define = argv['define'] ? JSON.parse(argv['define']) : 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
-
26
- if (!entry) {
27
- console.error('ssr-watch: missing --entry argument');
28
- process.exit(1);
29
- }
30
-
31
- (async () => {
32
- try {
33
- console.log('ssr-watch: starting SSR watcher for', entry);
34
- await compileEntry(entry, {
35
- target: 'node',
36
- output: {
37
- iife: false,
38
- filename: outFile,
39
- path: pathMod.resolve(process.cwd(), outDir),
40
- publicPath: '',
41
- library: { type: 'module' }
42
- },
43
- base,
44
- mode: 'development',
45
- watch: true,
46
- swcPlugins,
47
- define,
48
- moduleRules,
49
- onChange: () => {
50
- console.log('ssr-watch: SSR rebuilt');
51
- }
52
- } as any);
53
- // compileEntry with watch resolves after the initial build completes. Keep the
54
- // process alive so the watcher remains active, but first emit a clear marker
55
- // so the parent process can detect initial build completion.
56
- console.log('ssr-watch: initial-build-complete');
57
- await new Promise(() => { /* never resolve - keep worker alive */ });
58
- } catch (err) {
59
- console.error('ssr-watch: error', err);
60
- process.exit(1);
61
- }
62
- })();