@ecopages/lit 0.2.0-beta.4 → 0.2.0-beta.41

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