@ecopages/core 0.2.0-alpha.11 → 0.2.0-alpha.13
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/CHANGELOG.md +7 -10
- package/README.md +5 -4
- package/package.json +30 -6
- package/src/adapters/bun/hmr-manager.js +2 -2
- package/src/adapters/node/node-hmr-manager.js +2 -2
- package/src/adapters/node/server-adapter.d.ts +2 -2
- package/src/adapters/node/server-adapter.js +5 -5
- package/src/build/build-adapter.d.ts +8 -6
- package/src/build/build-adapter.js +44 -7
- package/src/eco/eco.js +18 -118
- package/src/eco/eco.utils.d.ts +1 -40
- package/src/eco/eco.utils.js +5 -35
- package/src/hmr/hmr-strategy.d.ts +8 -6
- package/src/integrations/ghtml/ghtml-renderer.d.ts +6 -1
- package/src/integrations/ghtml/ghtml-renderer.js +29 -28
- package/src/plugins/foreign-jsx-override-plugin.d.ts +31 -0
- package/src/plugins/foreign-jsx-override-plugin.js +35 -0
- package/src/plugins/integration-plugin.d.ts +90 -29
- package/src/plugins/integration-plugin.js +62 -19
- package/src/route-renderer/GRAPH.md +54 -84
- package/src/route-renderer/README.md +11 -19
- package/src/route-renderer/orchestration/component-render-context.d.ts +83 -0
- package/src/route-renderer/orchestration/component-render-context.js +147 -0
- package/src/route-renderer/orchestration/integration-renderer.d.ts +219 -81
- package/src/route-renderer/orchestration/integration-renderer.js +415 -171
- package/src/route-renderer/orchestration/queued-boundary-runtime.service.d.ts +93 -0
- package/src/route-renderer/orchestration/queued-boundary-runtime.service.js +155 -0
- package/src/route-renderer/orchestration/render-execution.service.d.ts +8 -70
- package/src/route-renderer/orchestration/render-execution.service.js +28 -113
- package/src/route-renderer/orchestration/render-output.utils.d.ts +46 -0
- package/src/route-renderer/orchestration/render-output.utils.js +65 -0
- package/src/route-renderer/orchestration/render-preparation.service.d.ts +0 -6
- package/src/route-renderer/orchestration/render-preparation.service.js +5 -13
- package/src/route-renderer/orchestration/template-serialization.d.ts +38 -0
- package/src/route-renderer/orchestration/template-serialization.js +45 -0
- package/src/route-renderer/page-loading/dependency-resolver.js +10 -8
- package/src/router/client/navigation-coordinator.js +2 -2
- package/src/router/server/fs-router-scanner.js +6 -1
- package/src/services/module-loading/node-bootstrap-plugin.js +14 -1
- package/src/services/module-loading/page-module-import.service.js +1 -1
- package/src/services/runtime-state/dev-graph.service.d.ts +5 -5
- package/src/services/runtime-state/dev-graph.service.js +10 -10
- package/src/types/public-types.d.ts +18 -3
- package/src/utils/html-escaping.d.ts +7 -0
- package/src/utils/html-escaping.js +6 -0
- package/src/eco/component-render-context.d.ts +0 -105
- package/src/eco/component-render-context.js +0 -94
- package/src/route-renderer/component-graph/component-graph-executor.d.ts +0 -33
- package/src/route-renderer/component-graph/component-graph-executor.js +0 -30
- package/src/route-renderer/component-graph/component-graph.d.ts +0 -53
- package/src/route-renderer/component-graph/component-graph.js +0 -94
- package/src/route-renderer/component-graph/component-marker.d.ts +0 -52
- package/src/route-renderer/component-graph/component-marker.js +0 -46
- package/src/route-renderer/component-graph/component-reference.d.ts +0 -11
- package/src/route-renderer/component-graph/component-reference.js +0 -39
- package/src/route-renderer/component-graph/marker-graph-resolver.d.ts +0 -79
- package/src/route-renderer/component-graph/marker-graph-resolver.js +0 -117
package/CHANGELOG.md
CHANGED
|
@@ -8,21 +8,18 @@ All notable changes to `@ecopages/core` are documented here.
|
|
|
8
8
|
|
|
9
9
|
### Features
|
|
10
10
|
|
|
11
|
-
- Added
|
|
12
|
-
- Added the browser-safe `eco` export, semantic `eco.html()` and `eco.layout()` helpers, and the internal `.eco` work directory.
|
|
13
|
-
- Added the public `EcoPagesAppConfig` export for published integration packages.
|
|
11
|
+
- Added app-owned runtime and build ownership around `createApp()`, host module loading, the browser-safe `eco` export, `eco.html()`, `eco.layout()`, and the published `EcoPagesAppConfig` surface.
|
|
14
12
|
|
|
15
13
|
### Refactoring
|
|
16
14
|
|
|
17
15
|
- Consolidated runtime state around shared module-loading services, app-owned build execution, and the universal `createApp()` boundary.
|
|
18
|
-
-
|
|
16
|
+
- Simplified route-renderer orchestration around renderer-owned boundary runtimes, shared string-boundary queue helpers, and a smaller component render context.
|
|
17
|
+
- Removed marker-era compatibility capture, the shared route-level fallback resolver, deprecated `@ecopages/core/node*` escape hatches, and other dead route-renderer internals.
|
|
19
18
|
|
|
20
19
|
### Bug Fixes
|
|
21
20
|
|
|
22
|
-
- Fixed
|
|
23
|
-
- Fixed
|
|
24
|
-
- Fixed deep marker-graph resolution, watch-mode route refreshes, and duplicate-core fallback references in mixed-runtime rendering.
|
|
25
|
-
- Fixed npm package output to rewrite workspace dependency versions and publish built JavaScript and declaration artifacts.
|
|
21
|
+
- Fixed mixed-integration page, layout, document, and component rendering to resolve foreign boundaries inside their owning renderer across the built-in integrations.
|
|
22
|
+
- Fixed host/runtime module loading, published build-helper exports, asset output normalization, explicit render flows, and static or preview build stability across Bun, Node, Vite, and Nitro.
|
|
26
23
|
|
|
27
24
|
### Documentation
|
|
28
25
|
|
|
@@ -30,12 +27,12 @@ All notable changes to `@ecopages/core` are documented here.
|
|
|
30
27
|
|
|
31
28
|
### Tests
|
|
32
29
|
|
|
33
|
-
- Added regression coverage for
|
|
30
|
+
- Added regression coverage for app-owned runtime services, Node fallback paths, and cross-runtime invalidation behavior.
|
|
34
31
|
|
|
35
32
|
---
|
|
36
33
|
|
|
37
34
|
## Migration Notes
|
|
38
35
|
|
|
39
|
-
- `createApp` is now the recommended entrypoint. Import it from `@ecopages/core`.
|
|
36
|
+
- `createApp` is now the recommended entrypoint. Import it from `@ecopages/core/create-app`.
|
|
40
37
|
- `defineApiHandler` keeps the same call shape, but the handler context is now explicitly runtime-agnostic.
|
|
41
38
|
- The old explicit `renderingMode` config option has been removed and full orchestration is always active.
|
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ export default config;
|
|
|
134
134
|
Start the application using `createApp`. It will choose the Bun adapter when Bun is available and fall back to Node otherwise.
|
|
135
135
|
|
|
136
136
|
```typescript
|
|
137
|
-
import { createApp } from '@ecopages/core';
|
|
137
|
+
import { createApp } from '@ecopages/core/create-app';
|
|
138
138
|
import appConfig from './eco.config';
|
|
139
139
|
|
|
140
140
|
const app = await createApp({ appConfig });
|
|
@@ -198,7 +198,7 @@ Attach the handler in your `app.ts` entry:
|
|
|
198
198
|
|
|
199
199
|
```typescript
|
|
200
200
|
// app.ts
|
|
201
|
-
import { createApp } from '@ecopages/core';
|
|
201
|
+
import { createApp } from '@ecopages/core/create-app';
|
|
202
202
|
import { helloWorld } from './handlers/hello';
|
|
203
203
|
import appConfig from './eco.config';
|
|
204
204
|
|
|
@@ -213,10 +213,11 @@ See the [official documentation](https://ecopages.app) for advanced usage, API h
|
|
|
213
213
|
|
|
214
214
|
## Import Structure
|
|
215
215
|
|
|
216
|
-
Use the root package
|
|
216
|
+
Use the `create-app` subpath for runtime startup and the root package for standard authoring helpers:
|
|
217
217
|
|
|
218
218
|
```ts
|
|
219
|
-
import { createApp
|
|
219
|
+
import { createApp } from '@ecopages/core/create-app';
|
|
220
|
+
import { defineApiHandler, defineGroupHandler, eco } from '@ecopages/core';
|
|
220
221
|
```
|
|
221
222
|
|
|
222
223
|
> [!NOTE]
|
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.13",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,14 +17,14 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-alpha.
|
|
21
|
-
"@ecopages/logger": "
|
|
20
|
+
"@ecopages/file-system": "0.2.0-alpha.13",
|
|
21
|
+
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.3",
|
|
23
23
|
"@worker-tools/html-rewriter": "0.1.0-pre.19",
|
|
24
|
-
"chokidar": "^
|
|
25
|
-
"esbuild": "^0.
|
|
24
|
+
"chokidar": "^5.0.0",
|
|
25
|
+
"esbuild": "^0.28.0",
|
|
26
26
|
"ghtml": "^4.0.2",
|
|
27
|
-
"oxc-parser": "^0.
|
|
27
|
+
"oxc-parser": "^0.124.0",
|
|
28
28
|
"ws": "^8.18.0"
|
|
29
29
|
},
|
|
30
30
|
"exports": {
|
|
@@ -64,6 +64,10 @@
|
|
|
64
64
|
"types": "./src/eco/eco.d.ts",
|
|
65
65
|
"default": "./src/eco/eco.js"
|
|
66
66
|
},
|
|
67
|
+
"./route-renderer/template-serialization": {
|
|
68
|
+
"types": "./src/route-renderer/orchestration/template-serialization.d.ts",
|
|
69
|
+
"default": "./src/route-renderer/orchestration/template-serialization.js"
|
|
70
|
+
},
|
|
67
71
|
"./declarations": {
|
|
68
72
|
"types": "./src/declarations.d.ts"
|
|
69
73
|
},
|
|
@@ -145,6 +149,14 @@
|
|
|
145
149
|
"types": "./src/build/runtime-specifier-alias-plugin.d.ts",
|
|
146
150
|
"default": "./src/build/runtime-specifier-alias-plugin.js"
|
|
147
151
|
},
|
|
152
|
+
"./build/runtime-specifier-aliases": {
|
|
153
|
+
"types": "./src/build/runtime-specifier-aliases.d.ts",
|
|
154
|
+
"default": "./src/build/runtime-specifier-aliases.js"
|
|
155
|
+
},
|
|
156
|
+
"./plugins/foreign-jsx-override-plugin": {
|
|
157
|
+
"types": "./src/plugins/foreign-jsx-override-plugin.d.ts",
|
|
158
|
+
"default": "./src/plugins/foreign-jsx-override-plugin.js"
|
|
159
|
+
},
|
|
148
160
|
"./adapters/bun/client-bridge": {
|
|
149
161
|
"types": "./src/adapters/bun/client-bridge.d.ts",
|
|
150
162
|
"default": "./src/adapters/bun/client-bridge.js"
|
|
@@ -197,6 +209,10 @@
|
|
|
197
209
|
"types": "./src/eco/eco.d.ts",
|
|
198
210
|
"default": "./src/eco/eco.js"
|
|
199
211
|
},
|
|
212
|
+
"./route-renderer/template-serialization.ts": {
|
|
213
|
+
"types": "./src/route-renderer/orchestration/template-serialization.d.ts",
|
|
214
|
+
"default": "./src/route-renderer/orchestration/template-serialization.js"
|
|
215
|
+
},
|
|
200
216
|
"./declarations.ts": {
|
|
201
217
|
"types": "./src/declarations.d.ts"
|
|
202
218
|
},
|
|
@@ -278,6 +294,14 @@
|
|
|
278
294
|
"types": "./src/build/runtime-specifier-alias-plugin.d.ts",
|
|
279
295
|
"default": "./src/build/runtime-specifier-alias-plugin.js"
|
|
280
296
|
},
|
|
297
|
+
"./build/runtime-specifier-aliases.ts": {
|
|
298
|
+
"types": "./src/build/runtime-specifier-aliases.d.ts",
|
|
299
|
+
"default": "./src/build/runtime-specifier-aliases.js"
|
|
300
|
+
},
|
|
301
|
+
"./plugins/foreign-jsx-override-plugin.ts": {
|
|
302
|
+
"types": "./src/plugins/foreign-jsx-override-plugin.d.ts",
|
|
303
|
+
"default": "./src/plugins/foreign-jsx-override-plugin.js"
|
|
304
|
+
},
|
|
281
305
|
"./adapters/bun/client-bridge.ts": {
|
|
282
306
|
"types": "./src/adapters/bun/client-bridge.d.ts",
|
|
283
307
|
"default": "./src/adapters/bun/client-bridge.js"
|
|
@@ -251,7 +251,7 @@ class HmrManager {
|
|
|
251
251
|
*/
|
|
252
252
|
async registerEntrypoint(entrypointPath) {
|
|
253
253
|
return await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
|
|
254
|
-
emit: async (normalizedEntrypoint
|
|
254
|
+
emit: async (normalizedEntrypoint) => await this.emitStrictEntrypoint(normalizedEntrypoint),
|
|
255
255
|
getMissingOutputError: (normalizedEntrypoint, outputPath) => new Error(
|
|
256
256
|
`[HMR] Integration failed to emit entrypoint ${normalizedEntrypoint} to ${outputPath}. Page entrypoints must be produced by their owning integration.`
|
|
257
257
|
)
|
|
@@ -278,7 +278,7 @@ class HmrManager {
|
|
|
278
278
|
* strategy processing without broadcasting, and then verifies that the owning
|
|
279
279
|
* integration emitted the expected file.
|
|
280
280
|
*/
|
|
281
|
-
async emitStrictEntrypoint(entrypointPath
|
|
281
|
+
async emitStrictEntrypoint(entrypointPath) {
|
|
282
282
|
await this.handleFileChange(entrypointPath, { broadcast: false });
|
|
283
283
|
}
|
|
284
284
|
/**
|
|
@@ -225,7 +225,7 @@ class NodeHmrManager {
|
|
|
225
225
|
*/
|
|
226
226
|
async registerEntrypoint(entrypointPath) {
|
|
227
227
|
return await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
|
|
228
|
-
emit: async (normalizedEntrypoint
|
|
228
|
+
emit: async (normalizedEntrypoint) => await this.emitStrictEntrypoint(normalizedEntrypoint),
|
|
229
229
|
getMissingOutputError: (normalizedEntrypoint, outputPath) => new Error(
|
|
230
230
|
`[HMR] Integration failed to emit entrypoint ${normalizedEntrypoint} to ${outputPath}. Page entrypoints must be produced by their owning integration.`
|
|
231
231
|
)
|
|
@@ -255,7 +255,7 @@ class NodeHmrManager {
|
|
|
255
255
|
* 3. Let the strategy chain try to emit the entrypoint without broadcasting.
|
|
256
256
|
* 4. Fail if the owning integration did not emit the expected output.
|
|
257
257
|
*/
|
|
258
|
-
async emitStrictEntrypoint(entrypointPath
|
|
258
|
+
async emitStrictEntrypoint(entrypointPath) {
|
|
259
259
|
await this.handleFileChange(entrypointPath, { broadcast: false });
|
|
260
260
|
}
|
|
261
261
|
/**
|
|
@@ -132,7 +132,7 @@ export declare class NodeServerAdapter extends SharedServerAdapter<NodeServerAda
|
|
|
132
132
|
* underlying socket closes early — into a 499 response so it does not
|
|
133
133
|
* incorrectly surface as a 500 in application logs.
|
|
134
134
|
*/
|
|
135
|
-
handleRequest(
|
|
135
|
+
handleRequest(request: Request): Promise<Response>;
|
|
136
136
|
/**
|
|
137
137
|
* Called once the HTTP server is bound and listening.
|
|
138
138
|
*
|
|
@@ -149,7 +149,7 @@ export declare class NodeServerAdapter extends SharedServerAdapter<NodeServerAda
|
|
|
149
149
|
* WebSocket upgrade requests that do not target `/_hmr` are rejected with an
|
|
150
150
|
* immediate socket destroy to prevent unhandled upgrade leaks.
|
|
151
151
|
*/
|
|
152
|
-
completeInitialization(
|
|
152
|
+
completeInitialization(server: NodeServerInstance): Promise<void>;
|
|
153
153
|
}
|
|
154
154
|
/**
|
|
155
155
|
* Factory function that creates and fully initialises a `NodeServerAdapter`.
|
|
@@ -269,12 +269,12 @@ class NodeServerAdapter extends SharedServerAdapter {
|
|
|
269
269
|
* underlying socket closes early — into a 499 response so it does not
|
|
270
270
|
* incorrectly surface as a 500 in application logs.
|
|
271
271
|
*/
|
|
272
|
-
async handleRequest(
|
|
272
|
+
async handleRequest(request) {
|
|
273
273
|
if (!this.initialized) {
|
|
274
274
|
throw new Error("Node server adapter is not initialized. Call createAdapter() first.");
|
|
275
275
|
}
|
|
276
276
|
try {
|
|
277
|
-
return await this.handleSharedRequest(
|
|
277
|
+
return await this.handleSharedRequest(request, {
|
|
278
278
|
apiHandlers: this.apiHandlers,
|
|
279
279
|
errorHandler: this.errorHandler,
|
|
280
280
|
serverInstance: this.serverInstance,
|
|
@@ -303,8 +303,8 @@ class NodeServerAdapter extends SharedServerAdapter {
|
|
|
303
303
|
* WebSocket upgrade requests that do not target `/_hmr` are rejected with an
|
|
304
304
|
* immediate socket destroy to prevent unhandled upgrade leaks.
|
|
305
305
|
*/
|
|
306
|
-
async completeInitialization(
|
|
307
|
-
this.serverInstance =
|
|
306
|
+
async completeInitialization(server) {
|
|
307
|
+
this.serverInstance = server;
|
|
308
308
|
if (this.options?.watch) {
|
|
309
309
|
const { NodeHmrManager } = await import("./node-hmr-manager.js");
|
|
310
310
|
const { WebSocketServer } = await import("ws");
|
|
@@ -313,7 +313,7 @@ class NodeServerAdapter extends SharedServerAdapter {
|
|
|
313
313
|
this.hmrManager = new NodeHmrManager({ appConfig: this.appConfig, bridge: this.bridge });
|
|
314
314
|
this.hmrManager.setEnabled(true);
|
|
315
315
|
await this.hmrManager.buildRuntime();
|
|
316
|
-
|
|
316
|
+
server.on("upgrade", (req, socket, head) => {
|
|
317
317
|
const url = new URL(req.url ?? "/", this.runtimeOrigin);
|
|
318
318
|
if (url.pathname === "/_hmr") {
|
|
319
319
|
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
@@ -107,6 +107,7 @@ export declare class BunBuildAdapter implements BuildAdapter {
|
|
|
107
107
|
private mapBunTarget;
|
|
108
108
|
private mapBunFormat;
|
|
109
109
|
private getOutputExtension;
|
|
110
|
+
private resolveConcreteOutputPath;
|
|
110
111
|
private resolveTemplatedOutputPath;
|
|
111
112
|
private relocateOutputFile;
|
|
112
113
|
private normalizeBunOutputs;
|
|
@@ -129,9 +130,10 @@ export declare function createBuildAdapter(options?: {
|
|
|
129
130
|
export declare const defaultBunBuildAdapter: BuildAdapter;
|
|
130
131
|
export declare const defaultViteHostBuildAdapter: BuildAdapter;
|
|
131
132
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
133
|
+
* Bun-native fallback export for callsites that still resolve build state
|
|
134
|
+
* globally.
|
|
135
|
+
*
|
|
136
|
+
* New app-aware code should prefer `getAppBuildAdapter()`.
|
|
135
137
|
*/
|
|
136
138
|
export declare const defaultBuildAdapter: BuildAdapter;
|
|
137
139
|
export declare function getDefaultBuildAdapter(ownership?: BuildOwnership): BuildAdapter;
|
|
@@ -223,9 +225,9 @@ export declare function setAppBuildExecutor(appConfig: EcoPagesAppConfig, buildE
|
|
|
223
225
|
*/
|
|
224
226
|
export declare function build(options: BuildOptions, executor?: BuildExecutor): Promise<BuildResult>;
|
|
225
227
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
228
|
-
*
|
|
228
|
+
* Bun-native fallback helper for callsites without app runtime context.
|
|
229
|
+
*
|
|
230
|
+
* New app-aware code should prefer `getAppTranspileOptions()`.
|
|
229
231
|
*/
|
|
230
232
|
export declare function getTranspileOptions(profile: BuildTranspileProfile): BuildTranspileOptions;
|
|
231
233
|
export declare function getAppTranspileOptions(appConfig: EcoPagesAppConfig, profile: BuildTranspileProfile): BuildTranspileOptions;
|
|
@@ -215,7 +215,26 @@ class BunBuildAdapter {
|
|
|
215
215
|
}
|
|
216
216
|
return options.format === "cjs" || options.format === "esm" ? ".js" : ".js";
|
|
217
217
|
}
|
|
218
|
-
|
|
218
|
+
resolveConcreteOutputPath(outputPath) {
|
|
219
|
+
if (fs.existsSync(outputPath)) {
|
|
220
|
+
return outputPath;
|
|
221
|
+
}
|
|
222
|
+
if (!outputPath.includes("[hash]")) {
|
|
223
|
+
return outputPath;
|
|
224
|
+
}
|
|
225
|
+
const directory = path.dirname(outputPath);
|
|
226
|
+
if (!fs.existsSync(directory)) {
|
|
227
|
+
return void 0;
|
|
228
|
+
}
|
|
229
|
+
const basenamePattern = path.basename(outputPath);
|
|
230
|
+
const matcher = new RegExp(`^${this.escapeRegExp(basenamePattern).replace(/\\\[hash\\\]/g, "(.+)")}$`);
|
|
231
|
+
const matches = fs.readdirSync(directory).filter((candidate) => matcher.test(candidate)).sort();
|
|
232
|
+
if (matches.length === 0) {
|
|
233
|
+
return void 0;
|
|
234
|
+
}
|
|
235
|
+
return path.join(directory, matches[0]);
|
|
236
|
+
}
|
|
237
|
+
resolveTemplatedOutputPath(options, entrypointPath, concreteOutputPath) {
|
|
219
238
|
if (!options.outdir) {
|
|
220
239
|
return void 0;
|
|
221
240
|
}
|
|
@@ -233,6 +252,18 @@ class BunBuildAdapter {
|
|
|
233
252
|
resolvedPath += outputExtension;
|
|
234
253
|
}
|
|
235
254
|
resolvedPath = resolvedPath.replace(/^\.\//, "");
|
|
255
|
+
if (resolvedPath.includes("[hash]")) {
|
|
256
|
+
if (!concreteOutputPath) {
|
|
257
|
+
return path.join(outdir, resolvedPath);
|
|
258
|
+
}
|
|
259
|
+
const concreteRelativePath = path.relative(outdir, concreteOutputPath).split(path.sep).join("/");
|
|
260
|
+
const matcher = new RegExp(`^${this.escapeRegExp(resolvedPath).replace(/\\\[hash\\\]/g, "(.+)")}$`);
|
|
261
|
+
const match = concreteRelativePath.match(matcher);
|
|
262
|
+
if (!match?.[1]) {
|
|
263
|
+
return concreteOutputPath;
|
|
264
|
+
}
|
|
265
|
+
resolvedPath = resolvedPath.replaceAll("[hash]", match[1]);
|
|
266
|
+
}
|
|
236
267
|
return path.join(outdir, resolvedPath);
|
|
237
268
|
}
|
|
238
269
|
relocateOutputFile(currentPath, targetPath) {
|
|
@@ -252,12 +283,16 @@ class BunBuildAdapter {
|
|
|
252
283
|
const canMapEntrypointsByIndex = options.entrypoints.length === normalizedOutputs.length;
|
|
253
284
|
if (canMapEntrypointsByIndex) {
|
|
254
285
|
for (const [index, entrypointPath] of options.entrypoints.entries()) {
|
|
255
|
-
const
|
|
286
|
+
const concreteOutputPath = this.resolveConcreteOutputPath(normalizedOutputs[index].path);
|
|
287
|
+
const expectedOutputPath = this.resolveTemplatedOutputPath(options, entrypointPath, concreteOutputPath);
|
|
256
288
|
if (!expectedOutputPath) {
|
|
257
289
|
continue;
|
|
258
290
|
}
|
|
259
291
|
normalizedOutputs[index] = {
|
|
260
|
-
path: this.relocateOutputFile(
|
|
292
|
+
path: this.relocateOutputFile(
|
|
293
|
+
concreteOutputPath ?? normalizedOutputs[index].path,
|
|
294
|
+
expectedOutputPath
|
|
295
|
+
)
|
|
261
296
|
};
|
|
262
297
|
}
|
|
263
298
|
return {
|
|
@@ -268,12 +303,13 @@ class BunBuildAdapter {
|
|
|
268
303
|
return {
|
|
269
304
|
...result,
|
|
270
305
|
outputs: normalizedOutputs.map((output) => {
|
|
271
|
-
|
|
306
|
+
const concreteOutputPath = this.resolveConcreteOutputPath(output.path) ?? output.path;
|
|
307
|
+
if (path.extname(concreteOutputPath) !== "") {
|
|
272
308
|
return output;
|
|
273
309
|
}
|
|
274
|
-
const normalizedPath = `${
|
|
310
|
+
const normalizedPath = `${concreteOutputPath}.js`;
|
|
275
311
|
return {
|
|
276
|
-
path: this.relocateOutputFile(
|
|
312
|
+
path: this.relocateOutputFile(concreteOutputPath, normalizedPath)
|
|
277
313
|
};
|
|
278
314
|
})
|
|
279
315
|
};
|
|
@@ -448,7 +484,8 @@ async function collectConfiguredAppBuildManifestContributions(appConfig) {
|
|
|
448
484
|
for (const integration of appConfig.integrations) {
|
|
449
485
|
integration.setConfig(appConfig);
|
|
450
486
|
await integration.prepareBuildContributions();
|
|
451
|
-
runtimePlugins.push(...integration.plugins);
|
|
487
|
+
runtimePlugins.push(...integration.plugins ?? []);
|
|
488
|
+
browserBundlePlugins.push(...integration.browserBuildPlugins ?? []);
|
|
452
489
|
}
|
|
453
490
|
return {
|
|
454
491
|
runtimePlugins,
|
package/src/eco/eco.js
CHANGED
|
@@ -1,133 +1,33 @@
|
|
|
1
|
-
import { createNodeId, createPropsRef, createSlotRef, getComponentRenderContext } from "./component-render-context.js";
|
|
2
|
-
import { createComponentMarker, parseComponentMarkers } from "../route-renderer/component-graph/component-marker.js";
|
|
3
1
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
} from "../route-renderer/
|
|
7
|
-
import {
|
|
8
|
-
function isMarkerSerializableTemplateLike(value) {
|
|
9
|
-
return typeof value === "object" && value !== null && Array.isArray(value.strings) && (value.values === void 0 || Array.isArray(value.values));
|
|
10
|
-
}
|
|
11
|
-
function serializeDeferredChildren(value, seen = /* @__PURE__ */ new Set()) {
|
|
12
|
-
if (typeof value === "string") {
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
if (typeof value === "number" || typeof value === "bigint") {
|
|
16
|
-
return String(value);
|
|
17
|
-
}
|
|
18
|
-
if (typeof value === "boolean" || value == null) {
|
|
19
|
-
return "";
|
|
20
|
-
}
|
|
21
|
-
if (Array.isArray(value)) {
|
|
22
|
-
return value.map((item) => serializeDeferredChildren(item, seen) ?? "").join("");
|
|
23
|
-
}
|
|
24
|
-
if (isMarkerSerializableTemplateLike(value)) {
|
|
25
|
-
const values = value.values ?? [];
|
|
26
|
-
let html2 = "";
|
|
27
|
-
for (let index = 0; index < value.strings.length; index += 1) {
|
|
28
|
-
html2 += value.strings[index] ?? "";
|
|
29
|
-
if (index < values.length) {
|
|
30
|
-
html2 += serializeDeferredChildren(values[index], seen) ?? "";
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return html2;
|
|
34
|
-
}
|
|
35
|
-
if (typeof value === "object" && value !== null) {
|
|
36
|
-
if (seen.has(value)) {
|
|
37
|
-
return "";
|
|
38
|
-
}
|
|
39
|
-
seen.add(value);
|
|
40
|
-
const serialized = Object.values(value).map((entry) => serializeDeferredChildren(entry, seen) ?? "").join("");
|
|
41
|
-
seen.delete(value);
|
|
42
|
-
return serialized.length > 0 ? serialized : void 0;
|
|
43
|
-
}
|
|
44
|
-
return void 0;
|
|
45
|
-
}
|
|
46
|
-
function getRuntimeComponentHint(render, options) {
|
|
47
|
-
const stack = new Error().stack;
|
|
48
|
-
if (stack) {
|
|
49
|
-
for (const line of stack.split("\n").slice(1)) {
|
|
50
|
-
const trimmed = line.trim();
|
|
51
|
-
if (trimmed.includes("/packages/core/src/eco/eco.ts") || trimmed.includes("createComponentFactory") || trimmed.includes("component (") || trimmed.includes("layout (") || trimmed.includes("html (") || trimmed.includes("page (")) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
const match = trimmed.match(/(?:at\s+.*?\()?(.+?:\d+:\d+)\)?$/);
|
|
55
|
-
if (match?.[1]) {
|
|
56
|
-
return match[1];
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
const renderSignature = typeof render === "function" ? render.toString() : void 0;
|
|
61
|
-
if (!renderSignature) {
|
|
62
|
-
return void 0;
|
|
63
|
-
}
|
|
64
|
-
return JSON.stringify({
|
|
65
|
-
integration: options.integration,
|
|
66
|
-
render: renderSignature,
|
|
67
|
-
stylesheets: options.dependencies?.stylesheets,
|
|
68
|
-
scripts: options.dependencies?.scripts
|
|
69
|
-
});
|
|
70
|
-
}
|
|
2
|
+
finalizeComponentRender,
|
|
3
|
+
interceptComponentBoundary
|
|
4
|
+
} from "../route-renderer/orchestration/component-render-context.js";
|
|
5
|
+
import { isThenable } from "../route-renderer/orchestration/render-output.utils.js";
|
|
71
6
|
function createComponentFactory(options) {
|
|
72
7
|
const integrationName = options.integration ?? options.__eco?.integration;
|
|
73
8
|
const comp = ((props) => {
|
|
74
|
-
const
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const storedProps = { ...componentProps };
|
|
86
|
-
const serializedChildren = serializeDeferredChildren(componentProps.children);
|
|
87
|
-
renderContext.propsByRef[propsRef] = serializedChildren === void 0 ? storedProps : { ...storedProps, children: serializedChildren };
|
|
88
|
-
let slotRef;
|
|
89
|
-
if (typeof serializedChildren === "string" && serializedChildren.includes("<eco-marker")) {
|
|
90
|
-
const childMarkers = parseComponentMarkers(serializedChildren);
|
|
91
|
-
if (childMarkers.length > 0) {
|
|
92
|
-
slotRef = createSlotRef(renderContext);
|
|
93
|
-
renderContext.slotChildrenByRef[slotRef] = childMarkers.map((marker) => marker.nodeId);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
return createComponentMarker({
|
|
97
|
-
nodeId,
|
|
98
|
-
integration: integrationName,
|
|
99
|
-
componentRef,
|
|
100
|
-
propsRef,
|
|
101
|
-
slotRef
|
|
102
|
-
});
|
|
9
|
+
const componentProps = props ?? {};
|
|
10
|
+
const renderInline = () => finalizeComponentRender(comp, options.render(props));
|
|
11
|
+
const boundaryRender = interceptComponentBoundary({
|
|
12
|
+
component: comp,
|
|
13
|
+
props: componentProps,
|
|
14
|
+
targetIntegration: integrationName
|
|
15
|
+
});
|
|
16
|
+
if (isThenable(boundaryRender)) {
|
|
17
|
+
return boundaryRender.then(
|
|
18
|
+
(resolvedBoundaryRender) => resolvedBoundaryRender !== void 0 ? resolvedBoundaryRender : renderInline()
|
|
19
|
+
);
|
|
103
20
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (lazyTriggers && lazyTriggers.length > 0) {
|
|
107
|
-
const triggerId = lazyTriggers[0].triggerId;
|
|
108
|
-
if (isThenable(content)) {
|
|
109
|
-
return content.then((resolvedContent) => addTriggerAttribute(resolvedContent, triggerId));
|
|
110
|
-
}
|
|
111
|
-
return addTriggerAttribute(content, triggerId);
|
|
21
|
+
if (boundaryRender !== void 0) {
|
|
22
|
+
return boundaryRender;
|
|
112
23
|
}
|
|
113
|
-
|
|
114
|
-
if (lazyGroups && lazyGroups.length > 0) {
|
|
115
|
-
if (isThenable(content)) {
|
|
116
|
-
return content.then((resolvedContent) => wrapWithScriptsInjector(resolvedContent, lazyGroups));
|
|
117
|
-
}
|
|
118
|
-
return wrapWithScriptsInjector(content, lazyGroups);
|
|
119
|
-
}
|
|
120
|
-
return content;
|
|
24
|
+
return renderInline();
|
|
121
25
|
});
|
|
122
26
|
comp.config = {
|
|
123
27
|
__eco: options.__eco,
|
|
124
28
|
integration: options.integration,
|
|
125
29
|
dependencies: options.dependencies
|
|
126
30
|
};
|
|
127
|
-
const runtimeHint = getRuntimeComponentHint(options.render, options);
|
|
128
|
-
if (runtimeHint) {
|
|
129
|
-
registerRuntimeComponentHint(comp, runtimeHint);
|
|
130
|
-
}
|
|
131
31
|
return comp;
|
|
132
32
|
}
|
|
133
33
|
function component(options) {
|
package/src/eco/eco.utils.d.ts
CHANGED
|
@@ -1,40 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/**
|
|
3
|
-
* Returns `true` when `value` is a thenable (Promise-like) object.
|
|
4
|
-
*
|
|
5
|
-
* Used to transparently handle both synchronous and asynchronous component
|
|
6
|
-
* render results without requiring every caller to branch on `instanceof Promise`.
|
|
7
|
-
*
|
|
8
|
-
* @typeParam T Expected resolved type of the thenable.
|
|
9
|
-
*/
|
|
10
|
-
export declare function isThenable<T>(value: unknown): value is PromiseLike<T>;
|
|
11
|
-
/**
|
|
12
|
-
* Injects `data-eco-trigger` into the first real HTML element opening tag of
|
|
13
|
-
* a component's rendered output string.
|
|
14
|
-
*
|
|
15
|
-
* The scan skips over leading whitespace, HTML comments (`<!-- -->`), CDATA
|
|
16
|
-
* sections, and doctype declarations so that the attribute is always placed on
|
|
17
|
-
* the first actual element — not spurious markup that can precede it.
|
|
18
|
-
*
|
|
19
|
-
* The insertion point is the end of the element's tag name, before any existing
|
|
20
|
-
* attributes or the closing `>`, which produces output like:
|
|
21
|
-
*
|
|
22
|
-
* ```html
|
|
23
|
-
* <my-element data-eco-trigger="eco-trigger-abc123" class="foo">…</my-element>
|
|
24
|
-
* ```
|
|
25
|
-
*
|
|
26
|
-
* When no eligible opening tag is found the original string is returned
|
|
27
|
-
* unchanged so callers never receive a broken fragment.
|
|
28
|
-
*
|
|
29
|
-
* @param content Rendered HTML string (or any value coercible to string).
|
|
30
|
-
* @param triggerId Stable trigger identifier produced by `buildResolvedLazyTriggers`.
|
|
31
|
-
*/
|
|
32
|
-
export declare function addTriggerAttribute(content: unknown, triggerId: string): string;
|
|
33
|
-
/**
|
|
34
|
-
* Wraps rendered component output in a `<scripts-injector>` element that
|
|
35
|
-
* carries an inline injector map for the legacy (non-global-injector) path.
|
|
36
|
-
*
|
|
37
|
-
* @param content Rendered component HTML.
|
|
38
|
-
* @param lazyGroups Resolved lazy script groups attached to the component config.
|
|
39
|
-
*/
|
|
40
|
-
export declare function wrapWithScriptsInjector(content: unknown, lazyGroups: NonNullable<EcoComponent['config']>['_resolvedLazyScripts']): string;
|
|
1
|
+
export { addTriggerAttribute, isThenable, wrapWithScriptsInjector, } from '../route-renderer/orchestration/render-output.utils.js';
|
package/src/eco/eco.utils.js
CHANGED
|
@@ -1,38 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const str = String(content);
|
|
7
|
-
let i = 0;
|
|
8
|
-
while (i < str.length) {
|
|
9
|
-
if (str[i] !== "<") {
|
|
10
|
-
i++;
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
13
|
-
const next = str[i + 1];
|
|
14
|
-
if (next === "!" || next === "?") {
|
|
15
|
-
const end = str.indexOf(">", i);
|
|
16
|
-
if (end === -1) break;
|
|
17
|
-
i = end + 1;
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
if (next && /[a-zA-Z]/.test(next)) {
|
|
21
|
-
const tagSlice = str.slice(i + 1);
|
|
22
|
-
const nameEnd = tagSlice.search(/[\s/>]/);
|
|
23
|
-
if (nameEnd === -1) break;
|
|
24
|
-
const insertAt = i + 1 + nameEnd;
|
|
25
|
-
return `${str.slice(0, insertAt)} data-eco-trigger="${triggerId}"${str.slice(insertAt)}`;
|
|
26
|
-
}
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
return str;
|
|
30
|
-
}
|
|
31
|
-
function wrapWithScriptsInjector(content, lazyGroups) {
|
|
32
|
-
const wrappedContent = String(content);
|
|
33
|
-
const injectorMapScript = buildInjectorMapScript(lazyGroups ?? []);
|
|
34
|
-
return `<scripts-injector><script type="ecopages/injector-map">${injectorMapScript}<\/script>${wrappedContent}<\/scripts-injector>`;
|
|
35
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
addTriggerAttribute,
|
|
3
|
+
isThenable,
|
|
4
|
+
wrapWithScriptsInjector
|
|
5
|
+
} from "../route-renderer/orchestration/render-output.utils.js";
|
|
36
6
|
export {
|
|
37
7
|
addTriggerAttribute,
|
|
38
8
|
isThenable,
|
|
@@ -18,11 +18,13 @@ import type { ClientBridgeEvent } from '../types/public-types.js';
|
|
|
18
18
|
* async process(filePath: string): Promise<HmrAction> {
|
|
19
19
|
* return {
|
|
20
20
|
* type: 'broadcast',
|
|
21
|
-
* events: [{ type: 'update', path: filePath, timestamp: Date.now() }]
|
|
21
|
+
* events: [{ type: 'update', path: filePath, timestamp: Date.now() }],
|
|
22
22
|
* };
|
|
23
23
|
* }
|
|
24
24
|
* }
|
|
25
|
-
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
26
28
|
* Defines the category of an HMR strategy, which determines its execution priority.
|
|
27
29
|
* Strategies are evaluated in descending order: INTEGRATION → ASSET → SCRIPT → FALLBACK.
|
|
28
30
|
*
|
|
@@ -63,7 +65,7 @@ export interface HmrAction {
|
|
|
63
65
|
type: 'broadcast' | 'none';
|
|
64
66
|
/**
|
|
65
67
|
* The HMR events to broadcast, if type is 'broadcast'.
|
|
66
|
-
*
|
|
68
|
+
* Multiple events may be broadcast in one action.
|
|
67
69
|
*/
|
|
68
70
|
events?: ClientBridgeEvent[];
|
|
69
71
|
}
|
|
@@ -75,8 +77,8 @@ export interface HmrAction {
|
|
|
75
77
|
* whether they match the changed file path.
|
|
76
78
|
*
|
|
77
79
|
* @remarks
|
|
78
|
-
* Strategies
|
|
79
|
-
* produce the same result when processed by the same strategy.
|
|
80
|
+
* Strategies are expected to be stateless and idempotent. The same file change
|
|
81
|
+
* should produce the same result when processed by the same strategy.
|
|
80
82
|
*
|
|
81
83
|
* @example
|
|
82
84
|
* ```typescript
|
|
@@ -121,7 +123,7 @@ export declare abstract class HmrStrategy {
|
|
|
121
123
|
* Determines if this strategy can handle the given file path.
|
|
122
124
|
*
|
|
123
125
|
* @param filePath - Absolute path to the changed file
|
|
124
|
-
* @returns
|
|
126
|
+
* @returns `true` when this strategy should process the file
|
|
125
127
|
*
|
|
126
128
|
* @example
|
|
127
129
|
* ```typescript
|