wirejs-scripts 3.0.190 → 3.0.191

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 (4) hide show
  1. package/README.md +7 -6
  2. package/bin.js +55 -42
  3. package/package.json +3 -3
  4. package/ssg.js +1 -7
package/README.md CHANGED
@@ -103,12 +103,12 @@ export async function generate(context: Context) {
103
103
  </html>`;
104
104
  }
105
105
 
106
- export function hydrate() {
106
+ export function onload() {
107
107
  wireHydrate('counter', () => Counter());
108
108
  }
109
109
  ```
110
110
 
111
- `generate()` is used for the server-side render. The exported `hydrate()` function is used as the browser entrypoint. This lets a module share selected components/functions between server generation and the client bundle while keeping server-only code inside `generate()` or behind server-only imports.
111
+ `generate()` is used for the server-side render. The exported `onload()` function is used as the browser entrypoint. This lets a module share selected components/functions between server generation and the client bundle while keeping server-only code inside `generate()` or behind server-only imports.
112
112
 
113
113
  If `generate()` returns a DOM/document-like value, hosting serializes it as HTML. If it returns a string or other non-DOM body, hosting sends that body with a content type inferred from the requested extension when possible, unless `context.responseHeaders['content-type']` overrides it.
114
114
 
@@ -116,6 +116,7 @@ Routing notes:
116
116
 
117
117
  - Request paths are matched against compiled `.js` files under `dist/ssr/`.
118
118
  - `%` in an SSR filename acts as a wildcard pattern.
119
+ - Wildcard SSR routes use a canonical encoded client script path such as `/wild/%25.client.js`, so all instances of the route share one cacheable browser bundle.
119
120
  - Direct `/ssr` and `/ssr/*` static-file access is blocked; SSR modules are executable server code, not public assets.
120
121
  - If SSR code changes `context.location`, hosting can emit a redirect-style response when no explicit `responseCode` is set.
121
122
  - Pre-extension filenames work for SSR non-HTML responses: `src/ssr/feed.xml.ts` compiles to a handler for `/feed.xml`; `src/ssr/data.json.ts` handles `/data.json`.
@@ -126,7 +127,7 @@ SSR/SSG pages may register browser-side hydration through the DOM package used b
126
127
 
127
128
  For SSR, the compiled server module lives under `dist/ssr/*.js` and the browser bundle is served through the corresponding `*.client.js` path when hydration is needed.
128
129
 
129
- For SSG HTML outputs, `wirejs-scripts` probes for an exported `hydrate` path and emits a browser IIFE bundle only when appropriate. If no hydrate export exists, it emits a no-op client bundle rather than failing the whole SSG build. Non-HTML SSG outputs do not receive sibling browser JS bundles.
130
+ For SSG HTML outputs, `wirejs-scripts` emits a browser IIFE bundle for the page module. The generated HTML references that bundle only when the server/build module exports `onload()`. Non-HTML SSG outputs do not receive sibling browser JS bundles.
130
131
 
131
132
  A useful pattern is to split code by what can safely run in both places:
132
133
 
@@ -141,12 +142,12 @@ export async function generate(context) {
141
142
  }
142
143
 
143
144
  // browser-only entrypoint: attaches behavior to generated markup
144
- export function hydrate() {
145
+ export function onload() {
145
146
  wireHydrate('todos', () => TodoList());
146
147
  }
147
148
  ```
148
149
 
149
- Because the browser bundle starts from the exported `hydrate()` entrypoint, you can avoid pulling server-only dependencies into the client by keeping them out of the functions imported/called by `hydrate()`.
150
+ Because the browser bundle starts from the exported `onload()` entrypoint, you can avoid pulling server-only dependencies into the client by keeping them out of the functions imported/called by `onload()`.
150
151
 
151
152
  ## SSG handling
152
153
 
@@ -161,7 +162,7 @@ export async function generate() {
161
162
  }
162
163
  ```
163
164
 
164
- SSG uses the same shared-component pattern as SSR. For static HTML with browser behavior, export both `generate()` and `hydrate()` from the page module. The server/build side calls `generate()` once, and the emitted client bundle calls `hydrate()` in the browser.
165
+ SSG uses the same shared-component pattern as SSR. For static HTML with browser behavior, export an `onload()` function from the page module and call your DOM library's hydration registration there. The server/build side calls `generate()` once, and the emitted client bundle imports the page module and calls exported `onload()` in the browser.
165
166
 
166
167
  ### Non-HTML SSG outputs
167
168
 
package/bin.js CHANGED
@@ -299,6 +299,7 @@ async function compile(watch = false) {
299
299
  const staticDir = path.join(CWD, 'static');
300
300
  const distDir = path.join(CWD, 'dist');
301
301
  const ssrOutDir = path.join(distDir, 'ssr');
302
+ const preSsrClientWrapperDir = path.join(CWD, 'pre-dist', '.wirejs', 'ssr-client-wrappers');
302
303
 
303
304
  /**
304
305
  * Find all .ts files recursively in a directory.
@@ -326,6 +327,25 @@ async function compile(watch = false) {
326
327
  return explicitExt || 'html';
327
328
  }
328
329
 
330
+ async function createPageClientWrapper(sourceFile, sourceRoot, wrapperRoot) {
331
+ const relativeToSource = path.relative(sourceRoot, sourceFile);
332
+ const wrapperPath = path.join(wrapperRoot, relativeToSource);
333
+ await fs.promises.mkdir(path.dirname(wrapperPath), { recursive: true });
334
+
335
+ const rawImportPath = path.relative(path.dirname(wrapperPath), sourceFile).replace(/\\/g, '/');
336
+ const importPath = rawImportPath.startsWith('.') ? rawImportPath : `./${rawImportPath}`;
337
+ const wrapperSource = [
338
+ `import * as page from ${JSON.stringify(importPath)};`,
339
+ '',
340
+ 'if (typeof page["onload"] === "function") {',
341
+ '\tpage["onload"]();',
342
+ '}',
343
+ ].join('\n');
344
+
345
+ await fs.promises.writeFile(wrapperPath, wrapperSource, 'utf8');
346
+ return wrapperPath;
347
+ }
348
+
329
349
  /**
330
350
  * Build SSG files and write the generated HTML to dist/.
331
351
  * Also produces browser-compatible IIFE bundles at dist/*.js so that
@@ -340,33 +360,6 @@ async function compile(watch = false) {
340
360
  const preDist = path.join(CWD, 'pre-dist');
341
361
  const configuredSsgExternals = await resolveSsgExternals();
342
362
 
343
- async function createSsgHydrateWrapper(ssgFile, withHydrateImport) {
344
- const relativeToSsg = path.relative(ssgDir, ssgFile);
345
- const wrapperPath = path.join(preSsgHydrateWrapperDir, relativeToSsg);
346
- await fs.promises.mkdir(path.dirname(wrapperPath), { recursive: true });
347
-
348
- const rawImportPath = path.relative(path.dirname(wrapperPath), ssgFile).replace(/\\/g, '/');
349
- const importPath = rawImportPath.startsWith('.') ? rawImportPath : `./${rawImportPath}`;
350
- const wrapperSource = withHydrateImport
351
- ? [
352
- `import { hydrate } from ${JSON.stringify(importPath)};`,
353
- '',
354
- 'if (typeof hydrate === "function") {',
355
- '\thydrate();',
356
- '}',
357
- ].join('\n')
358
- : '// No hydrate export in source; this client bundle is intentionally a no-op.\n';
359
-
360
- await fs.promises.writeFile(wrapperPath, wrapperSource, 'utf8');
361
- return wrapperPath;
362
- }
363
-
364
- function isMissingHydrateExportError(error) {
365
- const messages = [error?.message ?? '', ...((error?.errors ?? []).map(e => e?.text ?? ''))]
366
- .join('\n')
367
- .toLowerCase();
368
- return messages.includes('no matching export') && messages.includes('hydrate');
369
- }
370
363
 
371
364
  async function buildSsgHydrationIife(entryPoint, browserExternal) {
372
365
  await esbuild.build({
@@ -380,9 +373,9 @@ async function compile(watch = false) {
380
373
  conditions: ['wirejs:client'],
381
374
  external: browserExternal,
382
375
  sourcemap: true,
383
- // This path intentionally probes named hydrate exports and falls back to no-op.
384
- // Keep probe failures out of stdout/stderr and handle them in catch logic.
385
- logLevel: 'silent',
376
+ logOverride: {
377
+ 'import-is-undefined': 'silent',
378
+ },
386
379
  });
387
380
  }
388
381
 
@@ -411,14 +404,8 @@ async function compile(watch = false) {
411
404
  // 2. Build browser-side bundles only for HTML SSG outputs.
412
405
  // Non-HTML artifacts should not emit a sibling .js bundle.
413
406
  if (outputExtension === 'html') {
414
- let hydrateWrapperFile = await createSsgHydrateWrapper(ssgFile, true);
415
- try {
416
- await buildSsgHydrationIife(hydrateWrapperFile, browserExternal);
417
- } catch (error) {
418
- if (!isMissingHydrateExportError(error)) throw error;
419
- hydrateWrapperFile = await createSsgHydrateWrapper(ssgFile, false);
420
- await buildSsgHydrationIife(hydrateWrapperFile, browserExternal);
421
- }
407
+ const clientWrapperFile = await createPageClientWrapper(ssgFile, ssgDir, preSsgHydrateWrapperDir);
408
+ await buildSsgHydrationIife(clientWrapperFile, browserExternal);
422
409
  }
423
410
  }
424
411
 
@@ -513,13 +500,33 @@ async function compile(watch = false) {
513
500
  outbase: ssrDir,
514
501
  entryNames: '[dir]/[name]',
515
502
  bundle: true,
516
- platform: 'browser',
503
+ platform: 'node',
517
504
  format: 'esm',
518
505
  conditions: ['wirejs:client'],
519
506
  sourcemap: true,
520
507
  });
521
508
  await ssrCtx.rebuild();
522
509
  ssrCtx.watch();
510
+
511
+ const ssrClientWrapperFiles = await Promise.all(ssrFiles.map(ssrFile =>
512
+ createPageClientWrapper(ssrFile, ssrDir, preSsrClientWrapperDir)
513
+ ));
514
+ const ssrClientCtx = await esbuild.context({
515
+ entryPoints: ssrClientWrapperFiles,
516
+ outdir: ssrOutDir,
517
+ outbase: preSsrClientWrapperDir,
518
+ entryNames: '[dir]/[name].client',
519
+ bundle: true,
520
+ platform: 'browser',
521
+ format: 'esm',
522
+ conditions: ['wirejs:client'],
523
+ sourcemap: true,
524
+ logOverride: {
525
+ 'import-is-undefined': 'silent',
526
+ },
527
+ });
528
+ await ssrClientCtx.rebuild();
529
+ ssrClientCtx.watch();
523
530
  }
524
531
 
525
532
  // SSG watch (debounced + queued to avoid overlapping rebuilds)
@@ -656,16 +663,22 @@ async function compile(watch = false) {
656
663
  // preserveSymlinks: true,
657
664
  });
658
665
  // client bundle
666
+ const ssrClientWrapperFiles = await Promise.all(ssrFiles.map(ssrFile =>
667
+ createPageClientWrapper(ssrFile, ssrDir, preSsrClientWrapperDir)
668
+ ));
659
669
  await esbuild.build({
660
- entryPoints: ssrFiles,
670
+ entryPoints: ssrClientWrapperFiles,
661
671
  outdir: ssrOutDir,
662
- outbase: ssrDir,
672
+ outbase: preSsrClientWrapperDir,
663
673
  entryNames: '[dir]/[name].client',
664
674
  bundle: true,
665
675
  platform: 'browser',
666
- format: 'iife',
676
+ format: 'esm',
667
677
  conditions: ['wirejs:client'],
668
678
  sourcemap: true,
679
+ logOverride: {
680
+ 'import-is-undefined': 'silent',
681
+ },
669
682
  // preserveSymlinks: true,
670
683
  });
671
684
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wirejs-scripts",
3
- "version": "3.0.190",
3
+ "version": "3.0.191",
4
4
  "description": "Basic build and start commands for wirejs apps",
5
5
  "type": "module",
6
6
  "bin": {
@@ -28,7 +28,7 @@
28
28
  "node-cron": "^4.2.1",
29
29
  "rimraf": "^6.0.1",
30
30
  "wirejs-dom": "^1.0.44",
31
- "wirejs-hosting": "^0.1.192",
32
- "wirejs-resources": "^0.1.192"
31
+ "wirejs-hosting": "^0.1.193",
32
+ "wirejs-resources": "^0.1.193"
33
33
  }
34
34
  }
package/ssg.js CHANGED
@@ -36,13 +36,7 @@ async function build(filename, outputExtension = 'html') {
36
36
  const doc = generated;
37
37
  const doctype = doc?.parentNode?.doctype?.name || '';
38
38
 
39
- let hydrationsFound = 0;
40
- while (globalThis.pendingDehydrations?.length > 0) {
41
- globalThis.pendingDehydrations.shift()(doc);
42
- hydrationsFound++;
43
- }
44
-
45
- if (hydrationsFound) {
39
+ if (typeof module.onload === 'function') {
46
40
  const script = doc.parentNode.createElement('script');
47
41
  script.src = filename.substring((`${SRC_DIR}/ssg`).length);
48
42
  doc.parentNode.body.appendChild(script);