@ecopages/core 0.2.0-alpha.25 → 0.2.0-alpha.27
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 +63 -7
- package/package.json +4 -47
- package/src/adapters/bun/create-app.ts +54 -2
- package/src/adapters/bun/hmr-manager.test.ts +0 -2
- package/src/adapters/bun/hmr-manager.ts +1 -24
- package/src/adapters/bun/server-adapter.ts +30 -4
- package/src/adapters/node/node-hmr-manager.test.ts +0 -2
- package/src/adapters/node/node-hmr-manager.ts +2 -25
- package/src/adapters/shared/explicit-static-render-preparation.ts +58 -0
- package/src/adapters/shared/explicit-static-route-matcher.test.ts +6 -6
- package/src/adapters/shared/explicit-static-route-matcher.ts +22 -31
- package/src/adapters/shared/file-route-middleware-pipeline.test.ts +5 -10
- package/src/adapters/shared/file-route-middleware-pipeline.ts +8 -17
- package/src/adapters/shared/fs-server-response-factory.test.ts +32 -43
- package/src/adapters/shared/fs-server-response-factory.ts +15 -37
- package/src/adapters/shared/fs-server-response-matcher.test.ts +65 -39
- package/src/adapters/shared/fs-server-response-matcher.ts +94 -43
- package/src/adapters/shared/hmr-manager.contract.test.ts +0 -4
- package/src/adapters/shared/render-context.ts +3 -3
- package/src/adapters/shared/server-adapter.test.ts +53 -0
- package/src/adapters/shared/server-adapter.ts +228 -159
- package/src/adapters/shared/server-route-handler.test.ts +6 -5
- package/src/adapters/shared/server-route-handler.ts +4 -4
- package/src/adapters/shared/server-static-builder.test.ts +4 -4
- package/src/adapters/shared/server-static-builder.ts +4 -4
- package/src/config/README.md +1 -1
- package/src/config/config-builder.test.ts +0 -1
- package/src/config/config-builder.ts +2 -7
- package/src/dev/host-runtime.ts +34 -0
- package/src/eco/eco.browser.test.ts +2 -2
- package/src/eco/eco.browser.ts +2 -2
- package/src/eco/eco.test.ts +6 -6
- package/src/eco/eco.ts +12 -12
- package/src/eco/eco.types.ts +3 -3
- package/src/errors/index.ts +1 -0
- package/src/hmr/client/hmr-runtime.ts +4 -2
- package/src/hmr/strategies/js-hmr-strategy.test.ts +0 -1
- package/src/hmr/strategies/js-hmr-strategy.ts +0 -6
- package/src/integrations/ghtml/ghtml-renderer.test.ts +7 -7
- package/src/integrations/ghtml/ghtml-renderer.ts +1 -11
- package/src/plugins/eco-component-meta-plugin.ts +0 -1
- package/src/plugins/integration-plugin.test.ts +9 -14
- package/src/plugins/integration-plugin.ts +34 -22
- package/src/plugins/processor.ts +17 -0
- package/src/route-renderer/GRAPH.md +81 -289
- package/src/route-renderer/README.md +67 -105
- package/src/route-renderer/orchestration/component-render-context.ts +45 -38
- package/src/route-renderer/orchestration/declared-ownership-graph.ts +62 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.ts +383 -0
- package/src/route-renderer/orchestration/integration-renderer.test.ts +118 -121
- package/src/route-renderer/orchestration/integration-renderer.ts +362 -403
- package/src/route-renderer/orchestration/ownership-planning.service.ts +97 -0
- package/src/route-renderer/orchestration/ownership-validation.service.ts +76 -0
- package/src/route-renderer/orchestration/processed-asset-dedupe.ts +1 -1
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.test.ts → queued-foreign-subtree-resolution.service.test.ts} +76 -71
- package/src/route-renderer/orchestration/{queued-boundary-runtime.service.ts → queued-foreign-subtree-resolution.service.ts} +68 -63
- package/src/route-renderer/orchestration/render-output.utils.ts +21 -13
- package/src/route-renderer/orchestration/{render-preparation.service.test.ts → route-render-orchestrator.prepare-render-options.test.ts} +160 -85
- package/src/route-renderer/orchestration/route-render-orchestrator.test.ts +265 -0
- package/src/route-renderer/orchestration/{render-preparation.service.ts → route-render-orchestrator.ts} +244 -160
- package/src/route-renderer/page-loading/component-dependency-collection.ts +9 -3
- package/src/route-renderer/page-loading/declared-asset-collection.ts +2 -5
- package/src/route-renderer/page-loading/dependency-resolver.test.ts +107 -11
- package/src/route-renderer/page-loading/dependency-resolver.ts +6 -12
- package/src/route-renderer/page-loading/ecopages-virtual-imports.ts +1 -1
- package/src/route-renderer/page-loading/lazy-entry-collection.ts +1 -1
- package/src/route-renderer/page-loading/lazy-trigger-planning.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-aggregation.ts +1 -1
- package/src/route-renderer/page-loading/module-declaration-scripts.ts +1 -1
- package/src/route-renderer/page-loading/page-dependency-bundling.ts +105 -66
- package/src/route-renderer/route-renderer.ts +28 -31
- package/src/router/README.md +16 -19
- package/src/router/server/route-registry.test.ts +176 -0
- package/src/router/server/route-registry.ts +382 -0
- package/src/services/README.md +1 -2
- package/src/services/assets/asset-processing-service/asset-dependency-keys.ts +1 -1
- package/src/services/assets/asset-processing-service/asset-processing.service.test.ts +1 -4
- package/src/services/assets/asset-processing-service/asset-processing.service.ts +1 -2
- package/src/services/assets/asset-processing-service/assets.types.ts +3 -0
- package/src/services/assets/asset-processing-service/grouped-content-bundles.ts +1 -1
- package/src/services/assets/asset-processing-service/index.ts +1 -0
- package/src/{route-renderer/orchestration/page-packaging.service.test.ts → services/assets/asset-processing-service/page-package.test.ts} +38 -14
- package/src/services/assets/asset-processing-service/page-package.ts +93 -0
- package/src/services/assets/asset-processing-service/processors/base/base-script-processor.ts +4 -5
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.test.ts +13 -10
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.ts +3 -0
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.ts +6 -0
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.ts +1 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.ts +2 -0
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.ts +1 -1
- package/src/services/html/html-transformer.service.test.ts +1 -4
- package/src/services/module-loading/app-server-module-transpiler.service.ts +1 -3
- package/src/services/module-loading/node-bootstrap-plugin.ts +17 -3
- package/src/services/module-loading/page-module-import.service.ts +0 -1
- package/src/services/module-loading/source-module-support.ts +1 -1
- package/src/static-site-generator/static-site-generator.test.ts +124 -32
- package/src/static-site-generator/static-site-generator.ts +168 -185
- package/src/types/internal-types.ts +13 -12
- package/src/types/public-types.ts +55 -39
- package/src/watchers/project-watcher.test-helpers.ts +4 -3
- package/src/route-renderer/orchestration/boundary-planning.service.ts +0 -146
- package/src/route-renderer/orchestration/page-packaging.service.ts +0 -85
- package/src/route-renderer/orchestration/render-execution.service.test.ts +0 -196
- package/src/route-renderer/orchestration/render-execution.service.ts +0 -182
- package/src/route-renderer/orchestration/route-shell-composer.service.ts +0 -162
- package/src/router/server/fs-router-scanner.test.ts +0 -83
- package/src/router/server/fs-router-scanner.ts +0 -224
- package/src/router/server/fs-router.test.ts +0 -214
- package/src/router/server/fs-router.ts +0 -122
- package/src/services/runtime-state/runtime-specifier-registry.service.ts +0 -96
package/README.md
CHANGED
|
@@ -31,14 +31,14 @@ flowchart TD
|
|
|
31
31
|
B --> D[App build manifest]
|
|
32
32
|
B --> E[Build executor]
|
|
33
33
|
B --> F[Dev graph service]
|
|
34
|
-
B --> G[
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
B --> G[Host module loader boundary]
|
|
35
|
+
G --> H[PageModuleImportService]
|
|
36
|
+
E --> H
|
|
37
37
|
E --> I
|
|
38
|
-
E -->
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
D -->
|
|
38
|
+
E --> I[BrowserBundleService]
|
|
39
|
+
H --> J[Runtime app adapter]
|
|
40
|
+
J --> K[Bun adapter or Node adapter]
|
|
41
|
+
D --> I
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
### Development Invalidation And HMR Flow
|
|
@@ -178,6 +178,12 @@ export const MyButton = eco.component({
|
|
|
178
178
|
});
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
Dependency ownership affects final asset packaging:
|
|
182
|
+
|
|
183
|
+
- Stylesheets and scripts declared from `eco.html()` stay Html-owned and can be emitted as shared app-wide assets.
|
|
184
|
+
- Stylesheets and scripts declared from Pages, Layouts, or Components are resolved into page-owned assets for the rendered route.
|
|
185
|
+
- This split is intentional. Shared Html assets can be cached across routes, while page-owned assets can change without invalidating the global shell.
|
|
186
|
+
|
|
181
187
|
### 5. API Handlers
|
|
182
188
|
|
|
183
189
|
Add server-side routes using `defineApiHandler`. Register them on your `app` instance before starting:
|
|
@@ -228,3 +234,53 @@ import { defineApiHandler, defineGroupHandler, eco } from '@ecopages/core';
|
|
|
228
234
|
Use runtime-specific subpaths only when you explicitly need Bun-native APIs that bypass the universal abstractions:
|
|
229
235
|
|
|
230
236
|
- `@ecopages/core/bun`
|
|
237
|
+
|
|
238
|
+
## Entry Point Roles
|
|
239
|
+
|
|
240
|
+
The published subpaths are grouped by architectural role rather than by source folder.
|
|
241
|
+
|
|
242
|
+
### App Authoring
|
|
243
|
+
|
|
244
|
+
Use these entrypoints when building an Ecopages app:
|
|
245
|
+
|
|
246
|
+
- `@ecopages/core`
|
|
247
|
+
- `@ecopages/core/create-app`
|
|
248
|
+
- `@ecopages/core/config-builder`
|
|
249
|
+
- `@ecopages/core/errors`
|
|
250
|
+
- `@ecopages/core/html`
|
|
251
|
+
- `@ecopages/core/hash`
|
|
252
|
+
- `@ecopages/core/declarations`
|
|
253
|
+
- `@ecopages/core/env`
|
|
254
|
+
- `@ecopages/core/bun`
|
|
255
|
+
|
|
256
|
+
### Browser Navigation
|
|
257
|
+
|
|
258
|
+
Use these entrypoints when a browser runtime needs to coordinate document ownership and link intent:
|
|
259
|
+
|
|
260
|
+
- `@ecopages/core/router/navigation-coordinator`
|
|
261
|
+
- `@ecopages/core/router/link-intent`
|
|
262
|
+
|
|
263
|
+
### Extension Authoring
|
|
264
|
+
|
|
265
|
+
Use these entrypoints when implementing integrations, processors, or source transforms:
|
|
266
|
+
|
|
267
|
+
- `@ecopages/core/plugins/integration-plugin`
|
|
268
|
+
- `@ecopages/core/plugins/processor`
|
|
269
|
+
- `@ecopages/core/plugins/source-transform`
|
|
270
|
+
- `@ecopages/core/route-renderer/integration-renderer`
|
|
271
|
+
- `@ecopages/core/services/asset-processing-service`
|
|
272
|
+
- `@ecopages/core/hmr/hmr-strategy`
|
|
273
|
+
- `@ecopages/core/integrations/ghtml`
|
|
274
|
+
|
|
275
|
+
### Host And Runtime Composition
|
|
276
|
+
|
|
277
|
+
Use these entrypoints only when implementing host adapters or framework-owned bundling seams:
|
|
278
|
+
|
|
279
|
+
- `@ecopages/core/dev/host-runtime`
|
|
280
|
+
- `@ecopages/core/build/build-adapter`
|
|
281
|
+
- `@ecopages/core/build/build-types`
|
|
282
|
+
- `@ecopages/core/build/runtime-specifier-alias-plugin`
|
|
283
|
+
- `@ecopages/core/build/runtime-specifier-aliases`
|
|
284
|
+
- `@ecopages/core/plugins/foreign-jsx-override-plugin`
|
|
285
|
+
|
|
286
|
+
These host-facing entrypoints are narrower compatibility seams. App code and most extensions should prefer the app-authoring or extension-authoring surfaces.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.27",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -66,23 +66,11 @@
|
|
|
66
66
|
"types": "./src/router/client/link-intent.ts",
|
|
67
67
|
"default": "./src/router/client/link-intent.ts"
|
|
68
68
|
},
|
|
69
|
-
"./router/client/navigation-coordinator": {
|
|
70
|
-
"types": "./src/router/client/navigation-coordinator.ts",
|
|
71
|
-
"default": "./src/router/client/navigation-coordinator.ts"
|
|
72
|
-
},
|
|
73
|
-
"./errors/locals-access-error": {
|
|
74
|
-
"types": "./src/errors/locals-access-error.ts",
|
|
75
|
-
"default": "./src/errors/locals-access-error.ts"
|
|
76
|
-
},
|
|
77
69
|
"./eco": {
|
|
78
70
|
"browser": "./src/eco/eco.browser.ts",
|
|
79
71
|
"types": "./src/eco/eco.ts",
|
|
80
72
|
"default": "./src/eco/eco.ts"
|
|
81
73
|
},
|
|
82
|
-
"./route-renderer/template-serialization": {
|
|
83
|
-
"types": "./src/route-renderer/orchestration/template-serialization.ts",
|
|
84
|
-
"default": "./src/route-renderer/orchestration/template-serialization.ts"
|
|
85
|
-
},
|
|
86
74
|
"./declarations": {
|
|
87
75
|
"types": "./src/declarations.d.ts"
|
|
88
76
|
},
|
|
@@ -101,45 +89,18 @@
|
|
|
101
89
|
"types": "./src/adapters/bun/index.ts",
|
|
102
90
|
"default": "./src/adapters/bun/index.ts"
|
|
103
91
|
},
|
|
104
|
-
"./bun/create-app": {
|
|
105
|
-
"types": "./src/adapters/bun/create-app.ts",
|
|
106
|
-
"default": "./src/adapters/bun/create-app.ts"
|
|
107
|
-
},
|
|
108
92
|
"./hmr/hmr-strategy": {
|
|
109
93
|
"types": "./src/hmr/hmr-strategy.ts",
|
|
110
94
|
"default": "./src/hmr/hmr-strategy.ts"
|
|
111
95
|
},
|
|
112
|
-
"./
|
|
113
|
-
"types": "./src/
|
|
96
|
+
"./dev/host-runtime": {
|
|
97
|
+
"types": "./src/dev/host-runtime.ts",
|
|
98
|
+
"default": "./src/dev/host-runtime.ts"
|
|
114
99
|
},
|
|
115
100
|
"./services/asset-processing-service": {
|
|
116
101
|
"types": "./src/services/assets/asset-processing-service/index.ts",
|
|
117
102
|
"default": "./src/services/assets/asset-processing-service/index.ts"
|
|
118
103
|
},
|
|
119
|
-
"./services/invalidation/development-invalidation.service": {
|
|
120
|
-
"types": "./src/services/invalidation/development-invalidation.service.ts",
|
|
121
|
-
"default": "./src/services/invalidation/development-invalidation.service.ts"
|
|
122
|
-
},
|
|
123
|
-
"./services/module-loading/app-server-module-transpiler.service": {
|
|
124
|
-
"types": "./src/services/module-loading/app-server-module-transpiler.service.ts",
|
|
125
|
-
"default": "./src/services/module-loading/app-server-module-transpiler.service.ts"
|
|
126
|
-
},
|
|
127
|
-
"./host-module-loader": {
|
|
128
|
-
"types": "./src/services/module-loading/host-module-loader-registry.ts",
|
|
129
|
-
"default": "./src/services/module-loading/host-module-loader-registry.ts"
|
|
130
|
-
},
|
|
131
|
-
"./utils/deep-merge": {
|
|
132
|
-
"types": "./src/utils/deep-merge.ts",
|
|
133
|
-
"default": "./src/utils/deep-merge.ts"
|
|
134
|
-
},
|
|
135
|
-
"./utils/invariant": {
|
|
136
|
-
"types": "./src/utils/invariant.ts",
|
|
137
|
-
"default": "./src/utils/invariant.ts"
|
|
138
|
-
},
|
|
139
|
-
"./utils/parse-cli-args": {
|
|
140
|
-
"types": "./src/utils/parse-cli-args.ts",
|
|
141
|
-
"default": "./src/utils/parse-cli-args.ts"
|
|
142
|
-
},
|
|
143
104
|
"./plugins/processor": {
|
|
144
105
|
"types": "./src/plugins/processor.ts",
|
|
145
106
|
"default": "./src/plugins/processor.ts"
|
|
@@ -172,10 +133,6 @@
|
|
|
172
133
|
"types": "./src/plugins/foreign-jsx-override-plugin.ts",
|
|
173
134
|
"default": "./src/plugins/foreign-jsx-override-plugin.ts"
|
|
174
135
|
},
|
|
175
|
-
"./adapters/bun/client-bridge": {
|
|
176
|
-
"types": "./src/adapters/bun/client-bridge.ts",
|
|
177
|
-
"default": "./src/adapters/bun/client-bridge.ts"
|
|
178
|
-
},
|
|
179
136
|
"./html": {
|
|
180
137
|
"import": "./src/utils/html.ts",
|
|
181
138
|
"require": "./src/utils/html.ts"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import type { Server } from 'bun';
|
|
12
12
|
import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from '../../config/constants.ts';
|
|
13
|
+
import { StaticContentServer } from '../../dev/sc-server.ts';
|
|
13
14
|
import { appLogger } from '../../global/app-logger.ts';
|
|
14
15
|
import { getBunRuntime } from '../../utils/runtime.ts';
|
|
15
16
|
import type { ApiHandlerContext, RouteGroupBuilder } from '../../types/public-types.ts';
|
|
@@ -47,6 +48,41 @@ export class BunEcopagesApp<WebSocketData = undefined> extends SharedApplication
|
|
|
47
48
|
serverAdapter: BunServerAdapterResult | undefined;
|
|
48
49
|
private server: Server<WebSocketData> | null = null;
|
|
49
50
|
|
|
51
|
+
private async startStaticPreviewServer(port: number, hostname: string): Promise<void> {
|
|
52
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
53
|
+
|
|
54
|
+
for (let attempt = 0; attempt < 20; attempt += 1) {
|
|
55
|
+
try {
|
|
56
|
+
const previewServer = StaticContentServer.createServer({
|
|
57
|
+
appConfig: this.appConfig,
|
|
58
|
+
options: { port },
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (previewServer.server?.port) {
|
|
62
|
+
appLogger.info(`Preview running at http://${hostname}:${previewServer.server.port}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
break;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
69
|
+
const errorCode =
|
|
70
|
+
typeof error === 'object' && error !== null && 'code' in error
|
|
71
|
+
? String((error as { code?: unknown }).code)
|
|
72
|
+
: undefined;
|
|
73
|
+
const isPortReleaseRace = errorCode === 'EADDRINUSE' || errorMessage.includes('EADDRINUSE');
|
|
74
|
+
|
|
75
|
+
if (!isPortReleaseRace || attempt === 19) {
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
appLogger.error('Failed to start preview server');
|
|
84
|
+
}
|
|
85
|
+
|
|
50
86
|
public async fetch(request: Request): Promise<Response> {
|
|
51
87
|
if (!this.serverAdapter) {
|
|
52
88
|
this.serverAdapter = await this.initializeServerAdapter();
|
|
@@ -137,13 +173,22 @@ export class BunEcopagesApp<WebSocketData = undefined> extends SharedApplication
|
|
|
137
173
|
|
|
138
174
|
const enableHmr = dev || (!preview && !build);
|
|
139
175
|
const serverOptions = this.serverAdapter.getServerOptions({ enableHmr });
|
|
176
|
+
const configuredHostname = String(serverOptions.hostname ?? DEFAULT_ECOPAGES_HOSTNAME);
|
|
177
|
+
const configuredPort = Number(serverOptions.port ?? DEFAULT_ECOPAGES_PORT);
|
|
178
|
+
const runtimeServerOptions =
|
|
179
|
+
(preview || build) && requiresFetchRuntime
|
|
180
|
+
? {
|
|
181
|
+
...serverOptions,
|
|
182
|
+
port: 0,
|
|
183
|
+
}
|
|
184
|
+
: serverOptions;
|
|
140
185
|
|
|
141
186
|
const bun = getBunRuntime();
|
|
142
187
|
if (!bun) {
|
|
143
188
|
throw new Error('Bun runtime is required for the Bun adapter');
|
|
144
189
|
}
|
|
145
190
|
|
|
146
|
-
const bunServer = bun.serve(
|
|
191
|
+
const bunServer = bun.serve(runtimeServerOptions as Bun.Serve.Options<WebSocketData>);
|
|
147
192
|
this.server = bunServer as Server<WebSocketData>;
|
|
148
193
|
|
|
149
194
|
await this.serverAdapter.completeInitialization(this.server).catch((error: Error) => {
|
|
@@ -157,8 +202,15 @@ export class BunEcopagesApp<WebSocketData = undefined> extends SharedApplication
|
|
|
157
202
|
|
|
158
203
|
if (build || preview) {
|
|
159
204
|
appLogger.debugTime('Building static pages');
|
|
160
|
-
await this.serverAdapter.buildStatic({ preview });
|
|
205
|
+
await this.serverAdapter.buildStatic({ preview: false });
|
|
161
206
|
this.server.stop(true);
|
|
207
|
+
|
|
208
|
+
if (preview) {
|
|
209
|
+
const previewHostname = configuredHostname;
|
|
210
|
+
const previewPort = configuredPort;
|
|
211
|
+
await this.startStaticPreviewServer(previewPort, previewHostname);
|
|
212
|
+
}
|
|
213
|
+
|
|
162
214
|
appLogger.debugTimeEnd('Building static pages');
|
|
163
215
|
|
|
164
216
|
if (build) {
|
|
@@ -227,13 +227,11 @@ test('HmrManager stop clears retained registration state', async () => {
|
|
|
227
227
|
fs.writeFileSync(outputPath, 'export default 1;', 'utf8');
|
|
228
228
|
});
|
|
229
229
|
|
|
230
|
-
manager.registerSpecifierMap({ react: '/assets/vendors/react.js' });
|
|
231
230
|
await manager.registerEntrypoint(entrypointPath);
|
|
232
231
|
|
|
233
232
|
manager.stop();
|
|
234
233
|
|
|
235
234
|
assert.equal(manager.getWatchedFiles().size, 0);
|
|
236
|
-
assert.equal(manager.getSpecifierMap().size, 0);
|
|
237
235
|
});
|
|
238
236
|
|
|
239
237
|
test('HmrManager keeps internal browser and server-module outputs out of distDir', async () => {
|
|
@@ -20,7 +20,6 @@ import {
|
|
|
20
20
|
NoopEntrypointDependencyGraph,
|
|
21
21
|
setAppEntrypointDependencyGraph,
|
|
22
22
|
} from '../../services/runtime-state/entrypoint-dependency-graph.service.ts';
|
|
23
|
-
import { getAppRuntimeSpecifierRegistry } from '../../services/runtime-state/runtime-specifier-registry.service.ts';
|
|
24
23
|
import type { ServerModuleTranspiler } from '../../services/module-loading/server-module-transpiler.service.ts';
|
|
25
24
|
import { resolveInternalExecutionDir, resolveInternalWorkDir } from '../../utils/resolve-work-dir.ts';
|
|
26
25
|
|
|
@@ -60,7 +59,6 @@ export class HmrManager implements IHmrManager {
|
|
|
60
59
|
private readonly entrypointRegistrar: HmrEntrypointRegistrar;
|
|
61
60
|
private readonly browserBundleService: BrowserBundleService;
|
|
62
61
|
private readonly entrypointDependencyGraph: ReturnType<typeof getAppEntrypointDependencyGraph>;
|
|
63
|
-
private readonly runtimeSpecifierRegistry: ReturnType<typeof getAppRuntimeSpecifierRegistry>;
|
|
64
62
|
private readonly serverModuleTranspiler: ServerModuleTranspiler;
|
|
65
63
|
private wsHandler!: {
|
|
66
64
|
open: (ws: BunSocket) => void;
|
|
@@ -86,7 +84,6 @@ export class HmrManager implements IHmrManager {
|
|
|
86
84
|
? existingEntrypointDependencyGraph
|
|
87
85
|
: new NoopEntrypointDependencyGraph();
|
|
88
86
|
setAppEntrypointDependencyGraph(this.appConfig, this.entrypointDependencyGraph);
|
|
89
|
-
this.runtimeSpecifierRegistry = getAppRuntimeSpecifierRegistry(this.appConfig);
|
|
90
87
|
this.serverModuleTranspiler = getAppServerModuleTranspiler(this.appConfig);
|
|
91
88
|
this.cleanDistDir();
|
|
92
89
|
this.initializeStrategies();
|
|
@@ -132,7 +129,6 @@ export class HmrManager implements IHmrManager {
|
|
|
132
129
|
private initializeStrategies(): void {
|
|
133
130
|
const jsContext = {
|
|
134
131
|
getWatchedFiles: () => this.watchedFiles,
|
|
135
|
-
getSpecifierMap: () => this.runtimeSpecifierRegistry.getAll(),
|
|
136
132
|
getDistDir: () => this.distDir,
|
|
137
133
|
getPlugins: () => this.plugins,
|
|
138
134
|
getSrcDir: () => this.appConfig.absolutePaths.srcDir,
|
|
@@ -168,19 +164,6 @@ export class HmrManager implements IHmrManager {
|
|
|
168
164
|
return this.enabled;
|
|
169
165
|
}
|
|
170
166
|
|
|
171
|
-
/**
|
|
172
|
-
* Registers runtime bare-specifier mappings exposed by integrations.
|
|
173
|
-
*
|
|
174
|
-
* @remarks
|
|
175
|
-
* These mappings are consumed by framework-owned HMR strategies that preserve
|
|
176
|
-
* shared runtime imports in browser bundles. The registry stays generic so
|
|
177
|
-
* these mappings can later support broader import-map-style runtime features
|
|
178
|
-
* without moving integration semantics into core.
|
|
179
|
-
*/
|
|
180
|
-
public registerSpecifierMap(map: Record<string, string>): void {
|
|
181
|
-
this.runtimeSpecifierRegistry.register(map);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
167
|
public getWebSocketHandler(): BunSocketHandler {
|
|
185
168
|
const open = (ws: BunSocket) => {
|
|
186
169
|
this.bridge.subscribe(ws);
|
|
@@ -272,10 +255,6 @@ export class HmrManager implements IHmrManager {
|
|
|
272
255
|
return this.watchedFiles;
|
|
273
256
|
}
|
|
274
257
|
|
|
275
|
-
public getSpecifierMap(): Map<string, string> {
|
|
276
|
-
return this.runtimeSpecifierRegistry.getAll();
|
|
277
|
-
}
|
|
278
|
-
|
|
279
258
|
public getDistDir(): string {
|
|
280
259
|
return this.distDir;
|
|
281
260
|
}
|
|
@@ -287,7 +266,6 @@ export class HmrManager implements IHmrManager {
|
|
|
287
266
|
public getDefaultContext(): DefaultHmrContext {
|
|
288
267
|
return {
|
|
289
268
|
getWatchedFiles: () => this.watchedFiles,
|
|
290
|
-
getSpecifierMap: () => this.runtimeSpecifierRegistry.getAll(),
|
|
291
269
|
getDistDir: () => this.distDir,
|
|
292
270
|
getPlugins: () => this.plugins,
|
|
293
271
|
getSrcDir: () => this.appConfig.absolutePaths.srcDir,
|
|
@@ -390,7 +368,7 @@ export class HmrManager implements IHmrManager {
|
|
|
390
368
|
* @remarks
|
|
391
369
|
* Emitted `_hmr` files remain on disk because parallel app processes may share
|
|
392
370
|
* the same dist directory. The in-memory indexes are cleared so stale
|
|
393
|
-
* entrypoints
|
|
371
|
+
* entrypoints cannot leak through a reused manager object.
|
|
394
372
|
*/
|
|
395
373
|
public stop() {
|
|
396
374
|
this.entrypointRegistrations.clear();
|
|
@@ -399,7 +377,6 @@ export class HmrManager implements IHmrManager {
|
|
|
399
377
|
}
|
|
400
378
|
this.watchers.clear();
|
|
401
379
|
this.watchedFiles.clear();
|
|
402
|
-
this.runtimeSpecifierRegistry.clear();
|
|
403
380
|
this.entrypointDependencyGraph.reset();
|
|
404
381
|
this.plugins = [];
|
|
405
382
|
}
|
|
@@ -11,6 +11,7 @@ import { SharedServerAdapter } from '../shared/server-adapter.ts';
|
|
|
11
11
|
import type { ServerAdapterResult } from '../abstract/server-adapter.ts';
|
|
12
12
|
import { ApiResponseBuilder } from '../shared/api-response.ts';
|
|
13
13
|
import { installSharedRuntimeBuildExecutor } from '../shared/runtime-bootstrap.ts';
|
|
14
|
+
import { StaticContentServer } from '../../dev/sc-server.ts';
|
|
14
15
|
|
|
15
16
|
import { ServerRouteHandler, type ServerRouteHandlerParams } from '../shared/server-route-handler.ts';
|
|
16
17
|
import { ServerStaticBuilder, type ServerStaticBuilderParams } from '../shared/server-static-builder.ts';
|
|
@@ -352,11 +353,36 @@ export class BunServerAdapter extends SharedServerAdapter<BunServerAdapterParams
|
|
|
352
353
|
});
|
|
353
354
|
}
|
|
354
355
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
356
|
+
const buildRuntimeOrigin = this.serverInstance
|
|
357
|
+
? `http://${this.serverInstance.hostname || DEFAULT_ECOPAGES_HOSTNAME}:${this.serverInstance.port || DEFAULT_ECOPAGES_PORT}`
|
|
358
|
+
: undefined;
|
|
359
|
+
|
|
360
|
+
await this.staticBuilder.build(
|
|
361
|
+
{ ...options, preview: false, baseUrl: buildRuntimeOrigin },
|
|
362
|
+
{
|
|
363
|
+
router: this.router,
|
|
364
|
+
routeRendererFactory: this.routeRendererFactory,
|
|
365
|
+
staticRoutes: this.staticRoutes,
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
if (!options?.preview) {
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const previewHostname = this.serveOptions.hostname || DEFAULT_ECOPAGES_HOSTNAME;
|
|
374
|
+
const previewPort = Number(this.serveOptions.port || DEFAULT_ECOPAGES_PORT);
|
|
375
|
+
const previewServer = StaticContentServer.createServer({
|
|
376
|
+
appConfig: this.appConfig,
|
|
377
|
+
options: { port: previewPort },
|
|
359
378
|
});
|
|
379
|
+
|
|
380
|
+
if (previewServer.server?.port) {
|
|
381
|
+
appLogger.info(`Preview running at http://${previewHostname}:${previewServer.server.port}`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
appLogger.error('Failed to start preview server');
|
|
360
386
|
}
|
|
361
387
|
|
|
362
388
|
/**
|
|
@@ -283,13 +283,11 @@ test('NodeHmrManager stop clears retained registration state', async () => {
|
|
|
283
283
|
fs.writeFileSync(outputPath, 'export default 1;', 'utf8');
|
|
284
284
|
});
|
|
285
285
|
|
|
286
|
-
manager.registerSpecifierMap({ react: '/assets/vendors/react.js' });
|
|
287
286
|
await manager.registerEntrypoint(entrypointPath);
|
|
288
287
|
|
|
289
288
|
manager.stop();
|
|
290
289
|
|
|
291
290
|
assert.equal(manager.getWatchedFiles().size, 0);
|
|
292
|
-
assert.equal(manager.getSpecifierMap().size, 0);
|
|
293
291
|
assert.equal(config.runtime?.entrypointDependencyGraph?.getDependencyEntrypoints(entrypointPath).size, 0);
|
|
294
292
|
});
|
|
295
293
|
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
setAppEntrypointDependencyGraph,
|
|
20
20
|
type EntrypointDependencyGraph,
|
|
21
21
|
} from '../../services/runtime-state/entrypoint-dependency-graph.service.ts';
|
|
22
|
-
import { getAppRuntimeSpecifierRegistry } from '../../services/runtime-state/runtime-specifier-registry.service.ts';
|
|
23
22
|
import type { ServerModuleTranspiler } from '../../services/module-loading/server-module-transpiler.service.ts';
|
|
24
23
|
import { resolveInternalExecutionDir, resolveInternalWorkDir } from '../../utils/resolve-work-dir.ts';
|
|
25
24
|
|
|
@@ -59,7 +58,6 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
59
58
|
private readonly entrypointRegistrar: HmrEntrypointRegistrar;
|
|
60
59
|
private readonly browserBundleService: BrowserBundleService;
|
|
61
60
|
private readonly entrypointDependencyGraph: EntrypointDependencyGraph;
|
|
62
|
-
private readonly runtimeSpecifierRegistry: ReturnType<typeof getAppRuntimeSpecifierRegistry>;
|
|
63
61
|
private readonly serverModuleTranspiler: ServerModuleTranspiler;
|
|
64
62
|
|
|
65
63
|
constructor({ appConfig, bridge }: NodeHmrManagerParams) {
|
|
@@ -81,7 +79,6 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
81
79
|
? existingEntrypointDependencyGraph
|
|
82
80
|
: new InMemoryEntrypointDependencyGraph();
|
|
83
81
|
setAppEntrypointDependencyGraph(this.appConfig, this.entrypointDependencyGraph);
|
|
84
|
-
this.runtimeSpecifierRegistry = getAppRuntimeSpecifierRegistry(this.appConfig);
|
|
85
82
|
this.serverModuleTranspiler = getAppServerModuleTranspiler(this.appConfig);
|
|
86
83
|
this.cleanDistDir();
|
|
87
84
|
this.initializeStrategies();
|
|
@@ -123,7 +120,6 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
123
120
|
private initializeStrategies(): void {
|
|
124
121
|
const jsContext = {
|
|
125
122
|
getWatchedFiles: () => this.watchedFiles,
|
|
126
|
-
getSpecifierMap: () => this.runtimeSpecifierRegistry.getAll(),
|
|
127
123
|
getDistDir: () => this.distDir,
|
|
128
124
|
getPlugins: () => this.plugins,
|
|
129
125
|
getSrcDir: () => this.appConfig.absolutePaths.srcDir,
|
|
@@ -154,19 +150,6 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
154
150
|
return this.enabled;
|
|
155
151
|
}
|
|
156
152
|
|
|
157
|
-
/**
|
|
158
|
-
* Registers runtime bare-specifier mappings exposed by integrations.
|
|
159
|
-
*
|
|
160
|
-
* @remarks
|
|
161
|
-
* These mappings are consumed by framework-owned HMR strategies such as the
|
|
162
|
-
* React integration strategy when they rewrite browser bundles. The registry
|
|
163
|
-
* stays generic so the same mappings can support broader import-map-style
|
|
164
|
-
* runtime features later without moving integration semantics into core.
|
|
165
|
-
*/
|
|
166
|
-
public registerSpecifierMap(map: Record<string, string>): void {
|
|
167
|
-
this.runtimeSpecifierRegistry.register(map);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
153
|
public async buildRuntime(): Promise<void> {
|
|
171
154
|
const runtimeSource = path.resolve(import.meta.dirname, '../../hmr/client/hmr-runtime.ts');
|
|
172
155
|
|
|
@@ -239,10 +222,6 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
239
222
|
return this.watchedFiles;
|
|
240
223
|
}
|
|
241
224
|
|
|
242
|
-
public getSpecifierMap(): Map<string, string> {
|
|
243
|
-
return this.runtimeSpecifierRegistry.getAll();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
225
|
public getDistDir(): string {
|
|
247
226
|
return this.distDir;
|
|
248
227
|
}
|
|
@@ -254,7 +233,6 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
254
233
|
public getDefaultContext(): DefaultHmrContext {
|
|
255
234
|
return {
|
|
256
235
|
getWatchedFiles: () => this.watchedFiles,
|
|
257
|
-
getSpecifierMap: () => this.runtimeSpecifierRegistry.getAll(),
|
|
258
236
|
getDistDir: () => this.distDir,
|
|
259
237
|
getPlugins: () => this.plugins,
|
|
260
238
|
getSrcDir: () => this.appConfig.absolutePaths.srcDir,
|
|
@@ -361,8 +339,8 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
361
339
|
* @remarks
|
|
362
340
|
* The manager intentionally does not remove emitted `_hmr` files from disk
|
|
363
341
|
* because multiple app processes may share the same dist directory during test
|
|
364
|
-
* runs. It does clear in-memory indexes so old entrypoints
|
|
365
|
-
*
|
|
342
|
+
* runs. It does clear in-memory indexes so old entrypoints and dependencies
|
|
343
|
+
* cannot leak across a reused manager instance.
|
|
366
344
|
*/
|
|
367
345
|
public stop() {
|
|
368
346
|
this.entrypointRegistrations.clear();
|
|
@@ -371,7 +349,6 @@ export class NodeHmrManager implements IHmrManager {
|
|
|
371
349
|
}
|
|
372
350
|
this.watchers.clear();
|
|
373
351
|
this.watchedFiles.clear();
|
|
374
|
-
this.runtimeSpecifierRegistry.clear();
|
|
375
352
|
this.entrypointDependencyGraph.reset();
|
|
376
353
|
this.plugins = [];
|
|
377
354
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { EcoPagesAppConfig } from '../../types/internal-types.ts';
|
|
2
|
+
import type { EcoFunctionComponent, EcoPageComponent } from '../../types/public-types.ts';
|
|
3
|
+
import type { ExplicitViewRenderer, ExplicitViewRendererResolver } from '../../route-renderer/route-renderer.ts';
|
|
4
|
+
|
|
5
|
+
type ExplicitStaticRenderPreparationResult = {
|
|
6
|
+
renderer: ExplicitViewRenderer;
|
|
7
|
+
props: Record<string, unknown>;
|
|
8
|
+
view: EcoFunctionComponent<Record<string, unknown>, any>;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function getViewIntegrationName(view: {
|
|
12
|
+
config?: { integration?: string; __eco?: { integration?: string } };
|
|
13
|
+
}): string | undefined {
|
|
14
|
+
return view.config?.integration ?? view.config?.__eco?.integration;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resolves the renderer and static props needed to render one explicit static
|
|
19
|
+
* view at runtime or during static generation.
|
|
20
|
+
*/
|
|
21
|
+
export async function prepareExplicitStaticRender(input: {
|
|
22
|
+
routePath: string;
|
|
23
|
+
view: EcoPageComponent<any>;
|
|
24
|
+
params: Record<string, string | string[]>;
|
|
25
|
+
appConfig: EcoPagesAppConfig;
|
|
26
|
+
runtimeOrigin: string;
|
|
27
|
+
routeRendererFactory: ExplicitViewRendererResolver;
|
|
28
|
+
errors: {
|
|
29
|
+
missingIntegration(routePath: string): string;
|
|
30
|
+
noRendererForIntegration(integrationName: string): string;
|
|
31
|
+
};
|
|
32
|
+
}): Promise<ExplicitStaticRenderPreparationResult> {
|
|
33
|
+
const integrationName = getViewIntegrationName(input.view);
|
|
34
|
+
if (!integrationName) {
|
|
35
|
+
throw new Error(input.errors.missingIntegration(input.routePath));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const renderer = input.routeRendererFactory.getExplicitViewRenderer(integrationName);
|
|
39
|
+
if (!renderer) {
|
|
40
|
+
throw new Error(input.errors.noRendererForIntegration(integrationName));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const props = input.view.staticProps
|
|
44
|
+
? (
|
|
45
|
+
await input.view.staticProps({
|
|
46
|
+
pathname: { params: input.params },
|
|
47
|
+
appConfig: input.appConfig,
|
|
48
|
+
runtimeOrigin: input.runtimeOrigin,
|
|
49
|
+
})
|
|
50
|
+
).props
|
|
51
|
+
: {};
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
renderer,
|
|
55
|
+
props,
|
|
56
|
+
view: input.view as EcoFunctionComponent<Record<string, unknown>, any>,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -246,7 +246,7 @@ describe('ExplicitStaticRouteMatcher', () => {
|
|
|
246
246
|
const matcher = new ExplicitStaticRouteMatcher({
|
|
247
247
|
appConfig: { baseUrl: 'http://localhost:3000' } as any,
|
|
248
248
|
routeRendererFactory: {
|
|
249
|
-
|
|
249
|
+
getExplicitViewRenderer: vi.fn(() => ({
|
|
250
250
|
renderToResponse,
|
|
251
251
|
})),
|
|
252
252
|
} as any,
|
|
@@ -269,7 +269,7 @@ describe('ExplicitStaticRouteMatcher', () => {
|
|
|
269
269
|
const matcher = new ExplicitStaticRouteMatcher({
|
|
270
270
|
appConfig: { baseUrl: 'http://localhost:3000' } as any,
|
|
271
271
|
routeRendererFactory: {
|
|
272
|
-
|
|
272
|
+
getExplicitViewRenderer: vi.fn(() => null),
|
|
273
273
|
} as any,
|
|
274
274
|
staticRoutes: [createMockRoute('/about', viewWithoutIntegration)],
|
|
275
275
|
});
|
|
@@ -282,7 +282,7 @@ describe('ExplicitStaticRouteMatcher', () => {
|
|
|
282
282
|
test('should throw error when renderer is not found', async () => {
|
|
283
283
|
const view = createMockView('nonexistent-integration');
|
|
284
284
|
const RendererFactory = {
|
|
285
|
-
|
|
285
|
+
getExplicitViewRenderer: vi.fn(() => null),
|
|
286
286
|
};
|
|
287
287
|
|
|
288
288
|
const matcher = new ExplicitStaticRouteMatcher({
|
|
@@ -303,7 +303,7 @@ describe('ExplicitStaticRouteMatcher', () => {
|
|
|
303
303
|
const mockResponse = new Response('<html>Test</html>');
|
|
304
304
|
const RenderToResponse = vi.fn(() => mockResponse);
|
|
305
305
|
const RendererFactory = {
|
|
306
|
-
|
|
306
|
+
getExplicitViewRenderer: vi.fn(() => ({
|
|
307
307
|
renderToResponse: RenderToResponse,
|
|
308
308
|
})),
|
|
309
309
|
};
|
|
@@ -331,7 +331,7 @@ describe('ExplicitStaticRouteMatcher', () => {
|
|
|
331
331
|
const mockResponse = new Response('<html>Test</html>');
|
|
332
332
|
const RenderToResponse = vi.fn(() => mockResponse);
|
|
333
333
|
const RendererFactory = {
|
|
334
|
-
|
|
334
|
+
getExplicitViewRenderer: vi.fn(() => ({
|
|
335
335
|
renderToResponse: RenderToResponse,
|
|
336
336
|
})),
|
|
337
337
|
};
|
|
@@ -357,7 +357,7 @@ describe('ExplicitStaticRouteMatcher', () => {
|
|
|
357
357
|
|
|
358
358
|
const mockResponse = new Response('<html>Test</html>');
|
|
359
359
|
const RendererFactory = {
|
|
360
|
-
|
|
360
|
+
getExplicitViewRenderer: vi.fn(() => ({
|
|
361
361
|
renderToResponse: vi.fn(() => mockResponse),
|
|
362
362
|
})),
|
|
363
363
|
};
|