@ecopages/core 0.2.0-alpha.39 → 0.2.0-alpha.40

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.
Files changed (56) hide show
  1. package/package.json +2 -2
  2. package/src/adapters/bun/create-app.d.ts +8 -1
  3. package/src/adapters/bun/create-app.js +52 -65
  4. package/src/adapters/bun/hmr-manager.d.ts +19 -103
  5. package/src/adapters/bun/hmr-manager.js +26 -280
  6. package/src/adapters/bun/runtime-host.d.ts +52 -0
  7. package/src/adapters/bun/runtime-host.js +56 -0
  8. package/src/adapters/bun/server-adapter.d.ts +89 -28
  9. package/src/adapters/bun/server-adapter.js +113 -61
  10. package/src/adapters/bun/static-preview-host.d.ts +28 -0
  11. package/src/adapters/bun/static-preview-host.js +45 -0
  12. package/src/adapters/node/create-app.d.ts +9 -3
  13. package/src/adapters/node/create-app.js +24 -81
  14. package/src/adapters/node/http-request-bridge.d.ts +57 -0
  15. package/src/adapters/node/http-request-bridge.js +118 -0
  16. package/src/adapters/node/node-hmr-manager.d.ts +22 -91
  17. package/src/adapters/node/node-hmr-manager.js +26 -272
  18. package/src/adapters/node/runtime-host.d.ts +57 -0
  19. package/src/adapters/node/runtime-host.js +92 -0
  20. package/src/adapters/node/server-adapter-dependencies.d.ts +19 -0
  21. package/src/adapters/node/server-adapter-dependencies.js +18 -0
  22. package/src/adapters/node/server-adapter.d.ts +10 -37
  23. package/src/adapters/node/server-adapter.js +55 -125
  24. package/src/adapters/node/static-preview-host.d.ts +55 -0
  25. package/src/adapters/node/static-preview-host.js +68 -0
  26. package/src/adapters/shared/runtime-app-bootstrap.d.ts +26 -0
  27. package/src/adapters/shared/runtime-app-bootstrap.js +46 -0
  28. package/src/adapters/shared/runtime-host.d.ts +12 -0
  29. package/src/adapters/shared/runtime-host.js +0 -0
  30. package/src/adapters/shared/shared-hmr-manager.d.ts +59 -0
  31. package/src/adapters/shared/shared-hmr-manager.js +239 -0
  32. package/src/adapters/shared/static-preview-host.d.ts +10 -0
  33. package/src/adapters/shared/static-preview-host.js +0 -0
  34. package/src/build/build-adapter.js +12 -1
  35. package/src/build/esbuild-build-adapter.d.ts +1 -0
  36. package/src/build/esbuild-build-adapter.js +13 -0
  37. package/src/hmr/strategies/js-hmr-strategy.js +0 -4
  38. package/src/plugins/integration-plugin.d.ts +6 -1
  39. package/src/route-renderer/orchestration/integration-renderer.d.ts +32 -14
  40. package/src/route-renderer/orchestration/integration-renderer.js +80 -14
  41. package/src/route-renderer/orchestration/processed-asset-dedupe.d.ts +1 -0
  42. package/src/route-renderer/orchestration/processed-asset-dedupe.js +15 -11
  43. package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +22 -8
  44. package/src/route-renderer/orchestration/route-render-orchestrator.js +59 -10
  45. package/src/services/assets/asset-processing-service/page-package.d.ts +4 -1
  46. package/src/services/assets/asset-processing-service/page-package.js +11 -5
  47. package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +3 -1
  48. package/src/services/html/html-rewriter-provider.service.d.ts +3 -0
  49. package/src/services/html/html-transformer.service.d.ts +10 -1
  50. package/src/services/html/html-transformer.service.js +80 -9
  51. package/src/services/module-loading/page-module-import.service.js +2 -2
  52. package/src/types/public-types.d.ts +24 -7
  53. package/src/adapters/bun/server-lifecycle.d.ts +0 -63
  54. package/src/adapters/bun/server-lifecycle.js +0 -92
  55. package/src/adapters/shared/runtime-bootstrap.d.ts +0 -38
  56. package/src/adapters/shared/runtime-bootstrap.js +0 -43
@@ -0,0 +1,46 @@
1
+ import { DEFAULT_ECOPAGES_HOSTNAME, DEFAULT_ECOPAGES_PORT } from "../../config/constants.js";
2
+ function normalizeOriginHostname(hostname) {
3
+ if (hostname.includes(":") && !hostname.startsWith("[") && !hostname.endsWith("]")) {
4
+ return `[${hostname}]`;
5
+ }
6
+ return hostname;
7
+ }
8
+ function resolveServeRuntimeOrigin(serveOptions) {
9
+ const hostname = normalizeOriginHostname(String(serveOptions.hostname ?? DEFAULT_ECOPAGES_HOSTNAME));
10
+ const port = Number(serveOptions.port ?? DEFAULT_ECOPAGES_PORT);
11
+ return `http://${hostname}:${port}`;
12
+ }
13
+ function resolveRuntimeBinding(options) {
14
+ const env = options.env ?? process.env;
15
+ const preferredPort = options.cliArgs.port ?? (env.ECOPAGES_PORT ? Number(env.ECOPAGES_PORT) : void 0) ?? DEFAULT_ECOPAGES_PORT;
16
+ const preferredHostname = options.cliArgs.hostname ?? env.ECOPAGES_HOSTNAME ?? DEFAULT_ECOPAGES_HOSTNAME;
17
+ return {
18
+ preferredPort,
19
+ preferredHostname,
20
+ runtimeOrigin: resolveServeRuntimeOrigin({
21
+ hostname: preferredHostname,
22
+ port: preferredPort
23
+ }),
24
+ serveOptions: {
25
+ port: preferredPort,
26
+ hostname: preferredHostname,
27
+ ...options.serverOptions ?? {}
28
+ },
29
+ watch: options.cliArgs.dev
30
+ };
31
+ }
32
+ function resolveStaticRuntimeMode(options) {
33
+ const requiresFetchRuntime = options.appConfig.integrations.some(
34
+ (integration) => integration.staticBuildStep === "fetch"
35
+ );
36
+ const canBuildWithoutRuntimeServer = (options.cliArgs.build || options.cliArgs.preview) && !requiresFetchRuntime;
37
+ return {
38
+ requiresFetchRuntime,
39
+ canBuildWithoutRuntimeServer
40
+ };
41
+ }
42
+ export {
43
+ resolveRuntimeBinding,
44
+ resolveServeRuntimeOrigin,
45
+ resolveStaticRuntimeMode
46
+ };
@@ -0,0 +1,12 @@
1
+ export interface RuntimeHost<TServer, TServeOptions> {
2
+ start(options: RuntimeHostStartOptions<TServeOptions>): Promise<TServer>;
3
+ stop(server: TServer, options?: {
4
+ force?: boolean;
5
+ }): Promise<void>;
6
+ getOrigin(server: TServer, fallbackServeOptions: TServeOptions): string;
7
+ }
8
+ export interface RuntimeHostStartOptions<TServeOptions> {
9
+ serveOptions: TServeOptions;
10
+ handleRequest(request: Request): Promise<Response>;
11
+ onError(error: Error): Promise<void> | void;
12
+ }
File without changes
@@ -0,0 +1,59 @@
1
+ import fs from 'node:fs';
2
+ import type { DefaultHmrContext, EcoPagesAppConfig, IHmrManager, IClientBridge } from '../../types/internal-types.js';
3
+ import type { EcoBuildPlugin } from '../../build/build-types.js';
4
+ import { type HmrStrategy } from '../../hmr/hmr-strategy.js';
5
+ import type { ClientBridgeEvent } from '../../types/public-types.js';
6
+ import { HmrEntrypointRegistrar } from './hmr-entrypoint-registrar.js';
7
+ import { BrowserBundleService } from '../../services/assets/browser-bundle.service.js';
8
+ import { type EntrypointDependencyGraph } from '../../services/runtime-state/entrypoint-dependency-graph.service.js';
9
+ import type { ServerModuleTranspiler } from '../../services/module-loading/server-module-transpiler.service.js';
10
+ type HandleFileChangeOptions = {
11
+ broadcast?: boolean;
12
+ };
13
+ type SharedHmrManagerParams = {
14
+ appConfig: EcoPagesAppConfig;
15
+ bridge: IClientBridge;
16
+ registrationTimeoutMs?: number;
17
+ };
18
+ export declare abstract class SharedHmrManager implements IHmrManager {
19
+ readonly appConfig: EcoPagesAppConfig;
20
+ protected readonly bridge: IClientBridge;
21
+ protected watchers: Map<string, fs.FSWatcher>;
22
+ protected watchedFiles: Map<string, string>;
23
+ protected entrypointRegistrations: Map<string, Promise<string>>;
24
+ protected distDir: string;
25
+ protected plugins: EcoBuildPlugin[];
26
+ protected enabled: boolean;
27
+ protected strategies: HmrStrategy[];
28
+ protected readonly entrypointRegistrar: HmrEntrypointRegistrar;
29
+ protected readonly browserBundleService: BrowserBundleService;
30
+ protected readonly entrypointDependencyGraph: EntrypointDependencyGraph;
31
+ protected readonly serverModuleTranspiler: ServerModuleTranspiler;
32
+ constructor({ appConfig, bridge, registrationTimeoutMs }: SharedHmrManagerParams);
33
+ protected abstract createEntrypointDependencyGraph(existingEntrypointDependencyGraph: EntrypointDependencyGraph): EntrypointDependencyGraph;
34
+ protected shouldSkipMissingFileChange(_filePath: string): boolean;
35
+ protected onRuntimeBundleFailure(error: unknown): void;
36
+ protected ensureDistDir(): void;
37
+ protected shouldJsStrategyProcessEntrypoint(entrypointPath: string): boolean;
38
+ protected initializeStrategies(): void;
39
+ registerStrategy(strategy: HmrStrategy): void;
40
+ setPlugins(plugins: EcoBuildPlugin[]): void;
41
+ setEnabled(enabled: boolean): void;
42
+ isEnabled(): boolean;
43
+ buildRuntime(): Promise<void>;
44
+ getRuntimePath(): string;
45
+ broadcast(event: ClientBridgeEvent): void;
46
+ handleFileChange(filePath: string, options?: HandleFileChangeOptions): Promise<void>;
47
+ getOutputUrl(entrypointPath: string): string | undefined;
48
+ getWatchedFiles(): Map<string, string>;
49
+ getDistDir(): string;
50
+ getPlugins(): EcoBuildPlugin[];
51
+ getDefaultContext(): DefaultHmrContext;
52
+ protected clearFailedEntrypointRegistration(entrypointPath: string): void;
53
+ registerEntrypoint(entrypointPath: string): Promise<string>;
54
+ registerScriptEntrypoint(entrypointPath: string): Promise<string>;
55
+ protected emitStrictEntrypoint(entrypointPath: string): Promise<void>;
56
+ protected emitScriptEntrypoint(entrypointPath: string, outputPath: string): Promise<void>;
57
+ stop(): void;
58
+ }
59
+ export {};
@@ -0,0 +1,239 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { RESOLVED_ASSETS_DIR } from "../../config/constants.js";
4
+ import { getAppBuildExecutor } from "../../build/build-adapter.js";
5
+ import { fileSystem } from "@ecopages/file-system";
6
+ import { HmrStrategyType } from "../../hmr/hmr-strategy.js";
7
+ import { DefaultHmrStrategy } from "../../hmr/strategies/default-hmr-strategy.js";
8
+ import { JsHmrStrategy } from "../../hmr/strategies/js-hmr-strategy.js";
9
+ import { appLogger } from "../../global/app-logger.js";
10
+ import { HmrEntrypointRegistrar } from "./hmr-entrypoint-registrar.js";
11
+ import { BrowserBundleService } from "../../services/assets/browser-bundle.service.js";
12
+ import { getAppServerModuleTranspiler } from "../../services/module-loading/app-server-module-transpiler.service.js";
13
+ import {
14
+ getAppEntrypointDependencyGraph,
15
+ setAppEntrypointDependencyGraph
16
+ } from "../../services/runtime-state/entrypoint-dependency-graph.service.js";
17
+ import { resolveInternalExecutionDir, resolveInternalWorkDir } from "../../utils/resolve-work-dir.js";
18
+ class SharedHmrManager {
19
+ appConfig;
20
+ bridge;
21
+ watchers = /* @__PURE__ */ new Map();
22
+ watchedFiles = /* @__PURE__ */ new Map();
23
+ entrypointRegistrations = /* @__PURE__ */ new Map();
24
+ distDir;
25
+ plugins = [];
26
+ enabled = true;
27
+ strategies = [];
28
+ entrypointRegistrar;
29
+ browserBundleService;
30
+ entrypointDependencyGraph;
31
+ serverModuleTranspiler;
32
+ constructor({ appConfig, bridge, registrationTimeoutMs = 4e3 }) {
33
+ this.appConfig = appConfig;
34
+ this.bridge = bridge;
35
+ this.distDir = path.join(resolveInternalWorkDir(this.appConfig), RESOLVED_ASSETS_DIR, "_hmr");
36
+ this.entrypointRegistrar = new HmrEntrypointRegistrar({
37
+ srcDir: this.appConfig.absolutePaths.srcDir,
38
+ distDir: this.distDir,
39
+ entrypointRegistrations: this.entrypointRegistrations,
40
+ watchedFiles: this.watchedFiles,
41
+ clearFailedRegistration: (entrypointPath) => this.clearFailedEntrypointRegistration(entrypointPath),
42
+ registrationTimeoutMs
43
+ });
44
+ this.browserBundleService = new BrowserBundleService(appConfig);
45
+ this.entrypointDependencyGraph = this.createEntrypointDependencyGraph(
46
+ getAppEntrypointDependencyGraph(appConfig)
47
+ );
48
+ setAppEntrypointDependencyGraph(this.appConfig, this.entrypointDependencyGraph);
49
+ this.serverModuleTranspiler = getAppServerModuleTranspiler(this.appConfig);
50
+ this.ensureDistDir();
51
+ this.initializeStrategies();
52
+ }
53
+ shouldSkipMissingFileChange(_filePath) {
54
+ return false;
55
+ }
56
+ onRuntimeBundleFailure(error) {
57
+ appLogger.error("[HMR] Failed to build runtime script:", error);
58
+ }
59
+ ensureDistDir() {
60
+ fileSystem.ensureDir(this.distDir);
61
+ }
62
+ shouldJsStrategyProcessEntrypoint(entrypointPath) {
63
+ return !this.strategies.some((strategy) => {
64
+ if (strategy.type !== HmrStrategyType.INTEGRATION || strategy.priority <= HmrStrategyType.SCRIPT) {
65
+ return false;
66
+ }
67
+ try {
68
+ return strategy.matches(entrypointPath);
69
+ } catch (error) {
70
+ appLogger.error(error);
71
+ return false;
72
+ }
73
+ });
74
+ }
75
+ initializeStrategies() {
76
+ const jsContext = {
77
+ getWatchedFiles: () => this.watchedFiles,
78
+ getDistDir: () => this.distDir,
79
+ getPlugins: () => this.plugins,
80
+ getSrcDir: () => this.appConfig.absolutePaths.srcDir,
81
+ getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
82
+ getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
83
+ getTemplateExtensions: () => this.appConfig.templatesExt,
84
+ getBrowserBundleService: () => this.browserBundleService,
85
+ getEntrypointDependencyGraph: () => this.entrypointDependencyGraph,
86
+ shouldProcessEntrypoint: (entrypointPath) => this.shouldJsStrategyProcessEntrypoint(entrypointPath)
87
+ };
88
+ this.strategies = [new JsHmrStrategy(jsContext), new DefaultHmrStrategy()];
89
+ }
90
+ registerStrategy(strategy) {
91
+ this.strategies.push(strategy);
92
+ }
93
+ setPlugins(plugins) {
94
+ this.plugins = [...plugins];
95
+ }
96
+ setEnabled(enabled) {
97
+ this.enabled = enabled;
98
+ }
99
+ isEnabled() {
100
+ return this.enabled;
101
+ }
102
+ async buildRuntime() {
103
+ const runtimeSource = path.resolve(import.meta.dirname, "../../hmr/client/hmr-runtime.js");
104
+ try {
105
+ const result = await this.browserBundleService.bundle({
106
+ profile: "hmr-runtime",
107
+ entrypoints: [runtimeSource],
108
+ outdir: this.distDir,
109
+ naming: "_hmr_runtime.js",
110
+ minify: false,
111
+ plugins: this.plugins
112
+ });
113
+ if (!result.success) {
114
+ this.onRuntimeBundleFailure(result.logs);
115
+ }
116
+ } catch (error) {
117
+ this.onRuntimeBundleFailure(error);
118
+ }
119
+ }
120
+ getRuntimePath() {
121
+ return path.join(this.distDir, "_hmr_runtime.js");
122
+ }
123
+ broadcast(event) {
124
+ appLogger.debug(
125
+ `[HMR] Broadcasting ${event.type} event, path=${event.path || "all"}, subscribers=${this.bridge.subscriberCount}`
126
+ );
127
+ this.bridge.broadcast(event);
128
+ }
129
+ async handleFileChange(filePath, options = {}) {
130
+ if (this.shouldSkipMissingFileChange(filePath) && !fileSystem.exists(filePath)) {
131
+ appLogger.debug(`[${this.constructor.name}] Skipping missing file change: ${filePath}`);
132
+ this.clearFailedEntrypointRegistration(filePath);
133
+ return;
134
+ }
135
+ const sorted = [...this.strategies].sort((a, b) => b.priority - a.priority);
136
+ const strategy = sorted.find((candidate) => {
137
+ try {
138
+ return candidate.matches(filePath);
139
+ } catch (error) {
140
+ appLogger.error(error);
141
+ return false;
142
+ }
143
+ });
144
+ if (!strategy) {
145
+ appLogger.warn(`[HMR] No strategy found for ${filePath}`);
146
+ return;
147
+ }
148
+ appLogger.debug(`[${this.constructor.name}] Selected strategy: ${strategy.constructor.name}`);
149
+ const action = await strategy.process(filePath);
150
+ const shouldBroadcast = options.broadcast ?? true;
151
+ if (shouldBroadcast && action.type === "broadcast" && action.events) {
152
+ for (const event of action.events) {
153
+ this.broadcast(event);
154
+ }
155
+ }
156
+ }
157
+ getOutputUrl(entrypointPath) {
158
+ return this.watchedFiles.get(entrypointPath);
159
+ }
160
+ getWatchedFiles() {
161
+ return this.watchedFiles;
162
+ }
163
+ getDistDir() {
164
+ return this.distDir;
165
+ }
166
+ getPlugins() {
167
+ return this.plugins;
168
+ }
169
+ getDefaultContext() {
170
+ return {
171
+ getWatchedFiles: () => this.watchedFiles,
172
+ getDistDir: () => this.distDir,
173
+ getPlugins: () => this.plugins,
174
+ getSrcDir: () => this.appConfig.absolutePaths.srcDir,
175
+ getLayoutsDir: () => this.appConfig.absolutePaths.layoutsDir,
176
+ getPagesDir: () => this.appConfig.absolutePaths.pagesDir,
177
+ getBuildExecutor: () => getAppBuildExecutor(this.appConfig),
178
+ getBrowserBundleService: () => this.browserBundleService,
179
+ importServerModule: async (filePath) => await this.serverModuleTranspiler.importModule({
180
+ filePath,
181
+ outdir: path.join(resolveInternalExecutionDir(this.appConfig), ".server-modules"),
182
+ externalPackages: true
183
+ })
184
+ };
185
+ }
186
+ clearFailedEntrypointRegistration(entrypointPath) {
187
+ this.watchedFiles.delete(entrypointPath);
188
+ this.entrypointDependencyGraph.clearEntrypointDependencies(entrypointPath);
189
+ }
190
+ async registerEntrypoint(entrypointPath) {
191
+ return await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
192
+ emit: async (normalizedEntrypoint) => await this.emitStrictEntrypoint(normalizedEntrypoint),
193
+ getMissingOutputError: (normalizedEntrypoint, outputPath) => new Error(
194
+ `[HMR] Integration failed to emit entrypoint ${normalizedEntrypoint} to ${outputPath}. Page entrypoints must be produced by their owning integration.`
195
+ )
196
+ });
197
+ }
198
+ async registerScriptEntrypoint(entrypointPath) {
199
+ return await this.entrypointRegistrar.registerEntrypoint(entrypointPath, {
200
+ emit: async (normalizedEntrypoint, outputPath) => await this.emitScriptEntrypoint(normalizedEntrypoint, outputPath),
201
+ getMissingOutputError: (normalizedEntrypoint) => new Error(`[HMR] Failed to register script entrypoint: ${normalizedEntrypoint}`)
202
+ });
203
+ }
204
+ async emitStrictEntrypoint(entrypointPath) {
205
+ await this.handleFileChange(entrypointPath, { broadcast: false });
206
+ }
207
+ async emitScriptEntrypoint(entrypointPath, outputPath) {
208
+ const naming = path.relative(this.distDir, outputPath).split(path.sep).join("/");
209
+ const buildResult = await this.browserBundleService.bundle({
210
+ profile: "hmr-entrypoint",
211
+ entrypoints: [entrypointPath],
212
+ outdir: this.distDir,
213
+ naming,
214
+ minify: false,
215
+ plugins: this.plugins
216
+ });
217
+ if (!buildResult.success) {
218
+ appLogger.error(`[HMR] Generic script entrypoint build failed for ${entrypointPath}:`, buildResult.logs);
219
+ return;
220
+ }
221
+ const entrypointDependencies = buildResult.dependencyGraph?.entrypoints?.[entrypointPath];
222
+ if (entrypointDependencies) {
223
+ this.entrypointDependencyGraph.setEntrypointDependencies(entrypointPath, entrypointDependencies);
224
+ }
225
+ }
226
+ stop() {
227
+ this.entrypointRegistrations.clear();
228
+ for (const watcher of this.watchers.values()) {
229
+ watcher.close();
230
+ }
231
+ this.watchers.clear();
232
+ this.watchedFiles.clear();
233
+ this.entrypointDependencyGraph.reset();
234
+ this.plugins = [];
235
+ }
236
+ }
237
+ export {
238
+ SharedHmrManager
239
+ };
@@ -0,0 +1,10 @@
1
+ import type { EcoPagesAppConfig } from '../../types/internal-types.js';
2
+ export interface StaticPreviewHost {
3
+ start(options: StaticPreviewHostStartOptions): Promise<number | null>;
4
+ stop(force?: boolean): Promise<void>;
5
+ }
6
+ export interface StaticPreviewHostStartOptions {
7
+ appConfig: EcoPagesAppConfig;
8
+ hostname: string;
9
+ port: number;
10
+ }
File without changes
@@ -213,7 +213,15 @@ class BunBuildAdapter {
213
213
  if (entryExtension === ".toml") {
214
214
  return ".toml";
215
215
  }
216
- return options.format === "cjs" || options.format === "esm" ? ".js" : ".js";
216
+ if (options.target !== "browser") {
217
+ if (options.format === "cjs") {
218
+ return ".cjs";
219
+ }
220
+ if (options.format === "esm") {
221
+ return ".mjs";
222
+ }
223
+ }
224
+ return ".js";
217
225
  }
218
226
  resolveConcreteOutputPath(outputPath) {
219
227
  if (fs.existsSync(outputPath)) {
@@ -570,6 +578,9 @@ async function collectConfiguredAppBuildManifestContributions(appConfig) {
570
578
  };
571
579
  }
572
580
  async function setupAppRuntimePlugins(options) {
581
+ for (const loader of options.appConfig.loaders.values()) {
582
+ options.onRuntimePlugin?.(loader);
583
+ }
573
584
  for (const processor of options.appConfig.processors.values()) {
574
585
  await processor.setup();
575
586
  if (processor.plugins) {
@@ -9,6 +9,7 @@ export declare const ESBUILD_ADAPTER_BRAND: unique symbol;
9
9
  export declare class EsbuildBuildAdapter implements BuildAdapter {
10
10
  readonly ownership: "bun-native";
11
11
  readonly [ESBUILD_ADAPTER_BRAND] = true;
12
+ private getJavaScriptOutExtension;
12
13
  private collectWorkspaceNodePaths;
13
14
  private getFallbackNodePaths;
14
15
  private rewriteAliasedRuntimeSpecifiers;
@@ -33,6 +33,18 @@ const ESBUILD_ADAPTER_BRAND = /* @__PURE__ */ Symbol.for("EsbuildBuildAdapter");
33
33
  class EsbuildBuildAdapter {
34
34
  ownership = "bun-native";
35
35
  [ESBUILD_ADAPTER_BRAND] = true;
36
+ getJavaScriptOutExtension(options) {
37
+ if (options.target === "browser") {
38
+ return void 0;
39
+ }
40
+ if (options.format === "cjs") {
41
+ return ".cjs";
42
+ }
43
+ if (options.format === "esm") {
44
+ return ".mjs";
45
+ }
46
+ return void 0;
47
+ }
36
48
  collectWorkspaceNodePaths(scanRoot, maxDepth = 3) {
37
49
  const normalizedRoot = path.resolve(scanRoot);
38
50
  const cached = workspaceNodePathsCache.get(normalizedRoot);
@@ -364,6 +376,7 @@ class EsbuildBuildAdapter {
364
376
  external: options.external,
365
377
  ...options.target !== "browser" && options.externalPackages !== false ? { packages: "external" } : {},
366
378
  target: transpileTarget,
379
+ ...this.getJavaScriptOutExtension(options) ? { outExtension: { ".js": this.getJavaScriptOutExtension(options) } } : {},
367
380
  metafile: true,
368
381
  write: true,
369
382
  plugins: esbuildPlugins,
@@ -36,10 +36,6 @@ class JsHmrStrategy extends HmrStrategy {
36
36
  if (watchedFiles.has(filePath)) {
37
37
  return true;
38
38
  }
39
- const entrypointDependencyGraph = this.context.getEntrypointDependencyGraph();
40
- if (entrypointDependencyGraph.supportsSelectiveInvalidation()) {
41
- return entrypointDependencyGraph.getDependencyEntrypoints(filePath).size > 0;
42
- }
43
39
  return true;
44
40
  }
45
41
  /**
@@ -8,6 +8,8 @@ import type { AssetDefinition, ProcessedAsset } from '../services/assets/asset-p
8
8
  import type { RuntimeCapabilityDeclaration } from './runtime-capability.js';
9
9
  export type { RuntimeCapabilityDeclaration, RuntimeCapabilityTag } from './runtime-capability.js';
10
10
  export type { EcoBuildLoader, EcoBuildOnLoadArgs, EcoBuildOnLoadResult, EcoBuildOnResolveArgs, EcoBuildOnResolveResult, EcoBuildPlugin, EcoBuildPluginBuilder, } from '../build/build-types.js';
11
+ export type { PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../types/public-types.js';
12
+ export type { HtmlDocumentContribution, HtmlDocumentContributionContext, } from '../route-renderer/orchestration/integration-renderer.js';
11
13
  /**
12
14
  * Type-erased integration plugin stored in app-level registries.
13
15
  *
@@ -84,7 +86,10 @@ type RendererClass<C> = new (options: {
84
86
  *
85
87
  * Core owns lifecycle ordering. Integrations declare contributions through the
86
88
  * hooks on this class, while `ConfigBuilder.build()` and app startup decide when
87
- * those hooks run.
89
+ * those hooks run. For page-browser and document shaping, integrations should
90
+ * prefer the contribution contracts re-exported from this module:
91
+ * `PageBrowserGraphContribution` / `PageBrowserGraphContributionContext` and
92
+ * `HtmlDocumentContribution` / `HtmlDocumentContributionContext`.
88
93
  */
89
94
  export declare abstract class IntegrationPlugin<C = EcoPagesElement> {
90
95
  readonly name: string;
@@ -4,9 +4,10 @@
4
4
  * @module
5
5
  */
6
6
  import type { EcoPagesAppConfig, IHmrManager } from '../../types/internal-types.js';
7
- import type { ComponentRenderInput, ComponentRenderResult, ForeignSubtreeRenderPayload, EcoComponent, EcoComponentDependencies, EcoPageFile, EcoPagesElement, BaseIntegrationContext, HtmlTemplateProps, IntegrationRendererRenderOptions, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
7
+ import type { ComponentRenderInput, ComponentRenderResult, ForeignSubtreeRenderPayload, EcoComponent, EcoComponentDependencies, EcoPageFile, EcoPagesElement, BaseIntegrationContext, HtmlTemplateProps, IntegrationRendererRenderOptions, PageBrowserGraphContribution, PageBrowserGraphContributionContext, PageBrowserGraphResult, PageMetadataProps, RouteRendererBody, RouteRendererOptions, RouteRenderResult } from '../../types/public-types.js';
8
8
  import { type AssetProcessingService, type ProcessedAsset } from '../../services/assets/asset-processing-service/index.js';
9
9
  import { HtmlTransformerService } from '../../services/html/html-transformer.service.js';
10
+ import type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
10
11
  import { HttpError } from '../../errors/http-error.js';
11
12
  import { DependencyResolverService } from '../page-loading/dependency-resolver.js';
12
13
  import { PageModuleLoaderService } from '../page-loading/page-module-loader.js';
@@ -33,6 +34,12 @@ export interface RenderToResponseContext {
33
34
  status?: number;
34
35
  headers?: HeadersInit;
35
36
  }
37
+ export type HtmlDocumentContributionContext<C = EcoPagesElement> = {
38
+ renderOptions?: IntegrationRendererRenderOptions<C>;
39
+ partial?: boolean;
40
+ };
41
+ export type { PageBrowserGraphContribution, PageBrowserGraphContributionContext } from '../../types/public-types.js';
42
+ export type { HtmlDocumentContribution } from '../../services/html/html-transformer.service.js';
36
43
  /**
37
44
  * The IntegrationRenderer class is an abstract class that provides a base for rendering integration-specific components in the EcoPages framework.
38
45
  * It handles the import of page files, collection of dependencies, and preparation of render options.
@@ -123,6 +130,8 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
123
130
  * @returns Resolved processed assets
124
131
  */
125
132
  protected prepareViewDependencies(view: EcoComponent, layout?: EcoComponent): Promise<ProcessedAsset[]>;
133
+ protected resolvePageBrowserGraphForFile(filePath: string): Promise<PageBrowserGraphResult | undefined>;
134
+ protected mergePageBrowserGraphIntoPagePackage(pageBrowserGraph?: PageBrowserGraphResult): PageBrowserGraphResult | undefined;
126
135
  /**
127
136
  * Merges component-scoped assets into the active HTML transformer state.
128
137
  *
@@ -329,14 +338,10 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
329
338
  */
330
339
  protected createRouteRenderOrchestratorAdapter(): RouteRenderOrchestratorAdapter<C>;
331
340
  protected resolveRouteRenderInputs(routeOptions: RouteRendererOptions): Promise<RouteRenderOrchestratorResolvedInputs>;
332
- protected resolveRouteAssets(input: {
333
- routeOptions: RouteRendererOptions;
341
+ protected resolveRouteDependencies(input: {
334
342
  components: (EcoComponent | Partial<EcoComponent>)[];
335
343
  }): Promise<{
336
344
  resolvedDependencies: ProcessedAsset[];
337
- pageBrowserGraph?: {
338
- assets: ProcessedAsset[];
339
- };
340
345
  }>;
341
346
  protected resolveRoutePageComponentRender(input: {
342
347
  Page: EcoComponent;
@@ -346,7 +351,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
346
351
  }): Promise<ComponentRenderResult | undefined>;
347
352
  protected renderRouteBody(renderOptions: IntegrationRendererRenderOptions<C>): Promise<RouteRendererBody>;
348
353
  protected getRouteHtmlFinalization(renderOptions: IntegrationRendererRenderOptions<C>): RouteHtmlFinalization;
349
- protected transformRouteResponse(response: Response): Promise<RouteRendererBody>;
354
+ protected transformRouteResponse(response: Response, htmlContributions?: HtmlDocumentContribution[]): Promise<RouteRendererBody>;
350
355
  /**
351
356
  * Prepares the render options for the integration renderer.
352
357
  * It imports the page file, collects dependencies, and prepares the render options.
@@ -399,6 +404,7 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
399
404
  componentRootAttributes?: Record<string, string>;
400
405
  documentAttributes?: Record<string, string>;
401
406
  transformHtml?: boolean;
407
+ htmlContributions?: HtmlDocumentContribution[];
402
408
  }): Promise<string>;
403
409
  /**
404
410
  * Returns document-level attributes to stamp onto the rendered `<html>` tag.
@@ -407,6 +413,16 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
407
413
  * other runtime coordination markers without relying on script sniffing.
408
414
  */
409
415
  protected getDocumentAttributes(_renderOptions: IntegrationRendererRenderOptions<C>): Record<string, string> | undefined;
416
+ /**
417
+ * Returns declarative HTML fragments that core should inject into the final document.
418
+ *
419
+ * @remarks
420
+ * Integrations may contribute document markup here, but core retains ownership
421
+ * of the final HTML rewrite pipeline and placement semantics. This is the
422
+ * supported document-markup extension point for integrations instead of custom
423
+ * response finalization logic.
424
+ */
425
+ protected getHtmlDocumentContributions(_options: HtmlDocumentContributionContext<C>): HtmlDocumentContribution[] | undefined;
410
426
  /**
411
427
  * Returns a renderer instance for a given integration name.
412
428
  *
@@ -486,15 +502,17 @@ export declare abstract class IntegrationRenderer<C = EcoPagesElement> {
486
502
  */
487
503
  protected getRootTagName(html: string): string | undefined;
488
504
  /**
489
- * Builds the Page Browser Graph owned by this integration for one Page.
490
- * This method can be optionally overridden by the specific integration renderer.
505
+ * Collects declarative Page Browser Graph contributions for one Page.
506
+ *
507
+ * @remarks
508
+ * Integrations may describe page-scoped browser requirements here, while core
509
+ * retains ownership of dependency processing and final graph assembly. This is
510
+ * the supported page-browser extension point for integrations.
491
511
  *
492
- * @param file - The file path to build assets for.
493
- * @returns The structured Page Browser Graph or undefined.
512
+ * @param context - The route file path and already imported page module.
513
+ * @returns Declarative dependencies or pre-resolved assets for the Page.
494
514
  */
495
- protected buildPageBrowserGraph(_file: string): Promise<{
496
- assets: ProcessedAsset[];
497
- } | undefined>;
515
+ protected collectPageBrowserGraphContribution(_context: PageBrowserGraphContributionContext): Promise<PageBrowserGraphContribution | undefined>;
498
516
  /**
499
517
  * Creates the per-render foreign-child runtime adopted by the shared component
500
518
  * render context.