@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 CHANGED
@@ -250,11 +250,17 @@ interface RenderToStringOptions {
250
250
  snapshotTarget?: 'container' | 'body' | 'head'
251
251
 
252
252
  // Runtime Configuration
253
- exposeGlobals?: boolean // Default: true
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);}})();