@ecopages/lit 0.2.0-rc.3 → 0.2.0-rc.5

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 CHANGED
@@ -55,6 +55,12 @@ When a non-Lit render pass reaches a Lit-owned foreign child, Ecopages hands tha
55
55
 
56
56
  Important:
57
57
 
58
- - Components that may render foreign children must declare those children in `config.dependencies.components`.
58
+ - Direct local imports of declared Eco Components contribute their Dependencies automatically, including Foreign Children and named `export { X } from` barrels. Use `dependencies.components` for `export *` barrels and package imports.
59
59
  - Ecopages validates ownership from declared dependencies during render preparation instead of relying on post-render HTML discovery.
60
60
  - Lit keeps slot transport, shadow-root handling, and SSR preload behavior inside the Lit renderer.
61
+
62
+ ## Discovered assets and lazy custom elements
63
+
64
+ 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.
65
+
66
+ Keep custom-element registration explicit with `scripts: [{ src: './counter.script.ts', ssr: true, lazy: { 'on:visible': true } }]`. The module executes before server rendering and loads in the browser on visibility. `ssr: true` no longer emits 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/lit",
3
- "version": "0.2.0-rc.3",
3
+ "version": "0.2.0-rc.5",
4
4
  "description": "Ecopages lit integration",
5
5
  "keywords": [
6
6
  "ecopages",
@@ -11,6 +11,9 @@
11
11
  "main": "./src/lit.plugin.js",
12
12
  "types": "./src/lit.plugin.d.ts",
13
13
  "type": "module",
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
14
17
  "exports": {
15
18
  ".": {
16
19
  "default": "./src/lit.plugin.js",
@@ -39,9 +42,9 @@
39
42
  "directory": "packages/integrations/lit"
40
43
  },
41
44
  "peerDependencies": {
42
- "@ecopages/core": "0.2.0-rc.3",
45
+ "@ecopages/core": "0.2.0-rc.5",
43
46
  "@lit-labs/ssr": "^3.3.0",
44
47
  "@lit-labs/ssr-client": "^1.1.7",
45
48
  "lit": "^3.2.1"
46
49
  }
47
- }
50
+ }
package/src/console.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  declare const originalConsoleWarn: {
2
+ (...data: any[]): void;
2
3
  (...data: any[]): void;
3
4
  (...data: any[]): void;
4
5
  (message?: any, ...optionalParams: any[]): void;
package/src/dom-shim.d.ts CHANGED
@@ -1 +1,3 @@
1
1
  import '@lit-labs/ssr/lib/install-global-dom-shim.js';
2
+ /** Repairs the minimal Lit DOM surface after another Integration installs its SSR globals. */
3
+ export declare function ensureLitDomShim(): void;
package/src/dom-shim.js CHANGED
@@ -1,14 +1,18 @@
1
1
  import { installWindowOnGlobal } from '@lit-labs/ssr/lib/dom-shim.js';
2
2
  import '@lit-labs/ssr/lib/install-global-dom-shim.js';
3
- if (typeof globalThis.document?.createTreeWalker !== 'function') {
4
- if (typeof globalThis.window === 'undefined') {
5
- installWindowOnGlobal();
6
- }
7
- else {
8
- Object.defineProperty(globalThis.document, 'createTreeWalker', {
9
- configurable: true,
10
- writable: true,
11
- value: () => ({}),
12
- });
3
+ /** Repairs the minimal Lit DOM surface after another Integration installs its SSR globals. */
4
+ export function ensureLitDomShim() {
5
+ if (typeof globalThis.document?.createTreeWalker !== 'function') {
6
+ if (typeof globalThis.window === 'undefined') {
7
+ installWindowOnGlobal();
8
+ }
9
+ else {
10
+ Object.defineProperty(globalThis.document, 'createTreeWalker', {
11
+ configurable: true,
12
+ writable: true,
13
+ value: () => ({}),
14
+ });
15
+ }
13
16
  }
14
17
  }
18
+ ensureLitDomShim();
@@ -1,7 +1,4 @@
1
- /**
2
- * This module contains the Lit renderer
3
- * @module
4
- */
1
+ import { createLitServerModuleImporter } from './lit-server-module-loader.js';
5
2
  import './dom-shim.js';
6
3
  import { IntegrationRenderer, } from '@ecopages/core/route-renderer/orchestration/integration-renderer';
7
4
  import { resolveDocumentShellLayouts } from '@ecopages/core/route-renderer/orchestration/document-shell/layout-shell-props.service';
@@ -137,6 +134,7 @@ export class LitRenderer extends IntegrationRenderer {
137
134
  });
138
135
  }
139
136
  ssrLazyPreloader = new LitSsrLazyPreloader({
137
+ importServerModule: createLitServerModuleImporter(this.appConfig),
140
138
  resolveDependencyPath: this.resolveDependencyPath.bind(this),
141
139
  processDependencies: this.assetProcessingService?.processDependencies?.bind(this.assetProcessingService),
142
140
  });
@@ -0,0 +1,6 @@
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>;
@@ -0,0 +1,18 @@
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
+ }
@@ -7,6 +7,7 @@ export interface LitSsrLazyPreloaderOptions {
7
7
  resolveDependencyPath: (componentDir: string, sourcePath: string) => string;
8
8
  processDependencies?: ProcessDependencies;
9
9
  preferSourceImports?: boolean;
10
+ importServerModule?: (scriptPath: string, registryKey: string) => Promise<unknown>;
10
11
  }
11
12
  /**
12
13
  * Encapsulates SSR lazy script preload behavior for Lit components.
@@ -19,10 +20,12 @@ export declare class LitSsrLazyPreloader {
19
20
  private readonly resolveDependencyPath;
20
21
  private readonly processDependencies?;
21
22
  private readonly preferSourceImports;
23
+ private readonly importServerModule?;
24
+ private activeRegistry;
22
25
  private readonly ssrPreloadedScripts;
23
26
  private readonly ssrPreloadFailedScripts;
24
27
  private readonly ssrPreloadEntrypointCache;
25
- constructor({ resolveDependencyPath, processDependencies, preferSourceImports }: LitSsrLazyPreloaderOptions);
28
+ constructor({ resolveDependencyPath, processDependencies, preferSourceImports, importServerModule, }: LitSsrLazyPreloaderOptions);
26
29
  private getErrorCode;
27
30
  /**
28
31
  * Detects preload failures that are expected for browser-only modules.
@@ -1,7 +1,18 @@
1
+ import { ensureLitDomShim } from './dom-shim.js';
1
2
  import { getComponentIdentity } from '@ecopages/core';
2
3
  import { AssetFactory } from '@ecopages/core/services/asset-processing-service';
3
4
  import path from 'node:path';
4
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
+ }
5
16
  /**
6
17
  * Encapsulates SSR lazy script preload behavior for Lit components.
7
18
  *
@@ -13,10 +24,13 @@ export class LitSsrLazyPreloader {
13
24
  resolveDependencyPath;
14
25
  processDependencies;
15
26
  preferSourceImports;
27
+ importServerModule;
28
+ activeRegistry;
16
29
  ssrPreloadedScripts = new Set();
17
30
  ssrPreloadFailedScripts = new Set();
18
31
  ssrPreloadEntrypointCache = new Map();
19
- constructor({ resolveDependencyPath, processDependencies, preferSourceImports }) {
32
+ constructor({ resolveDependencyPath, processDependencies, preferSourceImports, importServerModule, }) {
33
+ this.importServerModule = importServerModule;
20
34
  this.resolveDependencyPath = resolveDependencyPath;
21
35
  this.processDependencies = processDependencies;
22
36
  this.preferSourceImports = preferSourceImports ?? typeof Bun !== 'undefined';
@@ -87,6 +101,13 @@ export class LitSsrLazyPreloader {
87
101
  * Preloads SSR-eligible lazy scripts to register custom elements before render.
88
102
  */
89
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
+ }
90
111
  const scripts = this.collectSsrPreloadScripts(components);
91
112
  if (scripts.length === 0) {
92
113
  return;
@@ -102,13 +123,20 @@ export class LitSsrLazyPreloader {
102
123
  return true;
103
124
  })
104
125
  .map(async (scriptPath) => {
105
- const preloadEntrypoint = await this.resolveSsrPreloadEntrypoint(scriptPath);
106
- if (!preloadEntrypoint) {
107
- this.ssrPreloadFailedScripts.add(scriptPath);
108
- return;
109
- }
110
126
  try {
111
- await import(/* @vite-ignore */ pathToFileURL(preloadEntrypoint).href);
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
+ }
112
140
  this.ssrPreloadedScripts.add(scriptPath);
113
141
  }
114
142
  catch (error) {
@@ -143,6 +171,7 @@ export class LitSsrLazyPreloader {
143
171
  const processed = await this.processDependencies([
144
172
  AssetFactory.createInlineFileScript({
145
173
  filepath: scriptPath,
174
+ excludeFromHtml: true,
146
175
  position: 'head',
147
176
  bundle: true,
148
177
  attributes: {
@@ -1,6 +1,7 @@
1
1
  import type { EcoComponent, PageParams, PageQuery } from '@ecopages/core';
2
2
  import type { StaticExportContext } from '@ecopages/core/plugins/integration-plugin';
3
3
  import type { AssetDefinition } from '@ecopages/core/services/asset-processing-service';
4
+ import { type LitSsrLazyPreloaderOptions } from './lit-ssr-lazy-preloader.js';
4
5
  import { LitStaticRenderWorkerClient } from './lit-static-render-worker-client.js';
5
6
  import type { LitStaticRenderCacheStrategy } from './lit-static-render-protocol.js';
6
7
  type LitStaticRenderWorkerClientContract = Pick<LitStaticRenderWorkerClient, 'start' | 'renderPage' | 'dispose'>;
@@ -10,6 +11,8 @@ type LitStaticRenderSessionOptions = {
10
11
  filepath?: string;
11
12
  }>>;
12
13
  preferSourceImports?: boolean;
14
+ importServerModule?: LitSsrLazyPreloaderOptions['importServerModule'];
15
+ getInvalidationVersion?: () => number;
13
16
  createWorkerClient?: (input: {
14
17
  configModulePath: string;
15
18
  runtimeOrigin: string;
@@ -32,6 +35,9 @@ export declare class LitStaticRenderSession {
32
35
  private readonly createWorkerClient;
33
36
  private workerClient;
34
37
  private workerIdentity;
38
+ private readonly getInvalidationVersion;
39
+ private workerInvalidationVersion;
40
+ private renderChain;
35
41
  constructor(options: LitStaticRenderSessionOptions);
36
42
  /**
37
43
  * Ensures the static render worker is running for the given identity.
@@ -19,11 +19,16 @@ export class LitStaticRenderSession {
19
19
  createWorkerClient;
20
20
  workerClient = null;
21
21
  workerIdentity = null;
22
+ getInvalidationVersion;
23
+ workerInvalidationVersion = 0;
24
+ renderChain = Promise.resolve();
22
25
  constructor(options) {
26
+ this.getInvalidationVersion = options.getInvalidationVersion ?? (() => 0);
23
27
  this.preloader = new LitSsrLazyPreloader({
24
28
  resolveDependencyPath: options.resolveDependencyPath,
25
29
  processDependencies: options.processDependencies,
26
30
  preferSourceImports: options.preferSourceImports,
31
+ importServerModule: options.importServerModule,
27
32
  });
28
33
  this.createWorkerClient =
29
34
  options.createWorkerClient ??
@@ -58,6 +63,7 @@ export class LitStaticRenderSession {
58
63
  runtimeOrigin: input.runtimeOrigin,
59
64
  });
60
65
  await this.workerClient.start();
66
+ this.workerInvalidationVersion = this.getInvalidationVersion();
61
67
  }
62
68
  async preloadStaticRoutes(context) {
63
69
  await this.preloadLitRoutes(context);
@@ -72,7 +78,19 @@ export class LitStaticRenderSession {
72
78
  if (!this.workerClient) {
73
79
  throw new Error('Lit static render worker is not active');
74
80
  }
75
- return this.workerClient.renderPage(input);
81
+ const render = this.renderChain.then(async () => {
82
+ const version = this.getInvalidationVersion();
83
+ if (version !== this.workerInvalidationVersion && this.workerIdentity) {
84
+ const identity = this.workerIdentity;
85
+ await this.dispose();
86
+ await this.ensureWorker(identity);
87
+ }
88
+ if (!this.workerClient)
89
+ throw new Error('Lit static render worker is not active');
90
+ return this.workerClient.renderPage(input);
91
+ });
92
+ this.renderChain = render.then(() => undefined, () => undefined);
93
+ return render;
76
94
  }
77
95
  isExpectedSsrPreloadError(error) {
78
96
  return this.preloader.isExpectedSsrPreloadError(error);
package/src/lit.plugin.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { createLitServerModuleImporter } from './lit-server-module-loader.js';
1
2
  /**
2
3
  * This module contains the Lit plugin
3
4
  * @module
@@ -66,6 +67,8 @@ export class LitPlugin extends IntegrationPlugin {
66
67
  return new LitStaticRenderSession({
67
68
  resolveDependencyPath: (componentDir, sourcePath) => path.join(componentDir, sourcePath),
68
69
  processDependencies: this.assetProcessingService.processDependencies.bind(this.assetProcessingService),
70
+ getInvalidationVersion: () => this.appConfig?.runtime?.serverInvalidationState?.getServerInvalidationVersion() ?? 0,
71
+ importServerModule: createLitServerModuleImporter(this.appConfig),
69
72
  preferSourceImports: typeof Bun !== 'undefined',
70
73
  });
71
74
  }