@ecopages/lit 0.2.0-beta.8 → 0.2.0-rc.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.
@@ -1,255 +1,227 @@
1
- import "./dom-shim.js";
2
- import { IntegrationRenderer } from "@ecopages/core/route-renderer/integration-renderer";
3
- import { getActiveLitStaticRenderSession } from "./lit-static-render-coordinator.js";
4
- import { LitSsrLazyPreloader } from "./lit-ssr-lazy-preloader.js";
5
- import { LIT_PLUGIN_NAME } from "./lit.constants.js";
6
- import {
7
- injectLitRenderedChildren,
8
- LIT_COMPONENT_CHILDREN_SLOT_MARKER,
9
- normalizeLitHtml,
10
- renderLitValueToString
11
- } from "./utils/lit-html-rendering.js";
12
- class LitRenderer extends IntegrationRenderer {
13
- name = LIT_PLUGIN_NAME;
14
- async execute(options) {
15
- const session = getActiveLitStaticRenderSession();
16
- if (session) {
17
- const html = await session.renderPageInWorker({
18
- filePath: options.file,
19
- params: options.params ?? {}
20
- });
21
- return {
22
- body: html
23
- };
1
+ /**
2
+ * This module contains the Lit renderer
3
+ * @module
4
+ */
5
+ import './dom-shim.js';
6
+ import { IntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
7
+ import { getForeignSubtreeResolutionContextKey, resolveOwningIntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/foreign-child/owning-renderer-resolution';
8
+ import { LitSsrLazyPreloader } from './lit-ssr-lazy-preloader.js';
9
+ import { LIT_PLUGIN_NAME } from './lit.constants.js';
10
+ import { injectLitRenderedChildren, LIT_COMPONENT_CHILDREN_SLOT_MARKER, normalizeLitHtml, renderLitValueToString, } from './utils/lit-html-rendering.js';
11
+ /**
12
+ * A renderer for the Lit integration.
13
+ */
14
+ export class LitRenderer extends IntegrationRenderer {
15
+ name = LIT_PLUGIN_NAME;
16
+ getRenderSession;
17
+ constructor(options) {
18
+ const { getRenderSession, ...rendererOptions } = options;
19
+ super(rendererOptions);
20
+ this.getRenderSession = getRenderSession;
24
21
  }
25
- return super.execute(options);
26
- }
27
- isFunctionComponent(component) {
28
- return typeof component === "function";
29
- }
30
- async resolveQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken) {
31
- if (children === void 0) {
32
- return void 0;
22
+ /**
23
+ * Renders a Lit page route, preferring the plugin worker when available.
24
+ *
25
+ * @remarks
26
+ * Activates the integration runtime before reading {@link getRenderSession}
27
+ * so a renderer constructed before plugin setup still sees the session.
28
+ * When `options.locals` is present, rendering stays in-process: request locals
29
+ * are not structured-cloned across the worker boundary.
30
+ */
31
+ async execute(options) {
32
+ await this.ensureIntegrationRuntimeActivated();
33
+ const renderSession = this.getRenderSession?.();
34
+ if (renderSession && options.locals === undefined) {
35
+ const params = options.params ?? {};
36
+ const result = await renderSession.renderPageInWorker({
37
+ filePath: options.file,
38
+ params,
39
+ query: options.query,
40
+ });
41
+ return {
42
+ body: result.html,
43
+ cacheStrategy: result.cacheStrategy,
44
+ };
45
+ }
46
+ return super.execute(options);
33
47
  }
34
- if (typeof children !== "string") {
35
- return children;
48
+ isFunctionComponent(component) {
49
+ return typeof component === 'function';
36
50
  }
37
- let renderedChildren = children;
38
- renderedChildren = await this.foreignSubtreeExecutionService.resolveQueuedTokens(
39
- renderedChildren,
40
- queuedResolutionsByToken,
41
- resolveToken
42
- );
43
- return renderedChildren;
44
- }
45
- async resolveQueuedForeignSubtreeHtml(html, runtimeContext) {
46
- const queuedForeignSubtreeResolution = await this.foreignSubtreeExecutionService.resolveQueuedHtml({
47
- currentIntegrationName: this.name,
48
- html,
49
- runtimeContext,
50
- queueLabel: "Lit",
51
- getOwningRenderer: (integrationName, rendererCache) => this.getIntegrationRendererForName(integrationName, rendererCache),
52
- applyAttributesToFirstElement: (resolvedHtml, attributes) => this.htmlTransformer.applyAttributesToFirstElement(resolvedHtml, attributes),
53
- dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets),
54
- renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => {
55
- const renderedChildren = await this.resolveQueuedForeignSubtreeChildren(
56
- children,
57
- queuedResolutionsByToken,
58
- resolveToken
59
- );
60
- if (typeof renderedChildren !== "string") {
61
- return {
62
- assets: [],
63
- children: renderedChildren
64
- };
51
+ async resolveQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken) {
52
+ if (children === undefined) {
53
+ return undefined;
54
+ }
55
+ if (typeof children !== 'string') {
56
+ return children;
57
+ }
58
+ let renderedChildren = children;
59
+ renderedChildren = await this.foreignSubtreeExecutionService.resolveQueuedTokens(renderedChildren, queuedResolutionsByToken, resolveToken);
60
+ return renderedChildren;
61
+ }
62
+ async renderLitQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken) {
63
+ const renderedChildren = await this.resolveQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken);
64
+ if (typeof renderedChildren !== 'string') {
65
+ return {
66
+ assets: [],
67
+ children: renderedChildren,
68
+ };
65
69
  }
66
70
  return {
67
- assets: [],
68
- html: renderedChildren
71
+ assets: [],
72
+ html: renderedChildren,
69
73
  };
70
- }
74
+ }
75
+ /**
76
+ * Renders a Lit component for component-level orchestration.
77
+ *
78
+ * SSR-eligible lazy scripts are preloaded first so custom elements registered
79
+ * by the component can render their server markup even when the Lit renderer is
80
+ * entered through cross-integration foreign-child handoff.
81
+ *
82
+ * Includes component-scoped dependency assets when declared.
83
+ */
84
+ async renderComponent(input) {
85
+ await this.preloadSsrLazyScripts([input.component]);
86
+ if (!this.isFunctionComponent(input.component)) {
87
+ throw new TypeError('Lit renderer expected a callable component.');
88
+ }
89
+ const component = input.component;
90
+ let renderedChildren;
91
+ if (input.children !== undefined) {
92
+ renderedChildren =
93
+ typeof input.children === 'string' ? input.children : await renderLitValueToString(input.children);
94
+ }
95
+ let props = input.props;
96
+ if (renderedChildren !== undefined) {
97
+ props = {
98
+ ...input.props,
99
+ children: LIT_COMPONENT_CHILDREN_SLOT_MARKER,
100
+ };
101
+ }
102
+ const content = await component(props);
103
+ const renderedHtml = await renderLitValueToString(content);
104
+ const html = renderedChildren === undefined ? renderedHtml : injectLitRenderedChildren(renderedHtml, renderedChildren);
105
+ const queuedForeignSubtreeResolution = await this.foreignSubtreeExecutionService.resolveQueuedHtml({
106
+ currentIntegrationName: this.name,
107
+ html,
108
+ runtimeContext: this.foreignSubtreeExecutionService.getQueuedRuntimeContext(input, getForeignSubtreeResolutionContextKey(this.name)),
109
+ queueLabel: 'Lit',
110
+ getOwningRenderer: (integrationName, rendererCache) => resolveOwningIntegrationRenderer({
111
+ appConfig: this.appConfig,
112
+ runtimeOrigin: this.runtimeOrigin,
113
+ currentIntegrationName: this.name,
114
+ currentRenderer: this,
115
+ integrationName,
116
+ cache: rendererCache,
117
+ }),
118
+ applyAttributesToFirstElement: (resolvedHtml, attributes) => this.htmlTransformer.applyAttributesToFirstElement(resolvedHtml, attributes),
119
+ dedupeProcessedAssets: (assets) => this.htmlTransformer.dedupeProcessedAssets(assets),
120
+ renderQueuedChildren: (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.renderLitQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken),
121
+ });
122
+ const hasDependencies = Boolean(input.component.config?.dependencies);
123
+ const canResolveAssets = typeof this.assetProcessingService?.processDependencies === 'function';
124
+ const assets = hasDependencies && canResolveAssets
125
+ ? await this.processComponentDependencies([input.component])
126
+ : undefined;
127
+ return this.finalizeIslandComponentRender(input, {
128
+ html: queuedForeignSubtreeResolution.html,
129
+ canAttachAttributes: true,
130
+ rootTag: this.getRootTagName(queuedForeignSubtreeResolution.html),
131
+ integrationName: this.name,
132
+ assets: this.htmlTransformer.dedupeProcessedAssets([
133
+ ...(assets ?? []),
134
+ ...queuedForeignSubtreeResolution.assets,
135
+ ]),
136
+ });
137
+ }
138
+ ssrLazyPreloader = new LitSsrLazyPreloader({
139
+ resolveDependencyPath: this.resolveDependencyPath.bind(this),
140
+ processDependencies: this.assetProcessingService?.processDependencies?.bind(this.assetProcessingService),
71
141
  });
72
- return {
73
- html: queuedForeignSubtreeResolution.html,
74
- assets: queuedForeignSubtreeResolution.assets.length > 0 ? queuedForeignSubtreeResolution.assets : void 0
75
- };
76
- }
77
- shouldRenderPageComponent() {
78
- return false;
79
- }
80
- isLitManagedComponent(component) {
81
- return component?.config?.integration === this.name || component?.config?.__eco?.integration === this.name;
82
- }
83
- /**
84
- * Renders a Lit component for component-level orchestration.
85
- *
86
- * SSR-eligible lazy scripts are preloaded first so custom elements registered
87
- * by the component can render their server markup even when the Lit renderer is
88
- * entered through cross-integration foreign-child handoff.
89
- *
90
- * Includes component-scoped dependency assets when declared.
91
- */
92
- async renderComponent(input) {
93
- await this.preloadSsrLazyScripts([input.component]);
94
- if (!this.isFunctionComponent(input.component)) {
95
- throw new TypeError("Lit renderer expected a callable component.");
142
+ /**
143
+ * Detects preload failures that are expected for browser-only modules.
144
+ *
145
+ * These errors are treated as non-fatal during SSR preload because some
146
+ * lazy client scripts intentionally depend on browser globals.
147
+ */
148
+ isExpectedSsrPreloadError(error) {
149
+ return this.ssrLazyPreloader.isExpectedSsrPreloadError(error);
96
150
  }
97
- const component = input.component;
98
- let renderedChildren;
99
- if (input.children !== void 0) {
100
- renderedChildren = typeof input.children === "string" ? input.children : await renderLitValueToString(input.children);
151
+ /**
152
+ * Collects lazy script file paths eligible for SSR preloading.
153
+ *
154
+ * Only per-entry lazy script dependencies with `ssr: true` are collected.
155
+ * File-backed entries are required (`src` must be present);
156
+ * inline content lazy entries are intentionally skipped.
157
+ */
158
+ collectSsrPreloadScripts(components) {
159
+ return this.ssrLazyPreloader.collectSsrPreloadScripts(components);
101
160
  }
102
- let props = input.props;
103
- if (renderedChildren !== void 0) {
104
- props = {
105
- ...input.props,
106
- children: LIT_COMPONENT_CHILDREN_SLOT_MARKER
107
- };
161
+ /**
162
+ * Preloads SSR-eligible lazy scripts to register custom elements before render.
163
+ */
164
+ async preloadSsrLazyScripts(components) {
165
+ const renderSession = this.getRenderSession?.();
166
+ if (renderSession) {
167
+ await renderSession.preloadSsrLazyScripts(components);
168
+ return;
169
+ }
170
+ await this.ssrLazyPreloader.preloadSsrLazyScripts(components);
108
171
  }
109
- const content = await component(props);
110
- const renderedHtml = await renderLitValueToString(content);
111
- const html = renderedChildren === void 0 ? renderedHtml : injectLitRenderedChildren(renderedHtml, renderedChildren);
112
- const queuedForeignSubtreeResolution = await this.resolveQueuedForeignSubtreeHtml(
113
- html,
114
- this.getQueuedForeignSubtreeResolutionContext(input)
115
- );
116
- const hasDependencies = Boolean(input.component.config?.dependencies);
117
- const canResolveAssets = typeof this.assetProcessingService?.processDependencies === "function";
118
- const assets = hasDependencies && canResolveAssets ? await this.processComponentDependencies([input.component]) : void 0;
119
- return {
120
- html: queuedForeignSubtreeResolution.html,
121
- canAttachAttributes: true,
122
- rootTag: this.getRootTagName(queuedForeignSubtreeResolution.html),
123
- integrationName: this.name,
124
- assets: this.htmlTransformer.dedupeProcessedAssets([
125
- ...assets ?? [],
126
- ...queuedForeignSubtreeResolution.assets ?? []
127
- ])
128
- };
129
- }
130
- ssrLazyPreloader = new LitSsrLazyPreloader({
131
- resolveDependencyPath: this.resolveDependencyPath.bind(this),
132
- processDependencies: this.assetProcessingService?.processDependencies?.bind(this.assetProcessingService)
133
- });
134
- /**
135
- * Detects preload failures that are expected for browser-only modules.
136
- *
137
- * These errors are treated as non-fatal during SSR preload because some
138
- * lazy client scripts intentionally depend on browser globals.
139
- */
140
- isExpectedSsrPreloadError(error) {
141
- return this.ssrLazyPreloader.isExpectedSsrPreloadError(error);
142
- }
143
- /**
144
- * Collects lazy script file paths eligible for SSR preloading.
145
- *
146
- * Only per-entry lazy script dependencies with `ssr: true` are collected.
147
- * File-backed entries are required (`src` must be present);
148
- * inline content lazy entries are intentionally skipped.
149
- */
150
- collectSsrPreloadScripts(components) {
151
- return this.ssrLazyPreloader.collectSsrPreloadScripts(components);
152
- }
153
- /**
154
- * Preloads SSR-eligible lazy scripts to register custom elements before render.
155
- */
156
- async preloadSsrLazyScripts(components) {
157
- const session = getActiveLitStaticRenderSession();
158
- if (session) {
159
- await session.preloadSsrLazyScripts(components);
160
- return;
172
+ /**
173
+ * Resolves the concrete JS entrypoint used for SSR preloading.
174
+ *
175
+ * Scripts are passed through the asset pipeline so preload imports can use
176
+ * the same processed output shape as runtime dependencies.
177
+ */
178
+ async resolveSsrPreloadEntrypoint(scriptPath) {
179
+ return this.ssrLazyPreloader.resolveSsrPreloadEntrypoint(scriptPath);
161
180
  }
162
- await this.ssrLazyPreloader.preloadSsrLazyScripts(components);
163
- }
164
- /**
165
- * Resolves the concrete JS entrypoint used for SSR preloading.
166
- *
167
- * Scripts are passed through the asset pipeline so preload imports can use
168
- * the same processed output shape as runtime dependencies.
169
- */
170
- async resolveSsrPreloadEntrypoint(scriptPath) {
171
- return this.ssrLazyPreloader.resolveSsrPreloadEntrypoint(scriptPath);
172
- }
173
- async render({
174
- params,
175
- query,
176
- props,
177
- locals,
178
- metadata,
179
- Page,
180
- Layout,
181
- HtmlTemplate
182
- }) {
183
- try {
184
- await this.preloadSsrLazyScripts([Page, Layout]);
185
- return await this.renderPageWithDocumentShell({
186
- page: {
187
- component: Page,
188
- props: {
189
- params,
190
- query,
191
- ...props,
192
- locals
193
- }
194
- },
195
- layout: Layout ? {
196
- component: Layout,
197
- props: locals ? { locals } : {}
198
- } : void 0,
199
- htmlTemplate: HtmlTemplate,
200
- metadata,
201
- pageProps: props || {},
202
- transformDocumentHtml: normalizeLitHtml
203
- });
204
- } catch (error) {
205
- throw this.createRenderError("Error rendering page", error);
181
+ async render({ params, query, props, locals, metadata, Page, Layout, HtmlTemplate, }) {
182
+ try {
183
+ await this.preloadSsrLazyScripts([Page, Layout]);
184
+ return await this.renderPageWithDocumentShell({
185
+ page: {
186
+ component: Page,
187
+ props: {
188
+ params,
189
+ query,
190
+ ...props,
191
+ locals,
192
+ },
193
+ },
194
+ layout: Layout
195
+ ? {
196
+ component: Layout,
197
+ props: locals ? { locals } : {},
198
+ }
199
+ : undefined,
200
+ htmlTemplate: HtmlTemplate,
201
+ metadata,
202
+ pageProps: props || {},
203
+ transformDocumentHtml: normalizeLitHtml,
204
+ });
205
+ }
206
+ catch (error) {
207
+ throw this.createRenderError('Error rendering page', error);
208
+ }
206
209
  }
207
- }
208
- async renderToResponse(view, props, ctx) {
209
- try {
210
- if (ctx.partial) {
211
- return this.renderPartialViewResponse({
212
- view,
213
- props,
214
- ctx,
215
- transformHtml: normalizeLitHtml
216
- });
217
- }
218
- const viewConfig = view.config;
219
- const Layout = viewConfig?.layout;
220
- const HtmlTemplate = await this.getHtmlTemplate();
221
- const metadata = await this.resolveViewMetadata(view, props);
222
- const normalizedProps = props ?? {};
223
- await this.preloadSsrLazyScripts([view, Layout]);
224
- await this.prepareViewDependencies(view, Layout);
225
- const pageRender = await this.renderComponentWithForeignChildren({
226
- component: view,
227
- props: normalizedProps
228
- });
229
- const layoutRender = Layout ? await this.renderComponentWithForeignChildren({
230
- component: Layout,
231
- props: {},
232
- children: pageRender.html
233
- }) : void 0;
234
- const documentRender = await this.renderComponentWithForeignChildren({
235
- component: HtmlTemplate,
236
- props: {
237
- metadata,
238
- pageProps: normalizedProps
239
- },
240
- children: layoutRender?.html ?? pageRender.html
241
- });
242
- this.appendProcessedDependencies(pageRender.assets, layoutRender?.assets, documentRender.assets);
243
- const body = await this.finalizeResolvedHtml({
244
- html: `${this.DOC_TYPE}${normalizeLitHtml(documentRender.html)}`,
245
- partial: false
246
- });
247
- return this.createHtmlResponse(body, ctx);
248
- } catch (error) {
249
- throw this.createRenderError("Error rendering view", error);
210
+ async renderToResponse(view, props, ctx) {
211
+ try {
212
+ const layouts = view.config?.layouts;
213
+ const Layout = layouts?.[layouts.length - 1];
214
+ await this.preloadSsrLazyScripts([view, Layout]);
215
+ return await this.renderViewWithDocumentShell({
216
+ view,
217
+ props,
218
+ ctx,
219
+ layout: Layout,
220
+ transformDocumentHtml: normalizeLitHtml,
221
+ });
222
+ }
223
+ catch (error) {
224
+ throw this.createRenderError('Error rendering view', error);
225
+ }
250
226
  }
251
- }
252
227
  }
253
- export {
254
- LitRenderer
255
- };
@@ -1,4 +1,4 @@
1
- import type { EcoComponent } from '@ecopages/core';
1
+ import { type EcoComponent } from '@ecopages/core';
2
2
  import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
3
3
  type ProcessDependencies = (dependencies: AssetDefinition[], integrationName: string) => Promise<Array<{
4
4
  filepath?: string;