@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.
- package/package.json +2 -2
- package/src/adapters/bun/create-app.d.ts +8 -1
- package/src/adapters/bun/create-app.js +52 -65
- package/src/adapters/bun/hmr-manager.d.ts +19 -103
- package/src/adapters/bun/hmr-manager.js +26 -280
- package/src/adapters/bun/runtime-host.d.ts +52 -0
- package/src/adapters/bun/runtime-host.js +56 -0
- package/src/adapters/bun/server-adapter.d.ts +89 -28
- package/src/adapters/bun/server-adapter.js +113 -61
- package/src/adapters/bun/static-preview-host.d.ts +28 -0
- package/src/adapters/bun/static-preview-host.js +45 -0
- package/src/adapters/node/create-app.d.ts +9 -3
- package/src/adapters/node/create-app.js +24 -81
- package/src/adapters/node/http-request-bridge.d.ts +57 -0
- package/src/adapters/node/http-request-bridge.js +118 -0
- package/src/adapters/node/node-hmr-manager.d.ts +22 -91
- package/src/adapters/node/node-hmr-manager.js +26 -272
- package/src/adapters/node/runtime-host.d.ts +57 -0
- package/src/adapters/node/runtime-host.js +92 -0
- package/src/adapters/node/server-adapter-dependencies.d.ts +19 -0
- package/src/adapters/node/server-adapter-dependencies.js +18 -0
- package/src/adapters/node/server-adapter.d.ts +10 -37
- package/src/adapters/node/server-adapter.js +55 -125
- package/src/adapters/node/static-preview-host.d.ts +55 -0
- package/src/adapters/node/static-preview-host.js +68 -0
- package/src/adapters/shared/runtime-app-bootstrap.d.ts +26 -0
- package/src/adapters/shared/runtime-app-bootstrap.js +46 -0
- package/src/adapters/shared/runtime-host.d.ts +12 -0
- package/src/adapters/shared/runtime-host.js +0 -0
- package/src/adapters/shared/shared-hmr-manager.d.ts +59 -0
- package/src/adapters/shared/shared-hmr-manager.js +239 -0
- package/src/adapters/shared/static-preview-host.d.ts +10 -0
- package/src/adapters/shared/static-preview-host.js +0 -0
- package/src/build/build-adapter.js +12 -1
- package/src/build/esbuild-build-adapter.d.ts +1 -0
- package/src/build/esbuild-build-adapter.js +13 -0
- package/src/hmr/strategies/js-hmr-strategy.js +0 -4
- package/src/plugins/integration-plugin.d.ts +6 -1
- package/src/route-renderer/orchestration/integration-renderer.d.ts +32 -14
- package/src/route-renderer/orchestration/integration-renderer.js +80 -14
- package/src/route-renderer/orchestration/processed-asset-dedupe.d.ts +1 -0
- package/src/route-renderer/orchestration/processed-asset-dedupe.js +15 -11
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +22 -8
- package/src/route-renderer/orchestration/route-render-orchestrator.js +59 -10
- package/src/services/assets/asset-processing-service/page-package.d.ts +4 -1
- package/src/services/assets/asset-processing-service/page-package.js +11 -5
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +3 -1
- package/src/services/html/html-rewriter-provider.service.d.ts +3 -0
- package/src/services/html/html-transformer.service.d.ts +10 -1
- package/src/services/html/html-transformer.service.js +80 -9
- package/src/services/module-loading/page-module-import.service.js +2 -2
- package/src/types/public-types.d.ts +24 -7
- package/src/adapters/bun/server-lifecycle.d.ts +0 -63
- package/src/adapters/bun/server-lifecycle.js +0 -92
- package/src/adapters/shared/runtime-bootstrap.d.ts +0 -38
- 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
|
-
|
|
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
|
|
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
|
-
*
|
|
490
|
-
*
|
|
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
|
|
493
|
-
* @returns
|
|
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
|
|
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.
|