@ecopages/core 0.2.0-beta.2 → 0.2.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +163 -3
- package/src/adapters/abstract/application-adapter.d.ts +104 -5
- package/src/adapters/abstract/application-adapter.js +125 -1
- package/src/adapters/abstract/server-adapter.d.ts +6 -2
- package/src/adapters/abstract/ws-pattern-matcher.d.ts +60 -0
- package/src/adapters/abstract/ws-pattern-matcher.js +61 -0
- package/src/adapters/bun/client-bridge.d.ts +1 -0
- package/src/adapters/bun/client-bridge.js +3 -0
- package/src/adapters/bun/create-app.d.ts +6 -4
- package/src/adapters/bun/create-app.js +65 -43
- package/src/adapters/bun/server-adapter.d.ts +90 -22
- package/src/adapters/bun/server-adapter.js +266 -84
- package/src/adapters/bun/static-preview-host.js +25 -20
- package/src/adapters/create-app.d.ts +13 -11
- package/src/adapters/create-app.js +2 -51
- package/src/adapters/index.d.ts +1 -1
- package/src/adapters/index.js +1 -2
- package/src/adapters/node/create-app.d.ts +5 -1
- package/src/adapters/node/create-app.js +51 -12
- package/src/adapters/node/node-client-bridge.d.ts +1 -0
- package/src/adapters/node/node-client-bridge.js +3 -0
- package/src/adapters/node/server-adapter-dependencies.js +4 -0
- package/src/adapters/node/server-adapter.d.ts +62 -20
- package/src/adapters/node/server-adapter.js +160 -131
- package/src/adapters/node/static-content-server.d.ts +1 -0
- package/src/adapters/node/static-content-server.js +10 -2
- package/src/adapters/node/static-preview-host.js +27 -12
- package/src/adapters/shared/bun-user-websocket-lifecycle.d.ts +22 -0
- package/src/adapters/shared/bun-user-websocket-lifecycle.js +96 -0
- package/src/adapters/shared/copy-runtime-public-dir.d.ts +6 -0
- package/src/adapters/shared/copy-runtime-public-dir.js +24 -0
- package/src/adapters/shared/fs-server-response-matcher.d.ts +2 -0
- package/src/adapters/shared/fs-server-response-matcher.js +40 -18
- package/src/adapters/shared/hmr-entrypoint-registrar.d.ts +21 -30
- package/src/adapters/shared/hmr-entrypoint-registrar.js +49 -54
- package/src/adapters/shared/hmr-html-response.d.ts +1 -1
- package/src/adapters/shared/hmr-html-response.js +4 -1
- package/src/adapters/shared/node-http-websocket-upgrades.d.ts +21 -0
- package/src/adapters/shared/node-http-websocket-upgrades.js +100 -0
- package/src/adapters/shared/port-manager.d.ts +116 -0
- package/src/adapters/shared/port-manager.js +196 -0
- package/src/adapters/shared/runtime-app-bootstrap.d.ts +2 -1
- package/src/adapters/shared/runtime-app-bootstrap.js +3 -6
- package/src/adapters/shared/runtime-server-lifecycle.d.ts +46 -0
- package/src/adapters/shared/runtime-server-lifecycle.js +50 -0
- package/src/adapters/shared/server-adapter.d.ts +2 -1
- package/src/adapters/shared/server-adapter.js +13 -6
- package/src/adapters/shared/server-route-handler.d.ts +4 -1
- package/src/adapters/shared/server-route-handler.js +5 -2
- package/src/adapters/shared/server-static-builder.d.ts +23 -32
- package/src/adapters/shared/server-static-builder.js +100 -73
- package/src/adapters/shared/shared-hmr-manager.d.ts +45 -11
- package/src/adapters/shared/shared-hmr-manager.js +204 -58
- package/src/adapters/shared/static-preview-host.d.ts +5 -0
- package/src/adapters/shared/websocket-lifecycle.d.ts +24 -0
- package/src/adapters/shared/websocket-lifecycle.js +55 -0
- package/src/build/README.md +77 -14
- package/src/build/app-build-manifest-runtime.d.ts +9 -0
- package/src/build/app-build-manifest-runtime.js +87 -0
- package/src/build/build-adapter.d.ts +17 -319
- package/src/build/build-adapter.js +28 -94
- package/src/build/build-contracts.d.ts +97 -0
- package/src/build/build-contracts.js +0 -0
- package/src/build/build-input-fingerprint.d.ts +28 -0
- package/src/build/build-input-fingerprint.js +31 -0
- package/src/build/build-profile-options.d.ts +7 -0
- package/src/build/build-profile-options.js +37 -0
- package/src/build/build-runtime.d.ts +24 -0
- package/src/build/build-runtime.js +65 -0
- package/src/build/deduping-build-executor.d.ts +28 -0
- package/src/build/deduping-build-executor.js +56 -0
- package/src/build/jsx-ownership-plugins.d.ts +24 -0
- package/src/build/jsx-ownership-plugins.js +41 -0
- package/src/build/pages-unified-graph-build.d.ts +31 -0
- package/src/build/pages-unified-graph-build.js +206 -0
- package/src/build/parallel-build-executor.d.ts +21 -0
- package/src/build/parallel-build-executor.js +52 -0
- package/src/build/production-build-cache.d.ts +20 -0
- package/src/build/production-build-cache.js +63 -0
- package/src/build/rolldown-adapter-helpers.d.ts +3 -6
- package/src/build/rolldown-adapter-helpers.js +71 -6
- package/src/build/rolldown-build-adapter.d.ts +2 -6
- package/src/build/rolldown-build-adapter.js +3 -3
- package/src/build/rolldown-build-invocation-metrics.d.ts +12 -0
- package/src/build/rolldown-build-invocation-metrics.js +43 -0
- package/src/build/rolldown-plugin-bridge.d.ts +15 -1
- package/src/build/rolldown-plugin-bridge.js +20 -5
- package/src/build/rolldown-source-transform-pass.d.ts +15 -0
- package/src/build/rolldown-source-transform-pass.js +58 -0
- package/src/build/runtime-build-executor.d.ts +8 -15
- package/src/build/runtime-build-executor.js +12 -15
- package/src/build/runtime-build-output-normalizer.d.ts +7 -0
- package/src/build/runtime-build-output-normalizer.js +94 -2
- package/src/build/server-entry-build-cache.d.ts +44 -0
- package/src/build/server-entry-build-cache.js +161 -0
- package/src/client/navigation-scripts/classification.d.ts +16 -0
- package/src/client/navigation-scripts/classification.js +41 -0
- package/src/client/navigation-scripts/index.d.ts +3 -0
- package/src/client/navigation-scripts/index.js +23 -0
- package/src/client/navigation-scripts/navigation-scripts.test.browser.js +65 -0
- package/src/client/navigation-scripts/registry.d.ts +11 -0
- package/src/client/navigation-scripts/registry.js +12 -0
- package/src/client/navigation-scripts/rerun-queue.d.ts +37 -0
- package/src/client/navigation-scripts/rerun-queue.js +73 -0
- package/src/client/scroll.d.ts +17 -0
- package/src/client/scroll.js +25 -0
- package/src/client/scroll.test.browser.js +37 -0
- package/src/client/view-transitions.d.ts +16 -0
- package/src/client/view-transitions.js +63 -0
- package/src/config/config-builder.d.ts +12 -7
- package/src/config/config-builder.js +12 -9
- package/src/dev/client-bridge-registry.d.ts +5 -0
- package/src/dev/client-bridge-registry.js +15 -0
- package/src/dev/dev-client-ownership.d.ts +7 -0
- package/src/dev/dev-client-ownership.js +6 -0
- package/src/dev/hmr-manager-registry.d.ts +8 -0
- package/src/dev/hmr-manager-registry.js +15 -0
- package/src/dev/host-runtime.d.ts +3 -0
- package/src/dev/host-runtime.js +7 -0
- package/src/dev/runtime-server-started-message.d.ts +2 -0
- package/src/dev/runtime-server-started-message.js +6 -0
- package/src/eco/README.md +11 -0
- package/src/eco/eco-declared-component.d.ts +14 -0
- package/src/eco/eco-declared-component.js +16 -0
- package/src/eco/eco.browser.js +5 -6
- package/src/eco/eco.js +5 -6
- package/src/eco/eco.types.d.ts +2 -2
- package/src/eco/page-layout-normalization.d.ts +21 -0
- package/src/eco/page-layout-normalization.js +59 -0
- package/src/errors/index.d.ts +1 -0
- package/src/errors/index.js +7 -1
- package/src/errors/undeclared-component-dependency-error.d.ts +11 -0
- package/src/errors/undeclared-component-dependency-error.js +17 -0
- package/src/hmr/client/hmr-runtime.js +5 -2
- package/src/hmr/hmr-entrypoint-output.d.ts +43 -0
- package/src/hmr/hmr-entrypoint-output.js +53 -0
- package/src/hmr/hmr-strategy.d.ts +15 -0
- package/src/hmr/hmr-strategy.js +19 -0
- package/src/hmr/strategies/js-hmr-strategy.d.ts +3 -5
- package/src/hmr/strategies/js-hmr-strategy.js +67 -19
- package/src/hmr/strategies/server-rendered-template-hmr-strategy.d.ts +17 -0
- package/src/hmr/strategies/server-rendered-template-hmr-strategy.js +29 -0
- package/src/integrations/ghtml/ghtml-renderer.d.ts +2 -6
- package/src/integrations/ghtml/ghtml-renderer.js +2 -51
- package/src/integrations/ghtml/ghtml.plugin.d.ts +3 -16
- package/src/integrations/ghtml/ghtml.plugin.js +7 -14
- package/src/plugins/define-integration.d.ts +26 -0
- package/src/plugins/define-integration.js +19 -0
- package/src/plugins/eco-component-meta-plugin.js +5 -8
- package/src/plugins/foreign-jsx-override-plugin.js +4 -6
- package/src/plugins/integration-plugin.d.ts +19 -9
- package/src/plugins/integration-plugin.js +7 -4
- package/src/plugins/jsx-import-source.utils.d.ts +8 -0
- package/src/plugins/jsx-import-source.utils.js +26 -0
- package/src/plugins/processor.d.ts +19 -4
- package/src/plugins/processor.js +22 -4
- package/src/plugins/source-transform.d.ts +31 -0
- package/src/plugins/source-transform.js +23 -1
- package/src/route-renderer/GRAPH.md +8 -8
- package/src/route-renderer/README.md +30 -28
- package/src/route-renderer/orchestration/component-graph-collectors.d.ts +10 -0
- package/src/route-renderer/orchestration/component-graph-collectors.js +143 -0
- package/src/route-renderer/orchestration/component-graph.d.ts +36 -0
- package/src/route-renderer/orchestration/component-graph.js +98 -0
- package/src/route-renderer/orchestration/component-render-context.d.ts +3 -10
- package/src/route-renderer/orchestration/component-render-context.js +3 -1
- package/src/route-renderer/orchestration/declared-ownership-graph.d.ts +1 -18
- package/src/route-renderer/orchestration/declared-ownership-graph.js +5 -31
- package/src/route-renderer/orchestration/document-shell-render.service.d.ts +90 -0
- package/src/route-renderer/orchestration/document-shell-render.service.js +120 -0
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.d.ts +59 -29
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +164 -35
- package/src/route-renderer/orchestration/global-injector-assets.service.d.ts +7 -0
- package/src/route-renderer/orchestration/global-injector-assets.service.js +32 -0
- package/src/route-renderer/orchestration/integration-renderer.d.ts +18 -54
- package/src/route-renderer/orchestration/integration-renderer.js +113 -195
- package/src/route-renderer/orchestration/integration-route-render-adapter.d.ts +38 -0
- package/src/route-renderer/orchestration/integration-route-render-adapter.js +27 -0
- package/src/route-renderer/orchestration/layout-shell-props.service.d.ts +20 -0
- package/src/route-renderer/orchestration/layout-shell-props.service.js +31 -0
- package/src/route-renderer/orchestration/ownership-validation.service.d.ts +5 -0
- package/src/route-renderer/orchestration/ownership-validation.service.js +29 -1
- package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.d.ts +9 -0
- package/src/route-renderer/orchestration/page-browser-graph-contribution.loader.js +7 -0
- package/src/route-renderer/orchestration/page-browser-graph.service.d.ts +25 -0
- package/src/route-renderer/orchestration/page-browser-graph.service.js +174 -0
- package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
- package/src/route-renderer/orchestration/render-output.utils.js +4 -0
- package/src/route-renderer/orchestration/route-html-finalization.service.d.ts +17 -0
- package/src/route-renderer/orchestration/route-html-finalization.service.js +26 -0
- package/src/route-renderer/orchestration/route-prepared-options.builder.d.ts +26 -0
- package/src/route-renderer/orchestration/route-prepared-options.builder.js +54 -0
- package/src/route-renderer/orchestration/route-prepared-options.utils.d.ts +4 -0
- package/src/route-renderer/orchestration/route-prepared-options.utils.js +33 -0
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +8 -39
- package/src/route-renderer/orchestration/route-render-orchestrator.js +53 -461
- package/src/route-renderer/orchestration/string-markup-renderer.d.ts +14 -0
- package/src/route-renderer/orchestration/string-markup-renderer.js +55 -0
- package/src/route-renderer/page-loading/component-dependency-collection.js +5 -2
- package/src/router/client/link-intent.d.ts +11 -21
- package/src/router/client/link-intent.js +10 -0
- package/src/router/client/link-intent.test.browser.js +12 -1
- package/src/router/client/link-navigation-policy.d.ts +45 -0
- package/src/router/client/link-navigation-policy.js +101 -0
- package/src/router/client/link-navigation-policy.test.browser.d.ts +1 -0
- package/src/router/client/link-navigation-policy.test.browser.js +92 -0
- package/src/router/client/navigation-coordinator.d.ts +1 -1
- package/src/router/client/navigation-coordinator.js +12 -2
- package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
- package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
- package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
- package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.d.ts +0 -1
- package/src/services/assets/asset-processing-service/processors/script/file-script.processor.js +18 -15
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.d.ts +0 -3
- package/src/services/assets/asset-processing-service/processors/stylesheet/content-stylesheet.processor.js +2 -31
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.d.ts +0 -3
- package/src/services/assets/asset-processing-service/processors/stylesheet/file-stylesheet.processor.js +2 -30
- package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.d.ts +2 -0
- package/src/services/assets/asset-processing-service/processors/stylesheet/stylesheet-processor-pipeline.js +43 -0
- package/src/services/assets/browser-bundle.service.d.ts +3 -1
- package/src/services/assets/browser-bundle.service.js +17 -5
- package/src/services/html/html-transformer.service.d.ts +1 -1
- package/src/services/html/html-transformer.service.js +4 -4
- package/src/services/invalidation/development-invalidation.service.d.ts +20 -2
- package/src/services/invalidation/development-invalidation.service.js +56 -2
- package/src/services/module-loading/README.md +49 -0
- package/src/services/module-loading/app-server-module-transpiler.service.js +5 -17
- package/src/services/module-loading/page-module-import.service.d.ts +5 -1
- package/src/services/module-loading/page-module-import.service.js +96 -45
- package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
- package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
- package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
- package/src/services/module-loading/route-module-build-cache.js +38 -0
- package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
- package/src/services/module-loading/route-module-build-cache.store.js +207 -0
- package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
- package/src/services/module-loading/route-module-build-manifest.js +116 -0
- package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
- package/src/services/module-loading/route-module-dependency-hasher.js +122 -0
- package/src/services/module-loading/server-module-transpiler.service.js +1 -1
- package/src/static-site-generator/README.md +33 -0
- package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
- package/src/static-site-generator/static-build-invalidation.js +59 -0
- package/src/static-site-generator/static-export-context.d.ts +22 -0
- package/src/static-site-generator/static-export-context.js +0 -0
- package/src/static-site-generator/static-site-generator.d.ts +17 -20
- package/src/static-site-generator/static-site-generator.js +188 -85
- package/src/types/internal-types.d.ts +16 -9
- package/src/types/public-types.d.ts +144 -40
- package/src/utils/parse-cli-args.d.ts +2 -0
- package/src/utils/parse-cli-args.js +6 -0
- package/src/utils/server-utils.module.d.ts +3 -2
- package/src/utils/server-utils.module.js +5 -6
- package/src/utils/static-file-extensions.d.ts +12 -0
- package/src/utils/static-file-extensions.js +48 -0
- package/src/watchers/project-watcher-ignore.d.ts +11 -0
- package/src/watchers/project-watcher-ignore.js +16 -0
- package/src/watchers/project-watcher.d.ts +26 -3
- package/src/watchers/project-watcher.js +132 -30
- package/src/watchers/project-watcher.test-helpers.js +5 -5
- package/src/build/rolldown-dev-build-adapter.d.ts +0 -82
- package/src/build/rolldown-dev-build-adapter.js +0 -166
- package/src/hmr/hmr.postcss.test.e2e.js +0 -31
- package/src/hmr/hmr.test.e2e.js +0 -43
- package/src/route-renderer/orchestration/ownership-planning.service.d.ts +0 -24
- package/src/route-renderer/orchestration/ownership-planning.service.js +0 -63
- package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.d.ts +0 -91
- package/src/route-renderer/orchestration/queued-foreign-subtree-resolution.service.js +0 -170
- /package/src/{hmr/hmr.postcss.test.e2e.d.ts → client/navigation-scripts/navigation-scripts.test.browser.d.ts} +0 -0
- /package/src/{hmr/hmr.test.e2e.d.ts → client/scroll.test.browser.d.ts} +0 -0
|
@@ -2,11 +2,18 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { RESOLVED_ASSETS_DIR } from "../../config/constants.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
removeStaleHmrEntrypointOutput,
|
|
7
|
+
isRegisteredScriptEntrypoint,
|
|
8
|
+
isBrowserOnlyRegisteredScriptEntrypoint
|
|
9
|
+
} from "../../hmr/hmr-entrypoint-output.js";
|
|
10
|
+
import { requireBuildRuntime } from "../../build/build-runtime.js";
|
|
6
11
|
import { fileSystem } from "@ecopages/file-system";
|
|
7
12
|
import { HmrStrategyType } from "../../hmr/hmr-strategy.js";
|
|
8
13
|
import { DefaultHmrStrategy } from "../../hmr/strategies/default-hmr-strategy.js";
|
|
9
14
|
import { JsHmrStrategy } from "../../hmr/strategies/js-hmr-strategy.js";
|
|
15
|
+
import { ServerRenderedTemplateHmrStrategy } from "../../hmr/strategies/server-rendered-template-hmr-strategy.js";
|
|
16
|
+
import { DevelopmentInvalidationService } from "../../services/invalidation/development-invalidation.service.js";
|
|
10
17
|
import { appLogger } from "../../global/app-logger.js";
|
|
11
18
|
import { HmrEntrypointRegistrar } from "./hmr-entrypoint-registrar.js";
|
|
12
19
|
import { BrowserBundleService } from "../../services/assets/browser-bundle.service.js";
|
|
@@ -20,29 +27,27 @@ class SharedHmrManager {
|
|
|
20
27
|
appConfig;
|
|
21
28
|
bridge;
|
|
22
29
|
watchers = /* @__PURE__ */ new Map();
|
|
23
|
-
watchedFiles = /* @__PURE__ */ new Map();
|
|
24
|
-
entrypointRegistrations = /* @__PURE__ */ new Map();
|
|
25
30
|
distDir;
|
|
26
|
-
|
|
27
|
-
enabled = true;
|
|
31
|
+
enabled = false;
|
|
28
32
|
strategies = [];
|
|
29
33
|
entrypointRegistrar;
|
|
30
34
|
browserBundleService;
|
|
35
|
+
invalidationService;
|
|
31
36
|
entrypointDependencyGraph;
|
|
32
37
|
serverModuleTranspiler;
|
|
33
|
-
|
|
38
|
+
runtimeBuildPromise = null;
|
|
39
|
+
runtimeReady = false;
|
|
40
|
+
constructor({ appConfig, bridge }) {
|
|
34
41
|
this.appConfig = appConfig;
|
|
35
42
|
this.bridge = bridge;
|
|
36
43
|
this.distDir = path.join(resolveInternalWorkDir(this.appConfig), RESOLVED_ASSETS_DIR, "_hmr");
|
|
37
44
|
this.entrypointRegistrar = new HmrEntrypointRegistrar({
|
|
38
45
|
srcDir: this.appConfig.absolutePaths.srcDir,
|
|
39
46
|
distDir: this.distDir,
|
|
40
|
-
|
|
41
|
-
watchedFiles: this.watchedFiles,
|
|
42
|
-
clearFailedRegistration: (entrypointPath) => this.clearFailedEntrypointRegistration(entrypointPath),
|
|
43
|
-
registrationTimeoutMs
|
|
47
|
+
clearFailedRegistration: (entrypointPath) => this.clearFailedEntrypointRegistration(entrypointPath)
|
|
44
48
|
});
|
|
45
49
|
this.browserBundleService = new BrowserBundleService(appConfig);
|
|
50
|
+
this.invalidationService = new DevelopmentInvalidationService(appConfig);
|
|
46
51
|
this.entrypointDependencyGraph = this.createEntrypointDependencyGraph(
|
|
47
52
|
getAppEntrypointDependencyGraph(appConfig)
|
|
48
53
|
);
|
|
@@ -75,9 +80,8 @@ class SharedHmrManager {
|
|
|
75
80
|
}
|
|
76
81
|
initializeStrategies() {
|
|
77
82
|
const jsContext = {
|
|
78
|
-
getWatchedFiles: () => this.
|
|
83
|
+
getWatchedFiles: () => this.entrypointRegistrar.getWatchedFiles(),
|
|
79
84
|
getDistDir: () => this.distDir,
|
|
80
|
-
getPlugins: () => this.plugins,
|
|
81
85
|
getSrcDir: () => this.appConfig.absolutePaths.srcDir,
|
|
82
86
|
getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
|
|
83
87
|
getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
|
|
@@ -86,41 +90,81 @@ class SharedHmrManager {
|
|
|
86
90
|
getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
|
|
87
91
|
shouldProcessEntrypoint: (entrypointPath) => this.shouldJsStrategyProcessEntrypoint(entrypointPath)
|
|
88
92
|
};
|
|
89
|
-
|
|
93
|
+
const invalidationService = new DevelopmentInvalidationService(this.appConfig);
|
|
94
|
+
this.strategies = [
|
|
95
|
+
new JsHmrStrategy(jsContext),
|
|
96
|
+
new ServerRenderedTemplateHmrStrategy(invalidationService),
|
|
97
|
+
new DefaultHmrStrategy()
|
|
98
|
+
];
|
|
90
99
|
}
|
|
91
100
|
registerStrategy(strategy) {
|
|
92
101
|
this.strategies.push(strategy);
|
|
93
102
|
}
|
|
94
|
-
setPlugins(plugins) {
|
|
95
|
-
this.plugins = [...plugins];
|
|
96
|
-
}
|
|
97
103
|
setEnabled(enabled) {
|
|
98
104
|
this.enabled = enabled;
|
|
99
105
|
}
|
|
100
106
|
isEnabled() {
|
|
101
107
|
return this.enabled;
|
|
102
108
|
}
|
|
109
|
+
isRuntimeReady() {
|
|
110
|
+
return this.runtimeReady && fileSystem.exists(this.getRuntimePath());
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Builds the browser HMR runtime once per manager session and reuses the in-flight build for concurrent callers.
|
|
114
|
+
*/
|
|
115
|
+
async ensureRuntimeReady() {
|
|
116
|
+
if (this.runtimeReady) {
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (this.runtimeBuildPromise) {
|
|
120
|
+
return this.runtimeBuildPromise;
|
|
121
|
+
}
|
|
122
|
+
this.runtimeBuildPromise = this.buildRuntimeInternal();
|
|
123
|
+
try {
|
|
124
|
+
const ready = await this.runtimeBuildPromise;
|
|
125
|
+
this.runtimeReady = ready;
|
|
126
|
+
return ready;
|
|
127
|
+
} finally {
|
|
128
|
+
this.runtimeBuildPromise = null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
103
131
|
async buildRuntime() {
|
|
132
|
+
await this.ensureRuntimeReady();
|
|
133
|
+
}
|
|
134
|
+
getRuntimePath() {
|
|
135
|
+
return path.join(this.distDir, "_hmr_runtime.js");
|
|
136
|
+
}
|
|
137
|
+
async buildRuntimeInternal() {
|
|
104
138
|
const runtimeSource = fileURLToPath(import.meta.resolve("@ecopages/core/hmr/client/hmr-runtime"));
|
|
139
|
+
const runtimePath = this.getRuntimePath();
|
|
140
|
+
removeStaleHmrEntrypointOutput(runtimePath, "HMR");
|
|
105
141
|
try {
|
|
106
142
|
const result = await this.browserBundleService.bundle({
|
|
107
143
|
profile: "hmr-runtime",
|
|
108
144
|
entrypoints: [runtimeSource],
|
|
109
145
|
outdir: this.distDir,
|
|
110
146
|
naming: "_hmr_runtime.js",
|
|
111
|
-
minify: false
|
|
112
|
-
plugins: this.plugins
|
|
147
|
+
minify: false
|
|
113
148
|
});
|
|
114
149
|
if (!result.success) {
|
|
115
150
|
this.onRuntimeBundleFailure(result.logs);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
const emittedRuntime = result.outputs.find(
|
|
154
|
+
(output) => path.resolve(output.path) === path.resolve(runtimePath)
|
|
155
|
+
);
|
|
156
|
+
if (!emittedRuntime || !fileSystem.exists(runtimePath)) {
|
|
157
|
+
this.onRuntimeBundleFailure(
|
|
158
|
+
new Error(`[HMR] Runtime bundle missing expected output at ${runtimePath}`)
|
|
159
|
+
);
|
|
160
|
+
return false;
|
|
116
161
|
}
|
|
162
|
+
return true;
|
|
117
163
|
} catch (error) {
|
|
118
164
|
this.onRuntimeBundleFailure(error);
|
|
165
|
+
return false;
|
|
119
166
|
}
|
|
120
167
|
}
|
|
121
|
-
getRuntimePath() {
|
|
122
|
-
return path.join(this.distDir, "_hmr_runtime.js");
|
|
123
|
-
}
|
|
124
168
|
broadcast(event) {
|
|
125
169
|
appLogger.debug(
|
|
126
170
|
`[HMR] Broadcasting ${event.type} event, path=${event.path || "all"}, subscribers=${this.bridge.subscriberCount}`
|
|
@@ -128,20 +172,16 @@ class SharedHmrManager {
|
|
|
128
172
|
this.bridge.broadcast(event);
|
|
129
173
|
}
|
|
130
174
|
async handleFileChange(filePath, options = {}) {
|
|
175
|
+
const resolvedFilePath = path.resolve(filePath);
|
|
131
176
|
if (this.shouldSkipMissingFileChange(filePath) && !fileSystem.exists(filePath)) {
|
|
132
177
|
appLogger.debug(`[${this.constructor.name}] Skipping missing file change: ${filePath}`);
|
|
133
178
|
this.clearFailedEntrypointRegistration(filePath);
|
|
134
179
|
return;
|
|
135
180
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
} catch (error) {
|
|
141
|
-
appLogger.error(error);
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
});
|
|
181
|
+
if (isRegisteredScriptEntrypoint(this.entrypointRegistrar.getRegistered(), resolvedFilePath)) {
|
|
182
|
+
await this.prepareRegisteredScriptChange(resolvedFilePath);
|
|
183
|
+
}
|
|
184
|
+
const strategy = this.selectChangeStrategy(filePath);
|
|
145
185
|
if (!strategy) {
|
|
146
186
|
appLogger.warn(`[HMR] No strategy found for ${filePath}`);
|
|
147
187
|
return;
|
|
@@ -155,27 +195,123 @@ class SharedHmrManager {
|
|
|
155
195
|
}
|
|
156
196
|
}
|
|
157
197
|
}
|
|
198
|
+
getStrategiesByPriority() {
|
|
199
|
+
return [...this.strategies].sort((left, right) => right.priority - left.priority);
|
|
200
|
+
}
|
|
201
|
+
selectChangeStrategy(filePath) {
|
|
202
|
+
return this.getStrategiesByPriority().find((candidate) => {
|
|
203
|
+
try {
|
|
204
|
+
return candidate.matches(filePath);
|
|
205
|
+
} catch (error) {
|
|
206
|
+
appLogger.error(error);
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
selectEntrypointEmitter(entrypointPath) {
|
|
212
|
+
return this.getStrategiesByPriority().find((candidate) => {
|
|
213
|
+
if (candidate.type !== HmrStrategyType.INTEGRATION) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
return candidate.canEmitEntrypoint(entrypointPath);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
appLogger.error(error);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
createUnownedEntrypointError(entrypointPath) {
|
|
225
|
+
return new Error(
|
|
226
|
+
`[HMR] No integration owns entrypoint ${entrypointPath}. Page entrypoints must be emitted by their owning integration.`
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Materializes one integration-owned page entrypoint during cold registration.
|
|
231
|
+
*
|
|
232
|
+
* @remarks
|
|
233
|
+
* Registration uses {@link HmrStrategy.canEmitEntrypoint} / {@link HmrStrategy.emitEntrypoint},
|
|
234
|
+
* not {@link HmrStrategy.matches} / {@link HmrStrategy.process}. File-change handling stays
|
|
235
|
+
* on `handleFileChange()`.
|
|
236
|
+
*/
|
|
237
|
+
async emitIntegrationEntrypoint(entrypointPath, outputPath) {
|
|
238
|
+
const emitter = this.selectEntrypointEmitter(entrypointPath);
|
|
239
|
+
if (!emitter) {
|
|
240
|
+
throw this.createUnownedEntrypointError(entrypointPath);
|
|
241
|
+
}
|
|
242
|
+
appLogger.debug(`[${this.constructor.name}] Selected entrypoint emitter: ${emitter.constructor.name}`);
|
|
243
|
+
await emitter.emitEntrypoint(entrypointPath, outputPath);
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Runs server invalidation and integration hooks before rebuilding a registered script entrypoint.
|
|
247
|
+
*
|
|
248
|
+
* @remarks
|
|
249
|
+
* Browser-only `*.script.ts` entrypoints skip server module invalidation and bypass-cache
|
|
250
|
+
* import; only integration change handlers and the downstream strategy rebuild run.
|
|
251
|
+
*/
|
|
252
|
+
async prepareRegisteredScriptChange(filePath) {
|
|
253
|
+
const isBrowserOnlyRegisteredScript = isBrowserOnlyRegisteredScriptEntrypoint(filePath);
|
|
254
|
+
if (!isBrowserOnlyRegisteredScript) {
|
|
255
|
+
this.invalidationService.invalidateServerModules([filePath]);
|
|
256
|
+
}
|
|
257
|
+
const handlers = this.appConfig.runtime?.registeredScriptEntrypointChangeHandlers ?? [];
|
|
258
|
+
for (const handler of handlers) {
|
|
259
|
+
try {
|
|
260
|
+
await handler(filePath);
|
|
261
|
+
} catch (error) {
|
|
262
|
+
const message = error instanceof Error ? error.message : `Failed to handle registered script entrypoint change for ${filePath}: ${String(error)}`;
|
|
263
|
+
this.broadcast({ type: "error", message });
|
|
264
|
+
throw error;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (isBrowserOnlyRegisteredScript) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const appModuleLoader = this.appConfig.runtime?.appModuleLoader;
|
|
271
|
+
if (!appModuleLoader) {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
await appModuleLoader.importModule({
|
|
275
|
+
filePath,
|
|
276
|
+
rootDir: this.appConfig.rootDir,
|
|
277
|
+
outdir: path.join(resolveInternalExecutionDir(this.appConfig), ".server-modules"),
|
|
278
|
+
externalPackages: true,
|
|
279
|
+
bypassCache: true
|
|
280
|
+
});
|
|
281
|
+
}
|
|
158
282
|
getOutputUrl(entrypointPath) {
|
|
159
|
-
return this.
|
|
283
|
+
return this.entrypointRegistrar.getRegistered().get(path.resolve(entrypointPath))?.outputUrl;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Returns the emitted HMR script output when the entrypoint is already registered
|
|
287
|
+
* and its browser bundle exists on disk.
|
|
288
|
+
*
|
|
289
|
+
* @remarks
|
|
290
|
+
* Disk artifacts alone are not enough: a fresh dev session must still register
|
|
291
|
+
* the entrypoint so file watchers can rebuild it on change.
|
|
292
|
+
*/
|
|
293
|
+
getResolvedScriptOutput(entrypointPath) {
|
|
294
|
+
const normalizedEntrypoint = path.resolve(entrypointPath);
|
|
295
|
+
const registered = this.entrypointRegistrar.getRegistered().get(normalizedEntrypoint);
|
|
296
|
+
if (!registered || !fileSystem.exists(registered.outputPath)) {
|
|
297
|
+
return void 0;
|
|
298
|
+
}
|
|
299
|
+
return registered;
|
|
160
300
|
}
|
|
161
301
|
getWatchedFiles() {
|
|
162
|
-
return this.
|
|
302
|
+
return this.entrypointRegistrar.getWatchedFiles();
|
|
163
303
|
}
|
|
164
304
|
getDistDir() {
|
|
165
305
|
return this.distDir;
|
|
166
306
|
}
|
|
167
|
-
getPlugins() {
|
|
168
|
-
return this.plugins;
|
|
169
|
-
}
|
|
170
307
|
getDefaultContext() {
|
|
171
308
|
return {
|
|
172
|
-
getWatchedFiles: () => this.
|
|
309
|
+
getWatchedFiles: () => this.entrypointRegistrar.getWatchedFiles(),
|
|
173
310
|
getDistDir: () => this.distDir,
|
|
174
|
-
getPlugins: () => this.plugins,
|
|
175
311
|
getSrcDir: () => this.appConfig.absolutePaths.srcDir,
|
|
176
312
|
getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
|
|
177
313
|
getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
|
|
178
|
-
getBuildExecutor: () =>
|
|
314
|
+
getBuildExecutor: () => requireBuildRuntime(this.appConfig).getProfile("browser-hmr"),
|
|
179
315
|
getBrowserBundleService: () => this.browserBundleService,
|
|
180
316
|
getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
|
|
181
317
|
importServerModule: async (filePath) => await this.serverModuleTranspiler.importModule({
|
|
@@ -185,27 +321,34 @@ class SharedHmrManager {
|
|
|
185
321
|
})
|
|
186
322
|
};
|
|
187
323
|
}
|
|
324
|
+
stop() {
|
|
325
|
+
this.runtimeReady = false;
|
|
326
|
+
this.entrypointRegistrar.clearAll();
|
|
327
|
+
for (const watcher of this.watchers.values()) {
|
|
328
|
+
watcher.close();
|
|
329
|
+
}
|
|
330
|
+
this.watchers.clear();
|
|
331
|
+
this.entrypointDependencyGraph.reset();
|
|
332
|
+
}
|
|
188
333
|
clearFailedEntrypointRegistration(entrypointPath) {
|
|
189
|
-
this.
|
|
334
|
+
this.entrypointRegistrar.clearRegistration(entrypointPath);
|
|
190
335
|
this.entrypointDependencyGraph.clearEntrypointDependencies(entrypointPath);
|
|
191
336
|
}
|
|
192
337
|
async registerEntrypoint(entrypointPath) {
|
|
193
|
-
|
|
194
|
-
emit: async (normalizedEntrypoint) => await this.
|
|
338
|
+
const resolved = await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
|
|
339
|
+
emit: async (normalizedEntrypoint, outputPath) => await this.emitIntegrationEntrypoint(normalizedEntrypoint, outputPath),
|
|
195
340
|
getMissingOutputError: (normalizedEntrypoint, outputPath) => new Error(
|
|
196
341
|
`[HMR] Integration failed to emit entrypoint ${normalizedEntrypoint} to ${outputPath}. Page entrypoints must be produced by their owning integration.`
|
|
197
342
|
)
|
|
198
343
|
});
|
|
344
|
+
return resolved.outputUrl;
|
|
199
345
|
}
|
|
200
346
|
async registerScriptEntrypoint(entrypointPath) {
|
|
201
347
|
return await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
|
|
202
348
|
emit: async (normalizedEntrypoint, outputPath) => await this.emitScriptEntrypoint(normalizedEntrypoint, outputPath),
|
|
203
|
-
getMissingOutputError: (normalizedEntrypoint) => new Error(`[HMR] Failed to register script entrypoint: ${normalizedEntrypoint}`)
|
|
349
|
+
getMissingOutputError: (normalizedEntrypoint, outputPath) => new Error(`[HMR] Failed to register script entrypoint: ${normalizedEntrypoint} to ${outputPath}`)
|
|
204
350
|
});
|
|
205
351
|
}
|
|
206
|
-
async emitStrictEntrypoint(entrypointPath) {
|
|
207
|
-
await this.handleFileChange(entrypointPath, { broadcast: false });
|
|
208
|
-
}
|
|
209
352
|
async emitScriptEntrypoint(entrypointPath, outputPath) {
|
|
210
353
|
const naming = path.relative(this.distDir, outputPath).split(path.sep).join("/");
|
|
211
354
|
const buildResult = await this.browserBundleService.bundle({
|
|
@@ -213,27 +356,30 @@ class SharedHmrManager {
|
|
|
213
356
|
entrypoints: [entrypointPath],
|
|
214
357
|
outdir: this.distDir,
|
|
215
358
|
naming,
|
|
216
|
-
minify: false
|
|
217
|
-
plugins: this.plugins
|
|
359
|
+
minify: false
|
|
218
360
|
});
|
|
219
361
|
if (!buildResult.success) {
|
|
220
|
-
|
|
221
|
-
|
|
362
|
+
throw new Error(
|
|
363
|
+
`[HMR] Generic script entrypoint build failed for ${entrypointPath}: ${JSON.stringify(buildResult.logs)}`
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
if (!fileSystem.exists(outputPath) && buildResult.outputs.length > 0) {
|
|
367
|
+
const resolvedOutputPath = path.resolve(outputPath);
|
|
368
|
+
const emittedOutput = buildResult.outputs.find((output) => path.resolve(output.path) === resolvedOutputPath)?.path ?? buildResult.outputs.find((output) => path.basename(output.path) === path.basename(outputPath))?.path;
|
|
369
|
+
if (emittedOutput && fileSystem.exists(emittedOutput)) {
|
|
370
|
+
fileSystem.ensureDir(path.dirname(outputPath));
|
|
371
|
+
if (path.resolve(emittedOutput) !== resolvedOutputPath) {
|
|
372
|
+
fileSystem.copyFile(emittedOutput, outputPath);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
222
375
|
}
|
|
223
376
|
const entrypointDependencies = buildResult.dependencyGraph?.entrypoints?.[entrypointPath];
|
|
224
377
|
if (entrypointDependencies) {
|
|
225
378
|
this.entrypointDependencyGraph.setEntrypointDependencies(entrypointPath, entrypointDependencies);
|
|
226
379
|
}
|
|
227
380
|
}
|
|
228
|
-
|
|
229
|
-
this.
|
|
230
|
-
for (const watcher of this.watchers.values()) {
|
|
231
|
-
watcher.close();
|
|
232
|
-
}
|
|
233
|
-
this.watchers.clear();
|
|
234
|
-
this.watchedFiles.clear();
|
|
235
|
-
this.entrypointDependencyGraph.reset();
|
|
236
|
-
this.plugins = [];
|
|
381
|
+
[Symbol.dispose]() {
|
|
382
|
+
this.stop();
|
|
237
383
|
}
|
|
238
384
|
}
|
|
239
385
|
export {
|
|
@@ -7,4 +7,9 @@ export interface StaticPreviewHostStartOptions {
|
|
|
7
7
|
appConfig: EcoPagesAppConfig;
|
|
8
8
|
hostname: string;
|
|
9
9
|
port: number;
|
|
10
|
+
/**
|
|
11
|
+
* When true, bind to the next available port if the preferred one is busy.
|
|
12
|
+
* Should remain false when the port was explicitly configured.
|
|
13
|
+
*/
|
|
14
|
+
allowPortFallback?: boolean;
|
|
10
15
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { EcopagesSocket, WebSocketCloseInfo } from '../../types/public-types.js';
|
|
2
|
+
/**
|
|
3
|
+
* @remarks
|
|
4
|
+
* Bun and Node expose different raw socket types but share the same
|
|
5
|
+
* {@link OutgoingWebSocketMessage} contract. Adapters supply only `send`/`close`;
|
|
6
|
+
* {@link createEcopagesSocket} owns message normalization and stream forwarding.
|
|
7
|
+
*/
|
|
8
|
+
export interface WebSocketSendTransport {
|
|
9
|
+
send(data: string | Uint8Array): void;
|
|
10
|
+
close(code?: number, reason?: string): void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* @remarks
|
|
14
|
+
* Bun and Node adapters previously duplicated outgoing-message dispatch and
|
|
15
|
+
* stream forwarding; this is the shared implementation both transports delegate to.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createEcopagesSocket<TContext, TParams extends Record<string, string>>(transport: WebSocketSendTransport, meta: {
|
|
18
|
+
kind: string;
|
|
19
|
+
params: TParams;
|
|
20
|
+
search: Record<string, string>;
|
|
21
|
+
context: TContext;
|
|
22
|
+
}): EcopagesSocket<TContext, TParams>;
|
|
23
|
+
export declare function toWebSocketCloseInfo(code: number, reason: string): WebSocketCloseInfo;
|
|
24
|
+
export declare function invokeWebSocketHandlerHook(kind: string, hookName: string, result: void | Promise<void>): void;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { appLogger } from "../../global/app-logger.js";
|
|
2
|
+
function sendOutgoingMessage(transport, message) {
|
|
3
|
+
if (typeof message === "string") {
|
|
4
|
+
transport.send(message);
|
|
5
|
+
} else if (message instanceof Blob) {
|
|
6
|
+
void message.arrayBuffer().then((buffer) => transport.send(new Uint8Array(buffer)));
|
|
7
|
+
} else if (message instanceof ArrayBuffer) {
|
|
8
|
+
transport.send(new Uint8Array(message));
|
|
9
|
+
} else if (ArrayBuffer.isView(message)) {
|
|
10
|
+
transport.send(new Uint8Array(message.buffer, message.byteOffset, message.byteLength));
|
|
11
|
+
} else {
|
|
12
|
+
transport.send(message);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function createEcopagesSocket(transport, meta) {
|
|
16
|
+
return {
|
|
17
|
+
kind: meta.kind,
|
|
18
|
+
params: meta.params,
|
|
19
|
+
search: meta.search,
|
|
20
|
+
context: meta.context,
|
|
21
|
+
send: (message) => sendOutgoingMessage(transport, message),
|
|
22
|
+
sendStream: async (stream) => {
|
|
23
|
+
const reader = stream.getReader();
|
|
24
|
+
try {
|
|
25
|
+
while (true) {
|
|
26
|
+
const { value, done } = await reader.read();
|
|
27
|
+
if (done) break;
|
|
28
|
+
if (value) transport.send(value);
|
|
29
|
+
}
|
|
30
|
+
} finally {
|
|
31
|
+
reader.releaseLock();
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
close: (code, reason) => transport.close(code, reason)
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function toWebSocketCloseInfo(code, reason) {
|
|
38
|
+
return {
|
|
39
|
+
code,
|
|
40
|
+
reason,
|
|
41
|
+
wasClean: code === 1e3 || code === 1001
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function invokeWebSocketHandlerHook(kind, hookName, result) {
|
|
45
|
+
if (result instanceof Promise) {
|
|
46
|
+
result.catch((error) => {
|
|
47
|
+
appLogger.error(`[WS:${kind}] ${hookName} failed:`, error);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
createEcopagesSocket,
|
|
53
|
+
invokeWebSocketHandlerHook,
|
|
54
|
+
toWebSocketCloseInfo
|
|
55
|
+
};
|
package/src/build/README.md
CHANGED
|
@@ -4,13 +4,15 @@ The build layer is the bundler contract for Ecopages. One bundled adapter is the
|
|
|
4
4
|
|
|
5
5
|
## Mental Model
|
|
6
6
|
|
|
7
|
-
Three concentric shapes, plus
|
|
7
|
+
Three concentric shapes, plus profile executors and a plugin injector:
|
|
8
8
|
|
|
9
9
|
| Shape | Lives in | Purpose |
|
|
10
10
|
| -------------------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
11
|
| `BuildAdapter` | `build-adapter.ts` | Low-level backend. Two implementations: the bundled adapter (the real bundler) and `ViteHostBuildAdapter` (a host-owned boundary marker that throws on direct use). |
|
|
12
|
-
| `
|
|
13
|
-
| `
|
|
12
|
+
| `BuildRuntime` | `build-runtime.ts` | Profile-based executor registry. Single entry point on `appConfig.runtime.buildRuntime`. |
|
|
13
|
+
| `BuildExecutor` | `build-contracts.ts` | Narrower runtime facade. Only `build` is exposed. Retrieved via `buildRuntime.getProfile(...)`. |
|
|
14
|
+
| `SerializedBuildExecutor` | `serialized-build-executor.ts` | FIFO queue around any `BuildExecutor`. Used for server-entry single-flight ordering. |
|
|
15
|
+
| `ParallelBuildExecutor` | `parallel-build-executor.ts` | Concurrency-limited wrapper for independent route-module and HMR browser builds. |
|
|
14
16
|
| `withBuildExecutorPlugins` | `build-adapter.ts` | Merges app-owned plugins into every `build` call. The single point of plugin injection. |
|
|
15
17
|
|
|
16
18
|
Plus one translation bridge:
|
|
@@ -20,29 +22,31 @@ Plus one translation bridge:
|
|
|
20
22
|
## Files
|
|
21
23
|
|
|
22
24
|
- `build-adapter.ts`: types, factories, app-owned helpers, `withBuildExecutorPlugins`.
|
|
25
|
+
- `build-runtime.ts`: profile-based executor installation (`server-entry`, `route-module`, `browser-hmr`).
|
|
23
26
|
- `build-types.ts`: the `EcoBuildPlugin` contract used by integrations and processors.
|
|
24
27
|
- `rolldown-build-adapter.ts`: the production `BuildAdapter`. Wraps the bundler and exposes a normalized `BuildResult` (outputs, dependency graph, logs).
|
|
25
28
|
- `rolldown-plugin-bridge.ts`: `EcoBuildPlugin[]` → bundler-plugin translation.
|
|
26
29
|
- `serialized-build-executor.ts`: FIFO queue primitive.
|
|
27
|
-
- `runtime-build-executor.ts`:
|
|
30
|
+
- `runtime-build-executor.ts`: server-adapter entrypoint that installs `BuildRuntime` via `installAppRuntimeBuildExecutor()`.
|
|
31
|
+
- `server-entry-build-cache.ts`: production server-entry bundle cache (`.eco/.server-entry/.build-cache.json` + `dist/.server/manifest.json`).
|
|
28
32
|
- `*.test.ts`: regression coverage.
|
|
29
33
|
|
|
30
34
|
## Default Flow
|
|
31
35
|
|
|
32
|
-
`ConfigBuilder.build()` creates one app-owned adapter
|
|
33
|
-
|
|
34
|
-
When a server adapter initializes, it calls `installAppRuntimeBuildExecutor(appConfig)`. That function reads the existing executor (or falls back to the app-owned adapter) and stores a fresh wrapper:
|
|
36
|
+
`ConfigBuilder.build()` creates one app-owned adapter and manifest. When a server adapter initializes, it calls `installAppRuntimeBuildExecutor(appConfig)`, which installs `BuildRuntime`:
|
|
35
37
|
|
|
36
38
|
```
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
BuildRuntime.getProfile(...)
|
|
40
|
+
├─ server-entry → SerializedBuildExecutor → withBuildExecutorPlugins → RolldownBuildAdapter
|
|
41
|
+
├─ route-module → ParallelBuildExecutor → withBuildExecutorPlugins → RolldownBuildAdapter
|
|
42
|
+
└─ browser-hmr → ParallelBuildExecutor → withBuildExecutorPlugins → RolldownBuildAdapter
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
All profiles use one-shot Rolldown in both dev and production. Route-module and browser-HMR builds run in parallel; server-entry stays serialized single-flight.
|
|
46
|
+
|
|
47
|
+
Runtime code should call `requireBuildRuntime(appConfig).getProfile('route-module' | 'browser-hmr' | 'server-entry')` instead of reading legacy executor slots.
|
|
44
48
|
|
|
45
|
-
The exported `defaultBuildAdapter` and the top-level `build()` / `getTranspileOptions()` helpers are non-app-aware escape hatches. New runtime code should prefer `getAppBuildAdapter()`, `
|
|
49
|
+
The exported `defaultBuildAdapter` and the top-level `build()` / `getTranspileOptions()` helpers are non-app-aware escape hatches. New runtime code should prefer `getAppBuildAdapter()`, `requireBuildRuntime()`, and `getAppTranspileOptions()`.
|
|
46
50
|
|
|
47
51
|
## Vite-Host Boundary
|
|
48
52
|
|
|
@@ -81,11 +85,70 @@ Plugin ordering: the bundler's `resolveId` and `load` are "first" hooks. The bri
|
|
|
81
85
|
|
|
82
86
|
These fields are kept in the type so existing call-sites compile. The proper fix is a more focused `BuildOptions` schema in a follow-up.
|
|
83
87
|
|
|
88
|
+
## Dev / watch path
|
|
89
|
+
|
|
90
|
+
`installBuildRuntime` (via `installAppRuntimeBuildExecutor`) installs profile-based executors:
|
|
91
|
+
|
|
92
|
+
| Profile | Backend | Concurrency |
|
|
93
|
+
| -------------- | ----------------- | -------------------- |
|
|
94
|
+
| `server-entry` | Rolldown one-shot | Serialized |
|
|
95
|
+
| `route-module` | Rolldown one-shot | Parallel |
|
|
96
|
+
| `browser-hmr` | Rolldown one-shot | Parallel (limit ≤ 3) |
|
|
97
|
+
|
|
98
|
+
`hmr-entrypoint` rebuilds use the `browser-hmr` profile. Other browser builds (`browser-script`, `hmr-runtime`) use `route-module`.
|
|
99
|
+
|
|
100
|
+
`ProjectWatcher` deduplicates watch roots, ignores `.eco/` and `dist/`, and coalesces duplicate chokidar events within 150ms.
|
|
101
|
+
|
|
102
|
+
## Metrics
|
|
103
|
+
|
|
104
|
+
Set `ECOPAGES_ROLLDOWN_BUILD_METRICS=1` to log Rolldown invocation counts during dev and production builds. Compare kitchen-sink dev navigation and `static-build-bench` before/after runtime changes.
|
|
105
|
+
|
|
106
|
+
## Production build caches
|
|
107
|
+
|
|
108
|
+
Two persisted cache layers accelerate production builds. Both use `.build-cache.json` manifests keyed by dependency hashes and a build-inputs fingerprint.
|
|
109
|
+
|
|
110
|
+
| Cache | On-disk location | Module |
|
|
111
|
+
| -------------------------------------- | --------------------------------------------------- | ----------------------------------- |
|
|
112
|
+
| Server-entry bundle | `.eco/.server-entry/.build-cache.json` | `server-entry-build-cache.ts` |
|
|
113
|
+
| Route-module transpile + static render | `<server-outdir>/.server-modules/.build-cache.json` | `route-module-build-cache.store.ts` |
|
|
114
|
+
|
|
115
|
+
`getInstalledServerEntryBuildExecutor()` returns the `server-entry` profile from `BuildRuntime`. `clearProductionBuildCaches()` wipes both manifest trees, resets in-memory route-module state, and clears `buildRuntime`.
|
|
116
|
+
|
|
117
|
+
The route-module registry (`route-module-build-cache-registry.ts`) shares one `RouteModuleBuildCache` per `(app, outdir)` pair. Legacy `.server-route-modules` outdirs are still read for migration but new writes go to `.server-modules`.
|
|
118
|
+
|
|
119
|
+
## Unified pages graph
|
|
120
|
+
|
|
121
|
+
Production static exports compile all template pages in one Rolldown invocation when `shouldBuildPagesUnifiedGraph()` is true (default in production; opt out with `ECOPAGES_UNIFIED_PAGES_GRAPH=0`).
|
|
122
|
+
|
|
123
|
+
| Artifact | Location |
|
|
124
|
+
| -------------- | ----------------------------------------------------- |
|
|
125
|
+
| Graph manifest | `.eco/.server-pages-graph/.build-cache.json` |
|
|
126
|
+
| Chunk outputs | `.eco/.server-modules/` (shared with per-route cache) |
|
|
127
|
+
|
|
128
|
+
`StaticSiteGenerator` calls `ensurePagesUnifiedGraphBuilt()` before the export loop. `PageModuleImportService` imports prebuilt chunks via `importPagesUnifiedGraphModule()` and falls back to per-page Rolldown on miss.
|
|
129
|
+
|
|
130
|
+
`ECOPAGES_ROLLDOWN_BUILD_METRICS=1` enables `rolldown-build-invocation-metrics.ts` counters used by bench and parity tests.
|
|
131
|
+
|
|
132
|
+
Build-input fingerprinting lives in `build-input-fingerprint.ts` and is shared with server-entry cache, unified pages graph, and static-render invalidation.
|
|
133
|
+
|
|
134
|
+
## JSX Ownership Plugins
|
|
135
|
+
|
|
136
|
+
Mixed-integration apps need explicit `@jsxImportSource` handling in two different shapes:
|
|
137
|
+
|
|
138
|
+
| Helper | Use when | Behavior |
|
|
139
|
+
| ------------------------------------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
140
|
+
| `getJsxOwnershipPlugins()` | App-wide server/transpile builds | Each JSX integration extension gets its **own** `jsxImportSource` so native files keep their owning runtime. |
|
|
141
|
+
| `getHostScopedJsxOwnershipPlugins()` | One integration's **client** bundle graph | Foreign `.tsx`/`.jsx` files compile with the **host** integration JSX runtime (for example React bundling `.kita.tsx`). |
|
|
142
|
+
| `eco-component-meta-plugin` | Component metadata injection | Prepends the **owning** integration pragma only when injecting `__eco` metadata into native files. |
|
|
143
|
+
|
|
144
|
+
`foreign-jsx-override-plugin.ts` is the shared implementation. Prefer the helpers above instead of calling it directly from integrations.
|
|
145
|
+
|
|
84
146
|
## Testing Strategy
|
|
85
147
|
|
|
86
148
|
- `rolldown-build-adapter.test.ts` covers the adapter's `build`, `resolve`, `getTranspileOptions`, and dependency-graph extraction end-to-end.
|
|
87
149
|
- `rolldown-plugin-bridge.test.ts` covers the `EcoBuildPlugin[]` → plugin translation in isolation.
|
|
88
150
|
- `build-adapter.test.ts` covers the app-owned helpers, the `BuildOwnership` routing, the `withBuildExecutorPlugins` injection, and the default-fallback behaviour.
|
|
89
|
-
- `
|
|
151
|
+
- `build-runtime.test.ts` covers profile executor installation and parallelism.
|
|
152
|
+
- `runtime-build-executor.test.ts` covers the runtime wrapper: plugin injection and the Vite-host rejection path.
|
|
90
153
|
|
|
91
154
|
If you change option mapping or plugin-bridge semantics, update the adapter and bridge tests first. If you change the app-owned helper contracts, update `build-adapter.test.ts` first.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EcoBuildPlugin } from './build-types.js';
|
|
2
|
+
import type { AppBuildManifest } from './build-manifest.js';
|
|
3
|
+
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
4
|
+
export declare function collectConfiguredAppBuildManifestContributions(appConfig: EcoPagesAppConfig): Promise<Pick<AppBuildManifest, 'runtimePlugins' | 'browserBundlePlugins' | 'browserRuntimeManifest'>>;
|
|
5
|
+
export declare function setupAppRuntimePlugins(options: {
|
|
6
|
+
appConfig: EcoPagesAppConfig;
|
|
7
|
+
runtimeOrigin: string;
|
|
8
|
+
onRuntimePlugin?: (plugin: EcoBuildPlugin) => void;
|
|
9
|
+
}): Promise<void>;
|