@ecopages/lit 0.2.0-rc.6 → 0.2.0-rc.8

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
@@ -47,7 +47,15 @@ This setup lets Kita own the page shell while Lit owns the nested Lit component
47
47
 
48
48
  - `.lit.tsx` route files.
49
49
  - Nested Lit component boundaries rendered inside pages owned by other integrations.
50
- - The Lit hydration support script required for SSR custom elements and declarative shadow DOM.
50
+ - The Lit hydration support script required for SSR custom elements and declarative shadow DOM. Injected synchronously into `document.head` so `globalThis.litElementHydrateSupport` is available before any custom element connects.
51
+
52
+ ## Hydration Architecture
53
+
54
+ Lit component SSR reuses existing declarative shadow roots rather than replacing host nodes. When hydrated:
55
+
56
+ 1. The server renders declarative shadow roots (`<template shadowrootmode="open">`) and stamps `defer-hydration` on nodes needing client hydration.
57
+ 2. The global `lit-hydrate-support` script initializes `globalThis.litElementHydrateSupport` before custom element definitions execute.
58
+ 3. Custom elements register via their client scripts, remove `defer-hydration`, and perform in-place hydration against the preserved SSR shadow roots.
51
59
 
52
60
  ## Mixed Rendering
53
61
 
@@ -57,10 +65,11 @@ Important:
57
65
 
58
66
  - Direct local imports of declared Eco Components contribute their Dependencies automatically, including Foreign Children and named `export { X } from` barrels. Use `dependencies.components` for `export *` barrels and package imports.
59
67
  - Ecopages validates ownership from declared dependencies during render preparation instead of relying on post-render HTML discovery.
68
+ - Route-level dependency resolvers can supply Components at request time; Lit includes those resolved roots when deciding whether a Foreign Subtree needs handoff.
60
69
  - Lit keeps slot transport, shadow-root handling, and SSR preload behavior inside the Lit renderer.
61
70
 
62
71
  ## Discovered assets and lazy custom elements
63
72
 
64
73
  A Lit Component can import a relative stylesheet with `import './counter.css'`; core extracts it into the shared asset pipeline. Direct local imports of that Component make its Dependencies available to the Lit SSR preloader, including through Foreign Child rendering.
65
74
 
66
- Keep custom-element registration explicit with `scripts: [{ src: './counter.script.ts', ssr: true, lazy: { 'on:visible': true } }]`. The module executes before server rendering and loads in the browser on visibility. `ssr: true` no longer emits an additional eager browser script. Omit `lazy` when eager browser loading is intended. SSR preload artifacts are excluded from HTML, and hydration support must be available before the browser registers the element.
75
+ Keep custom-element registration explicit with `scripts: [{ src: './counter.script.ts', ssr: true, lazy: { 'on:visible': true } }]`. Entries with `ssr: true` execute on the server before rendering (eager or lazy) and load in the browser according to `lazy`. `ssr: true` does not emit an additional eager browser script. Omit `lazy` when eager browser loading is intended. SSR preload artifacts are excluded from HTML, and hydration support must be available before the browser registers the element. Lit-owned page routes preload those scripts in the static-render worker through the app module loader so `@lit-labs/ssr` sees the registered custom element.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/lit",
3
- "version": "0.2.0-rc.6",
3
+ "version": "0.2.0-rc.8",
4
4
  "description": "Ecopages lit integration",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -42,7 +42,7 @@
42
42
  "directory": "packages/integrations/lit"
43
43
  },
44
44
  "peerDependencies": {
45
- "@ecopages/core": "0.2.0-rc.6",
45
+ "@ecopages/core": "0.2.0-rc.8",
46
46
  "@lit-labs/ssr": "^3.3.0",
47
47
  "@lit-labs/ssr-client": "^1.1.7",
48
48
  "lit": "^3.2.1"
@@ -24,6 +24,8 @@ LitRenderer.execute() (main)
24
24
  └─ session.renderPageInWorker()
25
25
  └─ postMessage(render) ─────► RouteRendererFactory.execute()
26
26
  (no active session in worker)
27
+ LitRenderer.ssrScriptPreloader uses
28
+ importServerModule (no entrypoint resolver)
27
29
 
28
30
  beforeStaticExport
29
31
  └─ ensureWorker() only when `renderSession` is still null
package/src/dom-shim.js CHANGED
@@ -2,15 +2,49 @@ import { installWindowOnGlobal } from '@lit-labs/ssr/lib/dom-shim.js';
2
2
  import '@lit-labs/ssr/lib/install-global-dom-shim.js';
3
3
  /** Repairs the minimal Lit DOM surface after another Integration installs its SSR globals. */
4
4
  export function ensureLitDomShim() {
5
- if (typeof globalThis.document?.createTreeWalker !== 'function') {
6
- if (typeof globalThis.window === 'undefined') {
7
- installWindowOnGlobal();
5
+ if (typeof globalThis.window === 'undefined') {
6
+ installWindowOnGlobal();
7
+ }
8
+ if (typeof globalThis.document !== 'undefined' && typeof globalThis.document.createTreeWalker !== 'function') {
9
+ Object.defineProperty(globalThis.document, 'createTreeWalker', {
10
+ configurable: true,
11
+ writable: true,
12
+ value: () => ({}),
13
+ });
14
+ }
15
+ for (const target of [globalThis.Element, globalThis.HTMLElement]) {
16
+ if (typeof target === 'undefined' || !target?.prototype) {
17
+ continue;
18
+ }
19
+ if (!('attributes' in target.prototype)) {
20
+ Object.defineProperty(target.prototype, 'attributes', {
21
+ configurable: true,
22
+ enumerable: true,
23
+ get() {
24
+ if (typeof this.getAttributeNames === 'function') {
25
+ return this.getAttributeNames().map((name) => ({
26
+ name,
27
+ value: this.getAttribute(name),
28
+ }));
29
+ }
30
+ return [];
31
+ },
32
+ });
8
33
  }
9
- else {
10
- Object.defineProperty(globalThis.document, 'createTreeWalker', {
34
+ if (typeof target.prototype.attachShadow !== 'function') {
35
+ Object.defineProperty(target.prototype, 'attachShadow', {
11
36
  configurable: true,
12
37
  writable: true,
13
- value: () => ({}),
38
+ value: function attachShadow(init = { mode: 'open' }) {
39
+ const shadowRoot = {
40
+ mode: init?.mode ?? 'open',
41
+ host: this,
42
+ children: [],
43
+ childNodes: [],
44
+ };
45
+ this.shadowRoot = shadowRoot;
46
+ return shadowRoot;
47
+ },
14
48
  });
15
49
  }
16
50
  }
@@ -1 +1,11 @@
1
+ /**
2
+ * Pre-bundled Lit SSR hydration client runtime.
3
+ *
4
+ * @remarks
5
+ * Contains the compiled `@lit-labs/ssr-client/lit-element-hydrate-support.js`
6
+ * and `@lit-labs/ssr-client/lib/hydrate-lit-html.js` runtime. It defines
7
+ * `globalThis.litElementHydrateSupport`, which installs the `defer-hydration`
8
+ * observer and hooks into `LitElement.prototype.update` to hydrate existing
9
+ * server-rendered shadow roots instead of replacing them.
10
+ */
1
11
  export declare const litElementHydrateScript = "var k=globalThis,y=k.trustedTypes,p=y?y.createPolicy(\"lit-html\",{createHTML:(q)=>q}):void 0;var P=`lit$${Math.random().toFixed(9).slice(2)}$`,v=\"?\"+P,Kq=`<${v}>`,x=document,C=()=>x.createComment(\"\"),S=(q)=>q===null||typeof q!=\"object\"&&typeof q!=\"function\",L=Array.isArray,h=(q)=>L(q)||typeof q?.[Symbol.iterator]==\"function\";var U=/<(?:(!--|\\/[^a-zA-Z])|(\\/?[a-zA-Z][^>\\s]*)|(\\/?$))/g,m=/-->/g,a=/>/g,$=/>|[ \\t\\n\\f\\r](?:([^\\s\"'>=\\/]+)([ \\t\\n\\f\\r]*=[ \\t\\n\\f\\r]*(?:[^ \\t\\n\\f\\r\"'\\`<>=]|(\"|')|))|$)/g,c=/'/g,l=/\"/g,r=/^(?:script|style|textarea|title)$/i,M=(q)=>(D,...F)=>({_$litType$:q,strings:D,values:F}),Aq=M(1),Pq=M(2),Vq=M(3),V=Symbol.for(\"lit-noChange\"),B=Symbol.for(\"lit-nothing\"),n=new WeakMap,w=x.createTreeWalker(x,129);function o(q,D){if(!L(q)||!q.hasOwnProperty(\"raw\"))throw Error(\"invalid template strings array\");return p!==void 0?p.createHTML(D):D}var e=(q,D)=>{let F=q.length-1,J=[],G,O=D===2?\"<svg>\":D===3?\"<math>\":\"\",K=U;for(let W=0;W<F;W++){let Q=q[W],Y,X,b=-1,A=0;while(A<Q.length&&(K.lastIndex=A,X=K.exec(Q),X!==null))A=K.lastIndex,K===U?X[1]===\"!--\"?K=m:X[1]!==void 0?K=a:X[2]!==void 0?(r.test(X[2])&&(G=RegExp(\"</\"+X[2],\"g\")),K=$):X[3]!==void 0&&(K=$):K===$?X[0]===\">\"?(K=G??U,b=-1):X[1]===void 0?b=-2:(b=K.lastIndex-X[2].length,Y=X[1],K=X[3]===void 0?$:X[3]==='\"'?l:c):K===l||K===c?K=$:K===m||K===a?K=U:(K=$,G=void 0);const _=K===$&&q[W+1].startsWith(\"/>\")?\" \":\"\";O+=K===U?Q+Kq:b>=0?(J.push(Y),Q.slice(0,b)+\"$lit$\"+Q.slice(b)+P+_):Q+P+(b===-2?W:_)}return[o(q,O+(q[F]||\"<?>\")+(D===2?\"</svg>\":D===3?\"</math>\":\"\")),J]};class g{constructor({strings:q,_$litType$:D},F){let J;this.parts=[];let G=0,O=0,K=q.length-1,W=this.parts,[Q,Y]=e(q,D);if(this.el=g.createElement(Q,F),w.currentNode=this.el.content,D===2||D===3){const X=this.el.content.firstChild;X.replaceWith(...X.childNodes)}while((J=w.nextNode())!==null&&W.length<K){if(J.nodeType===1){if(J.hasAttributes())for(const X of J.getAttributeNames())if(X.endsWith(\"$lit$\")){const b=Y[O++],A=J.getAttribute(X).split(P),_=/([.?@])?(.*)/.exec(b);W.push({type:1,index:G,name:_[2],strings:A,ctor:_[1]===\".\"?R:_[1]===\"?\"?E:_[1]===\"@\"?d:I}),J.removeAttribute(X)}else X.startsWith(P)&&(W.push({type:6,index:G}),J.removeAttribute(X));if(r.test(J.tagName)){const X=J.textContent.split(P),b=X.length-1;if(b>0){J.textContent=y?y.emptyScript:\"\";for(let A=0;A<b;A++)J.append(X[A],C()),w.nextNode(),W.push({type:2,index:++G});J.append(X[b],C())}}}else if(J.nodeType===8)if(J.data===v)W.push({type:2,index:G});else{let X=-1;while((X=J.data.indexOf(P,X+1))!==-1)W.push({type:7,index:G}),X+=P.length-1}G++}}static createElement(q,D){const F=x.createElement(\"template\");return F.innerHTML=q,F}}function z(q,D,F=q,J){if(D===V)return D;let G=J!==void 0?F._$Co?.[J]:F._$Cl,O=S(D)?void 0:D._$litDirective$;return G?.constructor!==O&&(G?._$AO?.(!1),O===void 0?G=void 0:(G=new O(q),G._$AT(q,F,J)),J!==void 0?(F._$Co??=[])[J]=G:F._$Cl=G),G!==void 0&&(D=z(q,G._$AS(q,D.values),G,J)),D}class j{constructor(q,D){this._$AV=[],this._$AN=void 0,this._$AD=q,this._$AM=D}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(q){const{el:{content:D},parts:F}=this._$AD,J=(q?.creationScope??x).importNode(D,!0);w.currentNode=J;let G=w.nextNode(),O=0,K=0,W=F[0];while(W!==void 0){if(O===W.index){let Q;W.type===2?Q=new H(G,G.nextSibling,this,q):W.type===1?Q=new W.ctor(G,W.name,W.strings,this,q):W.type===6&&(Q=new f(G,this,q)),this._$AV.push(Q),W=F[++K]}O!==W?.index&&(G=w.nextNode(),O++)}return w.currentNode=x,J}p(q){let D=0;for(const F of this._$AV)F!==void 0&&(F.strings!==void 0?(F._$AI(q,F,D),D+=F.strings.length-2):F._$AI(q[D])),D++}}class H{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(q,D,F,J){this.type=2,this._$AH=B,this._$AN=void 0,this._$AA=q,this._$AB=D,this._$AM=F,this.options=J,this._$Cv=J?.isConnected??!0}get parentNode(){let q=this._$AA.parentNode,D=this._$AM;return D!==void 0&&q?.nodeType===11&&(q=D.parentNode),q}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(q,D=this){q=z(this,q,D),S(q)?q===B||q==null||q===\"\"?(this._$AH!==B&&this._$AR(),this._$AH=B):q!==this._$AH&&q!==V&&this._(q):q._$litType$!==void 0?this.$(q):q.nodeType!==void 0?this.T(q):h(q)?this.k(q):this._(q)}O(q){return this._$AA.parentNode.insertBefore(q,this._$AB)}T(q){this._$AH!==q&&(this._$AR(),this._$AH=this.O(q))}_(q){this._$AH!==B&&S(this._$AH)?this._$AA.nextSibling.data=q:this.T(x.createTextNode(q)),this._$AH=q}$(q){const{values:D,_$litType$:F}=q,J=typeof F==\"number\"?this._$AC(q):(F.el===void 0&&(F.el=g.createElement(o(F.h,F.h[0]),this.options)),F);if(this._$AH?._$AD===J)this._$AH.p(D);else{const G=new j(J,this),O=G.u(this.options);G.p(D),this.T(O),this._$AH=G}}_$AC(q){let D=n.get(q.strings);return D===void 0&&n.set(q.strings,D=new g(q)),D}k(q){L(this._$AH)||(this._$AH=[],this._$AR());let D=this._$AH,F,J=0;for(const G of q)J===D.length?D.push(F=new H(this.O(C()),this.O(C()),this,this.options)):F=D[J],F._$AI(G),J++;J<D.length&&(this._$AR(F&&F._$AB.nextSibling,J),D.length=J)}_$AR(q=this._$AA.nextSibling,D){for(this._$AP?.(!1,!0,D);q&&q!==this._$AB;){const F=q.nextSibling;q.remove(),q=F}}setConnected(q){this._$AM===void 0&&(this._$Cv=q,this._$AP?.(q))}}class I{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(q,D,F,J,G){this.type=1,this._$AH=B,this._$AN=void 0,this.element=q,this.name=D,this._$AM=J,this.options=G,F.length>2||F[0]!==\"\"||F[1]!==\"\"?(this._$AH=Array(F.length-1).fill(new String),this.strings=F):this._$AH=B}_$AI(q,D=this,F,J){let G=this.strings,O=!1;if(G===void 0)q=z(this,q,D,0),O=!S(q)||q!==this._$AH&&q!==V,O&&(this._$AH=q);else{let K=q,W,Q;for(q=G[0],W=0;W<G.length-1;W++)Q=z(this,K[F+W],D,W),Q===V&&(Q=this._$AH[W]),O||=!S(Q)||Q!==this._$AH[W],Q===B?q=B:q!==B&&(q+=(Q??\"\")+G[W+1]),this._$AH[W]=Q}O&&!J&&this.j(q)}j(q){q===B?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,q??\"\")}}class R extends I{constructor(){super(...arguments),this.type=3}j(q){this.element[this.name]=q===B?void 0:q}}class E extends I{constructor(){super(...arguments),this.type=4}j(q){this.element.toggleAttribute(this.name,!!q&&q!==B)}}class d extends I{constructor(q,D,F,J,G){super(q,D,F,J,G),this.type=5}_$AI(q,D=this){if((q=z(this,q,D,0)??B)===V)return;const F=this._$AH,J=q===B&&F!==B||q.capture!==F.capture||q.once!==F.once||q.passive!==F.passive,G=q!==B&&(F===B||J);J&&this.element.removeEventListener(this.name,this,F),G&&this.element.addEventListener(this.name,this,q),this._$AH=q}handleEvent(q){typeof this._$AH==\"function\"?this._$AH.call(this.options?.host??this.element,q):this._$AH.handleEvent(q)}}class f{constructor(q,D,F){this.element=q,this.type=6,this._$AN=void 0,this._$AM=D,this.options=F}get _$AU(){return this._$AM._$AU}_$AI(q){z(this,q)}}var Z={M:\"$lit$\",P,A:v,C:1,L:e,R:j,D:h,V:z,I:H,H:I,N:E,U:d,B:R,F:f},Oq=k.litHtmlPolyfillSupport;Oq?.(g,H),(k.litHtmlVersions??=[]).push(\"3.2.1\");var i=(q,D,F)=>{let J=F?.renderBefore??D,G=J._$litPart$;if(G===void 0){const O=F?.renderBefore??null;J._$litPart$=G=new H(D.insertBefore(C(),O),O,void 0,F??{})}return G._$AI(q),G};var u=null,s={boundAttributeSuffix:Z.M,marker:Z.P,markerMatch:Z.A,HTML_RESULT:Z.C,getTemplateHtml:Z.L,overrideDirectiveResolve:(q,D)=>class extends q{_$AS(F,J){return D(this,J)}},patchDirectiveResolve:(q,D)=>{if(q.prototype._$AS!==D){u??=q.prototype._$AS.name;for(let F=q.prototype;F!==Object.prototype;F=Object.getPrototypeOf(F))if(F.hasOwnProperty(u))return void(F[u]=D);throw Error(\"Internal error: It is possible that both dev mode and production mode Lit was mixed together during SSR. Please comment on the issue: https://github.com/lit/lit/issues/4527\")}},setDirectiveClass(q,D){q._$litDirective$=D},getAttributePartCommittedValue:(q,D,F)=>{let J=V;return q.j=(G)=>J=G,q._$AI(D,q,F),J},connectedDisconnectable:(q)=>({...q,_$AU:!0}),resolveDirective:Z.V,AttributePart:Z.H,PropertyPart:Z.B,BooleanAttributePart:Z.N,EventPart:Z.U,ElementPart:Z.F,TemplateInstance:Z.R,isIterable:Z.D,ChildPart:Z.I};var T={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6};var t=(q)=>q===null||typeof q!=\"object\"&&typeof q!=\"function\";var qq=(q,D)=>D===void 0?q?._$litType$!==void 0:q?._$litType$===D,Dq=(q)=>q?._$litType$?.h!=null;var Fq=(q)=>q.strings===void 0;var{TemplateInstance:Qq,isIterable:Wq,resolveDirective:Gq,ChildPart:N,ElementPart:Xq}=s,Jq=(q,D,F={})=>{if(D._$litPart$!==void 0)throw Error(\"container already contains a live render\");let J,G,O,K=[],W=document.createTreeWalker(D,NodeFilter.SHOW_COMMENT),Q;while((Q=W.nextNode())!==null){const Y=Q.data;if(Y.startsWith(\"lit-part\")){if(K.length===0&&J!==void 0)throw Error(`There must be only one root part per container. Found a part marker (${Q}) when we already have a root part marker (${G})`);O=Yq(q,Q,K,F),J===void 0&&(J=O),G??=Q}else if(Y.startsWith(\"lit-node\"))Zq(Q,K,F);else if(Y.startsWith(\"/lit-part\")){if(K.length===1&&O!==J)throw Error(\"internal error\");O=Bq(Q,O,K)}}if(J===void 0){const Y=D instanceof ShadowRoot?\"{container.host.localName}'s shadow root\":D instanceof DocumentFragment?\"DocumentFragment\":D.localName;console.error(`There should be exactly one root part in a render container, but we didn't find any in ${Y}.`)}D._$litPart$=J},Yq=(q,D,F,J)=>{let G,O;if(F.length===0)O=new N(D,null,void 0,J),G=q;else{const K=F[F.length-1];if(K.type===\"template-instance\")O=new N(D,null,K.instance,J),K.instance._$AV.push(O),G=K.result.values[K.instancePartIndex++],K.templatePartIndex++;else if(K.type===\"iterable\"){O=new N(D,null,K.part,J);const W=K.iterator.next();if(W.done)throw G=void 0,K.done=!0,Error(\"Unhandled shorter than expected iterable\");G=W.value,K.part._$AH.push(O)}else O=new N(D,null,K.part,J)}if(G=Gq(O,G),G===V)F.push({part:O,type:\"leaf\"});else if(t(G))F.push({part:O,type:\"leaf\"}),O._$AH=G;else if(qq(G)){if(Dq(G))throw Error(\"compiled templates are not supported\");const K=\"lit-part \"+bq(G);if(D.data!==K)throw Error(\"Hydration value mismatch: Unexpected TemplateResult rendered to part\");{const W=N.prototype._$AC(G),Q=new Qq(W,O);F.push({type:\"template-instance\",instance:Q,part:O,templatePartIndex:0,instancePartIndex:0,result:G}),O._$AH=Q}}else Wq(G)?(F.push({part:O,type:\"iterable\",value:G,iterator:G[Symbol.iterator](),done:!1}),O._$AH=[]):(F.push({part:O,type:\"leaf\"}),O._$AH=G??\"\");return O},Bq=(q,D,F)=>{if(D===void 0)throw Error(\"unbalanced part marker\");D._$AB=q;const J=F.pop();if(J.type===\"iterable\"&&!J.iterator.next().done)throw Error(\"unexpected longer than expected iterable\");if(F.length>0)return F[F.length-1].part},Zq=(q,D,F)=>{const J=/lit-node (\\d+)/.exec(q.data),G=Number.parseInt(J[1]),O=q.nextElementSibling;if(O===null)throw Error(\"could not find node for attribute parts\");O.removeAttribute(\"defer-hydration\");const K=D[D.length-1];if(K.type!==\"template-instance\")throw Error(\"Hydration value mismatch: Primitive found where TemplateResult expected. This usually occurs due to conditional rendering that resulted in a different value or template being rendered between the server and client.\");{const W=K.instance;for(;;){const Q=W._$AD.parts[K.templatePartIndex];if(Q===void 0||Q.type!==T.ATTRIBUTE&&Q.type!==T.ELEMENT||Q.index!==G)break;if(Q.type===T.ATTRIBUTE){const Y=new Q.ctor(O,Q.name,Q.strings,K.instance,F),X=Fq(Y)?K.result.values[K.instancePartIndex]:K.result.values,b=!(Y.type===T.EVENT||Y.type===T.PROPERTY);Y._$AI(X,Y,K.instancePartIndex,b),K.instancePartIndex+=Q.strings.length-1,W._$AV.push(Y)}else{const Y=new Xq(O,K.instance,F);Gq(Y,K.result.values[K.instancePartIndex++]),W._$AV.push(Y)}K.templatePartIndex++}}},bq=(q)=>{const D=new Uint32Array(2).fill(5381);for(const J of q.strings)for(let G=0;G<J.length;G++)D[G%2]=33*D[G%2]^J.charCodeAt(G);const F=String.fromCharCode(...new Uint8Array(D.buffer));return btoa(F)};globalThis.litElementHydrateSupport=({LitElement:q})=>{const D=Object.getOwnPropertyDescriptor(Object.getPrototypeOf(q),\"observedAttributes\").get;Object.defineProperty(q,\"observedAttributes\",{get(){return[...D.call(this),\"defer-hydration\"]}});const F=q.prototype.attributeChangedCallback;q.prototype.attributeChangedCallback=function(K,W,Q){K===\"defer-hydration\"&&Q===null&&J.call(this),F.call(this,K,W,Q)};const J=q.prototype.connectedCallback;q.prototype.connectedCallback=function(){this.hasAttribute(\"defer-hydration\")||J.call(this)};const G=q.prototype.createRenderRoot;q.prototype.createRenderRoot=function(){return this.shadowRoot?(this._$AG=!0,this.shadowRoot):G.call(this)};const O=Object.getPrototypeOf(q.prototype).update;q.prototype.update=function(K){const W=this.render();if(O.call(this,K),this._$AG){this._$AG=!1;for(let Q=0;Q<this.attributes.length;Q++){const Y=this.attributes[Q];if(Y.name.startsWith(\"hydrate-internals-\")){const X=Y.name.slice(18);this.removeAttribute(X),this.removeAttribute(Y.name)}}Jq(W,this.renderRoot,this.renderOptions)}else i(W,this.renderRoot,this.renderOptions)}};";
@@ -1 +1,11 @@
1
+ /**
2
+ * Pre-bundled Lit SSR hydration client runtime.
3
+ *
4
+ * @remarks
5
+ * Contains the compiled `@lit-labs/ssr-client/lit-element-hydrate-support.js`
6
+ * and `@lit-labs/ssr-client/lib/hydrate-lit-html.js` runtime. It defines
7
+ * `globalThis.litElementHydrateSupport`, which installs the `defer-hydration`
8
+ * observer and hooks into `LitElement.prototype.update` to hydrate existing
9
+ * server-rendered shadow roots instead of replacing them.
10
+ */
1
11
  export const litElementHydrateScript = 'var k=globalThis,y=k.trustedTypes,p=y?y.createPolicy("lit-html",{createHTML:(q)=>q}):void 0;var P=`lit$${Math.random().toFixed(9).slice(2)}$`,v="?"+P,Kq=`<${v}>`,x=document,C=()=>x.createComment(""),S=(q)=>q===null||typeof q!="object"&&typeof q!="function",L=Array.isArray,h=(q)=>L(q)||typeof q?.[Symbol.iterator]=="function";var U=/<(?:(!--|\\/[^a-zA-Z])|(\\/?[a-zA-Z][^>\\s]*)|(\\/?$))/g,m=/-->/g,a=/>/g,$=/>|[ \\t\\n\\f\\r](?:([^\\s"\'>=\\/]+)([ \\t\\n\\f\\r]*=[ \\t\\n\\f\\r]*(?:[^ \\t\\n\\f\\r"\'\\`<>=]|("|\')|))|$)/g,c=/\'/g,l=/"/g,r=/^(?:script|style|textarea|title)$/i,M=(q)=>(D,...F)=>({_$litType$:q,strings:D,values:F}),Aq=M(1),Pq=M(2),Vq=M(3),V=Symbol.for("lit-noChange"),B=Symbol.for("lit-nothing"),n=new WeakMap,w=x.createTreeWalker(x,129);function o(q,D){if(!L(q)||!q.hasOwnProperty("raw"))throw Error("invalid template strings array");return p!==void 0?p.createHTML(D):D}var e=(q,D)=>{let F=q.length-1,J=[],G,O=D===2?"<svg>":D===3?"<math>":"",K=U;for(let W=0;W<F;W++){let Q=q[W],Y,X,b=-1,A=0;while(A<Q.length&&(K.lastIndex=A,X=K.exec(Q),X!==null))A=K.lastIndex,K===U?X[1]==="!--"?K=m:X[1]!==void 0?K=a:X[2]!==void 0?(r.test(X[2])&&(G=RegExp("</"+X[2],"g")),K=$):X[3]!==void 0&&(K=$):K===$?X[0]===">"?(K=G??U,b=-1):X[1]===void 0?b=-2:(b=K.lastIndex-X[2].length,Y=X[1],K=X[3]===void 0?$:X[3]===\'"\'?l:c):K===l||K===c?K=$:K===m||K===a?K=U:(K=$,G=void 0);const _=K===$&&q[W+1].startsWith("/>")?" ":"";O+=K===U?Q+Kq:b>=0?(J.push(Y),Q.slice(0,b)+"$lit$"+Q.slice(b)+P+_):Q+P+(b===-2?W:_)}return[o(q,O+(q[F]||"<?>")+(D===2?"</svg>":D===3?"</math>":"")),J]};class g{constructor({strings:q,_$litType$:D},F){let J;this.parts=[];let G=0,O=0,K=q.length-1,W=this.parts,[Q,Y]=e(q,D);if(this.el=g.createElement(Q,F),w.currentNode=this.el.content,D===2||D===3){const X=this.el.content.firstChild;X.replaceWith(...X.childNodes)}while((J=w.nextNode())!==null&&W.length<K){if(J.nodeType===1){if(J.hasAttributes())for(const X of J.getAttributeNames())if(X.endsWith("$lit$")){const b=Y[O++],A=J.getAttribute(X).split(P),_=/([.?@])?(.*)/.exec(b);W.push({type:1,index:G,name:_[2],strings:A,ctor:_[1]==="."?R:_[1]==="?"?E:_[1]==="@"?d:I}),J.removeAttribute(X)}else X.startsWith(P)&&(W.push({type:6,index:G}),J.removeAttribute(X));if(r.test(J.tagName)){const X=J.textContent.split(P),b=X.length-1;if(b>0){J.textContent=y?y.emptyScript:"";for(let A=0;A<b;A++)J.append(X[A],C()),w.nextNode(),W.push({type:2,index:++G});J.append(X[b],C())}}}else if(J.nodeType===8)if(J.data===v)W.push({type:2,index:G});else{let X=-1;while((X=J.data.indexOf(P,X+1))!==-1)W.push({type:7,index:G}),X+=P.length-1}G++}}static createElement(q,D){const F=x.createElement("template");return F.innerHTML=q,F}}function z(q,D,F=q,J){if(D===V)return D;let G=J!==void 0?F._$Co?.[J]:F._$Cl,O=S(D)?void 0:D._$litDirective$;return G?.constructor!==O&&(G?._$AO?.(!1),O===void 0?G=void 0:(G=new O(q),G._$AT(q,F,J)),J!==void 0?(F._$Co??=[])[J]=G:F._$Cl=G),G!==void 0&&(D=z(q,G._$AS(q,D.values),G,J)),D}class j{constructor(q,D){this._$AV=[],this._$AN=void 0,this._$AD=q,this._$AM=D}get parentNode(){return this._$AM.parentNode}get _$AU(){return this._$AM._$AU}u(q){const{el:{content:D},parts:F}=this._$AD,J=(q?.creationScope??x).importNode(D,!0);w.currentNode=J;let G=w.nextNode(),O=0,K=0,W=F[0];while(W!==void 0){if(O===W.index){let Q;W.type===2?Q=new H(G,G.nextSibling,this,q):W.type===1?Q=new W.ctor(G,W.name,W.strings,this,q):W.type===6&&(Q=new f(G,this,q)),this._$AV.push(Q),W=F[++K]}O!==W?.index&&(G=w.nextNode(),O++)}return w.currentNode=x,J}p(q){let D=0;for(const F of this._$AV)F!==void 0&&(F.strings!==void 0?(F._$AI(q,F,D),D+=F.strings.length-2):F._$AI(q[D])),D++}}class H{get _$AU(){return this._$AM?._$AU??this._$Cv}constructor(q,D,F,J){this.type=2,this._$AH=B,this._$AN=void 0,this._$AA=q,this._$AB=D,this._$AM=F,this.options=J,this._$Cv=J?.isConnected??!0}get parentNode(){let q=this._$AA.parentNode,D=this._$AM;return D!==void 0&&q?.nodeType===11&&(q=D.parentNode),q}get startNode(){return this._$AA}get endNode(){return this._$AB}_$AI(q,D=this){q=z(this,q,D),S(q)?q===B||q==null||q===""?(this._$AH!==B&&this._$AR(),this._$AH=B):q!==this._$AH&&q!==V&&this._(q):q._$litType$!==void 0?this.$(q):q.nodeType!==void 0?this.T(q):h(q)?this.k(q):this._(q)}O(q){return this._$AA.parentNode.insertBefore(q,this._$AB)}T(q){this._$AH!==q&&(this._$AR(),this._$AH=this.O(q))}_(q){this._$AH!==B&&S(this._$AH)?this._$AA.nextSibling.data=q:this.T(x.createTextNode(q)),this._$AH=q}$(q){const{values:D,_$litType$:F}=q,J=typeof F=="number"?this._$AC(q):(F.el===void 0&&(F.el=g.createElement(o(F.h,F.h[0]),this.options)),F);if(this._$AH?._$AD===J)this._$AH.p(D);else{const G=new j(J,this),O=G.u(this.options);G.p(D),this.T(O),this._$AH=G}}_$AC(q){let D=n.get(q.strings);return D===void 0&&n.set(q.strings,D=new g(q)),D}k(q){L(this._$AH)||(this._$AH=[],this._$AR());let D=this._$AH,F,J=0;for(const G of q)J===D.length?D.push(F=new H(this.O(C()),this.O(C()),this,this.options)):F=D[J],F._$AI(G),J++;J<D.length&&(this._$AR(F&&F._$AB.nextSibling,J),D.length=J)}_$AR(q=this._$AA.nextSibling,D){for(this._$AP?.(!1,!0,D);q&&q!==this._$AB;){const F=q.nextSibling;q.remove(),q=F}}setConnected(q){this._$AM===void 0&&(this._$Cv=q,this._$AP?.(q))}}class I{get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}constructor(q,D,F,J,G){this.type=1,this._$AH=B,this._$AN=void 0,this.element=q,this.name=D,this._$AM=J,this.options=G,F.length>2||F[0]!==""||F[1]!==""?(this._$AH=Array(F.length-1).fill(new String),this.strings=F):this._$AH=B}_$AI(q,D=this,F,J){let G=this.strings,O=!1;if(G===void 0)q=z(this,q,D,0),O=!S(q)||q!==this._$AH&&q!==V,O&&(this._$AH=q);else{let K=q,W,Q;for(q=G[0],W=0;W<G.length-1;W++)Q=z(this,K[F+W],D,W),Q===V&&(Q=this._$AH[W]),O||=!S(Q)||Q!==this._$AH[W],Q===B?q=B:q!==B&&(q+=(Q??"")+G[W+1]),this._$AH[W]=Q}O&&!J&&this.j(q)}j(q){q===B?this.element.removeAttribute(this.name):this.element.setAttribute(this.name,q??"")}}class R extends I{constructor(){super(...arguments),this.type=3}j(q){this.element[this.name]=q===B?void 0:q}}class E extends I{constructor(){super(...arguments),this.type=4}j(q){this.element.toggleAttribute(this.name,!!q&&q!==B)}}class d extends I{constructor(q,D,F,J,G){super(q,D,F,J,G),this.type=5}_$AI(q,D=this){if((q=z(this,q,D,0)??B)===V)return;const F=this._$AH,J=q===B&&F!==B||q.capture!==F.capture||q.once!==F.once||q.passive!==F.passive,G=q!==B&&(F===B||J);J&&this.element.removeEventListener(this.name,this,F),G&&this.element.addEventListener(this.name,this,q),this._$AH=q}handleEvent(q){typeof this._$AH=="function"?this._$AH.call(this.options?.host??this.element,q):this._$AH.handleEvent(q)}}class f{constructor(q,D,F){this.element=q,this.type=6,this._$AN=void 0,this._$AM=D,this.options=F}get _$AU(){return this._$AM._$AU}_$AI(q){z(this,q)}}var Z={M:"$lit$",P,A:v,C:1,L:e,R:j,D:h,V:z,I:H,H:I,N:E,U:d,B:R,F:f},Oq=k.litHtmlPolyfillSupport;Oq?.(g,H),(k.litHtmlVersions??=[]).push("3.2.1");var i=(q,D,F)=>{let J=F?.renderBefore??D,G=J._$litPart$;if(G===void 0){const O=F?.renderBefore??null;J._$litPart$=G=new H(D.insertBefore(C(),O),O,void 0,F??{})}return G._$AI(q),G};var u=null,s={boundAttributeSuffix:Z.M,marker:Z.P,markerMatch:Z.A,HTML_RESULT:Z.C,getTemplateHtml:Z.L,overrideDirectiveResolve:(q,D)=>class extends q{_$AS(F,J){return D(this,J)}},patchDirectiveResolve:(q,D)=>{if(q.prototype._$AS!==D){u??=q.prototype._$AS.name;for(let F=q.prototype;F!==Object.prototype;F=Object.getPrototypeOf(F))if(F.hasOwnProperty(u))return void(F[u]=D);throw Error("Internal error: It is possible that both dev mode and production mode Lit was mixed together during SSR. Please comment on the issue: https://github.com/lit/lit/issues/4527")}},setDirectiveClass(q,D){q._$litDirective$=D},getAttributePartCommittedValue:(q,D,F)=>{let J=V;return q.j=(G)=>J=G,q._$AI(D,q,F),J},connectedDisconnectable:(q)=>({...q,_$AU:!0}),resolveDirective:Z.V,AttributePart:Z.H,PropertyPart:Z.B,BooleanAttributePart:Z.N,EventPart:Z.U,ElementPart:Z.F,TemplateInstance:Z.R,isIterable:Z.D,ChildPart:Z.I};var T={ATTRIBUTE:1,CHILD:2,PROPERTY:3,BOOLEAN_ATTRIBUTE:4,EVENT:5,ELEMENT:6};var t=(q)=>q===null||typeof q!="object"&&typeof q!="function";var qq=(q,D)=>D===void 0?q?._$litType$!==void 0:q?._$litType$===D,Dq=(q)=>q?._$litType$?.h!=null;var Fq=(q)=>q.strings===void 0;var{TemplateInstance:Qq,isIterable:Wq,resolveDirective:Gq,ChildPart:N,ElementPart:Xq}=s,Jq=(q,D,F={})=>{if(D._$litPart$!==void 0)throw Error("container already contains a live render");let J,G,O,K=[],W=document.createTreeWalker(D,NodeFilter.SHOW_COMMENT),Q;while((Q=W.nextNode())!==null){const Y=Q.data;if(Y.startsWith("lit-part")){if(K.length===0&&J!==void 0)throw Error(`There must be only one root part per container. Found a part marker (${Q}) when we already have a root part marker (${G})`);O=Yq(q,Q,K,F),J===void 0&&(J=O),G??=Q}else if(Y.startsWith("lit-node"))Zq(Q,K,F);else if(Y.startsWith("/lit-part")){if(K.length===1&&O!==J)throw Error("internal error");O=Bq(Q,O,K)}}if(J===void 0){const Y=D instanceof ShadowRoot?"{container.host.localName}\'s shadow root":D instanceof DocumentFragment?"DocumentFragment":D.localName;console.error(`There should be exactly one root part in a render container, but we didn\'t find any in ${Y}.`)}D._$litPart$=J},Yq=(q,D,F,J)=>{let G,O;if(F.length===0)O=new N(D,null,void 0,J),G=q;else{const K=F[F.length-1];if(K.type==="template-instance")O=new N(D,null,K.instance,J),K.instance._$AV.push(O),G=K.result.values[K.instancePartIndex++],K.templatePartIndex++;else if(K.type==="iterable"){O=new N(D,null,K.part,J);const W=K.iterator.next();if(W.done)throw G=void 0,K.done=!0,Error("Unhandled shorter than expected iterable");G=W.value,K.part._$AH.push(O)}else O=new N(D,null,K.part,J)}if(G=Gq(O,G),G===V)F.push({part:O,type:"leaf"});else if(t(G))F.push({part:O,type:"leaf"}),O._$AH=G;else if(qq(G)){if(Dq(G))throw Error("compiled templates are not supported");const K="lit-part "+bq(G);if(D.data!==K)throw Error("Hydration value mismatch: Unexpected TemplateResult rendered to part");{const W=N.prototype._$AC(G),Q=new Qq(W,O);F.push({type:"template-instance",instance:Q,part:O,templatePartIndex:0,instancePartIndex:0,result:G}),O._$AH=Q}}else Wq(G)?(F.push({part:O,type:"iterable",value:G,iterator:G[Symbol.iterator](),done:!1}),O._$AH=[]):(F.push({part:O,type:"leaf"}),O._$AH=G??"");return O},Bq=(q,D,F)=>{if(D===void 0)throw Error("unbalanced part marker");D._$AB=q;const J=F.pop();if(J.type==="iterable"&&!J.iterator.next().done)throw Error("unexpected longer than expected iterable");if(F.length>0)return F[F.length-1].part},Zq=(q,D,F)=>{const J=/lit-node (\\d+)/.exec(q.data),G=Number.parseInt(J[1]),O=q.nextElementSibling;if(O===null)throw Error("could not find node for attribute parts");O.removeAttribute("defer-hydration");const K=D[D.length-1];if(K.type!=="template-instance")throw Error("Hydration value mismatch: Primitive found where TemplateResult expected. This usually occurs due to conditional rendering that resulted in a different value or template being rendered between the server and client.");{const W=K.instance;for(;;){const Q=W._$AD.parts[K.templatePartIndex];if(Q===void 0||Q.type!==T.ATTRIBUTE&&Q.type!==T.ELEMENT||Q.index!==G)break;if(Q.type===T.ATTRIBUTE){const Y=new Q.ctor(O,Q.name,Q.strings,K.instance,F),X=Fq(Y)?K.result.values[K.instancePartIndex]:K.result.values,b=!(Y.type===T.EVENT||Y.type===T.PROPERTY);Y._$AI(X,Y,K.instancePartIndex,b),K.instancePartIndex+=Q.strings.length-1,W._$AV.push(Y)}else{const Y=new Xq(O,K.instance,F);Gq(Y,K.result.values[K.instancePartIndex++]),W._$AV.push(Y)}K.templatePartIndex++}}},bq=(q)=>{const D=new Uint32Array(2).fill(5381);for(const J of q.strings)for(let G=0;G<J.length;G++)D[G%2]=33*D[G%2]^J.charCodeAt(G);const F=String.fromCharCode(...new Uint8Array(D.buffer));return btoa(F)};globalThis.litElementHydrateSupport=({LitElement:q})=>{const D=Object.getOwnPropertyDescriptor(Object.getPrototypeOf(q),"observedAttributes").get;Object.defineProperty(q,"observedAttributes",{get(){return[...D.call(this),"defer-hydration"]}});const F=q.prototype.attributeChangedCallback;q.prototype.attributeChangedCallback=function(K,W,Q){K==="defer-hydration"&&Q===null&&J.call(this),F.call(this,K,W,Q)};const J=q.prototype.connectedCallback;q.prototype.connectedCallback=function(){this.hasAttribute("defer-hydration")||J.call(this)};const G=q.prototype.createRenderRoot;q.prototype.createRenderRoot=function(){return this.shadowRoot?(this._$AG=!0,this.shadowRoot):G.call(this)};const O=Object.getPrototypeOf(q.prototype).update;q.prototype.update=function(K){const W=this.render();if(O.call(this,K),this._$AG){this._$AG=!1;for(let Q=0;Q<this.attributes.length;Q++){const Y=this.attributes[Q];if(Y.name.startsWith("hydrate-internals-")){const X=Y.name.slice(18);this.removeAttribute(X),this.removeAttribute(Y.name)}}Jq(W,this.renderRoot,this.renderOptions)}else i(W,this.renderRoot,this.renderOptions)}};';
@@ -3,8 +3,8 @@
3
3
  * @module
4
4
  */
5
5
  import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPagesElement, IntegrationRendererRenderOptions, RouteRenderResult, RouteRendererBody, RouteRendererOptions } from '@ecopages/core';
6
- import './dom-shim.js';
7
6
  import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
7
+ import { type CustomElementSsrPreloadComponent } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
8
8
  import type { LitStaticRenderSession } from './lit-static-render-session.js';
9
9
  export type LitRendererOptions = ConstructorParameters<typeof IntegrationRenderer>[0] & {
10
10
  /**
@@ -23,6 +23,7 @@ export type LitRendererOptions = ConstructorParameters<typeof IntegrationRendere
23
23
  export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
24
24
  name: string;
25
25
  private readonly getRenderSession?;
26
+ private readonly ssrScriptPreloader;
26
27
  constructor(options: LitRendererOptions);
27
28
  /**
28
29
  * Renders a Lit page route, preferring the plugin worker when available.
@@ -40,14 +41,13 @@ export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
40
41
  /**
41
42
  * Renders a Lit component for component-level orchestration.
42
43
  *
43
- * SSR-eligible lazy scripts are preloaded first so custom elements registered
44
+ * SSR-eligible scripts are preloaded first so custom elements registered
44
45
  * by the component can render their server markup even when the Lit renderer is
45
46
  * entered through cross-integration foreign-child handoff.
46
47
  *
47
48
  * Includes component-scoped dependency assets when declared.
48
49
  */
49
50
  renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
50
- private readonly ssrLazyPreloader;
51
51
  /**
52
52
  * Detects preload failures that are expected for browser-only modules.
53
53
  *
@@ -56,24 +56,24 @@ export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
56
56
  */
57
57
  protected isExpectedSsrPreloadError(error: unknown): boolean;
58
58
  /**
59
- * Collects lazy script file paths eligible for SSR preloading.
59
+ * Collects script file paths eligible for SSR preloading.
60
60
  *
61
- * Only per-entry lazy script dependencies with `ssr: true` are collected.
61
+ * Per-entry script dependencies with `ssr: true` are collected (eager or lazy).
62
62
  * File-backed entries are required (`src` must be present);
63
63
  * inline content lazy entries are intentionally skipped.
64
64
  */
65
- protected collectSsrPreloadScripts(components: Array<EcoComponent | undefined>): string[];
65
+ protected collectSsrPreloadScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): string[];
66
66
  /**
67
- * Preloads SSR-eligible lazy scripts to register custom elements before render.
68
- */
69
- protected preloadSsrLazyScripts(components: Array<EcoComponent | undefined>): Promise<void>;
70
- /**
71
- * Resolves the concrete JS entrypoint used for SSR preloading.
67
+ * Preloads SSR-eligible scripts to register custom elements before render.
72
68
  *
73
- * Scripts are passed through the asset pipeline so preload imports can use
74
- * the same processed output shape as runtime dependencies.
69
+ * @remarks
70
+ * When a render session exists (main thread), preload uses the session so
71
+ * Bun source imports and the Node asset pipeline stay on that isolate.
72
+ * With no session (static-render worker), preload uses this renderer's
73
+ * app-module importer. A native source import in the worker leaves custom
74
+ * elements undefined for `@lit-labs/ssr`.
75
75
  */
76
- protected resolveSsrPreloadEntrypoint(scriptPath: string): Promise<string | null>;
77
- render({ params, query, props, locals, metadata, Page, Layout, layoutEntries, HtmlTemplate, }: IntegrationRendererRenderOptions): Promise<RouteRendererBody>;
76
+ protected preloadSsrScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): Promise<void>;
77
+ render({ params, query, props, locals, metadata, Page, Layout, layoutEntries, HtmlTemplate, resolvedPageDependencyComponents, }: IntegrationRendererRenderOptions): Promise<RouteRendererBody>;
78
78
  renderToResponse<P = Record<string, unknown>>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
79
79
  }
@@ -1,9 +1,13 @@
1
- import { createLitServerModuleImporter } from './lit-server-module-loader.js';
2
- import './dom-shim.js';
1
+ /**
2
+ * This module contains the Lit renderer
3
+ * @module
4
+ */
3
5
  import { IntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
4
6
  import { resolveDocumentShellLayouts } from '@ecopages/core/route-renderer/orchestration/document-shell/layout-shell-props.service';
5
7
  import { getForeignSubtreeResolutionContextKey, resolveOwningIntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/foreign-child/owning-renderer-resolution';
6
- import { LitSsrLazyPreloader } from './lit-ssr-lazy-preloader.js';
8
+ import { ensureLitDomShim } from './dom-shim.js';
9
+ import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
10
+ import { createCustomElementServerModuleImporter } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-server-module-importer';
7
11
  import { LIT_PLUGIN_NAME } from './lit.constants.js';
8
12
  import { injectLitRenderedChildren, LIT_COMPONENT_CHILDREN_SLOT_MARKER, normalizeLitHtml, renderLitValueToString, } from './utils/lit-html-rendering.js';
9
13
  /**
@@ -12,10 +16,18 @@ import { injectLitRenderedChildren, LIT_COMPONENT_CHILDREN_SLOT_MARKER, normaliz
12
16
  export class LitRenderer extends IntegrationRenderer {
13
17
  name = LIT_PLUGIN_NAME;
14
18
  getRenderSession;
19
+ ssrScriptPreloader;
15
20
  constructor(options) {
16
21
  const { getRenderSession, ...rendererOptions } = options;
17
22
  super(rendererOptions);
18
23
  this.getRenderSession = getRenderSession;
24
+ this.ssrScriptPreloader = new CustomElementScriptPreloader({
25
+ cacheScope: CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit,
26
+ logLabel: 'lit',
27
+ requireLazyScriptEntry: true,
28
+ resolveDependencyPath: this.resolveDependencyPath.bind(this),
29
+ importServerModule: createCustomElementServerModuleImporter(this.appConfig, '.lit-ssr'),
30
+ });
19
31
  }
20
32
  /**
21
33
  * Renders a Lit page route, preferring the plugin worker when available.
@@ -73,14 +85,14 @@ export class LitRenderer extends IntegrationRenderer {
73
85
  /**
74
86
  * Renders a Lit component for component-level orchestration.
75
87
  *
76
- * SSR-eligible lazy scripts are preloaded first so custom elements registered
88
+ * SSR-eligible scripts are preloaded first so custom elements registered
77
89
  * by the component can render their server markup even when the Lit renderer is
78
90
  * entered through cross-integration foreign-child handoff.
79
91
  *
80
92
  * Includes component-scoped dependency assets when declared.
81
93
  */
82
94
  async renderComponent(input) {
83
- await this.preloadSsrLazyScripts([input.component]);
95
+ await this.preloadSsrScripts([input.component]);
84
96
  if (!this.isFunctionComponent(input.component)) {
85
97
  throw new TypeError('Lit renderer expected a callable component.');
86
98
  }
@@ -133,11 +145,6 @@ export class LitRenderer extends IntegrationRenderer {
133
145
  ]),
134
146
  });
135
147
  }
136
- ssrLazyPreloader = new LitSsrLazyPreloader({
137
- importServerModule: createLitServerModuleImporter(this.appConfig),
138
- resolveDependencyPath: this.resolveDependencyPath.bind(this),
139
- processDependencies: this.assetProcessingService?.processDependencies?.bind(this.assetProcessingService),
140
- });
141
148
  /**
142
149
  * Detects preload failures that are expected for browser-only modules.
143
150
  *
@@ -145,41 +152,40 @@ export class LitRenderer extends IntegrationRenderer {
145
152
  * lazy client scripts intentionally depend on browser globals.
146
153
  */
147
154
  isExpectedSsrPreloadError(error) {
148
- return this.ssrLazyPreloader.isExpectedSsrPreloadError(error);
155
+ return this.ssrScriptPreloader.isExpectedSsrPreloadError(error);
149
156
  }
150
157
  /**
151
- * Collects lazy script file paths eligible for SSR preloading.
158
+ * Collects script file paths eligible for SSR preloading.
152
159
  *
153
- * Only per-entry lazy script dependencies with `ssr: true` are collected.
160
+ * Per-entry script dependencies with `ssr: true` are collected (eager or lazy).
154
161
  * File-backed entries are required (`src` must be present);
155
162
  * inline content lazy entries are intentionally skipped.
156
163
  */
157
164
  collectSsrPreloadScripts(components) {
158
- return this.ssrLazyPreloader.collectSsrPreloadScripts(components);
165
+ return this.ssrScriptPreloader.collectSsrPreloadScripts(components);
159
166
  }
160
167
  /**
161
- * Preloads SSR-eligible lazy scripts to register custom elements before render.
168
+ * Preloads SSR-eligible scripts to register custom elements before render.
169
+ *
170
+ * @remarks
171
+ * When a render session exists (main thread), preload uses the session so
172
+ * Bun source imports and the Node asset pipeline stay on that isolate.
173
+ * With no session (static-render worker), preload uses this renderer's
174
+ * app-module importer. A native source import in the worker leaves custom
175
+ * elements undefined for `@lit-labs/ssr`.
162
176
  */
163
- async preloadSsrLazyScripts(components) {
177
+ async preloadSsrScripts(components) {
178
+ ensureLitDomShim();
164
179
  const renderSession = this.getRenderSession?.();
165
180
  if (renderSession) {
166
- await renderSession.preloadSsrLazyScripts(components);
181
+ await renderSession.preloadSsrScripts(components);
167
182
  return;
168
183
  }
169
- await this.ssrLazyPreloader.preloadSsrLazyScripts(components);
170
- }
171
- /**
172
- * Resolves the concrete JS entrypoint used for SSR preloading.
173
- *
174
- * Scripts are passed through the asset pipeline so preload imports can use
175
- * the same processed output shape as runtime dependencies.
176
- */
177
- async resolveSsrPreloadEntrypoint(scriptPath) {
178
- return this.ssrLazyPreloader.resolveSsrPreloadEntrypoint(scriptPath);
184
+ await this.ssrScriptPreloader.preloadSsrScripts(components);
179
185
  }
180
- async render({ params, query, props, locals, metadata, Page, Layout, layoutEntries, HtmlTemplate, }) {
186
+ async render({ params, query, props, locals, metadata, Page, Layout, layoutEntries, HtmlTemplate, resolvedPageDependencyComponents, }) {
181
187
  try {
182
- await this.preloadSsrLazyScripts([Page, Layout]);
188
+ await this.preloadSsrScripts([Page, Layout, ...(resolvedPageDependencyComponents ?? [])]);
183
189
  return await this.renderPageWithDocumentShell({
184
190
  page: {
185
191
  component: Page,
@@ -200,6 +206,7 @@ export class LitRenderer extends IntegrationRenderer {
200
206
  htmlTemplate: HtmlTemplate,
201
207
  metadata,
202
208
  pageProps: props || {},
209
+ foreignChildRoots: resolvedPageDependencyComponents,
203
210
  transformDocumentHtml: normalizeLitHtml,
204
211
  });
205
212
  }
@@ -211,7 +218,7 @@ export class LitRenderer extends IntegrationRenderer {
211
218
  try {
212
219
  const layouts = view.config?.layouts;
213
220
  const Layout = layouts?.[layouts.length - 1];
214
- await this.preloadSsrLazyScripts([view, Layout]);
221
+ await this.preloadSsrScripts([view, Layout]);
215
222
  return await this.renderViewWithDocumentShell({
216
223
  view,
217
224
  props,
@@ -0,0 +1 @@
1
+ export { createCustomElementSsrPreloadEntrypointResolver as createLitSsrPreloadEntrypointResolver, type CustomElementSsrPreloadEntrypointResolverOptions as LitSsrPreloadEntrypointResolverOptions, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-ssr-preload-entrypoint';
@@ -0,0 +1 @@
1
+ export { createCustomElementSsrPreloadEntrypointResolver as createLitSsrPreloadEntrypointResolver, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-ssr-preload-entrypoint';
@@ -1,17 +1,15 @@
1
- import type { EcoComponent, PageParams, PageQuery } from '@ecopages/core';
1
+ import type { PageParams, PageQuery } from '@ecopages/core';
2
2
  import type { StaticExportContext } from '@ecopages/core/plugins/integration-plugin';
3
- import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
4
- import { type LitSsrLazyPreloaderOptions } from './lit-ssr-lazy-preloader.js';
3
+ import { type CustomElementScriptPreloaderOptions, type CustomElementSsrPreloadComponent } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
5
4
  import { LitStaticRenderWorkerClient } from './lit-static-render-worker-client.js';
6
5
  import type { LitStaticRenderCacheStrategy } from './lit-static-render-protocol.js';
6
+ import type { AssetDefinition, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
7
7
  type LitStaticRenderWorkerClientContract = Pick<LitStaticRenderWorkerClient, 'start' | 'renderPage' | 'dispose'>;
8
8
  type LitStaticRenderSessionOptions = {
9
9
  resolveDependencyPath: (componentDir: string, sourcePath: string) => string;
10
- processDependencies?: (dependencies: AssetDefinition[], integrationName: string) => Promise<Array<{
11
- filepath?: string;
12
- }>>;
13
10
  preferSourceImports?: boolean;
14
- importServerModule?: LitSsrLazyPreloaderOptions['importServerModule'];
11
+ processDependencies?: (dependencies: AssetDefinition[], integrationName: string) => Promise<ProcessedAsset[]>;
12
+ importServerModule?: CustomElementScriptPreloaderOptions['importServerModule'];
15
13
  getInvalidationVersion?: () => number;
16
14
  createWorkerClient?: (input: {
17
15
  configModulePath: string;
@@ -51,8 +49,8 @@ export declare class LitStaticRenderSession {
51
49
  runtimeOrigin: string;
52
50
  }): Promise<void>;
53
51
  preloadStaticRoutes(context: StaticExportContext): Promise<void>;
54
- preloadSsrLazyScripts(components: Array<EcoComponent | undefined>): Promise<void>;
55
- collectSsrPreloadScripts(components: Array<EcoComponent | undefined>): string[];
52
+ preloadSsrScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): Promise<void>;
53
+ collectSsrPreloadScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): string[];
56
54
  renderPageInWorker(input: {
57
55
  filePath: string;
58
56
  params: PageParams;
@@ -1,6 +1,8 @@
1
1
  import path from 'node:path';
2
- import { LitSsrLazyPreloader } from './lit-ssr-lazy-preloader.js';
2
+ import { ensureLitDomShim } from './dom-shim.js';
3
+ import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
3
4
  import { LIT_PLUGIN_NAME } from './lit.constants.js';
5
+ import { createLitSsrPreloadEntrypointResolver } from './lit-ssr-preload-entrypoint.js';
4
6
  import { LitStaticRenderWorkerClient } from './lit-static-render-worker-client.js';
5
7
  /**
6
8
  * Plugin-runtime-scoped Lit server render session.
@@ -24,10 +26,17 @@ export class LitStaticRenderSession {
24
26
  renderChain = Promise.resolve();
25
27
  constructor(options) {
26
28
  this.getInvalidationVersion = options.getInvalidationVersion ?? (() => 0);
27
- this.preloader = new LitSsrLazyPreloader({
29
+ const preferSourceImports = options.preferSourceImports ?? typeof Bun !== 'undefined';
30
+ this.preloader = new CustomElementScriptPreloader({
31
+ cacheScope: CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit,
32
+ logLabel: 'lit',
33
+ requireLazyScriptEntry: true,
28
34
  resolveDependencyPath: options.resolveDependencyPath,
29
- processDependencies: options.processDependencies,
30
- preferSourceImports: options.preferSourceImports,
35
+ preferSourceImports,
36
+ resolvePreloadEntrypoint: createLitSsrPreloadEntrypointResolver({
37
+ preferSourceImports,
38
+ processDependencies: options.processDependencies,
39
+ }),
31
40
  importServerModule: options.importServerModule,
32
41
  });
33
42
  this.createWorkerClient =
@@ -68,8 +77,9 @@ export class LitStaticRenderSession {
68
77
  async preloadStaticRoutes(context) {
69
78
  await this.preloadLitRoutes(context);
70
79
  }
71
- async preloadSsrLazyScripts(components) {
72
- await this.preloader.preloadSsrLazyScripts(components);
80
+ async preloadSsrScripts(components) {
81
+ ensureLitDomShim();
82
+ await this.preloader.preloadSsrScripts(components);
73
83
  }
74
84
  collectSsrPreloadScripts(components) {
75
85
  return this.preloader.collectSsrPreloadScripts(components);
@@ -124,7 +134,7 @@ export class LitStaticRenderSession {
124
134
  }
125
135
  }
126
136
  }
127
- await this.preloadSsrLazyScripts(components);
137
+ await this.preloadSsrScripts(components);
128
138
  }
129
139
  isLitRoute(filePath, appConfig) {
130
140
  const litIntegration = appConfig.integrations.find((plugin) => plugin.name === LIT_PLUGIN_NAME);
@@ -1,3 +1,4 @@
1
+ import type { IHmrManager } from '@ecopages/core';
1
2
  /**
2
3
  * This module contains the Lit plugin
3
4
  * @module
@@ -23,11 +24,22 @@ export declare class LitPlugin extends IntegrationPlugin {
23
24
  renderer: typeof LitRenderer;
24
25
  private renderSession;
25
26
  constructor(options?: Omit<IntegrationPluginConfig, 'name'>);
27
+ /**
28
+ * Returns the global integration dependencies required in the browser.
29
+ *
30
+ * @remarks
31
+ * Injects Lit's hydration support script into the document head before any
32
+ * custom element connects. While `@lit-labs/ssr-client/lit-element-hydrate-support.js`
33
+ * can be resolved directly in modern Bun and Node environments, an inline self-contained
34
+ * script is currently emitted to ensure synchronous availability before custom-element
35
+ * registration without requiring an import map for `@lit-labs/ssr-client` subpath imports.
36
+ */
26
37
  getDependencies(): AssetDefinition[];
27
38
  private createRenderSession;
28
39
  initializeRenderer(options?: {
29
40
  rendererModules?: unknown;
30
41
  }): LitRenderer;
42
+ setHmrManager(hmrManager: IHmrManager): void;
31
43
  setup(): Promise<void>;
32
44
  teardown(): Promise<void>;
33
45
  beforeStaticExport(context: StaticExportContext): Promise<void>;
package/src/lit.plugin.js CHANGED
@@ -1,4 +1,5 @@
1
- import { createLitServerModuleImporter } from './lit-server-module-loader.js';
1
+ import { createCustomElementServerModuleImporter } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-server-module-importer';
2
+ import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, invalidateCustomElementScriptPreload, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
2
3
  /**
3
4
  * This module contains the Lit plugin
4
5
  * @module
@@ -36,20 +37,18 @@ export class LitPlugin extends IntegrationPlugin {
36
37
  });
37
38
  this.integrationDependencies.unshift(...this.getDependencies());
38
39
  }
40
+ /**
41
+ * Returns the global integration dependencies required in the browser.
42
+ *
43
+ * @remarks
44
+ * Injects Lit's hydration support script into the document head before any
45
+ * custom element connects. While `@lit-labs/ssr-client/lit-element-hydrate-support.js`
46
+ * can be resolved directly in modern Bun and Node environments, an inline self-contained
47
+ * script is currently emitted to ensure synchronous availability before custom-element
48
+ * registration without requiring an import map for `@lit-labs/ssr-client` subpath imports.
49
+ */
39
50
  getDependencies() {
40
51
  return [
41
- /**
42
- * BUG ALERT
43
- * Due to an issue appeared in Bun 1.2.2, we need to use a workaround to import the hydrate script.
44
- * This is a temporary solution until the issue is resolved.
45
- * The litElementHydrateScript is the same file built on Bun 1.1.45.
46
- * https://github.com/oven-sh/bun/issues/17180
47
- *
48
- * AssetFactory.createNodeModuleScript({
49
- * position: 'head',
50
- * importPath: '@lit-labs/ssr-client/lit-element-hydrate-support.js'
51
- * })
52
- */
53
52
  AssetFactory.createInlineContentScript({
54
53
  position: 'head',
55
54
  content: `(() => {${litElementHydrateScript}})();`,
@@ -68,7 +67,7 @@ export class LitPlugin extends IntegrationPlugin {
68
67
  resolveDependencyPath: (componentDir, sourcePath) => path.join(componentDir, sourcePath),
69
68
  processDependencies: this.assetProcessingService.processDependencies.bind(this.assetProcessingService),
70
69
  getInvalidationVersion: () => this.appConfig?.runtime?.serverInvalidationState?.getServerInvalidationVersion() ?? 0,
71
- importServerModule: createLitServerModuleImporter(this.appConfig),
70
+ importServerModule: createCustomElementServerModuleImporter(this.appConfig, '.lit-ssr'),
72
71
  preferSourceImports: typeof Bun !== 'undefined',
73
72
  });
74
73
  }
@@ -79,6 +78,18 @@ export class LitPlugin extends IntegrationPlugin {
79
78
  });
80
79
  return this.attachRendererRuntimeServices(renderer);
81
80
  }
81
+ setHmrManager(hmrManager) {
82
+ super.setHmrManager(hmrManager);
83
+ if (!this.appConfig) {
84
+ return;
85
+ }
86
+ const runtime = this.appConfig.runtime ?? {};
87
+ this.appConfig.runtime = runtime;
88
+ runtime.registeredScriptEntrypointChangeHandlers ??= [];
89
+ runtime.registeredScriptEntrypointChangeHandlers.push((scriptPath) => {
90
+ invalidateCustomElementScriptPreload(scriptPath, CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit);
91
+ });
92
+ }
82
93
  async setup() {
83
94
  await super.setup();
84
95
  if (!this.appConfig?.absolutePaths?.config) {
@@ -1,6 +0,0 @@
1
- import type { EcoPagesAppConfig } from '@ecopages/core';
2
- /**
3
- * Uses server export conditions and isolates module evaluation by custom-element
4
- * registry. Browser asset output must never be evaluated as the SSR implementation.
5
- */
6
- export declare function createLitServerModuleImporter(appConfig: EcoPagesAppConfig): (filePath: string, registryKey: string) => Promise<unknown>;
@@ -1,18 +0,0 @@
1
- import path from 'node:path';
2
- /**
3
- * Uses server export conditions and isolates module evaluation by custom-element
4
- * registry. Browser asset output must never be evaluated as the SSR implementation.
5
- */
6
- export function createLitServerModuleImporter(appConfig) {
7
- return async (filePath, registryKey) => {
8
- const loader = appConfig.runtime?.appModuleLoader;
9
- if (!loader)
10
- throw new Error('Lit SSR requires the app server module loader');
11
- return loader.importModule({
12
- filePath,
13
- rootDir: appConfig.rootDir,
14
- outdir: path.join(appConfig.absolutePaths.workDir, '.lit-ssr', registryKey),
15
- forceBuild: true,
16
- });
17
- };
18
- }
@@ -1,47 +0,0 @@
1
- import { type EcoComponent } from '@ecopages/core';
2
- import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
3
- type ProcessDependencies = (dependencies: AssetDefinition[], integrationName: string) => Promise<Array<{
4
- filepath?: string;
5
- }>>;
6
- export interface LitSsrLazyPreloaderOptions {
7
- resolveDependencyPath: (componentDir: string, sourcePath: string) => string;
8
- processDependencies?: ProcessDependencies;
9
- preferSourceImports?: boolean;
10
- importServerModule?: (scriptPath: string, registryKey: string) => Promise<unknown>;
11
- }
12
- /**
13
- * Encapsulates SSR lazy script preload behavior for Lit components.
14
- *
15
- * Collects `dependencies.scripts` lazy entries with `ssr: true`, resolves the
16
- * processed entrypoint through the asset pipeline, and imports the module so
17
- * custom elements are registered before SSR rendering.
18
- */
19
- export declare class LitSsrLazyPreloader {
20
- private readonly resolveDependencyPath;
21
- private readonly processDependencies?;
22
- private readonly preferSourceImports;
23
- private readonly importServerModule?;
24
- private activeRegistry;
25
- private readonly ssrPreloadedScripts;
26
- private readonly ssrPreloadFailedScripts;
27
- private readonly ssrPreloadEntrypointCache;
28
- constructor({ resolveDependencyPath, processDependencies, preferSourceImports, importServerModule, }: LitSsrLazyPreloaderOptions);
29
- private getErrorCode;
30
- /**
31
- * Detects preload failures that are expected for browser-only modules.
32
- */
33
- isExpectedSsrPreloadError(error: unknown): boolean;
34
- /**
35
- * Collects lazy script file paths eligible for SSR preloading.
36
- */
37
- collectSsrPreloadScripts(components: Array<EcoComponent | undefined>): string[];
38
- /**
39
- * Preloads SSR-eligible lazy scripts to register custom elements before render.
40
- */
41
- preloadSsrLazyScripts(components: Array<EcoComponent | undefined>): Promise<void>;
42
- /**
43
- * Resolves the concrete JS entrypoint used for SSR preloading.
44
- */
45
- resolveSsrPreloadEntrypoint(scriptPath: string): Promise<string | null>;
46
- }
47
- export {};
@@ -1,198 +0,0 @@
1
- import { ensureLitDomShim } from './dom-shim.js';
2
- import { getComponentIdentity } from '@ecopages/core';
3
- import { AssetFactory } from '@ecopages/core/services/asset-processing-service';
4
- import path from 'node:path';
5
- import { pathToFileURL } from 'node:url';
6
- import { randomUUID } from 'node:crypto';
7
- const registryKeys = new WeakMap();
8
- function getRegistryKey(registry) {
9
- let key = registryKeys.get(registry);
10
- if (!key) {
11
- key = randomUUID();
12
- registryKeys.set(registry, key);
13
- }
14
- return key;
15
- }
16
- /**
17
- * Encapsulates SSR lazy script preload behavior for Lit components.
18
- *
19
- * Collects `dependencies.scripts` lazy entries with `ssr: true`, resolves the
20
- * processed entrypoint through the asset pipeline, and imports the module so
21
- * custom elements are registered before SSR rendering.
22
- */
23
- export class LitSsrLazyPreloader {
24
- resolveDependencyPath;
25
- processDependencies;
26
- preferSourceImports;
27
- importServerModule;
28
- activeRegistry;
29
- ssrPreloadedScripts = new Set();
30
- ssrPreloadFailedScripts = new Set();
31
- ssrPreloadEntrypointCache = new Map();
32
- constructor({ resolveDependencyPath, processDependencies, preferSourceImports, importServerModule, }) {
33
- this.importServerModule = importServerModule;
34
- this.resolveDependencyPath = resolveDependencyPath;
35
- this.processDependencies = processDependencies;
36
- this.preferSourceImports = preferSourceImports ?? typeof Bun !== 'undefined';
37
- }
38
- getErrorCode(error) {
39
- if (typeof error !== 'object' || error === null) {
40
- return '';
41
- }
42
- const code = Reflect.get(error, 'code');
43
- return code === undefined ? '' : String(code);
44
- }
45
- /**
46
- * Detects preload failures that are expected for browser-only modules.
47
- */
48
- isExpectedSsrPreloadError(error) {
49
- const errorMessage = error instanceof Error ? error.message : String(error);
50
- const errorCode = this.getErrorCode(error);
51
- if (errorCode === 'ERR_UNKNOWN_FILE_EXTENSION') {
52
- return true;
53
- }
54
- return (errorMessage.includes(`reading 'Element'`) ||
55
- errorMessage.includes('window is not defined') ||
56
- errorMessage.includes('document is not defined') ||
57
- errorMessage.includes('navigator is not defined'));
58
- }
59
- /**
60
- * Collects lazy script file paths eligible for SSR preloading.
61
- */
62
- collectSsrPreloadScripts(components) {
63
- const scriptPaths = new Set();
64
- const visitedConfigs = new Set();
65
- const collect = (component) => {
66
- const config = component?.config;
67
- if (!config || visitedConfigs.has(config)) {
68
- return;
69
- }
70
- visitedConfigs.add(config);
71
- const scriptEntries = config.dependencies?.scripts ?? [];
72
- const componentFile = getComponentIdentity(component)?.file;
73
- if (componentFile) {
74
- const componentDir = path.dirname(componentFile);
75
- for (const script of scriptEntries) {
76
- if (typeof script === 'string') {
77
- continue;
78
- }
79
- if (!script.lazy || script.ssr !== true) {
80
- continue;
81
- }
82
- if (!script.src) {
83
- continue;
84
- }
85
- scriptPaths.add(this.resolveDependencyPath(componentDir, script.src));
86
- }
87
- }
88
- for (const layout of config.layouts ?? []) {
89
- collect(layout);
90
- }
91
- for (const nestedComponent of config.dependencies?.components || []) {
92
- collect(nestedComponent);
93
- }
94
- };
95
- for (const component of components) {
96
- collect(component);
97
- }
98
- return Array.from(scriptPaths);
99
- }
100
- /**
101
- * Preloads SSR-eligible lazy scripts to register custom elements before render.
102
- */
103
- async preloadSsrLazyScripts(components) {
104
- ensureLitDomShim();
105
- const registry = globalThis.customElements;
106
- if (this.activeRegistry !== registry) {
107
- this.ssrPreloadedScripts.clear();
108
- this.ssrPreloadFailedScripts.clear();
109
- this.activeRegistry = registry;
110
- }
111
- const scripts = this.collectSsrPreloadScripts(components);
112
- if (scripts.length === 0) {
113
- return;
114
- }
115
- await Promise.all(scripts
116
- .filter((scriptPath) => {
117
- if (this.ssrPreloadedScripts.has(scriptPath)) {
118
- return false;
119
- }
120
- if (this.ssrPreloadFailedScripts.has(scriptPath)) {
121
- return false;
122
- }
123
- return true;
124
- })
125
- .map(async (scriptPath) => {
126
- try {
127
- const registryKey = registry ? getRegistryKey(registry) : 'default';
128
- if (this.importServerModule) {
129
- await this.importServerModule(scriptPath, registryKey);
130
- }
131
- else {
132
- const preloadEntrypoint = await this.resolveSsrPreloadEntrypoint(scriptPath);
133
- if (!preloadEntrypoint)
134
- throw new Error(`Cannot resolve SSR entry ${scriptPath}`);
135
- const importUrl = pathToFileURL(preloadEntrypoint);
136
- if (registry && typeof Bun === 'undefined')
137
- importUrl.searchParams.set('eco-ssr-registry', registryKey);
138
- await import(/* @vite-ignore */ importUrl.href);
139
- }
140
- this.ssrPreloadedScripts.add(scriptPath);
141
- }
142
- catch (error) {
143
- this.ssrPreloadFailedScripts.add(scriptPath);
144
- if (this.isExpectedSsrPreloadError(error)) {
145
- if (process.env.ECOPAGES_DEBUG === 'true') {
146
- console.warn(`[ecopages][lit] Skipping SSR preload for browser-only lazy script: ${scriptPath}`);
147
- }
148
- return;
149
- }
150
- console.warn(`[ecopages][lit] Failed to preload lazy SSR script: ${scriptPath}`, error);
151
- }
152
- }));
153
- }
154
- /**
155
- * Resolves the concrete JS entrypoint used for SSR preloading.
156
- */
157
- async resolveSsrPreloadEntrypoint(scriptPath) {
158
- const cachedEntrypoint = this.ssrPreloadEntrypointCache.get(scriptPath);
159
- if (cachedEntrypoint) {
160
- return cachedEntrypoint;
161
- }
162
- if (this.preferSourceImports) {
163
- this.ssrPreloadEntrypointCache.set(scriptPath, scriptPath);
164
- return scriptPath;
165
- }
166
- if (!this.processDependencies) {
167
- this.ssrPreloadEntrypointCache.set(scriptPath, scriptPath);
168
- return scriptPath;
169
- }
170
- try {
171
- const processed = await this.processDependencies([
172
- AssetFactory.createInlineFileScript({
173
- filepath: scriptPath,
174
- excludeFromHtml: true,
175
- position: 'head',
176
- bundle: true,
177
- attributes: {
178
- type: 'module',
179
- defer: '',
180
- },
181
- }),
182
- ], `lit-ssr-preload:${scriptPath}`);
183
- const entrypoint = processed[0]?.filepath;
184
- if (!entrypoint) {
185
- return scriptPath;
186
- }
187
- this.ssrPreloadEntrypointCache.set(scriptPath, entrypoint);
188
- return entrypoint;
189
- }
190
- catch (error) {
191
- if (process.env.ECOPAGES_DEBUG === 'true') {
192
- console.warn(`[ecopages][lit] Failed to resolve SSR preload entrypoint for: ${scriptPath}`, error);
193
- }
194
- this.ssrPreloadEntrypointCache.set(scriptPath, scriptPath);
195
- return scriptPath;
196
- }
197
- }
198
- }