@fictjs/ssr 0.19.0 → 0.21.0
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 +32 -3
- package/dist/chunk-DI4PFT6R.js +11 -0
- package/dist/fict-stream-runtime.js +1 -0
- package/dist/index.cjs +406 -80
- package/dist/index.d.cts +27 -1
- package/dist/index.d.ts +27 -1
- package/dist/index.js +396 -71
- package/dist/stream-runtime.cjs +36 -0
- package/dist/stream-runtime.d.cts +16 -0
- package/dist/stream-runtime.d.ts +16 -0
- package/dist/stream-runtime.js +8 -0
- package/package.json +14 -6
package/README.md
CHANGED
|
@@ -250,11 +250,17 @@ interface RenderToStringOptions {
|
|
|
250
250
|
snapshotTarget?: 'container' | 'body' | 'head'
|
|
251
251
|
|
|
252
252
|
// Runtime Configuration
|
|
253
|
-
exposeGlobals?: boolean // Default:
|
|
253
|
+
exposeGlobals?: boolean // Default: false; opt-in compatibility mode
|
|
254
254
|
manifest?: Record<string, string> | string
|
|
255
255
|
}
|
|
256
256
|
```
|
|
257
257
|
|
|
258
|
+
By default SSR does not write `window`, `document`, `Node`, or related DOM constructors to
|
|
259
|
+
`globalThis`. This keeps concurrent renders from racing over process-global DOM state. Components
|
|
260
|
+
should use Fict's render-provided document/ownerDocument paths; set `exposeGlobals: true` only for
|
|
261
|
+
legacy code that still reads DOM globals during server render. That compatibility mode is restored
|
|
262
|
+
on `dispose()`, but it is not concurrency-safe while overlapping renders are active.
|
|
263
|
+
|
|
258
264
|
### renderToDocument
|
|
259
265
|
|
|
260
266
|
```typescript
|
|
@@ -287,6 +293,29 @@ import { renderToStream } from '@fictjs/ssr'
|
|
|
287
293
|
const stream = renderToStream(() => <App />, { mode: 'shell' })
|
|
288
294
|
```
|
|
289
295
|
|
|
296
|
+
#### Strict CSP Streaming Runtime
|
|
297
|
+
|
|
298
|
+
For routes that disallow inline patch scripts, serve the packaged classic script
|
|
299
|
+
asset and enable observer patch mode:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { renderToStream } from '@fictjs/ssr'
|
|
303
|
+
|
|
304
|
+
const stream = renderToStream(() => <App />, {
|
|
305
|
+
mode: 'shell',
|
|
306
|
+
streamRuntime: 'external',
|
|
307
|
+
streamRuntimeSrc: '/assets/fict-stream-runtime.js',
|
|
308
|
+
})
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
The asset is published as `@fictjs/ssr/fict-stream-runtime.js`. Build tools that
|
|
312
|
+
need to materialize it themselves can import `createStreamRuntimeCode` from
|
|
313
|
+
`@fictjs/ssr/stream-runtime`.
|
|
314
|
+
|
|
315
|
+
Trusted Types deployments should use this external observer runtime. The patch
|
|
316
|
+
runtime moves `<template>` content with DOM APIs (`content` + `insertBefore`) and
|
|
317
|
+
does not call `innerHTML`, `insertAdjacentHTML`, `eval`, or `Function`.
|
|
318
|
+
|
|
290
319
|
### renderToPipeableStream
|
|
291
320
|
|
|
292
321
|
Node.js-style stream variant (compatible with `pipe()`).
|
|
@@ -305,7 +334,7 @@ await allReady
|
|
|
305
334
|
### renderToPartial
|
|
306
335
|
|
|
307
336
|
Generate a complete shell HTML plus a deferred patch stream for Partial Prerendering workflows.
|
|
308
|
-
This is an advanced API and currently considered **Preview** in v1.0.
|
|
337
|
+
This is an advanced API and currently considered **Experimental Preview** in v1.0; do not treat the return shape as frozen.
|
|
309
338
|
|
|
310
339
|
```typescript
|
|
311
340
|
import { renderToPartial } from '@fictjs/ssr'
|
|
@@ -450,7 +479,7 @@ Generated detailed `fict.manifest.json` during production build, mapping virtual
|
|
|
450
479
|
|
|
451
480
|
## Partial Prerendering
|
|
452
481
|
|
|
453
|
-
`renderToPartial()` enables a PPR-style split:
|
|
482
|
+
`renderToPartial()` is an **Experimental Preview** API that enables a PPR-style split:
|
|
454
483
|
|
|
455
484
|
1. **Shell phase**: serve/cache `shell` as static-first HTML.
|
|
456
485
|
2. **Deferred phase**: deliver `stream` patches for resolved Suspense boundaries.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/stream-runtime.ts
|
|
2
|
+
function createStreamRuntimeCode(options = {}) {
|
|
3
|
+
const observerMode = options.observerMode ?? true;
|
|
4
|
+
return `(function(){if(window.__FICT_STREAM)return;var cache=new Map();function find(id){var hit=cache.get(id);if(hit)return hit;var start=null,end=null;var w=document.createTreeWalker(document,NodeFilter.SHOW_COMMENT);while(w.nextNode()){var n=w.currentNode;var d=n.data;if(d==="fict:suspense-start:"+id)start=n;else if(d==="fict:suspense-end:"+id)end=n;if(start&&end)break;}if(start&&end){hit={start:start,end:end};cache.set(id,hit);}return hit;}function apply(id){var tpl=document.querySelector('template[data-fict-suspense="' + id + '"]');if(!tpl)return;var b=find(id);if(!b)return;var node=b.start.nextSibling;while(node&&node!==b.end){var next=node.nextSibling;node.parentNode&&node.parentNode.removeChild(node);node=next;}b.end.parentNode&&b.end.parentNode.insertBefore(tpl.content,b.end);tpl.parentNode&&tpl.parentNode.removeChild(tpl);}window.__FICT_STREAM={apply:apply};` + (observerMode ? 'function scan(root){var list=(root&&root.querySelectorAll?root:document).querySelectorAll("template[data-fict-suspense]");for(var i=0;i<list.length;i++){apply(list[i].getAttribute("data-fict-suspense"));}}if(typeof MutationObserver==="function"){new MutationObserver(function(muts){for(var i=0;i<muts.length;i++){for(var j=0;j<muts[i].addedNodes.length;j++){var n=muts[i].addedNodes[j];if(n.nodeType===1){if(n.matches&&n.matches("template[data-fict-suspense]"))apply(n.getAttribute("data-fict-suspense"));scan(n);}}}}).observe(document.documentElement||document,{childList:true,subtree:true});}if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",function(){scan(document);},{once:true});}else{scan(document);}' : "") + "})();";
|
|
5
|
+
}
|
|
6
|
+
var FICT_STREAM_RUNTIME_CODE = createStreamRuntimeCode({ observerMode: true });
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
createStreamRuntimeCode,
|
|
10
|
+
FICT_STREAM_RUNTIME_CODE
|
|
11
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(function(){if(window.__FICT_STREAM)return;var cache=new Map();function find(id){var hit=cache.get(id);if(hit)return hit;var start=null,end=null;var w=document.createTreeWalker(document,NodeFilter.SHOW_COMMENT);while(w.nextNode()){var n=w.currentNode;var d=n.data;if(d==="fict:suspense-start:"+id)start=n;else if(d==="fict:suspense-end:"+id)end=n;if(start&&end)break;}if(start&&end){hit={start:start,end:end};cache.set(id,hit);}return hit;}function apply(id){var tpl=document.querySelector('template[data-fict-suspense="' + id + '"]');if(!tpl)return;var b=find(id);if(!b)return;var node=b.start.nextSibling;while(node&&node!==b.end){var next=node.nextSibling;node.parentNode&&node.parentNode.removeChild(node);node=next;}b.end.parentNode&&b.end.parentNode.insertBefore(tpl.content,b.end);tpl.parentNode&&tpl.parentNode.removeChild(tpl);}window.__FICT_STREAM={apply:apply};function scan(root){var list=(root&&root.querySelectorAll?root:document).querySelectorAll("template[data-fict-suspense]");for(var i=0;i<list.length;i++){apply(list[i].getAttribute("data-fict-suspense"));}}if(typeof MutationObserver==="function"){new MutationObserver(function(muts){for(var i=0;i<muts.length;i++){for(var j=0;j<muts[i].addedNodes.length;j++){var n=muts[i].addedNodes[j];if(n.nodeType===1){if(n.matches&&n.matches("template[data-fict-suspense]"))apply(n.getAttribute("data-fict-suspense"));scan(n);}}}}).observe(document.documentElement||document,{childList:true,subtree:true});}if(document.readyState==="loading"){document.addEventListener("DOMContentLoaded",function(){scan(document);},{once:true});}else{scan(document);}})();
|