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.
- package/README.md +7 -6
- package/bin.js +55 -42
- package/package.json +3 -3
- 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
|
|
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 `
|
|
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`
|
|
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
|
|
145
|
+
export function onload() {
|
|
145
146
|
wireHydrate('todos', () => TodoList());
|
|
146
147
|
}
|
|
147
148
|
```
|
|
148
149
|
|
|
149
|
-
Because the browser bundle starts from the exported `
|
|
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
|
|
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
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
415
|
-
|
|
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: '
|
|
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:
|
|
670
|
+
entryPoints: ssrClientWrapperFiles,
|
|
661
671
|
outdir: ssrOutDir,
|
|
662
|
-
outbase:
|
|
672
|
+
outbase: preSsrClientWrapperDir,
|
|
663
673
|
entryNames: '[dir]/[name].client',
|
|
664
674
|
bundle: true,
|
|
665
675
|
platform: 'browser',
|
|
666
|
-
format: '
|
|
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.
|
|
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.
|
|
32
|
-
"wirejs-resources": "^0.1.
|
|
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
|
-
|
|
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);
|