@ecopages/lit 0.2.1-beta.0 → 0.3.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.
- package/README.md +26 -16
- package/package.json +15 -4
- package/src/STATIC_RENDER.md +75 -0
- package/src/console.d.ts +0 -1
- package/src/console.js +12 -6
- package/src/dom-shim.d.ts +10 -0
- package/src/dom-shim.js +59 -12
- package/src/lit-element-hydrate.d.ts +10 -0
- package/src/lit-element-hydrate.js +11 -4
- package/src/lit-renderer.d.ts +18 -51
- package/src/lit-renderer.js +111 -231
- package/src/lit-static-render-protocol.d.ts +40 -0
- package/src/lit-static-render-protocol.js +1 -0
- package/src/lit-static-render-result.d.ts +10 -0
- package/src/lit-static-render-result.js +21 -0
- package/src/lit-static-render-session.d.ts +65 -0
- package/src/lit-static-render-session.js +145 -0
- package/src/lit-static-render-worker-client.d.ts +26 -0
- package/src/lit-static-render-worker-client.js +181 -0
- package/src/lit-static-render-worker.d.ts +1 -0
- package/src/lit-static-render-worker.js +111 -0
- package/src/lit.constants.js +1 -4
- package/src/lit.plugin.d.ts +36 -3
- package/src/lit.plugin.js +138 -48
- package/src/styled-mixin.js +20 -12
- package/src/utils/lit-html-rendering.js +39 -46
- package/src/lit-ssr-lazy-preloader.d.ts +0 -44
- package/src/lit-ssr-lazy-preloader.js +0 -167
package/src/lit-renderer.js
CHANGED
|
@@ -1,236 +1,116 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} from
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
};
|
|
51
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* This module contains the Lit renderer hooks.
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
5
|
+
import { createIntegrationRenderer } from '@ecopages/core/integration';
|
|
6
|
+
import { resolveQueuedTokens } from '@ecopages/core/integration';
|
|
7
|
+
import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader, } from '@ecopages/core/integration';
|
|
8
|
+
import { requireAppRuntime } from '@ecopages/core/integration';
|
|
9
|
+
import { createCustomElementServerModuleImporter } from '@ecopages/core/integration';
|
|
10
|
+
import { ensureLitDomShim } from './dom-shim.js';
|
|
11
|
+
import { LIT_PLUGIN_NAME } from './lit.constants.js';
|
|
12
|
+
import { injectLitRenderedChildren, LIT_COMPONENT_CHILDREN_SLOT_MARKER, normalizeLitHtml, renderLitValueToString, } from './utils/lit-html-rendering.js';
|
|
13
|
+
/**
|
|
14
|
+
* Creates the Lit renderer class with a lazy worker-session lookup.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* The plugin may create its renderer before `setup()` assigns the static render session, so hooks read the
|
|
18
|
+
* session when they run instead of capturing its construction-time value.
|
|
19
|
+
*/
|
|
20
|
+
export function createLitRenderer(getRenderSession = () => undefined) {
|
|
21
|
+
return createIntegrationRenderer(LIT_PLUGIN_NAME, ({ appConfig, resolveComponentDependencies, resolveDependencyPath }) => {
|
|
22
|
+
const ssrScriptPreloader = new CustomElementScriptPreloader({
|
|
23
|
+
cacheScope: CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit,
|
|
24
|
+
preloadScopes: requireAppRuntime(appConfig).customElementSsrPreloadScopes,
|
|
25
|
+
logLabel: 'lit',
|
|
26
|
+
requireLazyScriptEntry: true,
|
|
27
|
+
resolveDependencyPath,
|
|
28
|
+
importServerModule: createCustomElementServerModuleImporter(appConfig, '.lit-ssr'),
|
|
29
|
+
});
|
|
30
|
+
const preloadSsrScripts = async (components) => {
|
|
31
|
+
ensureLitDomShim();
|
|
32
|
+
const renderSession = getRenderSession();
|
|
33
|
+
if (renderSession) {
|
|
34
|
+
await renderSession.preloadSsrScripts(components);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await ssrScriptPreloader.preloadSsrScripts(components);
|
|
38
|
+
};
|
|
52
39
|
return {
|
|
53
|
-
|
|
54
|
-
|
|
40
|
+
beforeRender: async (renderOptions) => preloadSsrScripts([
|
|
41
|
+
renderOptions.Page,
|
|
42
|
+
renderOptions.Layout,
|
|
43
|
+
...(renderOptions.pageDependencyComponents ?? []),
|
|
44
|
+
]),
|
|
45
|
+
transformDocumentHtml: normalizeLitHtml,
|
|
46
|
+
renderRemote: (routeOptions) => renderLitPageInWorker(routeOptions, getRenderSession),
|
|
47
|
+
renderComponent: async (input) => {
|
|
48
|
+
await preloadSsrScripts([input.component]);
|
|
49
|
+
if (!isFunctionComponent(input.component)) {
|
|
50
|
+
throw new TypeError('Lit renderer expected a callable component.');
|
|
51
|
+
}
|
|
52
|
+
const component = input.component;
|
|
53
|
+
let renderedChildren;
|
|
54
|
+
if (input.children !== undefined) {
|
|
55
|
+
renderedChildren =
|
|
56
|
+
typeof input.children === 'string'
|
|
57
|
+
? input.children
|
|
58
|
+
: await renderLitValueToString(input.children);
|
|
59
|
+
}
|
|
60
|
+
const props = renderedChildren === undefined
|
|
61
|
+
? input.props
|
|
62
|
+
: {
|
|
63
|
+
...input.props,
|
|
64
|
+
children: LIT_COMPONENT_CHILDREN_SLOT_MARKER,
|
|
65
|
+
};
|
|
66
|
+
const content = await component(props);
|
|
67
|
+
const renderedHtml = await renderLitValueToString(content);
|
|
68
|
+
const html = renderedChildren === undefined
|
|
69
|
+
? renderedHtml
|
|
70
|
+
: injectLitRenderedChildren(renderedHtml, renderedChildren);
|
|
71
|
+
const assets = input.component.config?.dependencies
|
|
72
|
+
? await resolveComponentDependencies([input.component])
|
|
73
|
+
: undefined;
|
|
74
|
+
return {
|
|
75
|
+
html,
|
|
76
|
+
canAttachAttributes: true,
|
|
77
|
+
integrationName: LIT_PLUGIN_NAME,
|
|
78
|
+
assets,
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
renderQueuedChildren: async (children, _runtimeContext, queuedResolutionsByToken, resolveToken) => {
|
|
82
|
+
if (children === undefined) {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
if (typeof children !== 'string') {
|
|
86
|
+
return { children };
|
|
87
|
+
}
|
|
88
|
+
return { html: await resolveQueuedTokens(children, queuedResolutionsByToken, resolveToken) };
|
|
89
|
+
},
|
|
55
90
|
};
|
|
56
|
-
}
|
|
57
91
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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.");
|
|
82
|
-
}
|
|
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);
|
|
87
|
-
}
|
|
88
|
-
let props = input.props;
|
|
89
|
-
if (renderedChildren !== void 0) {
|
|
90
|
-
props = {
|
|
91
|
-
...input.props,
|
|
92
|
-
children: LIT_COMPONENT_CHILDREN_SLOT_MARKER
|
|
93
|
-
};
|
|
94
|
-
}
|
|
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);
|
|
187
|
-
}
|
|
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);
|
|
92
|
+
}
|
|
93
|
+
export const LitRenderer = createLitRenderer();
|
|
94
|
+
function isFunctionComponent(component) {
|
|
95
|
+
return typeof component === 'function';
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Renders a Lit page route in its build-scoped worker when request data can cross the worker boundary.
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* Locals and explicit props stay in-process: locals are request objects, and the worker re-imports the route module.
|
|
102
|
+
*/
|
|
103
|
+
async function renderLitPageInWorker(options, getRenderSession) {
|
|
104
|
+
const renderSession = getRenderSession();
|
|
105
|
+
if (!renderSession || options.locals !== undefined || options.props !== undefined) {
|
|
106
|
+
return undefined;
|
|
231
107
|
}
|
|
232
|
-
|
|
108
|
+
const params = options.params ?? {};
|
|
109
|
+
const result = await renderSession.renderPageInWorker({
|
|
110
|
+
filePath: options.file,
|
|
111
|
+
params,
|
|
112
|
+
query: options.query,
|
|
113
|
+
});
|
|
114
|
+
const { html, ...metadata } = result;
|
|
115
|
+
return { body: html, ...metadata };
|
|
233
116
|
}
|
|
234
|
-
export {
|
|
235
|
-
LitRenderer
|
|
236
|
-
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PageParams, PageQuery } from '@ecopages/core';
|
|
2
|
+
import type { RouteRenderResult } from '@ecopages/core/integration';
|
|
3
|
+
/** A rendered page, its resolved metadata, and what the main thread needs to cache it. */
|
|
4
|
+
export type LitStaticRenderResult = Pick<RouteRenderResult, 'cacheStrategy' | 'sourceDependencyPaths' | 'metadata'> & {
|
|
5
|
+
html: string;
|
|
6
|
+
};
|
|
7
|
+
export type LitStaticRenderWorkerInitMessage = {
|
|
8
|
+
type: 'init';
|
|
9
|
+
configModulePath: string;
|
|
10
|
+
runtimeOrigin: string;
|
|
11
|
+
};
|
|
12
|
+
export type LitStaticRenderWorkerRenderMessage = {
|
|
13
|
+
type: 'render';
|
|
14
|
+
id: string;
|
|
15
|
+
filePath: string;
|
|
16
|
+
/** Route params; multi-value keys stay as `string[]`. */
|
|
17
|
+
params: PageParams;
|
|
18
|
+
/** Serializable route query; preserves multi-value keys as `string[]`. */
|
|
19
|
+
query?: PageQuery;
|
|
20
|
+
};
|
|
21
|
+
export type LitStaticRenderWorkerShutdownMessage = {
|
|
22
|
+
type: 'shutdown';
|
|
23
|
+
};
|
|
24
|
+
export type LitStaticRenderWorkerRequestMessage = LitStaticRenderWorkerInitMessage | LitStaticRenderWorkerRenderMessage | LitStaticRenderWorkerShutdownMessage;
|
|
25
|
+
export type LitStaticRenderWorkerReadyMessage = {
|
|
26
|
+
type: 'ready';
|
|
27
|
+
};
|
|
28
|
+
export type LitStaticRenderWorkerResultMessage = {
|
|
29
|
+
type: 'result';
|
|
30
|
+
id: string;
|
|
31
|
+
} & LitStaticRenderResult;
|
|
32
|
+
export type LitStaticRenderWorkerErrorMessage = {
|
|
33
|
+
type: 'error';
|
|
34
|
+
id?: string;
|
|
35
|
+
message: string;
|
|
36
|
+
};
|
|
37
|
+
export type LitStaticRenderWorkerShutdownCompleteMessage = {
|
|
38
|
+
type: 'shutdown-complete';
|
|
39
|
+
};
|
|
40
|
+
export type LitStaticRenderWorkerResponseMessage = LitStaticRenderWorkerReadyMessage | LitStaticRenderWorkerResultMessage | LitStaticRenderWorkerErrorMessage | LitStaticRenderWorkerShutdownCompleteMessage;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { RouteRenderResult } from '@ecopages/core/integration';
|
|
2
|
+
import type { LitStaticRenderResult } from './lit-static-render-protocol.js';
|
|
3
|
+
/**
|
|
4
|
+
* Turns a route render into the message the worker posts back to the main thread.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The resolved `metadata` travels with the HTML so the main thread can decide sitemap eligibility
|
|
8
|
+
* from the render instead of loading the page again.
|
|
9
|
+
*/
|
|
10
|
+
export declare function toLitStaticRenderResult(result: RouteRenderResult): Promise<LitStaticRenderResult>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns a route render into the message the worker posts back to the main thread.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The resolved `metadata` travels with the HTML so the main thread can decide sitemap eligibility
|
|
6
|
+
* from the render instead of loading the page again.
|
|
7
|
+
*/
|
|
8
|
+
export async function toLitStaticRenderResult(result) {
|
|
9
|
+
const { body, cacheStrategy, sourceDependencyPaths, metadata } = result;
|
|
10
|
+
const rendered = { cacheStrategy, sourceDependencyPaths, metadata };
|
|
11
|
+
if (typeof body === 'string') {
|
|
12
|
+
return { html: body, ...rendered };
|
|
13
|
+
}
|
|
14
|
+
if (Buffer.isBuffer(body)) {
|
|
15
|
+
return { html: body.toString('utf8'), ...rendered };
|
|
16
|
+
}
|
|
17
|
+
if (body instanceof ReadableStream) {
|
|
18
|
+
return { html: await new Response(body).text(), ...rendered };
|
|
19
|
+
}
|
|
20
|
+
throw new TypeError(`Unsupported Lit static render body type: ${typeof body}`);
|
|
21
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { PageParams, PageQuery } from '@ecopages/core';
|
|
2
|
+
import type { StaticExportContext } from '@ecopages/core/integration';
|
|
3
|
+
import { type CustomElementSsrPreloadComponent } from '@ecopages/core/integration';
|
|
4
|
+
import type { CustomElementScriptPreloadScopeState } from '@ecopages/core/integration';
|
|
5
|
+
import { LitStaticRenderWorkerClient } from './lit-static-render-worker-client.js';
|
|
6
|
+
import type { LitStaticRenderResult } from './lit-static-render-protocol.js';
|
|
7
|
+
import type { AssetDefinition, AssetProcessingIntegration, ProcessedAsset } from '@ecopages/core/integration';
|
|
8
|
+
type LitStaticRenderWorkerClientContract = Pick<LitStaticRenderWorkerClient, 'start' | 'renderPage' | 'dispose'>;
|
|
9
|
+
type LitStaticRenderSessionOptions = {
|
|
10
|
+
preloadScopes: Map<string, CustomElementScriptPreloadScopeState>;
|
|
11
|
+
integration?: AssetProcessingIntegration;
|
|
12
|
+
resolveDependencyPath: (componentDir: string, sourcePath: string) => string;
|
|
13
|
+
processDependencies: (dependencies: AssetDefinition[], integration?: AssetProcessingIntegration) => Promise<ProcessedAsset[]>;
|
|
14
|
+
getInvalidationVersion?: () => number;
|
|
15
|
+
createWorkerClient?: (input: {
|
|
16
|
+
configModulePath: string;
|
|
17
|
+
runtimeOrigin: string;
|
|
18
|
+
}) => LitStaticRenderWorkerClientContract;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Plugin-runtime-scoped Lit server render session.
|
|
22
|
+
*
|
|
23
|
+
* @remarks
|
|
24
|
+
* Owns worker lifecycle and SSR preload for Lit page routes during static export
|
|
25
|
+
* and runtime/dev requests. Injected into `LitRenderer` via a lazy accessor from
|
|
26
|
+
* `LitPlugin`; there is no process-global coordinator.
|
|
27
|
+
*
|
|
28
|
+
* Worker init identity is sticky for the first `{ configModulePath, runtimeOrigin }`.
|
|
29
|
+
* Later `ensureWorker` calls with the same identity are no-ops; a different identity
|
|
30
|
+
* recreates the worker so stale config/origin is never silently reused.
|
|
31
|
+
*/
|
|
32
|
+
export declare class LitStaticRenderSession {
|
|
33
|
+
private readonly preloader;
|
|
34
|
+
private readonly createWorkerClient;
|
|
35
|
+
private workerClient;
|
|
36
|
+
private workerIdentity;
|
|
37
|
+
private readonly getInvalidationVersion;
|
|
38
|
+
private workerInvalidationVersion;
|
|
39
|
+
private renderChain;
|
|
40
|
+
constructor(options: LitStaticRenderSessionOptions);
|
|
41
|
+
/**
|
|
42
|
+
* Ensures the static render worker is running for the given identity.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* First successful start sticks that identity. Matching re-entry is a no-op.
|
|
46
|
+
* A different `configModulePath` or `runtimeOrigin` disposes and recreates.
|
|
47
|
+
*/
|
|
48
|
+
ensureWorker(input: {
|
|
49
|
+
configModulePath: string;
|
|
50
|
+
runtimeOrigin: string;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
preloadStaticRoutes(context: StaticExportContext): Promise<void>;
|
|
53
|
+
preloadSsrScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): Promise<void>;
|
|
54
|
+
collectSsrPreloadScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): string[];
|
|
55
|
+
renderPageInWorker(input: {
|
|
56
|
+
filePath: string;
|
|
57
|
+
params: PageParams;
|
|
58
|
+
query?: PageQuery;
|
|
59
|
+
}): Promise<LitStaticRenderResult>;
|
|
60
|
+
isExpectedSsrPreloadError(error: unknown): boolean;
|
|
61
|
+
dispose(): Promise<void>;
|
|
62
|
+
private preloadLitRoutes;
|
|
63
|
+
private isLitRoute;
|
|
64
|
+
}
|
|
65
|
+
export {};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { ensureLitDomShim } from './dom-shim.js';
|
|
3
|
+
import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader, } from '@ecopages/core/integration';
|
|
4
|
+
import { LIT_PLUGIN_NAME } from './lit.constants.js';
|
|
5
|
+
import { createCustomElementSsrPipelineImporter } from '@ecopages/core/integration';
|
|
6
|
+
import { LitStaticRenderWorkerClient } from './lit-static-render-worker-client.js';
|
|
7
|
+
/**
|
|
8
|
+
* Plugin-runtime-scoped Lit server render session.
|
|
9
|
+
*
|
|
10
|
+
* @remarks
|
|
11
|
+
* Owns worker lifecycle and SSR preload for Lit page routes during static export
|
|
12
|
+
* and runtime/dev requests. Injected into `LitRenderer` via a lazy accessor from
|
|
13
|
+
* `LitPlugin`; there is no process-global coordinator.
|
|
14
|
+
*
|
|
15
|
+
* Worker init identity is sticky for the first `{ configModulePath, runtimeOrigin }`.
|
|
16
|
+
* Later `ensureWorker` calls with the same identity are no-ops; a different identity
|
|
17
|
+
* recreates the worker so stale config/origin is never silently reused.
|
|
18
|
+
*/
|
|
19
|
+
export class LitStaticRenderSession {
|
|
20
|
+
preloader;
|
|
21
|
+
createWorkerClient;
|
|
22
|
+
workerClient = null;
|
|
23
|
+
workerIdentity = null;
|
|
24
|
+
getInvalidationVersion;
|
|
25
|
+
workerInvalidationVersion = 0;
|
|
26
|
+
renderChain = Promise.resolve();
|
|
27
|
+
constructor(options) {
|
|
28
|
+
this.getInvalidationVersion = options.getInvalidationVersion ?? (() => 0);
|
|
29
|
+
this.preloader = new CustomElementScriptPreloader({
|
|
30
|
+
cacheScope: CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit,
|
|
31
|
+
preloadScopes: options.preloadScopes,
|
|
32
|
+
logLabel: 'lit',
|
|
33
|
+
requireLazyScriptEntry: true,
|
|
34
|
+
resolveDependencyPath: options.resolveDependencyPath,
|
|
35
|
+
importServerModule: createCustomElementSsrPipelineImporter({
|
|
36
|
+
integration: options.integration,
|
|
37
|
+
processDependencies: options.processDependencies,
|
|
38
|
+
}),
|
|
39
|
+
});
|
|
40
|
+
this.createWorkerClient =
|
|
41
|
+
options.createWorkerClient ??
|
|
42
|
+
((input) => new LitStaticRenderWorkerClient({
|
|
43
|
+
configModulePath: input.configModulePath,
|
|
44
|
+
runtimeOrigin: input.runtimeOrigin,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Ensures the static render worker is running for the given identity.
|
|
49
|
+
*
|
|
50
|
+
* @remarks
|
|
51
|
+
* First successful start sticks that identity. Matching re-entry is a no-op.
|
|
52
|
+
* A different `configModulePath` or `runtimeOrigin` disposes and recreates.
|
|
53
|
+
*/
|
|
54
|
+
async ensureWorker(input) {
|
|
55
|
+
if (this.workerClient &&
|
|
56
|
+
this.workerIdentity &&
|
|
57
|
+
this.workerIdentity.configModulePath === input.configModulePath &&
|
|
58
|
+
this.workerIdentity.runtimeOrigin === input.runtimeOrigin) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (this.workerClient) {
|
|
62
|
+
await this.dispose();
|
|
63
|
+
}
|
|
64
|
+
this.workerIdentity = {
|
|
65
|
+
configModulePath: input.configModulePath,
|
|
66
|
+
runtimeOrigin: input.runtimeOrigin,
|
|
67
|
+
};
|
|
68
|
+
this.workerClient = this.createWorkerClient({
|
|
69
|
+
configModulePath: input.configModulePath,
|
|
70
|
+
runtimeOrigin: input.runtimeOrigin,
|
|
71
|
+
});
|
|
72
|
+
await this.workerClient.start();
|
|
73
|
+
this.workerInvalidationVersion = this.getInvalidationVersion();
|
|
74
|
+
}
|
|
75
|
+
async preloadStaticRoutes(context) {
|
|
76
|
+
await this.preloadLitRoutes(context);
|
|
77
|
+
}
|
|
78
|
+
async preloadSsrScripts(components) {
|
|
79
|
+
ensureLitDomShim();
|
|
80
|
+
await this.preloader.preloadSsrScripts(components);
|
|
81
|
+
}
|
|
82
|
+
collectSsrPreloadScripts(components) {
|
|
83
|
+
return this.preloader.collectSsrPreloadScripts(components);
|
|
84
|
+
}
|
|
85
|
+
async renderPageInWorker(input) {
|
|
86
|
+
if (!this.workerClient) {
|
|
87
|
+
throw new Error('Lit static render worker is not active');
|
|
88
|
+
}
|
|
89
|
+
const render = this.renderChain.then(async () => {
|
|
90
|
+
const version = this.getInvalidationVersion();
|
|
91
|
+
if (version !== this.workerInvalidationVersion && this.workerIdentity) {
|
|
92
|
+
const identity = this.workerIdentity;
|
|
93
|
+
await this.dispose();
|
|
94
|
+
await this.ensureWorker(identity);
|
|
95
|
+
}
|
|
96
|
+
if (!this.workerClient)
|
|
97
|
+
throw new Error('Lit static render worker is not active');
|
|
98
|
+
return this.workerClient.renderPage(input);
|
|
99
|
+
});
|
|
100
|
+
this.renderChain = render.then(() => undefined, () => undefined);
|
|
101
|
+
return render;
|
|
102
|
+
}
|
|
103
|
+
isExpectedSsrPreloadError(error) {
|
|
104
|
+
return this.preloader.isExpectedSsrPreloadError(error);
|
|
105
|
+
}
|
|
106
|
+
async dispose() {
|
|
107
|
+
await this.workerClient?.dispose();
|
|
108
|
+
this.workerClient = null;
|
|
109
|
+
this.workerIdentity = null;
|
|
110
|
+
}
|
|
111
|
+
async preloadLitRoutes(context) {
|
|
112
|
+
if (!context.routeRendererFactory) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const routes = await context.router.listStaticGenerationRoutes({ runtimeOrigin: context.baseUrl });
|
|
116
|
+
const components = [];
|
|
117
|
+
for (const route of routes) {
|
|
118
|
+
const filePath = route.templateRoute.filePath;
|
|
119
|
+
if (!this.isLitRoute(filePath, context.appConfig)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
const pageRenderer = context.routeRendererFactory.getPageRenderer(filePath);
|
|
123
|
+
const pageModule = await pageRenderer.loadPageModule(filePath);
|
|
124
|
+
const page = pageModule.default;
|
|
125
|
+
if (page) {
|
|
126
|
+
components.push(page);
|
|
127
|
+
for (const layout of page.config?.layouts ?? []) {
|
|
128
|
+
components.push(layout);
|
|
129
|
+
}
|
|
130
|
+
for (const nestedComponent of page.config?.dependencies?.components ?? []) {
|
|
131
|
+
components.push(nestedComponent);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
await this.preloadSsrScripts(components);
|
|
136
|
+
}
|
|
137
|
+
isLitRoute(filePath, appConfig) {
|
|
138
|
+
const litIntegration = appConfig.integrations.find((plugin) => plugin.name === LIT_PLUGIN_NAME);
|
|
139
|
+
if (!litIntegration) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const normalizedPath = path.normalize(filePath);
|
|
143
|
+
return litIntegration.extensions.some((extension) => normalizedPath.endsWith(extension));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { PageParams, PageQuery } from '@ecopages/core';
|
|
2
|
+
import type { LitStaticRenderResult } from './lit-static-render-protocol.js';
|
|
3
|
+
export declare class LitStaticRenderWorkerClient {
|
|
4
|
+
private readonly configModulePath;
|
|
5
|
+
private readonly runtimeOrigin;
|
|
6
|
+
private worker;
|
|
7
|
+
private workerExitPromise;
|
|
8
|
+
private workerExitCode;
|
|
9
|
+
private readyPromise;
|
|
10
|
+
private pending;
|
|
11
|
+
private nextRenderId;
|
|
12
|
+
private shutdownHandler;
|
|
13
|
+
constructor({ configModulePath, runtimeOrigin }: {
|
|
14
|
+
configModulePath: string;
|
|
15
|
+
runtimeOrigin: string;
|
|
16
|
+
});
|
|
17
|
+
private rejectPending;
|
|
18
|
+
start(): Promise<void>;
|
|
19
|
+
renderPage(input: {
|
|
20
|
+
filePath: string;
|
|
21
|
+
params: PageParams;
|
|
22
|
+
query?: PageQuery;
|
|
23
|
+
}): Promise<LitStaticRenderResult>;
|
|
24
|
+
dispose(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export declare const litStaticRenderWorkerEntryPath: string;
|