@ecopages/lit 0.2.0-alpha.12 → 0.2.0-alpha.14

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/CHANGELOG.md CHANGED
@@ -6,20 +6,14 @@ All notable changes to `@ecopages/lit` are documented here.
6
6
 
7
7
  ## [UNRELEASED] — TBD
8
8
 
9
- ### Features
10
-
11
- - Aligned Lit with the shared orchestration, lazy dependency, and global injector flow.
12
-
13
9
  ### Bug Fixes
14
10
 
15
- - Fixed mixed-template SSR composition, placeholder cleanup, lazy preload handling, and browser-router bootstrap reruns.
16
- - Routed Lit SSR through the static template pipeline so registered custom elements emit declarative shadow DOM.
17
- - Fixed component-boundary SSR to inject serialized children through both direct Lit interpolation and repeated Lit child slots when Lit is hosted by other integrations.
11
+ - Fixed Lit document-shell composition, declarative shadow DOM SSR, nested child serialization, lazy preload handling, explicit render paths, and mixed-renderer boundary resolution.
18
12
 
19
- ### Refactoring
13
+ ### Documentation
20
14
 
21
- - Cleaned up ambient module declarations.
15
+ - Updated the README to document `.lit.tsx` route ownership, standalone setup, and mixed-renderer Lit boundary handling.
22
16
 
23
17
  ### Tests
24
18
 
25
- - Updated integration coverage for the orchestration pipeline and Node and esbuild compatibility.
19
+ - Updated integration coverage for explicit boundary composition and Node and esbuild compatibility.
package/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # @ecopages/lit
2
2
 
3
- Integration plugin for [Lit](https://lit.dev/) web components in Ecopages.
4
-
5
- This integration is optimized for use alongside `@ecopages/kitajs` (or another HTML JSX engine), allowing you to author your pages in Kitajs JSX templates while embedding interactive Lit components where needed.
3
+ Integration plugin for [Lit](https://lit.dev/) in Ecopages. Use it when Lit should own `.lit.tsx` routes or when another integration needs Lit to render nested custom-element boundaries.
6
4
 
7
5
  ## Installation
8
6
 
@@ -12,7 +10,21 @@ bunx jsr add @ecopages/lit
12
10
 
13
11
  ## Usage
14
12
 
15
- It is recommended to use `@ecopages/lit` in conjunction with `@ecopages/kitajs`. Configure your project to include both integrations in your `eco.config.ts`:
13
+ Register `litPlugin()` in your `eco.config.ts`.
14
+
15
+ ```ts
16
+ import { ConfigBuilder } from '@ecopages/core/config-builder';
17
+ import { litPlugin } from '@ecopages/lit';
18
+
19
+ const config = await new ConfigBuilder()
20
+ .setBaseUrl(import.meta.env.ECOPAGES_BASE_URL)
21
+ .setIntegrations([litPlugin()])
22
+ .build();
23
+
24
+ export default config;
25
+ ```
26
+
27
+ Lit also works well alongside an HTML-first renderer such as `@ecopages/kitajs` when you want Lit to own only the nested custom elements:
16
28
 
17
29
  ```ts
18
30
  import { ConfigBuilder } from '@ecopages/core/config-builder';
@@ -27,4 +39,14 @@ const config = await new ConfigBuilder()
27
39
  export default config;
28
40
  ```
29
41
 
30
- This setup enables server-rendered KitaJS pages that embed and hydrate Lit custom elements on the client.
42
+ This setup lets Kita own the page shell while Lit owns the nested Lit component boundaries.
43
+
44
+ ## What This Integration Owns
45
+
46
+ - `.lit.tsx` route files.
47
+ - Nested Lit component boundaries rendered inside pages owned by other integrations.
48
+ - The Lit hydration support script required for SSR custom elements and declarative shadow DOM.
49
+
50
+ ## Mixed Rendering
51
+
52
+ When a non-Lit render pass enters a Lit-owned component boundary, Ecopages hands that boundary to the Lit renderer. That keeps Lit SSR in charge of custom elements, declarative shadow DOM, and Lit-managed child content.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/lit",
3
- "version": "0.2.0-alpha.12",
3
+ "version": "0.2.0-alpha.14",
4
4
  "description": "Ecopages lit integration",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -31,7 +31,7 @@
31
31
  "directory": "packages/integrations/lit"
32
32
  },
33
33
  "peerDependencies": {
34
- "@ecopages/core": "0.2.0-alpha.12",
34
+ "@ecopages/core": "0.2.0-alpha.14",
35
35
  "@lit-labs/ssr": "^3.3.0",
36
36
  "@lit-labs/ssr-client": "^1.1.7",
37
37
  "lit": "^3.2.1"
@@ -10,6 +10,8 @@ import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/cor
10
10
  */
11
11
  export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
12
12
  name: string;
13
+ private resolveQueuedBoundaryChildren;
14
+ private resolveQueuedBoundaryHtml;
13
15
  private normalizeDeclarativeShadowRootMarkup;
14
16
  private createRenderableMarkup;
15
17
  protected shouldRenderPageComponent(): boolean;
@@ -23,11 +25,15 @@ export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
23
25
  *
24
26
  * SSR-eligible lazy scripts are preloaded first so custom elements registered
25
27
  * by the component can render their server markup even when the Lit renderer is
26
- * entered through cross-integration marker resolution.
28
+ * entered through cross-integration boundary handoff.
27
29
  *
28
30
  * Includes component-scoped dependency assets when declared.
29
31
  */
30
32
  renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
33
+ protected createComponentBoundaryRuntime(options: {
34
+ boundaryInput: ComponentRenderInput;
35
+ rendererCache: Map<string, IntegrationRenderer<any>>;
36
+ }): import("@ecopages/core").ComponentBoundaryRuntime;
31
37
  private readonly ssrLazyPreloader;
32
38
  /**
33
39
  * Detects preload failures that are expected for browser-only modules.
@@ -11,6 +11,40 @@ const DOUBLE_ESCAPED_COMPONENT_CHILDREN_SLOT_MARKER = "&amp;lt;!--eco-lit-compon
11
11
  const DUPLICATE_DECLARATIVE_SHADOW_ROOT_ATTRIBUTE = /\sshadowroot=(['"])(open|closed)\1(?=\sshadowrootmode=\1\2\1)/g;
12
12
  class LitRenderer extends IntegrationRenderer {
13
13
  name = PLUGIN_NAME;
14
+ async resolveQueuedBoundaryChildren(children, queuedResolutionsByToken, resolveToken) {
15
+ if (children === void 0) {
16
+ return void 0;
17
+ }
18
+ let renderedChildren = typeof children === "string" ? children : await this.renderValueToString(children);
19
+ renderedChildren = await this.resolveQueuedBoundaryTokens(
20
+ renderedChildren,
21
+ queuedResolutionsByToken,
22
+ resolveToken
23
+ );
24
+ return renderedChildren;
25
+ }
26
+ async resolveQueuedBoundaryHtml(html, runtimeContext) {
27
+ const queuedBoundaryResolution = await this.resolveRendererOwnedQueuedBoundaryHtml({
28
+ html,
29
+ runtimeContext,
30
+ queueLabel: "Lit",
31
+ renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => {
32
+ const renderedChildren = await this.resolveQueuedBoundaryChildren(
33
+ children,
34
+ queuedResolutionsByToken,
35
+ resolveToken
36
+ );
37
+ return {
38
+ assets: [],
39
+ html: renderedChildren
40
+ };
41
+ }
42
+ });
43
+ return {
44
+ html: queuedBoundaryResolution.html,
45
+ assets: queuedBoundaryResolution.assets.length > 0 ? queuedBoundaryResolution.assets : void 0
46
+ };
47
+ }
14
48
  normalizeDeclarativeShadowRootMarkup(markup) {
15
49
  return markup.replace(DUPLICATE_DECLARATIVE_SHADOW_ROOT_ATTRIBUTE, "");
16
50
  }
@@ -83,32 +117,51 @@ class LitRenderer extends IntegrationRenderer {
83
117
  *
84
118
  * SSR-eligible lazy scripts are preloaded first so custom elements registered
85
119
  * by the component can render their server markup even when the Lit renderer is
86
- * entered through cross-integration marker resolution.
120
+ * entered through cross-integration boundary handoff.
87
121
  *
88
122
  * Includes component-scoped dependency assets when declared.
89
123
  */
90
124
  async renderComponent(input) {
91
125
  await this.preloadSsrLazyScripts([input.component]);
92
126
  const component = input.component;
93
- const renderedChildren = input.children === void 0 ? void 0 : typeof input.children === "string" ? input.children : await this.renderValueToString(input.children);
94
- const props = renderedChildren === void 0 ? input.props : {
95
- ...input.props,
96
- children: COMPONENT_CHILDREN_SLOT_MARKER
97
- };
127
+ let renderedChildren;
128
+ if (input.children !== void 0) {
129
+ renderedChildren = typeof input.children === "string" ? input.children : await this.renderValueToString(input.children);
130
+ }
131
+ let props = input.props;
132
+ if (renderedChildren !== void 0) {
133
+ props = {
134
+ ...input.props,
135
+ children: COMPONENT_CHILDREN_SLOT_MARKER
136
+ };
137
+ }
98
138
  const content = await component(props);
99
139
  const renderedHtml = await this.renderValueToString(content);
100
140
  const html = renderedChildren === void 0 ? renderedHtml : this.injectRenderedChildren(renderedHtml, renderedChildren);
141
+ const queuedBoundaryResolution = await this.resolveQueuedBoundaryHtml(
142
+ html,
143
+ this.getQueuedBoundaryRuntime(input)
144
+ );
101
145
  const hasDependencies = Boolean(input.component.config?.dependencies);
102
146
  const canResolveAssets = typeof this.assetProcessingService?.processDependencies === "function";
103
147
  const assets = hasDependencies && canResolveAssets ? await this.processComponentDependencies([input.component]) : void 0;
104
148
  return {
105
- html,
149
+ html: queuedBoundaryResolution.html,
106
150
  canAttachAttributes: true,
107
- rootTag: this.getRootTagName(html),
151
+ rootTag: this.getRootTagName(queuedBoundaryResolution.html),
108
152
  integrationName: this.name,
109
- assets
153
+ assets: this.htmlTransformer.dedupeProcessedAssets([
154
+ ...assets ?? [],
155
+ ...queuedBoundaryResolution.assets ?? []
156
+ ])
110
157
  };
111
158
  }
159
+ createComponentBoundaryRuntime(options) {
160
+ return this.createQueuedBoundaryRuntime({
161
+ boundaryInput: options.boundaryInput,
162
+ rendererCache: options.rendererCache
163
+ });
164
+ }
112
165
  ssrLazyPreloader = new LitSsrLazyPreloader({
113
166
  resolveDependencyPath: this.resolveDependencyPath.bind(this),
114
167
  processDependencies: this.assetProcessingService?.processDependencies?.bind(this.assetProcessingService)
@@ -159,24 +212,24 @@ class LitRenderer extends IntegrationRenderer {
159
212
  }) {
160
213
  try {
161
214
  await this.preloadSsrLazyScripts([Page, Layout]);
162
- const pageContent = await Page({ params, query, ...props, locals });
163
- const pageHtml = await this.renderValueToString(pageContent);
164
- const children = Layout ? this.isLitManagedComponent(Layout) ? await this.renderValueToString(
165
- await Layout({
166
- children: pageContent,
167
- locals
168
- })
169
- ) : await Layout({
170
- children: pageHtml,
171
- locals
172
- }) : pageHtml;
173
- const renderedChildren = this.normalizeDeclarativeShadowRootMarkup(String(children));
174
- return this.DOC_TYPE + await this.renderHtmlTemplate({
175
- HtmlTemplate,
215
+ return await this.renderPageWithDocumentShell({
216
+ page: {
217
+ component: Page,
218
+ props: {
219
+ params,
220
+ query,
221
+ ...props,
222
+ locals
223
+ }
224
+ },
225
+ layout: Layout ? {
226
+ component: Layout,
227
+ props: locals ? { locals } : {}
228
+ } : void 0,
229
+ htmlTemplate: HtmlTemplate,
176
230
  metadata,
177
231
  pageProps: props || {},
178
- renderedChildren,
179
- isLitManagedHtmlTemplate: this.isLitManagedComponent(HtmlTemplate)
232
+ transformDocumentHtml: (html) => this.normalizeDeclarativeShadowRootMarkup(html)
180
233
  });
181
234
  } catch (error) {
182
235
  throw this.createRenderError("Error rendering page", error);
@@ -184,42 +237,41 @@ class LitRenderer extends IntegrationRenderer {
184
237
  }
185
238
  async renderToResponse(view, props, ctx) {
186
239
  try {
240
+ if (ctx.partial) {
241
+ return this.renderPartialViewResponse({
242
+ view,
243
+ props,
244
+ ctx,
245
+ transformHtml: (html) => this.normalizeDeclarativeShadowRootMarkup(html)
246
+ });
247
+ }
187
248
  const viewConfig = view.config;
188
249
  const Layout = viewConfig?.layout;
189
- const HtmlTemplate = ctx.partial ? void 0 : await this.getHtmlTemplate();
190
- const metadata = ctx.partial || !HtmlTemplate ? void 0 : view.metadata ? await view.metadata({
191
- params: {},
192
- query: {},
193
- props,
194
- appConfig: this.appConfig
195
- }) : this.appConfig.defaultMetadata;
250
+ const HtmlTemplate = await this.getHtmlTemplate();
251
+ const metadata = await this.resolveViewMetadata(view, props);
196
252
  await this.preloadSsrLazyScripts([view, Layout]);
197
- if (!ctx.partial) {
198
- await this.prepareViewDependencies(view, Layout);
199
- }
200
- const viewFn = view;
201
- const renderExecution = await this.captureHtmlRender(async () => {
202
- const pageContent = await viewFn(props);
203
- const pageHtml = await this.renderValueToString(pageContent);
204
- if (ctx.partial) {
205
- return this.normalizeDeclarativeShadowRootMarkup(pageHtml);
206
- }
207
- const children = Layout ? this.isLitManagedComponent(Layout) ? await this.renderValueToString(await Layout({ children: pageContent })) : await Layout({ children: pageHtml }) : pageHtml;
208
- const renderedChildren = this.normalizeDeclarativeShadowRootMarkup(String(children));
209
- return this.DOC_TYPE + await this.renderHtmlTemplate({
210
- HtmlTemplate,
211
- metadata: metadata ?? this.appConfig.defaultMetadata,
212
- pageProps: props ?? {},
213
- renderedChildren,
214
- isLitManagedHtmlTemplate: this.isLitManagedComponent(HtmlTemplate)
215
- });
253
+ await this.prepareViewDependencies(view, Layout);
254
+ const pageRender = await this.renderComponentBoundary({
255
+ component: view,
256
+ props: props ?? {}
257
+ });
258
+ const layoutRender = Layout ? await this.renderComponentBoundary({
259
+ component: Layout,
260
+ props: {},
261
+ children: pageRender.html
262
+ }) : void 0;
263
+ const documentRender = await this.renderComponentBoundary({
264
+ component: HtmlTemplate,
265
+ props: {
266
+ metadata,
267
+ pageProps: props ?? {}
268
+ },
269
+ children: layoutRender?.html ?? pageRender.html
216
270
  });
217
- const componentsToResolve = ctx.partial ? [view] : [HtmlTemplate, Layout, view].filter(Boolean);
218
- const body = await this.finalizeCapturedHtmlRender({
219
- html: renderExecution.html,
220
- componentsToResolve,
221
- graphContext: renderExecution.graphContext,
222
- partial: ctx.partial
271
+ this.appendProcessedDependencies(pageRender.assets, layoutRender?.assets, documentRender.assets);
272
+ const body = await this.finalizeResolvedHtml({
273
+ html: `${this.DOC_TYPE}${this.normalizeDeclarativeShadowRootMarkup(documentRender.html)}`,
274
+ partial: false
223
275
  });
224
276
  return this.createHtmlResponse(body, ctx);
225
277
  } catch (error) {
@@ -3,7 +3,7 @@
3
3
  * @module
4
4
  */
5
5
  import './console.js';
6
- import { IntegrationPlugin, type ComponentBoundaryPolicyInput, type IntegrationPluginConfig } from '@ecopages/core/plugins/integration-plugin';
6
+ import { IntegrationPlugin, type IntegrationPluginConfig } from '@ecopages/core/plugins/integration-plugin';
7
7
  import { type AssetDefinition } from '@ecopages/core/services/asset-processing-service';
8
8
  import { LitRenderer } from './lit-renderer.js';
9
9
  /**
@@ -18,17 +18,6 @@ export declare class LitPlugin extends IntegrationPlugin {
18
18
  renderer: typeof LitRenderer;
19
19
  constructor(options?: Omit<IntegrationPluginConfig, 'name'>);
20
20
  getDependencies(): AssetDefinition[];
21
- /**
22
- * Declares Lit's cross-integration boundary deferral rule.
23
- *
24
- * When a non-Lit render pass enters a Lit component boundary, the boundary is
25
- * deferred into marker-graph resolution so Lit SSR can render custom elements,
26
- * declarative shadow DOM, and other Lit-owned output through the Lit renderer.
27
- *
28
- * @param input Boundary metadata for the active render pass.
29
- * @returns `true` when the boundary should be deferred into the marker pass.
30
- */
31
- shouldDeferComponentBoundary(input: ComponentBoundaryPolicyInput): boolean;
32
21
  }
33
22
  /**
34
23
  * Factory function to create a Lit plugin instance
package/src/lit.plugin.js CHANGED
@@ -1,7 +1,5 @@
1
1
  import "./console.js";
2
- import {
3
- IntegrationPlugin
4
- } from "@ecopages/core/plugins/integration-plugin";
2
+ import { IntegrationPlugin } from "@ecopages/core/plugins/integration-plugin";
5
3
  import { AssetFactory } from "@ecopages/core/services/asset-processing-service";
6
4
  import { litElementHydrateScript } from "./lit-element-hydrate.js";
7
5
  import { LitRenderer } from "./lit-renderer.js";
@@ -41,19 +39,6 @@ class LitPlugin extends IntegrationPlugin {
41
39
  })
42
40
  ];
43
41
  }
44
- /**
45
- * Declares Lit's cross-integration boundary deferral rule.
46
- *
47
- * When a non-Lit render pass enters a Lit component boundary, the boundary is
48
- * deferred into marker-graph resolution so Lit SSR can render custom elements,
49
- * declarative shadow DOM, and other Lit-owned output through the Lit renderer.
50
- *
51
- * @param input Boundary metadata for the active render pass.
52
- * @returns `true` when the boundary should be deferred into the marker pass.
53
- */
54
- shouldDeferComponentBoundary(input) {
55
- return input.targetIntegration === this.name && input.currentIntegration !== this.name;
56
- }
57
42
  }
58
43
  function litPlugin(options) {
59
44
  return new LitPlugin(options);