@ecopages/lit 0.2.0-beta.0 → 0.2.0-beta.10
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/package.json +11 -3
- package/src/STATIC_RENDER.md +47 -0
- package/src/lit-renderer.d.ts +3 -2
- package/src/lit-renderer.js +37 -31
- package/src/lit-static-render-coordinator.d.ts +3 -0
- package/src/lit-static-render-coordinator.js +11 -0
- package/src/lit-static-render-protocol.d.ts +29 -0
- package/src/lit-static-render-protocol.js +0 -0
- package/src/lit-static-render-session.d.ts +37 -0
- package/src/lit-static-render-session.js +84 -0
- package/src/lit-static-render-worker-client.d.ts +20 -0
- package/src/lit-static-render-worker-client.js +112 -0
- package/src/lit-static-render-worker.d.ts +1 -0
- package/src/lit-static-render-worker.js +74 -0
- package/src/lit.plugin.d.ts +6 -1
- package/src/lit.plugin.js +49 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/lit",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.10",
|
|
4
4
|
"description": "Ecopages lit integration",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -20,18 +20,26 @@
|
|
|
20
20
|
"default": "./src/styled-mixin.js",
|
|
21
21
|
"types": "./src/styled-mixin.d.ts"
|
|
22
22
|
},
|
|
23
|
+
"./static-render-worker": {
|
|
24
|
+
"default": "./src/lit-static-render-worker.js",
|
|
25
|
+
"types": "./src/lit-static-render-worker.d.ts"
|
|
26
|
+
},
|
|
23
27
|
"./styled-mixin.ts": {
|
|
24
28
|
"default": "./src/styled-mixin.js",
|
|
25
29
|
"types": "./src/styled-mixin.d.ts"
|
|
30
|
+
},
|
|
31
|
+
"./static-render-worker.ts": {
|
|
32
|
+
"default": "./src/lit-static-render-worker.js",
|
|
33
|
+
"types": "./src/lit-static-render-worker.d.ts"
|
|
26
34
|
}
|
|
27
35
|
},
|
|
28
36
|
"repository": {
|
|
29
37
|
"type": "git",
|
|
30
|
-
"url": "https://github.com/ecopages/ecopages.git",
|
|
38
|
+
"url": "git+https://github.com/ecopages/ecopages.git",
|
|
31
39
|
"directory": "packages/integrations/lit"
|
|
32
40
|
},
|
|
33
41
|
"peerDependencies": {
|
|
34
|
-
"@ecopages/core": "0.2.0-beta.
|
|
42
|
+
"@ecopages/core": "0.2.0-beta.10",
|
|
35
43
|
"@lit-labs/ssr": "^3.3.0",
|
|
36
44
|
"@lit-labs/ssr-client": "^1.1.7",
|
|
37
45
|
"lit": "^3.2.1"
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Lit static render worker
|
|
2
|
+
|
|
3
|
+
Lit-first page routes (`.lit.tsx`) render through a dedicated worker thread during static export and runtime when the Lit plugin is active.
|
|
4
|
+
|
|
5
|
+
## Flow
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Main thread Worker thread
|
|
9
|
+
─────────── ─────────────
|
|
10
|
+
LitPlugin.setup()
|
|
11
|
+
└─ LitStaticRenderSession.ensureWorker()
|
|
12
|
+
└─ LitStaticRenderWorkerClient.start()
|
|
13
|
+
└─ postMessage(init) ──► setupAppRuntimePlugins()
|
|
14
|
+
installAppRuntimeBuildExecutor()
|
|
15
|
+
RouteRendererFactory
|
|
16
|
+
|
|
17
|
+
LitRenderer.execute() (main)
|
|
18
|
+
└─ session.renderPageInWorker()
|
|
19
|
+
└─ postMessage(render) ─────► RouteRendererFactory.execute()
|
|
20
|
+
(no active session in worker)
|
|
21
|
+
|
|
22
|
+
beforeStaticExport
|
|
23
|
+
└─ preloadStaticRoutes()
|
|
24
|
+
└─ preload SSR lazy scripts for Lit pages
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`ECOPAGES_LIT_STATIC_RENDER_WORKER=true` in the worker environment prevents nested worker creation during `LitPlugin.setup()`.
|
|
28
|
+
|
|
29
|
+
## Files
|
|
30
|
+
|
|
31
|
+
| File | Role |
|
|
32
|
+
| ------------------------------------ | --------------------------------------------------------------- |
|
|
33
|
+
| `lit-static-render-session.ts` | Build-scoped session: worker lifecycle + SSR preload |
|
|
34
|
+
| `lit-static-render-worker-client.ts` | Message protocol client |
|
|
35
|
+
| `lit-static-render-worker.ts` | Worker entry (exported as `@ecopages/lit/static-render-worker`) |
|
|
36
|
+
| `lit-static-render-protocol.ts` | Request/response message types |
|
|
37
|
+
| `lit-static-render-coordinator.ts` | Active session pointer for `LitRenderer` |
|
|
38
|
+
|
|
39
|
+
## Protocol
|
|
40
|
+
|
|
41
|
+
| Message | Direction | Purpose |
|
|
42
|
+
| ------------------ | ------------- | --------------------------------------- |
|
|
43
|
+
| `init` | main → worker | Load `eco.config.ts`, bootstrap runtime |
|
|
44
|
+
| `ready` | worker → main | Worker initialized |
|
|
45
|
+
| `render` | main → worker | Render one Lit page route to HTML |
|
|
46
|
+
| `result` / `error` | worker → main | Render outcome |
|
|
47
|
+
| `shutdown` | main → worker | Exit worker thread |
|
package/src/lit-renderer.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* This module contains the Lit renderer
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
|
-
import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPagesElement, IntegrationRendererRenderOptions, RouteRendererBody } from '@ecopages/core';
|
|
5
|
+
import type { ComponentRenderInput, ComponentRenderResult, EcoComponent, EcoPagesElement, IntegrationRendererRenderOptions, RouteRenderResult, RouteRendererBody, RouteRendererOptions } from '@ecopages/core';
|
|
6
6
|
import './dom-shim.js';
|
|
7
7
|
import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/core/route-renderer/integration-renderer';
|
|
8
8
|
/**
|
|
@@ -10,9 +10,10 @@ import { IntegrationRenderer, type RenderToResponseContext } from '@ecopages/cor
|
|
|
10
10
|
*/
|
|
11
11
|
export declare class LitRenderer extends IntegrationRenderer<EcoPagesElement> {
|
|
12
12
|
name: string;
|
|
13
|
+
execute(options: RouteRendererOptions): Promise<RouteRenderResult>;
|
|
13
14
|
private isFunctionComponent;
|
|
14
15
|
private resolveQueuedForeignSubtreeChildren;
|
|
15
|
-
private
|
|
16
|
+
private renderLitQueuedForeignSubtreeChildren;
|
|
16
17
|
protected shouldRenderPageComponent(): boolean;
|
|
17
18
|
private isLitManagedComponent;
|
|
18
19
|
/**
|
package/src/lit-renderer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import "./dom-shim.js";
|
|
2
2
|
import { IntegrationRenderer } from "@ecopages/core/route-renderer/integration-renderer";
|
|
3
|
+
import { getActiveLitStaticRenderSession } from "./lit-static-render-coordinator.js";
|
|
3
4
|
import { LitSsrLazyPreloader } from "./lit-ssr-lazy-preloader.js";
|
|
4
5
|
import { LIT_PLUGIN_NAME } from "./lit.constants.js";
|
|
5
6
|
import {
|
|
@@ -10,6 +11,19 @@ import {
|
|
|
10
11
|
} from "./utils/lit-html-rendering.js";
|
|
11
12
|
class LitRenderer extends IntegrationRenderer {
|
|
12
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
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return super.execute(options);
|
|
26
|
+
}
|
|
13
27
|
isFunctionComponent(component) {
|
|
14
28
|
return typeof component === "function";
|
|
15
29
|
}
|
|
@@ -28,36 +42,21 @@ class LitRenderer extends IntegrationRenderer {
|
|
|
28
42
|
);
|
|
29
43
|
return renderedChildren;
|
|
30
44
|
}
|
|
31
|
-
async
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
queuedResolutionsByToken,
|
|
44
|
-
resolveToken
|
|
45
|
-
);
|
|
46
|
-
if (typeof renderedChildren !== "string") {
|
|
47
|
-
return {
|
|
48
|
-
assets: [],
|
|
49
|
-
children: renderedChildren
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return {
|
|
53
|
-
assets: [],
|
|
54
|
-
html: renderedChildren
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
});
|
|
45
|
+
async renderLitQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken) {
|
|
46
|
+
const renderedChildren = await this.resolveQueuedForeignSubtreeChildren(
|
|
47
|
+
children,
|
|
48
|
+
queuedResolutionsByToken,
|
|
49
|
+
resolveToken
|
|
50
|
+
);
|
|
51
|
+
if (typeof renderedChildren !== "string") {
|
|
52
|
+
return {
|
|
53
|
+
assets: [],
|
|
54
|
+
children: renderedChildren
|
|
55
|
+
};
|
|
56
|
+
}
|
|
58
57
|
return {
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
assets: [],
|
|
59
|
+
html: renderedChildren
|
|
61
60
|
};
|
|
62
61
|
}
|
|
63
62
|
shouldRenderPageComponent() {
|
|
@@ -97,7 +96,9 @@ class LitRenderer extends IntegrationRenderer {
|
|
|
97
96
|
const html = renderedChildren === void 0 ? renderedHtml : injectLitRenderedChildren(renderedHtml, renderedChildren);
|
|
98
97
|
const queuedForeignSubtreeResolution = await this.resolveQueuedForeignSubtreeHtml(
|
|
99
98
|
html,
|
|
100
|
-
this.getQueuedForeignSubtreeResolutionContext(input)
|
|
99
|
+
this.getQueuedForeignSubtreeResolutionContext(input),
|
|
100
|
+
(children, _runtimeContext, queuedResolutionsByToken, resolveToken) => this.renderLitQueuedForeignSubtreeChildren(children, queuedResolutionsByToken, resolveToken),
|
|
101
|
+
"Lit"
|
|
101
102
|
);
|
|
102
103
|
const hasDependencies = Boolean(input.component.config?.dependencies);
|
|
103
104
|
const canResolveAssets = typeof this.assetProcessingService?.processDependencies === "function";
|
|
@@ -109,7 +110,7 @@ class LitRenderer extends IntegrationRenderer {
|
|
|
109
110
|
integrationName: this.name,
|
|
110
111
|
assets: this.htmlTransformer.dedupeProcessedAssets([
|
|
111
112
|
...assets ?? [],
|
|
112
|
-
...queuedForeignSubtreeResolution.assets
|
|
113
|
+
...queuedForeignSubtreeResolution.assets
|
|
113
114
|
])
|
|
114
115
|
};
|
|
115
116
|
}
|
|
@@ -140,6 +141,11 @@ class LitRenderer extends IntegrationRenderer {
|
|
|
140
141
|
* Preloads SSR-eligible lazy scripts to register custom elements before render.
|
|
141
142
|
*/
|
|
142
143
|
async preloadSsrLazyScripts(components) {
|
|
144
|
+
const session = getActiveLitStaticRenderSession();
|
|
145
|
+
if (session) {
|
|
146
|
+
await session.preloadSsrLazyScripts(components);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
143
149
|
await this.ssrLazyPreloader.preloadSsrLazyScripts(components);
|
|
144
150
|
}
|
|
145
151
|
/**
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
let activeSession = null;
|
|
2
|
+
function setActiveLitStaticRenderSession(session) {
|
|
3
|
+
activeSession = session;
|
|
4
|
+
}
|
|
5
|
+
function getActiveLitStaticRenderSession() {
|
|
6
|
+
return activeSession;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
getActiveLitStaticRenderSession,
|
|
10
|
+
setActiveLitStaticRenderSession
|
|
11
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type LitStaticRenderWorkerInitMessage = {
|
|
2
|
+
type: 'init';
|
|
3
|
+
configModulePath: string;
|
|
4
|
+
runtimeOrigin: string;
|
|
5
|
+
};
|
|
6
|
+
export type LitStaticRenderWorkerRenderMessage = {
|
|
7
|
+
type: 'render';
|
|
8
|
+
id: string;
|
|
9
|
+
filePath: string;
|
|
10
|
+
params: Record<string, string>;
|
|
11
|
+
};
|
|
12
|
+
export type LitStaticRenderWorkerShutdownMessage = {
|
|
13
|
+
type: 'shutdown';
|
|
14
|
+
};
|
|
15
|
+
export type LitStaticRenderWorkerRequestMessage = LitStaticRenderWorkerInitMessage | LitStaticRenderWorkerRenderMessage | LitStaticRenderWorkerShutdownMessage;
|
|
16
|
+
export type LitStaticRenderWorkerReadyMessage = {
|
|
17
|
+
type: 'ready';
|
|
18
|
+
};
|
|
19
|
+
export type LitStaticRenderWorkerResultMessage = {
|
|
20
|
+
type: 'result';
|
|
21
|
+
id: string;
|
|
22
|
+
html: string;
|
|
23
|
+
};
|
|
24
|
+
export type LitStaticRenderWorkerErrorMessage = {
|
|
25
|
+
type: 'error';
|
|
26
|
+
id?: string;
|
|
27
|
+
message: string;
|
|
28
|
+
};
|
|
29
|
+
export type LitStaticRenderWorkerResponseMessage = LitStaticRenderWorkerReadyMessage | LitStaticRenderWorkerResultMessage | LitStaticRenderWorkerErrorMessage;
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { EcoComponent } from '@ecopages/core';
|
|
2
|
+
import type { StaticExportContext } from '@ecopages/core/plugins/integration-plugin';
|
|
3
|
+
import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
|
|
4
|
+
type LitStaticRenderSessionOptions = {
|
|
5
|
+
resolveDependencyPath: (componentDir: string, sourcePath: string) => string;
|
|
6
|
+
processDependencies?: (dependencies: AssetDefinition[], integrationName: string) => Promise<Array<{
|
|
7
|
+
filepath?: string;
|
|
8
|
+
}>>;
|
|
9
|
+
preferSourceImports?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Build-scoped Lit server render session.
|
|
13
|
+
*
|
|
14
|
+
* Preloads SSR-eligible lazy scripts and renders Lit page routes through a
|
|
15
|
+
* dedicated worker thread for both static export and runtime/dev requests.
|
|
16
|
+
*/
|
|
17
|
+
export declare class LitStaticRenderSession {
|
|
18
|
+
private readonly preloader;
|
|
19
|
+
private workerClient;
|
|
20
|
+
constructor(options: LitStaticRenderSessionOptions);
|
|
21
|
+
ensureWorker(input: {
|
|
22
|
+
configModulePath: string;
|
|
23
|
+
runtimeOrigin: string;
|
|
24
|
+
}): Promise<void>;
|
|
25
|
+
preloadStaticRoutes(context: StaticExportContext): Promise<void>;
|
|
26
|
+
preloadSsrLazyScripts(components: Array<EcoComponent | undefined>): Promise<void>;
|
|
27
|
+
collectSsrPreloadScripts(components: Array<EcoComponent | undefined>): string[];
|
|
28
|
+
renderPageInWorker(input: {
|
|
29
|
+
filePath: string;
|
|
30
|
+
params: Record<string, string>;
|
|
31
|
+
}): Promise<string>;
|
|
32
|
+
isExpectedSsrPreloadError(error: unknown): boolean;
|
|
33
|
+
dispose(): Promise<void>;
|
|
34
|
+
private preloadLitRoutes;
|
|
35
|
+
private isLitRoute;
|
|
36
|
+
}
|
|
37
|
+
export {};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { LitSsrLazyPreloader } from "./lit-ssr-lazy-preloader.js";
|
|
3
|
+
import { LIT_PLUGIN_NAME } from "./lit.constants.js";
|
|
4
|
+
import { LitStaticRenderWorkerClient } from "./lit-static-render-worker-client.js";
|
|
5
|
+
class LitStaticRenderSession {
|
|
6
|
+
preloader;
|
|
7
|
+
workerClient = null;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.preloader = new LitSsrLazyPreloader({
|
|
10
|
+
resolveDependencyPath: options.resolveDependencyPath,
|
|
11
|
+
processDependencies: options.processDependencies,
|
|
12
|
+
preferSourceImports: options.preferSourceImports
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
async ensureWorker(input) {
|
|
16
|
+
if (this.workerClient) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
this.workerClient = new LitStaticRenderWorkerClient({
|
|
20
|
+
configModulePath: input.configModulePath,
|
|
21
|
+
runtimeOrigin: input.runtimeOrigin
|
|
22
|
+
});
|
|
23
|
+
await this.workerClient.start();
|
|
24
|
+
}
|
|
25
|
+
async preloadStaticRoutes(context) {
|
|
26
|
+
await this.preloadLitRoutes(context);
|
|
27
|
+
}
|
|
28
|
+
async preloadSsrLazyScripts(components) {
|
|
29
|
+
await this.preloader.preloadSsrLazyScripts(components);
|
|
30
|
+
}
|
|
31
|
+
collectSsrPreloadScripts(components) {
|
|
32
|
+
return this.preloader.collectSsrPreloadScripts(components);
|
|
33
|
+
}
|
|
34
|
+
async renderPageInWorker(input) {
|
|
35
|
+
if (!this.workerClient) {
|
|
36
|
+
throw new Error("Lit static render worker is not active");
|
|
37
|
+
}
|
|
38
|
+
return this.workerClient.renderPage(input);
|
|
39
|
+
}
|
|
40
|
+
isExpectedSsrPreloadError(error) {
|
|
41
|
+
return this.preloader.isExpectedSsrPreloadError(error);
|
|
42
|
+
}
|
|
43
|
+
async dispose() {
|
|
44
|
+
await this.workerClient?.dispose();
|
|
45
|
+
this.workerClient = null;
|
|
46
|
+
}
|
|
47
|
+
async preloadLitRoutes(context) {
|
|
48
|
+
if (!context.routeRendererFactory) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const routes = await context.router.listStaticGenerationRoutes({ runtimeOrigin: context.baseUrl });
|
|
52
|
+
const components = [];
|
|
53
|
+
for (const route of routes) {
|
|
54
|
+
const filePath = route.templateRoute.filePath;
|
|
55
|
+
if (!this.isLitRoute(filePath, context.appConfig)) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
const pageRenderer = context.routeRendererFactory.getPageRenderer(filePath);
|
|
59
|
+
const pageModule = await pageRenderer.loadPageModule(filePath);
|
|
60
|
+
const page = pageModule.default;
|
|
61
|
+
if (page) {
|
|
62
|
+
components.push(page);
|
|
63
|
+
if (page.config?.layout) {
|
|
64
|
+
components.push(page.config.layout);
|
|
65
|
+
}
|
|
66
|
+
for (const nestedComponent of page.config?.dependencies?.components ?? []) {
|
|
67
|
+
components.push(nestedComponent);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
await this.preloadSsrLazyScripts(components);
|
|
72
|
+
}
|
|
73
|
+
isLitRoute(filePath, appConfig) {
|
|
74
|
+
const litIntegration = appConfig.integrations.find((plugin) => plugin.name === LIT_PLUGIN_NAME);
|
|
75
|
+
if (!litIntegration) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
const normalizedPath = path.normalize(filePath);
|
|
79
|
+
return litIntegration.extensions.some((extension) => normalizedPath.endsWith(extension));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export {
|
|
83
|
+
LitStaticRenderSession
|
|
84
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare class LitStaticRenderWorkerClient {
|
|
2
|
+
private readonly configModulePath;
|
|
3
|
+
private readonly runtimeOrigin;
|
|
4
|
+
private worker;
|
|
5
|
+
private readyPromise;
|
|
6
|
+
private pending;
|
|
7
|
+
private nextRenderId;
|
|
8
|
+
constructor({ configModulePath, runtimeOrigin }: {
|
|
9
|
+
configModulePath: string;
|
|
10
|
+
runtimeOrigin: string;
|
|
11
|
+
});
|
|
12
|
+
private rejectPending;
|
|
13
|
+
start(): Promise<void>;
|
|
14
|
+
renderPage(input: {
|
|
15
|
+
filePath: string;
|
|
16
|
+
params: Record<string, string>;
|
|
17
|
+
}): Promise<string>;
|
|
18
|
+
dispose(): Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
export declare const litStaticRenderWorkerEntryPath: string;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
class LitStaticRenderWorkerClient {
|
|
4
|
+
configModulePath;
|
|
5
|
+
runtimeOrigin;
|
|
6
|
+
worker = null;
|
|
7
|
+
readyPromise = null;
|
|
8
|
+
pending = /* @__PURE__ */ new Map();
|
|
9
|
+
nextRenderId = 0;
|
|
10
|
+
constructor({ configModulePath, runtimeOrigin }) {
|
|
11
|
+
this.configModulePath = configModulePath;
|
|
12
|
+
this.runtimeOrigin = runtimeOrigin;
|
|
13
|
+
}
|
|
14
|
+
rejectPending(error) {
|
|
15
|
+
for (const pending of this.pending.values()) {
|
|
16
|
+
pending.reject(error);
|
|
17
|
+
}
|
|
18
|
+
this.pending.clear();
|
|
19
|
+
}
|
|
20
|
+
async start() {
|
|
21
|
+
if (this.readyPromise) {
|
|
22
|
+
return this.readyPromise;
|
|
23
|
+
}
|
|
24
|
+
this.readyPromise = new Promise((resolveReady, rejectReady) => {
|
|
25
|
+
const worker = new Worker(new URL("./lit-static-render-worker.js", import.meta.url), {
|
|
26
|
+
execArgv: [...process.execArgv],
|
|
27
|
+
env: {
|
|
28
|
+
...process.env,
|
|
29
|
+
ECOPAGES_LIT_STATIC_RENDER_WORKER: "true"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
this.worker = worker;
|
|
33
|
+
worker.on("message", (message) => {
|
|
34
|
+
if (message.type === "ready") {
|
|
35
|
+
resolveReady();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (message.type === "error") {
|
|
39
|
+
if (message.id && this.pending.has(message.id)) {
|
|
40
|
+
const pending = this.pending.get(message.id);
|
|
41
|
+
this.pending.delete(message.id);
|
|
42
|
+
pending.reject(new Error(message.message));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
rejectReady(new Error(message.message));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (message.type === "result") {
|
|
49
|
+
const pending = this.pending.get(message.id);
|
|
50
|
+
if (!pending) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.pending.delete(message.id);
|
|
54
|
+
pending.resolve(message.html);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
worker.on("error", (error) => {
|
|
58
|
+
rejectReady(error instanceof Error ? error : new Error(String(error)));
|
|
59
|
+
});
|
|
60
|
+
worker.on("exit", (code) => {
|
|
61
|
+
const exitError = new Error(
|
|
62
|
+
code === 0 ? "Lit static render worker exited" : `Lit static render worker exited with code ${code}`
|
|
63
|
+
);
|
|
64
|
+
this.rejectPending(exitError);
|
|
65
|
+
if (code !== 0) {
|
|
66
|
+
rejectReady(exitError);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
const initMessage = {
|
|
70
|
+
type: "init",
|
|
71
|
+
configModulePath: this.configModulePath,
|
|
72
|
+
runtimeOrigin: this.runtimeOrigin
|
|
73
|
+
};
|
|
74
|
+
worker.postMessage(initMessage);
|
|
75
|
+
});
|
|
76
|
+
return this.readyPromise;
|
|
77
|
+
}
|
|
78
|
+
async renderPage(input) {
|
|
79
|
+
await this.start();
|
|
80
|
+
const worker = this.worker;
|
|
81
|
+
if (!worker) {
|
|
82
|
+
throw new Error("Lit static render worker is not available");
|
|
83
|
+
}
|
|
84
|
+
const id = String(++this.nextRenderId);
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
this.pending.set(id, { resolve, reject });
|
|
87
|
+
const renderMessage = {
|
|
88
|
+
type: "render",
|
|
89
|
+
id,
|
|
90
|
+
filePath: input.filePath,
|
|
91
|
+
params: input.params
|
|
92
|
+
};
|
|
93
|
+
worker.postMessage(renderMessage);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async dispose() {
|
|
97
|
+
if (!this.worker) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.rejectPending(new Error("Lit static render worker was disposed"));
|
|
101
|
+
const worker = this.worker;
|
|
102
|
+
this.worker = null;
|
|
103
|
+
this.readyPromise = null;
|
|
104
|
+
worker.postMessage({ type: "shutdown" });
|
|
105
|
+
await worker.terminate();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const litStaticRenderWorkerEntryPath = fileURLToPath(new URL("./lit-static-render-worker.js", import.meta.url));
|
|
109
|
+
export {
|
|
110
|
+
LitStaticRenderWorkerClient,
|
|
111
|
+
litStaticRenderWorkerEntryPath
|
|
112
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './dom-shim.js';
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { parentPort } from "node:worker_threads";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { setupAppRuntimePlugins } from "@ecopages/core/build/build-adapter";
|
|
4
|
+
import { installAppRuntimeBuildExecutor } from "@ecopages/core/build/runtime-build-executor";
|
|
5
|
+
import { RouteRendererFactory } from "@ecopages/core/route-renderer/route-renderer";
|
|
6
|
+
import "./dom-shim.js";
|
|
7
|
+
let appConfig = null;
|
|
8
|
+
let routeRendererFactory = null;
|
|
9
|
+
async function initializeWorker(configModulePath, runtimeOrigin) {
|
|
10
|
+
const configModule = await import(
|
|
11
|
+
/* @vite-ignore */
|
|
12
|
+
pathToFileURL(configModulePath).href
|
|
13
|
+
);
|
|
14
|
+
appConfig = configModule.default ?? configModule;
|
|
15
|
+
await setupAppRuntimePlugins({
|
|
16
|
+
appConfig,
|
|
17
|
+
runtimeOrigin
|
|
18
|
+
});
|
|
19
|
+
installAppRuntimeBuildExecutor(appConfig);
|
|
20
|
+
routeRendererFactory = new RouteRendererFactory({
|
|
21
|
+
appConfig,
|
|
22
|
+
runtimeOrigin
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
async function renderPage(filePath, params) {
|
|
26
|
+
if (!routeRendererFactory) {
|
|
27
|
+
throw new Error("Lit static render worker is not initialized");
|
|
28
|
+
}
|
|
29
|
+
const result = await routeRendererFactory.getPageRenderer(filePath).execute({
|
|
30
|
+
file: filePath,
|
|
31
|
+
params
|
|
32
|
+
});
|
|
33
|
+
const body = result.body;
|
|
34
|
+
if (typeof body === "string") {
|
|
35
|
+
return body;
|
|
36
|
+
}
|
|
37
|
+
if (Buffer.isBuffer(body)) {
|
|
38
|
+
return body.toString("utf8");
|
|
39
|
+
}
|
|
40
|
+
if (body instanceof ReadableStream) {
|
|
41
|
+
return new Response(body).text();
|
|
42
|
+
}
|
|
43
|
+
throw new TypeError(`Unsupported Lit static render body type: ${typeof body}`);
|
|
44
|
+
}
|
|
45
|
+
function postMessage(message) {
|
|
46
|
+
parentPort?.postMessage(message);
|
|
47
|
+
}
|
|
48
|
+
if (!parentPort) {
|
|
49
|
+
throw new Error("Lit static render worker must be started as a worker thread");
|
|
50
|
+
}
|
|
51
|
+
parentPort.on("message", async (message) => {
|
|
52
|
+
try {
|
|
53
|
+
if (message.type === "init") {
|
|
54
|
+
await initializeWorker(message.configModulePath, message.runtimeOrigin);
|
|
55
|
+
postMessage({ type: "ready" });
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (message.type === "shutdown") {
|
|
59
|
+
process.exit(0);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (message.type === "render") {
|
|
63
|
+
const html = await renderPage(message.filePath, message.params);
|
|
64
|
+
postMessage({ type: "result", id: message.id, html });
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
68
|
+
if (message.type === "render") {
|
|
69
|
+
postMessage({ type: "error", id: message.id, message: errorMessage });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
postMessage({ type: "error", message: errorMessage });
|
|
73
|
+
}
|
|
74
|
+
});
|
package/src/lit.plugin.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* @module
|
|
4
4
|
*/
|
|
5
5
|
import './console.js';
|
|
6
|
-
import { IntegrationPlugin, type IntegrationPluginConfig } from '@ecopages/core/plugins/integration-plugin';
|
|
6
|
+
import { IntegrationPlugin, type IntegrationPluginConfig, type StaticExportContext } from '@ecopages/core/plugins/integration-plugin';
|
|
7
7
|
import { type AssetDefinition } from '@ecopages/core/services/asset-processing-service';
|
|
8
8
|
import { LitRenderer } from './lit-renderer.js';
|
|
9
9
|
/**
|
|
@@ -16,8 +16,13 @@ export declare const PLUGIN_NAME = "lit";
|
|
|
16
16
|
*/
|
|
17
17
|
export declare class LitPlugin extends IntegrationPlugin {
|
|
18
18
|
renderer: typeof LitRenderer;
|
|
19
|
+
private renderSession;
|
|
19
20
|
constructor(options?: Omit<IntegrationPluginConfig, 'name'>);
|
|
20
21
|
getDependencies(): AssetDefinition[];
|
|
22
|
+
private createRenderSession;
|
|
23
|
+
setup(): Promise<void>;
|
|
24
|
+
teardown(): Promise<void>;
|
|
25
|
+
beforeStaticExport(context: StaticExportContext): Promise<void>;
|
|
21
26
|
}
|
|
22
27
|
/**
|
|
23
28
|
* Factory function to create a Lit plugin instance
|
package/src/lit.plugin.js
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
import "./console.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
IntegrationPlugin
|
|
4
|
+
} from "@ecopages/core/plugins/integration-plugin";
|
|
5
|
+
import path from "node:path";
|
|
3
6
|
import { AssetFactory } from "@ecopages/core/services/asset-processing-service";
|
|
4
7
|
import { litElementHydrateScript } from "./lit-element-hydrate.js";
|
|
5
8
|
import { LIT_PLUGIN_NAME } from "./lit.constants.js";
|
|
6
9
|
import { LitRenderer } from "./lit-renderer.js";
|
|
10
|
+
import { setActiveLitStaticRenderSession } from "./lit-static-render-coordinator.js";
|
|
11
|
+
import { LitStaticRenderSession } from "./lit-static-render-session.js";
|
|
7
12
|
const PLUGIN_NAME = LIT_PLUGIN_NAME;
|
|
8
13
|
class LitPlugin extends IntegrationPlugin {
|
|
9
14
|
renderer = LitRenderer;
|
|
15
|
+
renderSession = null;
|
|
10
16
|
constructor(options) {
|
|
11
17
|
super({
|
|
12
18
|
name: PLUGIN_NAME,
|
|
13
19
|
extensions: [".lit.tsx"],
|
|
14
|
-
staticBuildStep: "fetch",
|
|
15
20
|
...options
|
|
16
21
|
});
|
|
17
22
|
this.integrationDependencies.unshift(...this.getDependencies());
|
|
@@ -40,6 +45,48 @@ class LitPlugin extends IntegrationPlugin {
|
|
|
40
45
|
})
|
|
41
46
|
];
|
|
42
47
|
}
|
|
48
|
+
createRenderSession() {
|
|
49
|
+
if (!this.appConfig || !this.assetProcessingService) {
|
|
50
|
+
throw new Error("Lit plugin must be initialized before starting the render worker");
|
|
51
|
+
}
|
|
52
|
+
return new LitStaticRenderSession({
|
|
53
|
+
resolveDependencyPath: (componentDir, sourcePath) => path.join(componentDir, sourcePath),
|
|
54
|
+
processDependencies: this.assetProcessingService.processDependencies.bind(this.assetProcessingService),
|
|
55
|
+
preferSourceImports: typeof Bun !== "undefined"
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
async setup() {
|
|
59
|
+
await super.setup();
|
|
60
|
+
if (!this.appConfig?.absolutePaths?.config) {
|
|
61
|
+
throw new Error("[ecopages][lit] Lit server rendering requires appConfig.absolutePaths.config");
|
|
62
|
+
}
|
|
63
|
+
if (process.env.ECOPAGES_LIT_STATIC_RENDER_WORKER === "true") {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
this.renderSession = this.createRenderSession();
|
|
67
|
+
await this.renderSession.ensureWorker({
|
|
68
|
+
configModulePath: this.appConfig.absolutePaths.config,
|
|
69
|
+
runtimeOrigin: this.runtimeOrigin
|
|
70
|
+
});
|
|
71
|
+
setActiveLitStaticRenderSession(this.renderSession);
|
|
72
|
+
}
|
|
73
|
+
async teardown() {
|
|
74
|
+
await this.renderSession?.dispose();
|
|
75
|
+
this.renderSession = null;
|
|
76
|
+
setActiveLitStaticRenderSession(null);
|
|
77
|
+
await super.teardown();
|
|
78
|
+
}
|
|
79
|
+
async beforeStaticExport(context) {
|
|
80
|
+
if (!this.renderSession) {
|
|
81
|
+
this.renderSession = this.createRenderSession();
|
|
82
|
+
await this.renderSession.ensureWorker({
|
|
83
|
+
configModulePath: context.appConfig.absolutePaths.config,
|
|
84
|
+
runtimeOrigin: context.baseUrl
|
|
85
|
+
});
|
|
86
|
+
setActiveLitStaticRenderSession(this.renderSession);
|
|
87
|
+
}
|
|
88
|
+
await this.renderSession.preloadStaticRoutes(context);
|
|
89
|
+
}
|
|
43
90
|
}
|
|
44
91
|
function litPlugin(options) {
|
|
45
92
|
return new LitPlugin(options);
|