@ecopages/lit 0.2.0-beta.9 → 0.2.0-rc.1

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