@ecopages/lit 0.2.0-rc.7 → 0.2.0-rc.8
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 +1 -1
- package/package.json +2 -2
- package/src/STATIC_RENDER.md +2 -0
- package/src/dom-shim.js +40 -6
- package/src/lit-renderer.d.ts +14 -15
- package/src/lit-renderer.js +35 -29
- package/src/lit-ssr-preload-entrypoint.d.ts +1 -0
- package/src/lit-ssr-preload-entrypoint.js +1 -0
- package/src/lit-static-render-session.d.ts +6 -8
- package/src/lit-static-render-session.js +17 -7
- package/src/lit.plugin.d.ts +2 -0
- package/src/lit.plugin.js +15 -2
- package/src/lit-server-module-loader.d.ts +0 -6
- package/src/lit-server-module-loader.js +0 -18
- package/src/lit-ssr-lazy-preloader.d.ts +0 -48
- package/src/lit-ssr-lazy-preloader.js +0 -198
package/README.md
CHANGED
|
@@ -72,4 +72,4 @@ Important:
|
|
|
72
72
|
|
|
73
73
|
A Lit Component can import a relative stylesheet with `import './counter.css'`; core extracts it into the shared asset pipeline. Direct local imports of that Component make its Dependencies available to the Lit SSR preloader, including through Foreign Child rendering.
|
|
74
74
|
|
|
75
|
-
Keep custom-element registration explicit with `scripts: [{ src: './counter.script.ts', ssr: true, lazy: { 'on:visible': true } }]`.
|
|
75
|
+
Keep custom-element registration explicit with `scripts: [{ src: './counter.script.ts', ssr: true, lazy: { 'on:visible': true } }]`. Entries with `ssr: true` execute on the server before rendering (eager or lazy) and load in the browser according to `lazy`. `ssr: true` does not emit an additional eager browser script. Omit `lazy` when eager browser loading is intended. SSR preload artifacts are excluded from HTML, and hydration support must be available before the browser registers the element. Lit-owned page routes preload those scripts in the static-render worker through the app module loader so `@lit-labs/ssr` sees the registered custom element.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/lit",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.8",
|
|
4
4
|
"description": "Ecopages lit integration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"directory": "packages/integrations/lit"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@ecopages/core": "0.2.0-rc.
|
|
45
|
+
"@ecopages/core": "0.2.0-rc.8",
|
|
46
46
|
"@lit-labs/ssr": "^3.3.0",
|
|
47
47
|
"@lit-labs/ssr-client": "^1.1.7",
|
|
48
48
|
"lit": "^3.2.1"
|
package/src/STATIC_RENDER.md
CHANGED
|
@@ -24,6 +24,8 @@ LitRenderer.execute() (main)
|
|
|
24
24
|
└─ session.renderPageInWorker()
|
|
25
25
|
└─ postMessage(render) ─────► RouteRendererFactory.execute()
|
|
26
26
|
(no active session in worker)
|
|
27
|
+
LitRenderer.ssrScriptPreloader uses
|
|
28
|
+
importServerModule (no entrypoint resolver)
|
|
27
29
|
|
|
28
30
|
beforeStaticExport
|
|
29
31
|
└─ ensureWorker() only when `renderSession` is still null
|
package/src/dom-shim.js
CHANGED
|
@@ -2,15 +2,49 @@ import { installWindowOnGlobal } from '@lit-labs/ssr/lib/dom-shim.js';
|
|
|
2
2
|
import '@lit-labs/ssr/lib/install-global-dom-shim.js';
|
|
3
3
|
/** Repairs the minimal Lit DOM surface after another Integration installs its SSR globals. */
|
|
4
4
|
export function ensureLitDomShim() {
|
|
5
|
-
if (typeof globalThis.
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
if (typeof globalThis.window === 'undefined') {
|
|
6
|
+
installWindowOnGlobal();
|
|
7
|
+
}
|
|
8
|
+
if (typeof globalThis.document !== 'undefined' && typeof globalThis.document.createTreeWalker !== 'function') {
|
|
9
|
+
Object.defineProperty(globalThis.document, 'createTreeWalker', {
|
|
10
|
+
configurable: true,
|
|
11
|
+
writable: true,
|
|
12
|
+
value: () => ({}),
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
for (const target of [globalThis.Element, globalThis.HTMLElement]) {
|
|
16
|
+
if (typeof target === 'undefined' || !target?.prototype) {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
if (!('attributes' in target.prototype)) {
|
|
20
|
+
Object.defineProperty(target.prototype, 'attributes', {
|
|
21
|
+
configurable: true,
|
|
22
|
+
enumerable: true,
|
|
23
|
+
get() {
|
|
24
|
+
if (typeof this.getAttributeNames === 'function') {
|
|
25
|
+
return this.getAttributeNames().map((name) => ({
|
|
26
|
+
name,
|
|
27
|
+
value: this.getAttribute(name),
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
return [];
|
|
31
|
+
},
|
|
32
|
+
});
|
|
8
33
|
}
|
|
9
|
-
|
|
10
|
-
Object.defineProperty(
|
|
34
|
+
if (typeof target.prototype.attachShadow !== 'function') {
|
|
35
|
+
Object.defineProperty(target.prototype, 'attachShadow', {
|
|
11
36
|
configurable: true,
|
|
12
37
|
writable: true,
|
|
13
|
-
value: (
|
|
38
|
+
value: function attachShadow(init = { mode: 'open' }) {
|
|
39
|
+
const shadowRoot = {
|
|
40
|
+
mode: init?.mode ?? 'open',
|
|
41
|
+
host: this,
|
|
42
|
+
children: [],
|
|
43
|
+
childNodes: [],
|
|
44
|
+
};
|
|
45
|
+
this.shadowRoot = shadowRoot;
|
|
46
|
+
return shadowRoot;
|
|
47
|
+
},
|
|
14
48
|
});
|
|
15
49
|
}
|
|
16
50
|
}
|
package/src/lit-renderer.d.ts
CHANGED
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
5
|
import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPagesElement, IntegrationRendererRenderOptions, RouteRenderResult, RouteRendererBody, RouteRendererOptions } from '@ecopages/core';
|
|
6
|
-
import './dom-shim.js';
|
|
7
6
|
import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
|
|
8
|
-
import { type
|
|
7
|
+
import { type CustomElementSsrPreloadComponent } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
|
|
9
8
|
import type { LitStaticRenderSession } from './lit-static-render-session.js';
|
|
10
9
|
export type LitRendererOptions = ConstructorParameters<typeof IntegrationRenderer>[0] & {
|
|
11
10
|
/**
|
|
@@ -24,6 +23,7 @@ export type LitRendererOptions = ConstructorParameters<typeof IntegrationRendere
|
|
|
24
23
|
export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
25
24
|
name: string;
|
|
26
25
|
private readonly getRenderSession?;
|
|
26
|
+
private readonly ssrScriptPreloader;
|
|
27
27
|
constructor(options: LitRendererOptions);
|
|
28
28
|
/**
|
|
29
29
|
* Renders a Lit page route, preferring the plugin worker when available.
|
|
@@ -41,14 +41,13 @@ export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
41
41
|
/**
|
|
42
42
|
* Renders a Lit component for component-level orchestration.
|
|
43
43
|
*
|
|
44
|
-
* SSR-eligible
|
|
44
|
+
* SSR-eligible scripts are preloaded first so custom elements registered
|
|
45
45
|
* by the component can render their server markup even when the Lit renderer is
|
|
46
46
|
* entered through cross-integration foreign-child handoff.
|
|
47
47
|
*
|
|
48
48
|
* Includes component-scoped dependency assets when declared.
|
|
49
49
|
*/
|
|
50
50
|
renderComponent(input: ComponentRenderInput): Promise<ComponentRenderResult>;
|
|
51
|
-
private readonly ssrLazyPreloader;
|
|
52
51
|
/**
|
|
53
52
|
* Detects preload failures that are expected for browser-only modules.
|
|
54
53
|
*
|
|
@@ -57,24 +56,24 @@ export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
|
57
56
|
*/
|
|
58
57
|
protected isExpectedSsrPreloadError(error: unknown): boolean;
|
|
59
58
|
/**
|
|
60
|
-
* Collects
|
|
59
|
+
* Collects script file paths eligible for SSR preloading.
|
|
61
60
|
*
|
|
62
|
-
*
|
|
61
|
+
* Per-entry script dependencies with `ssr: true` are collected (eager or lazy).
|
|
63
62
|
* File-backed entries are required (`src` must be present);
|
|
64
63
|
* inline content lazy entries are intentionally skipped.
|
|
65
64
|
*/
|
|
66
|
-
protected collectSsrPreloadScripts(components: Array<
|
|
65
|
+
protected collectSsrPreloadScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): string[];
|
|
67
66
|
/**
|
|
68
|
-
* Preloads SSR-eligible
|
|
69
|
-
*/
|
|
70
|
-
protected preloadSsrLazyScripts(components: Array<LitSsrPreloadComponent | undefined>): Promise<void>;
|
|
71
|
-
/**
|
|
72
|
-
* Resolves the concrete JS entrypoint used for SSR preloading.
|
|
67
|
+
* Preloads SSR-eligible scripts to register custom elements before render.
|
|
73
68
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* When a render session exists (main thread), preload uses the session so
|
|
71
|
+
* Bun source imports and the Node asset pipeline stay on that isolate.
|
|
72
|
+
* With no session (static-render worker), preload uses this renderer's
|
|
73
|
+
* app-module importer. A native source import in the worker leaves custom
|
|
74
|
+
* elements undefined for `@lit-labs/ssr`.
|
|
76
75
|
*/
|
|
77
|
-
protected
|
|
76
|
+
protected preloadSsrScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): Promise<void>;
|
|
78
77
|
render({ params, query, props, locals, metadata, Page, Layout, layoutEntries, HtmlTemplate, resolvedPageDependencyComponents, }: IntegrationRendererRenderOptions): Promise<RouteRendererBody>;
|
|
79
78
|
renderToResponse<P = Record<string, unknown>>(view: EcoComponent<P>, props: P, ctx: RenderToResponseContext): Promise<Response>;
|
|
80
79
|
}
|
package/src/lit-renderer.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* This module contains the Lit renderer
|
|
3
|
+
* @module
|
|
4
|
+
*/
|
|
3
5
|
import { IntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
|
|
4
6
|
import { resolveDocumentShellLayouts } from '@ecopages/core/route-renderer/orchestration/document-shell/layout-shell-props.service';
|
|
5
7
|
import { getForeignSubtreeResolutionContextKey, resolveOwningIntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/foreign-child/owning-renderer-resolution';
|
|
6
|
-
import {
|
|
8
|
+
import { ensureLitDomShim } from './dom-shim.js';
|
|
9
|
+
import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
|
|
10
|
+
import { createCustomElementServerModuleImporter } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-server-module-importer';
|
|
7
11
|
import { LIT_PLUGIN_NAME } from './lit.constants.js';
|
|
8
12
|
import { injectLitRenderedChildren, LIT_COMPONENT_CHILDREN_SLOT_MARKER, normalizeLitHtml, renderLitValueToString, } from './utils/lit-html-rendering.js';
|
|
9
13
|
/**
|
|
@@ -12,10 +16,18 @@ import { injectLitRenderedChildren, LIT_COMPONENT_CHILDREN_SLOT_MARKER, normaliz
|
|
|
12
16
|
export class LitRenderer extends IntegrationRenderer {
|
|
13
17
|
name = LIT_PLUGIN_NAME;
|
|
14
18
|
getRenderSession;
|
|
19
|
+
ssrScriptPreloader;
|
|
15
20
|
constructor(options) {
|
|
16
21
|
const { getRenderSession, ...rendererOptions } = options;
|
|
17
22
|
super(rendererOptions);
|
|
18
23
|
this.getRenderSession = getRenderSession;
|
|
24
|
+
this.ssrScriptPreloader = new CustomElementScriptPreloader({
|
|
25
|
+
cacheScope: CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit,
|
|
26
|
+
logLabel: 'lit',
|
|
27
|
+
requireLazyScriptEntry: true,
|
|
28
|
+
resolveDependencyPath: this.resolveDependencyPath.bind(this),
|
|
29
|
+
importServerModule: createCustomElementServerModuleImporter(this.appConfig, '.lit-ssr'),
|
|
30
|
+
});
|
|
19
31
|
}
|
|
20
32
|
/**
|
|
21
33
|
* Renders a Lit page route, preferring the plugin worker when available.
|
|
@@ -73,14 +85,14 @@ export class LitRenderer extends IntegrationRenderer {
|
|
|
73
85
|
/**
|
|
74
86
|
* Renders a Lit component for component-level orchestration.
|
|
75
87
|
*
|
|
76
|
-
* SSR-eligible
|
|
88
|
+
* SSR-eligible scripts are preloaded first so custom elements registered
|
|
77
89
|
* by the component can render their server markup even when the Lit renderer is
|
|
78
90
|
* entered through cross-integration foreign-child handoff.
|
|
79
91
|
*
|
|
80
92
|
* Includes component-scoped dependency assets when declared.
|
|
81
93
|
*/
|
|
82
94
|
async renderComponent(input) {
|
|
83
|
-
await this.
|
|
95
|
+
await this.preloadSsrScripts([input.component]);
|
|
84
96
|
if (!this.isFunctionComponent(input.component)) {
|
|
85
97
|
throw new TypeError('Lit renderer expected a callable component.');
|
|
86
98
|
}
|
|
@@ -133,11 +145,6 @@ export class LitRenderer extends IntegrationRenderer {
|
|
|
133
145
|
]),
|
|
134
146
|
});
|
|
135
147
|
}
|
|
136
|
-
ssrLazyPreloader = new LitSsrLazyPreloader({
|
|
137
|
-
importServerModule: createLitServerModuleImporter(this.appConfig),
|
|
138
|
-
resolveDependencyPath: this.resolveDependencyPath.bind(this),
|
|
139
|
-
processDependencies: this.assetProcessingService?.processDependencies?.bind(this.assetProcessingService),
|
|
140
|
-
});
|
|
141
148
|
/**
|
|
142
149
|
* Detects preload failures that are expected for browser-only modules.
|
|
143
150
|
*
|
|
@@ -145,41 +152,40 @@ export class LitRenderer extends IntegrationRenderer {
|
|
|
145
152
|
* lazy client scripts intentionally depend on browser globals.
|
|
146
153
|
*/
|
|
147
154
|
isExpectedSsrPreloadError(error) {
|
|
148
|
-
return this.
|
|
155
|
+
return this.ssrScriptPreloader.isExpectedSsrPreloadError(error);
|
|
149
156
|
}
|
|
150
157
|
/**
|
|
151
|
-
* Collects
|
|
158
|
+
* Collects script file paths eligible for SSR preloading.
|
|
152
159
|
*
|
|
153
|
-
*
|
|
160
|
+
* Per-entry script dependencies with `ssr: true` are collected (eager or lazy).
|
|
154
161
|
* File-backed entries are required (`src` must be present);
|
|
155
162
|
* inline content lazy entries are intentionally skipped.
|
|
156
163
|
*/
|
|
157
164
|
collectSsrPreloadScripts(components) {
|
|
158
|
-
return this.
|
|
165
|
+
return this.ssrScriptPreloader.collectSsrPreloadScripts(components);
|
|
159
166
|
}
|
|
160
167
|
/**
|
|
161
|
-
* Preloads SSR-eligible
|
|
168
|
+
* Preloads SSR-eligible scripts to register custom elements before render.
|
|
169
|
+
*
|
|
170
|
+
* @remarks
|
|
171
|
+
* When a render session exists (main thread), preload uses the session so
|
|
172
|
+
* Bun source imports and the Node asset pipeline stay on that isolate.
|
|
173
|
+
* With no session (static-render worker), preload uses this renderer's
|
|
174
|
+
* app-module importer. A native source import in the worker leaves custom
|
|
175
|
+
* elements undefined for `@lit-labs/ssr`.
|
|
162
176
|
*/
|
|
163
|
-
async
|
|
177
|
+
async preloadSsrScripts(components) {
|
|
178
|
+
ensureLitDomShim();
|
|
164
179
|
const renderSession = this.getRenderSession?.();
|
|
165
180
|
if (renderSession) {
|
|
166
|
-
await renderSession.
|
|
181
|
+
await renderSession.preloadSsrScripts(components);
|
|
167
182
|
return;
|
|
168
183
|
}
|
|
169
|
-
await this.
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Resolves the concrete JS entrypoint used for SSR preloading.
|
|
173
|
-
*
|
|
174
|
-
* Scripts are passed through the asset pipeline so preload imports can use
|
|
175
|
-
* the same processed output shape as runtime dependencies.
|
|
176
|
-
*/
|
|
177
|
-
async resolveSsrPreloadEntrypoint(scriptPath) {
|
|
178
|
-
return this.ssrLazyPreloader.resolveSsrPreloadEntrypoint(scriptPath);
|
|
184
|
+
await this.ssrScriptPreloader.preloadSsrScripts(components);
|
|
179
185
|
}
|
|
180
186
|
async render({ params, query, props, locals, metadata, Page, Layout, layoutEntries, HtmlTemplate, resolvedPageDependencyComponents, }) {
|
|
181
187
|
try {
|
|
182
|
-
await this.
|
|
188
|
+
await this.preloadSsrScripts([Page, Layout, ...(resolvedPageDependencyComponents ?? [])]);
|
|
183
189
|
return await this.renderPageWithDocumentShell({
|
|
184
190
|
page: {
|
|
185
191
|
component: Page,
|
|
@@ -212,7 +218,7 @@ export class LitRenderer extends IntegrationRenderer {
|
|
|
212
218
|
try {
|
|
213
219
|
const layouts = view.config?.layouts;
|
|
214
220
|
const Layout = layouts?.[layouts.length - 1];
|
|
215
|
-
await this.
|
|
221
|
+
await this.preloadSsrScripts([view, Layout]);
|
|
216
222
|
return await this.renderViewWithDocumentShell({
|
|
217
223
|
view,
|
|
218
224
|
props,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createCustomElementSsrPreloadEntrypointResolver as createLitSsrPreloadEntrypointResolver, type CustomElementSsrPreloadEntrypointResolverOptions as LitSsrPreloadEntrypointResolverOptions, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-ssr-preload-entrypoint';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createCustomElementSsrPreloadEntrypointResolver as createLitSsrPreloadEntrypointResolver, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-ssr-preload-entrypoint';
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import type { PageParams, PageQuery } from '@ecopages/core';
|
|
2
2
|
import type { StaticExportContext } from '@ecopages/core/plugins/integration-plugin';
|
|
3
|
-
import type
|
|
4
|
-
import { type LitSsrLazyPreloaderOptions, type LitSsrPreloadComponent } from './lit-ssr-lazy-preloader.js';
|
|
3
|
+
import { type CustomElementScriptPreloaderOptions, type CustomElementSsrPreloadComponent } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
|
|
5
4
|
import { LitStaticRenderWorkerClient } from './lit-static-render-worker-client.js';
|
|
6
5
|
import type { LitStaticRenderCacheStrategy } from './lit-static-render-protocol.js';
|
|
6
|
+
import type { AssetDefinition, ProcessedAsset } from '@ecopages/core/services/asset-processing-service';
|
|
7
7
|
type LitStaticRenderWorkerClientContract = Pick<LitStaticRenderWorkerClient, 'start' | 'renderPage' | 'dispose'>;
|
|
8
8
|
type LitStaticRenderSessionOptions = {
|
|
9
9
|
resolveDependencyPath: (componentDir: string, sourcePath: string) => string;
|
|
10
|
-
processDependencies?: (dependencies: AssetDefinition[], integrationName: string) => Promise<Array<{
|
|
11
|
-
filepath?: string;
|
|
12
|
-
}>>;
|
|
13
10
|
preferSourceImports?: boolean;
|
|
14
|
-
|
|
11
|
+
processDependencies?: (dependencies: AssetDefinition[], integrationName: string) => Promise<ProcessedAsset[]>;
|
|
12
|
+
importServerModule?: CustomElementScriptPreloaderOptions['importServerModule'];
|
|
15
13
|
getInvalidationVersion?: () => number;
|
|
16
14
|
createWorkerClient?: (input: {
|
|
17
15
|
configModulePath: string;
|
|
@@ -51,8 +49,8 @@ export declare class LitStaticRenderSession {
|
|
|
51
49
|
runtimeOrigin: string;
|
|
52
50
|
}): Promise<void>;
|
|
53
51
|
preloadStaticRoutes(context: StaticExportContext): Promise<void>;
|
|
54
|
-
|
|
55
|
-
collectSsrPreloadScripts(components: Array<
|
|
52
|
+
preloadSsrScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): Promise<void>;
|
|
53
|
+
collectSsrPreloadScripts(components: Array<CustomElementSsrPreloadComponent | undefined>): string[];
|
|
56
54
|
renderPageInWorker(input: {
|
|
57
55
|
filePath: string;
|
|
58
56
|
params: PageParams;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import {
|
|
2
|
+
import { ensureLitDomShim } from './dom-shim.js';
|
|
3
|
+
import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, CustomElementScriptPreloader, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
|
|
3
4
|
import { LIT_PLUGIN_NAME } from './lit.constants.js';
|
|
5
|
+
import { createLitSsrPreloadEntrypointResolver } from './lit-ssr-preload-entrypoint.js';
|
|
4
6
|
import { LitStaticRenderWorkerClient } from './lit-static-render-worker-client.js';
|
|
5
7
|
/**
|
|
6
8
|
* Plugin-runtime-scoped Lit server render session.
|
|
@@ -24,10 +26,17 @@ export class LitStaticRenderSession {
|
|
|
24
26
|
renderChain = Promise.resolve();
|
|
25
27
|
constructor(options) {
|
|
26
28
|
this.getInvalidationVersion = options.getInvalidationVersion ?? (() => 0);
|
|
27
|
-
|
|
29
|
+
const preferSourceImports = options.preferSourceImports ?? typeof Bun !== 'undefined';
|
|
30
|
+
this.preloader = new CustomElementScriptPreloader({
|
|
31
|
+
cacheScope: CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit,
|
|
32
|
+
logLabel: 'lit',
|
|
33
|
+
requireLazyScriptEntry: true,
|
|
28
34
|
resolveDependencyPath: options.resolveDependencyPath,
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
preferSourceImports,
|
|
36
|
+
resolvePreloadEntrypoint: createLitSsrPreloadEntrypointResolver({
|
|
37
|
+
preferSourceImports,
|
|
38
|
+
processDependencies: options.processDependencies,
|
|
39
|
+
}),
|
|
31
40
|
importServerModule: options.importServerModule,
|
|
32
41
|
});
|
|
33
42
|
this.createWorkerClient =
|
|
@@ -68,8 +77,9 @@ export class LitStaticRenderSession {
|
|
|
68
77
|
async preloadStaticRoutes(context) {
|
|
69
78
|
await this.preloadLitRoutes(context);
|
|
70
79
|
}
|
|
71
|
-
async
|
|
72
|
-
|
|
80
|
+
async preloadSsrScripts(components) {
|
|
81
|
+
ensureLitDomShim();
|
|
82
|
+
await this.preloader.preloadSsrScripts(components);
|
|
73
83
|
}
|
|
74
84
|
collectSsrPreloadScripts(components) {
|
|
75
85
|
return this.preloader.collectSsrPreloadScripts(components);
|
|
@@ -124,7 +134,7 @@ export class LitStaticRenderSession {
|
|
|
124
134
|
}
|
|
125
135
|
}
|
|
126
136
|
}
|
|
127
|
-
await this.
|
|
137
|
+
await this.preloadSsrScripts(components);
|
|
128
138
|
}
|
|
129
139
|
isLitRoute(filePath, appConfig) {
|
|
130
140
|
const litIntegration = appConfig.integrations.find((plugin) => plugin.name === LIT_PLUGIN_NAME);
|
package/src/lit.plugin.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { IHmrManager } from '@ecopages/core';
|
|
1
2
|
/**
|
|
2
3
|
* This module contains the Lit plugin
|
|
3
4
|
* @module
|
|
@@ -38,6 +39,7 @@ export declare class LitPlugin extends IntegrationPlugin {
|
|
|
38
39
|
initializeRenderer(options?: {
|
|
39
40
|
rendererModules?: unknown;
|
|
40
41
|
}): LitRenderer;
|
|
42
|
+
setHmrManager(hmrManager: IHmrManager): void;
|
|
41
43
|
setup(): Promise<void>;
|
|
42
44
|
teardown(): Promise<void>;
|
|
43
45
|
beforeStaticExport(context: StaticExportContext): Promise<void>;
|
package/src/lit.plugin.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createCustomElementServerModuleImporter } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-server-module-importer';
|
|
2
|
+
import { CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES, invalidateCustomElementScriptPreload, } from '@ecopages/core/route-renderer/orchestration/custom-element-scripts/custom-element-script-preloader';
|
|
2
3
|
/**
|
|
3
4
|
* This module contains the Lit plugin
|
|
4
5
|
* @module
|
|
@@ -66,7 +67,7 @@ export class LitPlugin extends IntegrationPlugin {
|
|
|
66
67
|
resolveDependencyPath: (componentDir, sourcePath) => path.join(componentDir, sourcePath),
|
|
67
68
|
processDependencies: this.assetProcessingService.processDependencies.bind(this.assetProcessingService),
|
|
68
69
|
getInvalidationVersion: () => this.appConfig?.runtime?.serverInvalidationState?.getServerInvalidationVersion() ?? 0,
|
|
69
|
-
importServerModule:
|
|
70
|
+
importServerModule: createCustomElementServerModuleImporter(this.appConfig, '.lit-ssr'),
|
|
70
71
|
preferSourceImports: typeof Bun !== 'undefined',
|
|
71
72
|
});
|
|
72
73
|
}
|
|
@@ -77,6 +78,18 @@ export class LitPlugin extends IntegrationPlugin {
|
|
|
77
78
|
});
|
|
78
79
|
return this.attachRendererRuntimeServices(renderer);
|
|
79
80
|
}
|
|
81
|
+
setHmrManager(hmrManager) {
|
|
82
|
+
super.setHmrManager(hmrManager);
|
|
83
|
+
if (!this.appConfig) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
const runtime = this.appConfig.runtime ?? {};
|
|
87
|
+
this.appConfig.runtime = runtime;
|
|
88
|
+
runtime.registeredScriptEntrypointChangeHandlers ??= [];
|
|
89
|
+
runtime.registeredScriptEntrypointChangeHandlers.push((scriptPath) => {
|
|
90
|
+
invalidateCustomElementScriptPreload(scriptPath, CUSTOM_ELEMENT_SSR_PRELOAD_CACHE_SCOPES.lit);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
80
93
|
async setup() {
|
|
81
94
|
await super.setup();
|
|
82
95
|
if (!this.appConfig?.absolutePaths?.config) {
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { EcoPagesAppConfig } from '@ecopages/core';
|
|
2
|
-
/**
|
|
3
|
-
* Uses server export conditions and isolates module evaluation by custom-element
|
|
4
|
-
* registry. Browser asset output must never be evaluated as the SSR implementation.
|
|
5
|
-
*/
|
|
6
|
-
export declare function createLitServerModuleImporter(appConfig: EcoPagesAppConfig): (filePath: string, registryKey: string) => Promise<unknown>;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
/**
|
|
3
|
-
* Uses server export conditions and isolates module evaluation by custom-element
|
|
4
|
-
* registry. Browser asset output must never be evaluated as the SSR implementation.
|
|
5
|
-
*/
|
|
6
|
-
export function createLitServerModuleImporter(appConfig) {
|
|
7
|
-
return async (filePath, registryKey) => {
|
|
8
|
-
const loader = appConfig.runtime?.appModuleLoader;
|
|
9
|
-
if (!loader)
|
|
10
|
-
throw new Error('Lit SSR requires the app server module loader');
|
|
11
|
-
return loader.importModule({
|
|
12
|
-
filePath,
|
|
13
|
-
rootDir: appConfig.rootDir,
|
|
14
|
-
outdir: path.join(appConfig.absolutePaths.workDir, '.lit-ssr', registryKey),
|
|
15
|
-
forceBuild: true,
|
|
16
|
-
});
|
|
17
|
-
};
|
|
18
|
-
}
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { type EcoComponent } from '@ecopages/core';
|
|
2
|
-
import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
|
|
3
|
-
export type LitSsrPreloadComponent = EcoComponent | Partial<EcoComponent>;
|
|
4
|
-
type ProcessDependencies = (dependencies: AssetDefinition[], integrationName: string) => Promise<Array<{
|
|
5
|
-
filepath?: string;
|
|
6
|
-
}>>;
|
|
7
|
-
export interface LitSsrLazyPreloaderOptions {
|
|
8
|
-
resolveDependencyPath: (componentDir: string, sourcePath: string) => string;
|
|
9
|
-
processDependencies?: ProcessDependencies;
|
|
10
|
-
preferSourceImports?: boolean;
|
|
11
|
-
importServerModule?: (scriptPath: string, registryKey: string) => Promise<unknown>;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Encapsulates SSR lazy script preload behavior for Lit components.
|
|
15
|
-
*
|
|
16
|
-
* Collects `dependencies.scripts` lazy entries with `ssr: true`, resolves the
|
|
17
|
-
* processed entrypoint through the asset pipeline, and imports the module so
|
|
18
|
-
* custom elements are registered before SSR rendering.
|
|
19
|
-
*/
|
|
20
|
-
export declare class LitSsrLazyPreloader {
|
|
21
|
-
private readonly resolveDependencyPath;
|
|
22
|
-
private readonly processDependencies?;
|
|
23
|
-
private readonly preferSourceImports;
|
|
24
|
-
private readonly importServerModule?;
|
|
25
|
-
private activeRegistry;
|
|
26
|
-
private readonly ssrPreloadedScripts;
|
|
27
|
-
private readonly ssrPreloadFailedScripts;
|
|
28
|
-
private readonly ssrPreloadEntrypointCache;
|
|
29
|
-
constructor({ resolveDependencyPath, processDependencies, preferSourceImports, importServerModule, }: LitSsrLazyPreloaderOptions);
|
|
30
|
-
private getErrorCode;
|
|
31
|
-
/**
|
|
32
|
-
* Detects preload failures that are expected for browser-only modules.
|
|
33
|
-
*/
|
|
34
|
-
isExpectedSsrPreloadError(error: unknown): boolean;
|
|
35
|
-
/**
|
|
36
|
-
* Collects lazy script file paths eligible for SSR preloading.
|
|
37
|
-
*/
|
|
38
|
-
collectSsrPreloadScripts(components: Array<LitSsrPreloadComponent | undefined>): string[];
|
|
39
|
-
/**
|
|
40
|
-
* Preloads SSR-eligible lazy scripts to register custom elements before render.
|
|
41
|
-
*/
|
|
42
|
-
preloadSsrLazyScripts(components: Array<LitSsrPreloadComponent | undefined>): Promise<void>;
|
|
43
|
-
/**
|
|
44
|
-
* Resolves the concrete JS entrypoint used for SSR preloading.
|
|
45
|
-
*/
|
|
46
|
-
resolveSsrPreloadEntrypoint(scriptPath: string): Promise<string | null>;
|
|
47
|
-
}
|
|
48
|
-
export {};
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import { ensureLitDomShim } from './dom-shim.js';
|
|
2
|
-
import { getComponentIdentity } from '@ecopages/core';
|
|
3
|
-
import { AssetFactory } from '@ecopages/core/services/asset-processing-service';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { randomUUID } from 'node:crypto';
|
|
7
|
-
const registryKeys = new WeakMap();
|
|
8
|
-
function getRegistryKey(registry) {
|
|
9
|
-
let key = registryKeys.get(registry);
|
|
10
|
-
if (!key) {
|
|
11
|
-
key = randomUUID();
|
|
12
|
-
registryKeys.set(registry, key);
|
|
13
|
-
}
|
|
14
|
-
return key;
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Encapsulates SSR lazy script preload behavior for Lit components.
|
|
18
|
-
*
|
|
19
|
-
* Collects `dependencies.scripts` lazy entries with `ssr: true`, resolves the
|
|
20
|
-
* processed entrypoint through the asset pipeline, and imports the module so
|
|
21
|
-
* custom elements are registered before SSR rendering.
|
|
22
|
-
*/
|
|
23
|
-
export class LitSsrLazyPreloader {
|
|
24
|
-
resolveDependencyPath;
|
|
25
|
-
processDependencies;
|
|
26
|
-
preferSourceImports;
|
|
27
|
-
importServerModule;
|
|
28
|
-
activeRegistry;
|
|
29
|
-
ssrPreloadedScripts = new Set();
|
|
30
|
-
ssrPreloadFailedScripts = new Set();
|
|
31
|
-
ssrPreloadEntrypointCache = new Map();
|
|
32
|
-
constructor({ resolveDependencyPath, processDependencies, preferSourceImports, importServerModule, }) {
|
|
33
|
-
this.importServerModule = importServerModule;
|
|
34
|
-
this.resolveDependencyPath = resolveDependencyPath;
|
|
35
|
-
this.processDependencies = processDependencies;
|
|
36
|
-
this.preferSourceImports = preferSourceImports ?? typeof Bun !== 'undefined';
|
|
37
|
-
}
|
|
38
|
-
getErrorCode(error) {
|
|
39
|
-
if (typeof error !== 'object' || error === null) {
|
|
40
|
-
return '';
|
|
41
|
-
}
|
|
42
|
-
const code = Reflect.get(error, 'code');
|
|
43
|
-
return code === undefined ? '' : String(code);
|
|
44
|
-
}
|
|
45
|
-
/**
|
|
46
|
-
* Detects preload failures that are expected for browser-only modules.
|
|
47
|
-
*/
|
|
48
|
-
isExpectedSsrPreloadError(error) {
|
|
49
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
50
|
-
const errorCode = this.getErrorCode(error);
|
|
51
|
-
if (errorCode === 'ERR_UNKNOWN_FILE_EXTENSION') {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
return (errorMessage.includes(`reading 'Element'`) ||
|
|
55
|
-
errorMessage.includes('window is not defined') ||
|
|
56
|
-
errorMessage.includes('document is not defined') ||
|
|
57
|
-
errorMessage.includes('navigator is not defined'));
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Collects lazy script file paths eligible for SSR preloading.
|
|
61
|
-
*/
|
|
62
|
-
collectSsrPreloadScripts(components) {
|
|
63
|
-
const scriptPaths = new Set();
|
|
64
|
-
const visitedConfigs = new Set();
|
|
65
|
-
const collect = (component) => {
|
|
66
|
-
const config = component?.config;
|
|
67
|
-
if (!config || visitedConfigs.has(config)) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
visitedConfigs.add(config);
|
|
71
|
-
const scriptEntries = config.dependencies?.scripts ?? [];
|
|
72
|
-
const componentFile = getComponentIdentity(component?.config)?.file;
|
|
73
|
-
if (componentFile) {
|
|
74
|
-
const componentDir = path.dirname(componentFile);
|
|
75
|
-
for (const script of scriptEntries) {
|
|
76
|
-
if (typeof script === 'string') {
|
|
77
|
-
continue;
|
|
78
|
-
}
|
|
79
|
-
if (!script.lazy || script.ssr !== true) {
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
if (!script.src) {
|
|
83
|
-
continue;
|
|
84
|
-
}
|
|
85
|
-
scriptPaths.add(this.resolveDependencyPath(componentDir, script.src));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
for (const layout of config.layouts ?? []) {
|
|
89
|
-
collect(layout);
|
|
90
|
-
}
|
|
91
|
-
for (const nestedComponent of config.dependencies?.components || []) {
|
|
92
|
-
collect(nestedComponent);
|
|
93
|
-
}
|
|
94
|
-
};
|
|
95
|
-
for (const component of components) {
|
|
96
|
-
collect(component);
|
|
97
|
-
}
|
|
98
|
-
return Array.from(scriptPaths);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Preloads SSR-eligible lazy scripts to register custom elements before render.
|
|
102
|
-
*/
|
|
103
|
-
async preloadSsrLazyScripts(components) {
|
|
104
|
-
ensureLitDomShim();
|
|
105
|
-
const registry = globalThis.customElements;
|
|
106
|
-
if (this.activeRegistry !== registry) {
|
|
107
|
-
this.ssrPreloadedScripts.clear();
|
|
108
|
-
this.ssrPreloadFailedScripts.clear();
|
|
109
|
-
this.activeRegistry = registry;
|
|
110
|
-
}
|
|
111
|
-
const scripts = this.collectSsrPreloadScripts(components);
|
|
112
|
-
if (scripts.length === 0) {
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
await Promise.all(scripts
|
|
116
|
-
.filter((scriptPath) => {
|
|
117
|
-
if (this.ssrPreloadedScripts.has(scriptPath)) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
if (this.ssrPreloadFailedScripts.has(scriptPath)) {
|
|
121
|
-
return false;
|
|
122
|
-
}
|
|
123
|
-
return true;
|
|
124
|
-
})
|
|
125
|
-
.map(async (scriptPath) => {
|
|
126
|
-
try {
|
|
127
|
-
const registryKey = registry ? getRegistryKey(registry) : 'default';
|
|
128
|
-
if (this.importServerModule) {
|
|
129
|
-
await this.importServerModule(scriptPath, registryKey);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const preloadEntrypoint = await this.resolveSsrPreloadEntrypoint(scriptPath);
|
|
133
|
-
if (!preloadEntrypoint)
|
|
134
|
-
throw new Error(`Cannot resolve SSR entry ${scriptPath}`);
|
|
135
|
-
const importUrl = pathToFileURL(preloadEntrypoint);
|
|
136
|
-
if (registry && typeof Bun === 'undefined')
|
|
137
|
-
importUrl.searchParams.set('eco-ssr-registry', registryKey);
|
|
138
|
-
await import(/* @vite-ignore */ importUrl.href);
|
|
139
|
-
}
|
|
140
|
-
this.ssrPreloadedScripts.add(scriptPath);
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
this.ssrPreloadFailedScripts.add(scriptPath);
|
|
144
|
-
if (this.isExpectedSsrPreloadError(error)) {
|
|
145
|
-
if (process.env.ECOPAGES_DEBUG === 'true') {
|
|
146
|
-
console.warn(`[ecopages][lit] Skipping SSR preload for browser-only lazy script: ${scriptPath}`);
|
|
147
|
-
}
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
console.warn(`[ecopages][lit] Failed to preload lazy SSR script: ${scriptPath}`, error);
|
|
151
|
-
}
|
|
152
|
-
}));
|
|
153
|
-
}
|
|
154
|
-
/**
|
|
155
|
-
* Resolves the concrete JS entrypoint used for SSR preloading.
|
|
156
|
-
*/
|
|
157
|
-
async resolveSsrPreloadEntrypoint(scriptPath) {
|
|
158
|
-
const cachedEntrypoint = this.ssrPreloadEntrypointCache.get(scriptPath);
|
|
159
|
-
if (cachedEntrypoint) {
|
|
160
|
-
return cachedEntrypoint;
|
|
161
|
-
}
|
|
162
|
-
if (this.preferSourceImports) {
|
|
163
|
-
this.ssrPreloadEntrypointCache.set(scriptPath, scriptPath);
|
|
164
|
-
return scriptPath;
|
|
165
|
-
}
|
|
166
|
-
if (!this.processDependencies) {
|
|
167
|
-
this.ssrPreloadEntrypointCache.set(scriptPath, scriptPath);
|
|
168
|
-
return scriptPath;
|
|
169
|
-
}
|
|
170
|
-
try {
|
|
171
|
-
const processed = await this.processDependencies([
|
|
172
|
-
AssetFactory.createInlineFileScript({
|
|
173
|
-
filepath: scriptPath,
|
|
174
|
-
excludeFromHtml: true,
|
|
175
|
-
position: 'head',
|
|
176
|
-
bundle: true,
|
|
177
|
-
attributes: {
|
|
178
|
-
type: 'module',
|
|
179
|
-
defer: '',
|
|
180
|
-
},
|
|
181
|
-
}),
|
|
182
|
-
], `lit-ssr-preload:${scriptPath}`);
|
|
183
|
-
const entrypoint = processed[0]?.filepath;
|
|
184
|
-
if (!entrypoint) {
|
|
185
|
-
return scriptPath;
|
|
186
|
-
}
|
|
187
|
-
this.ssrPreloadEntrypointCache.set(scriptPath, entrypoint);
|
|
188
|
-
return entrypoint;
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
if (process.env.ECOPAGES_DEBUG === 'true') {
|
|
192
|
-
console.warn(`[ecopages][lit] Failed to resolve SSR preload entrypoint for: ${scriptPath}`, error);
|
|
193
|
-
}
|
|
194
|
-
this.ssrPreloadEntrypointCache.set(scriptPath, scriptPath);
|
|
195
|
-
return scriptPath;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
}
|