@ecopages/core 0.2.0-beta.22 → 0.2.0-beta.24
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 +13 -3
- package/src/adapters/abstract/application-adapter.js +3 -0
- package/src/adapters/bun/create-app.js +2 -0
- package/src/adapters/node/create-app.js +2 -0
- package/src/adapters/shared/server-adapter.d.ts +1 -0
- package/src/adapters/shared/server-adapter.js +31 -22
- package/src/build/app-build-manifest-runtime.js +12 -1
- package/src/build/dev-browser-script-cache.d.ts +34 -0
- package/src/build/dev-browser-script-cache.js +90 -0
- package/src/build/lit-static-render-worker-context.d.ts +2 -0
- package/src/build/lit-static-render-worker-context.js +6 -0
- package/src/diagnostics/request-build-dedupe.d.ts +18 -0
- package/src/diagnostics/request-build-dedupe.js +33 -0
- package/src/diagnostics/startup-trace.d.ts +25 -0
- package/src/diagnostics/startup-trace.js +121 -0
- package/src/env.d.ts +2 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +4 -1
- package/src/plugins/integration-plugin.d.ts +8 -0
- package/src/plugins/processor.d.ts +6 -0
- package/src/plugins/processor.js +2 -1
- package/src/route-renderer/orchestration/page-browser-graph.service.js +15 -9
- package/src/route-renderer/page-loading/ecopages-virtual-imports.d.ts +6 -0
- package/src/route-renderer/page-loading/ecopages-virtual-imports.js +7 -2
- package/src/route-renderer/page-loading/module-declaration-aggregation.js +4 -0
- package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +3 -0
- package/src/services/assets/asset-processing-service/asset-processing.service.js +41 -4
- package/src/services/assets/asset-processing-service/finalize-processed-asset.d.ts +3 -0
- package/src/services/assets/asset-processing-service/finalize-processed-asset.js +7 -0
- package/src/services/assets/asset-processing-service/grouped-content-bundles.d.ts +2 -1
- package/src/services/assets/asset-processing-service/grouped-content-bundles.js +20 -10
- package/src/services/assets/asset-processing-service/index.d.ts +1 -0
- package/src/services/assets/asset-processing-service/index.js +1 -0
- package/src/services/assets/asset-processing-service/materialize-content-script-asset.d.ts +11 -0
- package/src/services/assets/asset-processing-service/materialize-content-script-asset.js +17 -0
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts +7 -0
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +105 -79
- package/src/services/assets/asset-processing-service/resolve-integration-plugin.d.ts +4 -0
- package/src/services/assets/asset-processing-service/resolve-integration-plugin.js +17 -0
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.d.ts +0 -1
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.js +5 -10
- package/src/services/assets/browser-bundle.service.js +10 -2
- package/src/services/validation/schema-validation-service.d.ts +1 -4
- package/src/services/validation/schema-validation-service.js +12 -13
- package/src/services/validation/standard-schema.types.d.ts +9 -62
- package/src/services/validation/validate-standard-schema.d.ts +7 -0
- package/src/services/validation/validate-standard-schema.js +14 -0
- package/src/types/public-types.d.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.24",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-beta.
|
|
20
|
+
"@ecopages/file-system": "0.2.0-beta.24",
|
|
21
21
|
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
23
23
|
"@oxc-project/runtime": "0.134.0",
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
"ghtml": "^4.0.2",
|
|
28
28
|
"oxc-parser": "^0.124.0",
|
|
29
29
|
"rolldown": "^1.1.0",
|
|
30
|
-
"ws": "^8.20.1"
|
|
30
|
+
"ws": "^8.20.1",
|
|
31
|
+
"@standard-schema/spec": "^1.1.0",
|
|
32
|
+
"@standard-schema/utils": "^0.3.0"
|
|
31
33
|
},
|
|
32
34
|
"exports": {
|
|
33
35
|
".": {
|
|
@@ -205,6 +207,10 @@
|
|
|
205
207
|
"types": "./src/build/jsx-ownership-plugins.d.ts",
|
|
206
208
|
"default": "./src/build/jsx-ownership-plugins.js"
|
|
207
209
|
},
|
|
210
|
+
"./build/lit-static-render-worker-context": {
|
|
211
|
+
"types": "./src/build/lit-static-render-worker-context.d.ts",
|
|
212
|
+
"default": "./src/build/lit-static-render-worker-context.js"
|
|
213
|
+
},
|
|
208
214
|
"./cache": {
|
|
209
215
|
"types": "./src/cache/index.d.ts",
|
|
210
216
|
"default": "./src/cache/index.js"
|
|
@@ -414,6 +420,10 @@
|
|
|
414
420
|
"types": "./src/build/jsx-ownership-plugins.d.ts",
|
|
415
421
|
"default": "./src/build/jsx-ownership-plugins.js"
|
|
416
422
|
},
|
|
423
|
+
"./build/lit-static-render-worker-context.ts": {
|
|
424
|
+
"types": "./src/build/lit-static-render-worker-context.d.ts",
|
|
425
|
+
"default": "./src/build/lit-static-render-worker-context.js"
|
|
426
|
+
},
|
|
417
427
|
"./cache.ts": {
|
|
418
428
|
"types": "./src/cache/index.d.ts",
|
|
419
429
|
"default": "./src/cache/index.js"
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
formatRuntimeServerStartedMessage
|
|
11
11
|
} from "../../dev/runtime-server-started-message.js";
|
|
12
12
|
import { parseCliArgs } from "../../utils/parse-cli-args.js";
|
|
13
|
+
import { startupTrace } from "../../diagnostics/startup-trace.js";
|
|
13
14
|
class AbstractApplicationAdapter {
|
|
14
15
|
appConfig;
|
|
15
16
|
serverOptions;
|
|
@@ -42,6 +43,7 @@ class AbstractApplicationAdapter {
|
|
|
42
43
|
setAppHostModuleLoader(this.appConfig, hostModuleLoader);
|
|
43
44
|
}
|
|
44
45
|
getAppModuleLoader(this.appConfig);
|
|
46
|
+
startupTrace.markConfigReady();
|
|
45
47
|
if (options.clearOutput) {
|
|
46
48
|
this.clearDistFolder().catch((error) => {
|
|
47
49
|
appLogger.error("Error clearing dist folder", error);
|
|
@@ -221,6 +223,7 @@ class AbstractApplicationAdapter {
|
|
|
221
223
|
}
|
|
222
224
|
notifyListening(origin) {
|
|
223
225
|
const normalizedOrigin = origin.replace(/\/$/, "");
|
|
226
|
+
startupTrace.markServerListening();
|
|
224
227
|
if (!this.onAppStartCallback) {
|
|
225
228
|
this.logServerStarted(normalizedOrigin);
|
|
226
229
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { appLogger } from "../../global/app-logger.js";
|
|
2
2
|
import { SharedApplicationAdapter } from "../shared/application-adapter.js";
|
|
3
3
|
import { resolveRuntimeBinding, resolveStaticRuntimeMode } from "../shared/runtime-app-bootstrap.js";
|
|
4
|
+
import { startupTrace } from "../../diagnostics/startup-trace.js";
|
|
4
5
|
import { createBunServerAdapter } from "./server-adapter.js";
|
|
5
6
|
import { BunRuntimeHost } from "./runtime-host.js";
|
|
6
7
|
import { hostOwnsDevClient } from "../../dev/dev-client-ownership.js";
|
|
@@ -101,6 +102,7 @@ class BunEcopagesApp extends SharedApplicationAdapter {
|
|
|
101
102
|
const enableHmr = dev || !preview && !build;
|
|
102
103
|
const serverOptions = this.serverAdapter.getServerOptions({ enableHmr });
|
|
103
104
|
const runtimeServerOptions = serverOptions;
|
|
105
|
+
startupTrace.beginServerListen();
|
|
104
106
|
this.server = await this.runtimeHost.start({
|
|
105
107
|
serveOptions: runtimeServerOptions,
|
|
106
108
|
handleRequest: async () => new Response(null, { status: 500 }),
|
|
@@ -5,6 +5,7 @@ import { createNodeServerAdapter } from "./server-adapter.js";
|
|
|
5
5
|
import { NodeHttpRequestBridge } from "./http-request-bridge.js";
|
|
6
6
|
import { NodeRuntimeHost } from "./runtime-host.js";
|
|
7
7
|
import { hostOwnsDevClient } from "../../dev/dev-client-ownership.js";
|
|
8
|
+
import { startupTrace } from "../../diagnostics/startup-trace.js";
|
|
8
9
|
class NodeEcopagesApp extends SharedApplicationAdapter {
|
|
9
10
|
serverAdapter;
|
|
10
11
|
server = null;
|
|
@@ -87,6 +88,7 @@ class NodeEcopagesApp extends SharedApplicationAdapter {
|
|
|
87
88
|
return;
|
|
88
89
|
}
|
|
89
90
|
const serveOptions = this.serverAdapter.getServerOptions();
|
|
91
|
+
startupTrace.beginServerListen();
|
|
90
92
|
this.server = await this.runtimeHost.start({
|
|
91
93
|
serveOptions,
|
|
92
94
|
handleRequest: async (request) => await this.serverAdapter.handleRequest(request),
|
|
@@ -27,6 +27,7 @@ export declare abstract class SharedServerAdapter<TOptions extends ServerAdapter
|
|
|
27
27
|
staticRoutes: StaticRoute[];
|
|
28
28
|
hmrManager?: any;
|
|
29
29
|
}): Promise<void>;
|
|
30
|
+
private ensureRouteRendererFactory;
|
|
30
31
|
protected createSharedWatchRefreshCallback(options: {
|
|
31
32
|
staticRoutes: StaticRoute[];
|
|
32
33
|
hmrManager?: any;
|
|
@@ -2,6 +2,8 @@ import path from "node:path";
|
|
|
2
2
|
import { AbstractServerAdapter } from "../abstract/server-adapter.js";
|
|
3
3
|
import { RouteRendererFactory } from "../../route-renderer/route-renderer.js";
|
|
4
4
|
import { RouteRegistry } from "../../router/server/route-registry.js";
|
|
5
|
+
import { startupTrace } from "../../diagnostics/startup-trace.js";
|
|
6
|
+
import { requestBuildDedupe } from "../../diagnostics/request-build-dedupe.js";
|
|
5
7
|
import { MemoryCacheStore } from "../../services/cache/memory-cache-store.js";
|
|
6
8
|
import { PageCacheService } from "../../services/cache/page-cache-service.js";
|
|
7
9
|
import { SchemaValidationService } from "../../services/validation/schema-validation-service.js";
|
|
@@ -29,9 +31,20 @@ class SharedServerAdapter extends AbstractServerAdapter {
|
|
|
29
31
|
schemaValidator = new SchemaValidationService();
|
|
30
32
|
hostOwnsDevClient = false;
|
|
31
33
|
async initializeSharedRouteHandling(options) {
|
|
34
|
+
this.ensureRouteRendererFactory();
|
|
32
35
|
await this.initSharedRouter();
|
|
33
36
|
this.configureSharedResponseHandlers(options.staticRoutes, options.hmrManager);
|
|
34
37
|
}
|
|
38
|
+
ensureRouteRendererFactory() {
|
|
39
|
+
if (this.routeRendererFactory) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
this.routeRendererFactory = new RouteRendererFactory({
|
|
43
|
+
appConfig: this.appConfig,
|
|
44
|
+
rendererModules: this.appConfig.runtime?.rendererModuleContext,
|
|
45
|
+
runtimeOrigin: this.runtimeOrigin
|
|
46
|
+
});
|
|
47
|
+
}
|
|
35
48
|
createSharedWatchRefreshCallback(options) {
|
|
36
49
|
return async () => {
|
|
37
50
|
try {
|
|
@@ -60,6 +73,7 @@ class SharedServerAdapter extends AbstractServerAdapter {
|
|
|
60
73
|
* Web Requests (`Request`) to their corresponding internal execution paths.
|
|
61
74
|
*/
|
|
62
75
|
async initSharedRouter() {
|
|
76
|
+
startupTrace.markPhaseStart("route-registry");
|
|
63
77
|
this.router = new RouteRegistry({
|
|
64
78
|
pagesDir: path.join(this.appConfig.rootDir, this.appConfig.srcDir, this.appConfig.pagesDir),
|
|
65
79
|
appConfig: this.appConfig,
|
|
@@ -69,18 +83,13 @@ class SharedServerAdapter extends AbstractServerAdapter {
|
|
|
69
83
|
pageModuleAdapter: this.createRouteRegistryPageModuleAdapter()
|
|
70
84
|
});
|
|
71
85
|
await this.router.init();
|
|
86
|
+
startupTrace.markPhaseEnd("route-registry");
|
|
72
87
|
}
|
|
73
88
|
createRouteRegistryPageModuleAdapter() {
|
|
74
|
-
|
|
89
|
+
this.ensureRouteRendererFactory();
|
|
75
90
|
return {
|
|
76
91
|
loadPageModule: async (filePath) => {
|
|
77
|
-
const module = await
|
|
78
|
-
filePath,
|
|
79
|
-
outdir: path.join(resolveInternalExecutionDir(this.appConfig), ".server-route-modules"),
|
|
80
|
-
externalPackages: true,
|
|
81
|
-
transpileErrorMessage: (details) => `Error transpiling route module: ${details}`,
|
|
82
|
-
noOutputMessage: (targetFilePath) => `No transpiled output generated for route module: ${targetFilePath}`
|
|
83
|
-
});
|
|
92
|
+
const module = await this.routeRendererFactory.getPageRenderer(filePath).loadPageModule(filePath);
|
|
84
93
|
const page = module.default;
|
|
85
94
|
return {
|
|
86
95
|
staticPaths: page?.staticPaths ?? module.getStaticPaths,
|
|
@@ -104,11 +113,7 @@ class SharedServerAdapter extends AbstractServerAdapter {
|
|
|
104
113
|
* @param hmrManager - The runtime-specific Hot Module Replacement orchestrator (if watching).
|
|
105
114
|
*/
|
|
106
115
|
configureSharedResponseHandlers(staticRoutes, hmrManager) {
|
|
107
|
-
this.
|
|
108
|
-
appConfig: this.appConfig,
|
|
109
|
-
rendererModules: this.appConfig.runtime?.rendererModuleContext,
|
|
110
|
-
runtimeOrigin: this.runtimeOrigin
|
|
111
|
-
});
|
|
116
|
+
this.ensureRouteRendererFactory();
|
|
112
117
|
const { fileSystemResponseMatcher, explicitStaticRouteMatcher } = this.createSharedResponseHandlerDependencies(staticRoutes);
|
|
113
118
|
this.fileSystemResponseMatcher = fileSystemResponseMatcher;
|
|
114
119
|
this.routeHandler = new ServerRouteHandler({
|
|
@@ -427,15 +432,19 @@ class SharedServerAdapter extends AbstractServerAdapter {
|
|
|
427
432
|
* native HTTP objects into Web Standard Requests.
|
|
428
433
|
*/
|
|
429
434
|
async handleSharedRequest(request, context) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
435
|
+
return requestBuildDedupe.run(
|
|
436
|
+
() => startupTrace.traceFirstRequest(request, async () => {
|
|
437
|
+
const hmrResponse = this.tryHandleSharedHmrRequest(request, context);
|
|
438
|
+
if (hmrResponse) {
|
|
439
|
+
return hmrResponse;
|
|
440
|
+
}
|
|
441
|
+
const apiResponse = await this.tryHandleSharedApiRequest(request, context);
|
|
442
|
+
if (apiResponse) {
|
|
443
|
+
return apiResponse;
|
|
444
|
+
}
|
|
445
|
+
return this.routeHandler.handleResponse(request);
|
|
446
|
+
})
|
|
447
|
+
);
|
|
439
448
|
}
|
|
440
449
|
}
|
|
441
450
|
export {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { mergeBrowserRuntimeManifests } from "./browser-runtime-manifest.js";
|
|
2
2
|
import { appLogger } from "../global/app-logger.js";
|
|
3
|
+
import { startupTrace } from "../diagnostics/startup-trace.js";
|
|
4
|
+
import { isLitStaticRenderWorkerThread } from "./lit-static-render-worker-context.js";
|
|
3
5
|
function patchAppRuntime(appConfig, patch) {
|
|
4
6
|
appConfig.runtime = {
|
|
5
7
|
...appConfig.runtime ?? {},
|
|
@@ -50,18 +52,26 @@ async function collectConfiguredAppBuildManifestContributions(appConfig) {
|
|
|
50
52
|
};
|
|
51
53
|
}
|
|
52
54
|
async function setupAppRuntimePlugins(options) {
|
|
55
|
+
startupTrace.markPhaseStart("setupAppRuntimePlugins");
|
|
53
56
|
if (options.appConfig.runtime?.runtimeAssetsPrepared) {
|
|
54
57
|
appLogger.debug("Skipped setupAppRuntimePlugins: runtime assets already prepared");
|
|
55
58
|
registerRuntimePlugins(options.appConfig, options.onRuntimePlugin);
|
|
59
|
+
startupTrace.markPhaseEnd("setupAppRuntimePlugins");
|
|
56
60
|
return;
|
|
57
61
|
}
|
|
62
|
+
if (isLitStaticRenderWorkerThread()) {
|
|
63
|
+
appLogger.debug("Lit static-render worker: skipping processor setup (main thread already prepared artifacts)");
|
|
64
|
+
}
|
|
58
65
|
appLogger.debugTime("setupAppRuntimePlugins");
|
|
59
66
|
try {
|
|
60
67
|
for (const loader of options.appConfig.loaders.values()) {
|
|
61
68
|
options.onRuntimePlugin?.(loader);
|
|
62
69
|
}
|
|
70
|
+
const skipProcessorSetup = isLitStaticRenderWorkerThread();
|
|
63
71
|
for (const processor of options.appConfig.processors.values()) {
|
|
64
|
-
|
|
72
|
+
if (!skipProcessorSetup) {
|
|
73
|
+
await processor.setup();
|
|
74
|
+
}
|
|
65
75
|
if (processor.plugins) {
|
|
66
76
|
for (const plugin of processor.plugins) {
|
|
67
77
|
options.onRuntimePlugin?.(plugin);
|
|
@@ -79,6 +89,7 @@ async function setupAppRuntimePlugins(options) {
|
|
|
79
89
|
patchAppRuntime(options.appConfig, { runtimeAssetsPrepared: true });
|
|
80
90
|
} finally {
|
|
81
91
|
appLogger.debugTimeEnd("setupAppRuntimePlugins");
|
|
92
|
+
startupTrace.markPhaseEnd("setupAppRuntimePlugins");
|
|
82
93
|
}
|
|
83
94
|
}
|
|
84
95
|
export {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
2
|
+
import type { ProcessedAsset } from '../services/assets/asset-processing-service/assets.types.js';
|
|
3
|
+
export declare const DEV_BROWSER_SCRIPT_CACHE_DIR = ".browser-script-bundles";
|
|
4
|
+
export declare const DEV_BROWSER_SCRIPT_CACHE_FILENAME = ".build-cache.json";
|
|
5
|
+
/** Output path restored from the dev disk cache before dependency metadata is merged. */
|
|
6
|
+
export type DevBrowserScriptCacheHit = {
|
|
7
|
+
filepath: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* @remarks
|
|
11
|
+
* Dev asset caching spans three scopes that should not be conflated:
|
|
12
|
+
* - request dedupe (`request-build-dedupe.ts`) — one in-flight build per request key
|
|
13
|
+
* - in-memory service cache (`AssetProcessingService`) — per-process reuse within one dev server
|
|
14
|
+
* - this disk manifest (`.eco/.browser-script-bundles`) — cross-request reuse of content-script bundles
|
|
15
|
+
*
|
|
16
|
+
* Disk entries intentionally store only output file paths. Declarative fields such as
|
|
17
|
+
* `groupedBundle`, `attributes`, and `packageRole` live on the originating
|
|
18
|
+
* `ContentScriptAsset` and are merged via `materializeContentScriptAsset()` on lookup.
|
|
19
|
+
*/
|
|
20
|
+
export interface DevBrowserScriptCacheEntry {
|
|
21
|
+
filepath: string;
|
|
22
|
+
builtAt: number;
|
|
23
|
+
}
|
|
24
|
+
export interface DevBrowserScriptCacheManifest {
|
|
25
|
+
invalidationVersion: string;
|
|
26
|
+
buildInputsFingerprint: string;
|
|
27
|
+
entries: Record<string, DevBrowserScriptCacheEntry>;
|
|
28
|
+
}
|
|
29
|
+
/** Returns whether dev-only cross-request content-script bundle cache is active. */
|
|
30
|
+
export declare function shouldUseDevBrowserScriptCache(): boolean;
|
|
31
|
+
/** Looks up one persisted dev content-script output path by dependency cache key. */
|
|
32
|
+
export declare function getDevBrowserScriptCacheEntry(appConfig: EcoPagesAppConfig, depKey: string): DevBrowserScriptCacheHit | null;
|
|
33
|
+
/** Persists one dev content-script bundle output for cross-request reuse. */
|
|
34
|
+
export declare function setDevBrowserScriptCacheEntry(appConfig: EcoPagesAppConfig, depKey: string, asset: ProcessedAsset): void;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
import { getAppBrowserBuildPlugins } from "./build-adapter.js";
|
|
4
|
+
import { createBuildInputsFingerprint, hashAppConfigFile } from "./build-input-fingerprint.js";
|
|
5
|
+
import {
|
|
6
|
+
readProductionCacheManifest,
|
|
7
|
+
writeProductionCacheManifest,
|
|
8
|
+
isProductionCacheManifestCurrent,
|
|
9
|
+
matchesProductionCacheFingerprint
|
|
10
|
+
} from "./production-build-cache.js";
|
|
11
|
+
import { getCorePackageVersion } from "../services/module-loading/route-module-build-manifest.js";
|
|
12
|
+
import { resolveInternalExecutionDir } from "../utils/resolve-work-dir.js";
|
|
13
|
+
import { isDevelopmentRuntime } from "../utils/runtime.js";
|
|
14
|
+
const DEV_BROWSER_SCRIPT_CACHE_DIR = ".browser-script-bundles";
|
|
15
|
+
const DEV_BROWSER_SCRIPT_CACHE_FILENAME = ".build-cache.json";
|
|
16
|
+
function getDevBrowserScriptCacheDir(appConfig) {
|
|
17
|
+
return path.join(resolveInternalExecutionDir(appConfig), DEV_BROWSER_SCRIPT_CACHE_DIR);
|
|
18
|
+
}
|
|
19
|
+
function getDevBrowserScriptCacheManifestPath(appConfig) {
|
|
20
|
+
return path.join(getDevBrowserScriptCacheDir(appConfig), DEV_BROWSER_SCRIPT_CACHE_FILENAME);
|
|
21
|
+
}
|
|
22
|
+
function createBrowserBuildKey(appConfig) {
|
|
23
|
+
try {
|
|
24
|
+
const pluginNames = getAppBrowserBuildPlugins(appConfig).map((plugin) => plugin.name).sort().join("|");
|
|
25
|
+
return pluginNames || "default";
|
|
26
|
+
} catch {
|
|
27
|
+
return "default";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function createDevBrowserScriptCacheInvalidationVersion(appConfig) {
|
|
31
|
+
return [getCorePackageVersion(), hashAppConfigFile(appConfig), createBrowserBuildKey(appConfig)].join("::");
|
|
32
|
+
}
|
|
33
|
+
function readManifest(appConfig) {
|
|
34
|
+
const manifest = readProductionCacheManifest(
|
|
35
|
+
getDevBrowserScriptCacheManifestPath(appConfig)
|
|
36
|
+
);
|
|
37
|
+
if (!manifest) {
|
|
38
|
+
return void 0;
|
|
39
|
+
}
|
|
40
|
+
const invalidationVersion = createDevBrowserScriptCacheInvalidationVersion(appConfig);
|
|
41
|
+
const buildInputsFingerprint = createBuildInputsFingerprint(appConfig);
|
|
42
|
+
if (!isProductionCacheManifestCurrent(manifest, invalidationVersion) || !matchesProductionCacheFingerprint(manifest, buildInputsFingerprint)) {
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
return manifest;
|
|
46
|
+
}
|
|
47
|
+
function writeManifest(appConfig, manifest) {
|
|
48
|
+
writeProductionCacheManifest(getDevBrowserScriptCacheManifestPath(appConfig), manifest);
|
|
49
|
+
}
|
|
50
|
+
function shouldUseDevBrowserScriptCache() {
|
|
51
|
+
return isDevelopmentRuntime();
|
|
52
|
+
}
|
|
53
|
+
function getDevBrowserScriptCacheEntry(appConfig, depKey) {
|
|
54
|
+
if (!shouldUseDevBrowserScriptCache()) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const manifest = readManifest(appConfig);
|
|
58
|
+
const entry = manifest?.entries[depKey];
|
|
59
|
+
if (!entry?.filepath || !fileSystem.exists(entry.filepath)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return { filepath: entry.filepath };
|
|
63
|
+
}
|
|
64
|
+
function setDevBrowserScriptCacheEntry(appConfig, depKey, asset) {
|
|
65
|
+
if (!shouldUseDevBrowserScriptCache() || !asset.filepath) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const invalidationVersion = createDevBrowserScriptCacheInvalidationVersion(appConfig);
|
|
69
|
+
const buildInputsFingerprint = createBuildInputsFingerprint(appConfig);
|
|
70
|
+
const existing = readManifest(appConfig);
|
|
71
|
+
const manifest = existing ?? {
|
|
72
|
+
invalidationVersion,
|
|
73
|
+
buildInputsFingerprint,
|
|
74
|
+
entries: {}
|
|
75
|
+
};
|
|
76
|
+
manifest.invalidationVersion = invalidationVersion;
|
|
77
|
+
manifest.buildInputsFingerprint = buildInputsFingerprint;
|
|
78
|
+
manifest.entries[depKey] = {
|
|
79
|
+
filepath: asset.filepath,
|
|
80
|
+
builtAt: Date.now()
|
|
81
|
+
};
|
|
82
|
+
writeManifest(appConfig, manifest);
|
|
83
|
+
}
|
|
84
|
+
export {
|
|
85
|
+
DEV_BROWSER_SCRIPT_CACHE_DIR,
|
|
86
|
+
DEV_BROWSER_SCRIPT_CACHE_FILENAME,
|
|
87
|
+
getDevBrowserScriptCacheEntry,
|
|
88
|
+
setDevBrowserScriptCacheEntry,
|
|
89
|
+
shouldUseDevBrowserScriptCache
|
|
90
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BuildResult } from '../build/build-adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* Per-request browser build dedupe cache.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* `DedupingBuildExecutor` only coalesces concurrent identical builds. Asset
|
|
7
|
+
* processing can call the same logical bundle several times in one request
|
|
8
|
+
* (page graph, HMR, content scripts) after the first build settles. This cache
|
|
9
|
+
* reuses the settled result until the request scope ends.
|
|
10
|
+
*/
|
|
11
|
+
declare class RequestBuildDedupe {
|
|
12
|
+
private readonly storage;
|
|
13
|
+
run<T>(fn: () => Promise<T>): Promise<T>;
|
|
14
|
+
dedupeBuild(key: string, build: () => Promise<BuildResult>): Promise<BuildResult>;
|
|
15
|
+
getCachedCountForTests(): number;
|
|
16
|
+
}
|
|
17
|
+
export declare const requestBuildDedupe: RequestBuildDedupe;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
class RequestBuildDedupe {
|
|
3
|
+
storage = new AsyncLocalStorage();
|
|
4
|
+
run(fn) {
|
|
5
|
+
if (this.storage.getStore()) {
|
|
6
|
+
return fn();
|
|
7
|
+
}
|
|
8
|
+
return this.storage.run(/* @__PURE__ */ new Map(), fn);
|
|
9
|
+
}
|
|
10
|
+
dedupeBuild(key, build) {
|
|
11
|
+
const store = this.storage.getStore();
|
|
12
|
+
if (!store) {
|
|
13
|
+
return build();
|
|
14
|
+
}
|
|
15
|
+
const existing = store.get(key);
|
|
16
|
+
if (existing) {
|
|
17
|
+
return existing;
|
|
18
|
+
}
|
|
19
|
+
const buildPromise = build().catch((error) => {
|
|
20
|
+
store.delete(key);
|
|
21
|
+
throw error;
|
|
22
|
+
});
|
|
23
|
+
store.set(key, buildPromise);
|
|
24
|
+
return buildPromise;
|
|
25
|
+
}
|
|
26
|
+
getCachedCountForTests() {
|
|
27
|
+
return this.storage.getStore()?.size ?? 0;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const requestBuildDedupe = new RequestBuildDedupe();
|
|
31
|
+
export {
|
|
32
|
+
requestBuildDedupe
|
|
33
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type StartupTracePhase = 'config-ready' | 'setupAppRuntimePlugins' | 'route-registry' | 'server-listen' | 'first-request-ssr';
|
|
2
|
+
declare class StartupTrace {
|
|
3
|
+
private readonly phaseStarts;
|
|
4
|
+
private readonly phases;
|
|
5
|
+
private firstRequestPending;
|
|
6
|
+
private firstRequestPath;
|
|
7
|
+
private firstRequestBundleCount;
|
|
8
|
+
private firstRequestClientBytes;
|
|
9
|
+
private summaryEmitted;
|
|
10
|
+
isEnabled(): boolean;
|
|
11
|
+
markPhaseStart(phase: StartupTracePhase): void;
|
|
12
|
+
markPhaseEnd(phase: StartupTracePhase): void;
|
|
13
|
+
markConfigReady(): void;
|
|
14
|
+
markServerListening(): void;
|
|
15
|
+
beginServerListen(): void;
|
|
16
|
+
recordBrowserBundle(outputs: Array<{
|
|
17
|
+
path: string;
|
|
18
|
+
}>): void;
|
|
19
|
+
traceFirstRequest<T>(request: Request, handler: () => Promise<T>): Promise<T>;
|
|
20
|
+
private emitFirstRequestSummary;
|
|
21
|
+
/** Resets mutable trace state for unit tests. */
|
|
22
|
+
resetForTests(): void;
|
|
23
|
+
}
|
|
24
|
+
export declare const startupTrace: StartupTrace;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { statSync } from "node:fs";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
const LOG_PREFIX = "[ecopages:startup-trace]";
|
|
4
|
+
function isStartupTraceEnabled() {
|
|
5
|
+
return process.env.ECOPAGES_STARTUP_TRACE === "true" || process.env.ECOPAGES_LOGGER_DEBUG === "true";
|
|
6
|
+
}
|
|
7
|
+
function wallMsSinceProcessStart() {
|
|
8
|
+
return Math.round(process.uptime() * 1e3);
|
|
9
|
+
}
|
|
10
|
+
function writeTraceLine(message) {
|
|
11
|
+
process.stderr.write(`${LOG_PREFIX} ${message}
|
|
12
|
+
`);
|
|
13
|
+
}
|
|
14
|
+
class StartupTrace {
|
|
15
|
+
phaseStarts = /* @__PURE__ */ new Map();
|
|
16
|
+
phases = /* @__PURE__ */ new Map();
|
|
17
|
+
firstRequestPending = true;
|
|
18
|
+
firstRequestPath;
|
|
19
|
+
firstRequestBundleCount = 0;
|
|
20
|
+
firstRequestClientBytes = 0;
|
|
21
|
+
summaryEmitted = false;
|
|
22
|
+
isEnabled() {
|
|
23
|
+
return isStartupTraceEnabled();
|
|
24
|
+
}
|
|
25
|
+
markPhaseStart(phase) {
|
|
26
|
+
if (!isStartupTraceEnabled() || this.phases.has(phase)) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
this.phaseStarts.set(phase, performance.now());
|
|
30
|
+
}
|
|
31
|
+
markPhaseEnd(phase) {
|
|
32
|
+
if (!isStartupTraceEnabled() || this.phases.has(phase)) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const startedAt = this.phaseStarts.get(phase);
|
|
36
|
+
if (startedAt === void 0) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const durationMs = Math.round(performance.now() - startedAt);
|
|
40
|
+
const record = {
|
|
41
|
+
durationMs,
|
|
42
|
+
wallMs: wallMsSinceProcessStart()
|
|
43
|
+
};
|
|
44
|
+
this.phases.set(phase, record);
|
|
45
|
+
writeTraceLine(`phase=${phase} durationMs=${durationMs} wallMs=${record.wallMs}`);
|
|
46
|
+
}
|
|
47
|
+
markConfigReady() {
|
|
48
|
+
if (!isStartupTraceEnabled() || this.phases.has("config-ready")) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const record = {
|
|
52
|
+
durationMs: 0,
|
|
53
|
+
wallMs: wallMsSinceProcessStart()
|
|
54
|
+
};
|
|
55
|
+
this.phases.set("config-ready", record);
|
|
56
|
+
writeTraceLine(`phase=config-ready wallMs=${record.wallMs}`);
|
|
57
|
+
}
|
|
58
|
+
markServerListening() {
|
|
59
|
+
this.markPhaseEnd("server-listen");
|
|
60
|
+
}
|
|
61
|
+
beginServerListen() {
|
|
62
|
+
this.markPhaseStart("server-listen");
|
|
63
|
+
}
|
|
64
|
+
recordBrowserBundle(outputs) {
|
|
65
|
+
if (!isStartupTraceEnabled() || !this.firstRequestPending) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
this.firstRequestBundleCount += 1;
|
|
69
|
+
for (const output of outputs) {
|
|
70
|
+
try {
|
|
71
|
+
if (fileSystem.exists(output.path)) {
|
|
72
|
+
this.firstRequestClientBytes += statSync(output.path).size;
|
|
73
|
+
}
|
|
74
|
+
} catch {
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async traceFirstRequest(request, handler) {
|
|
79
|
+
if (!isStartupTraceEnabled() || !this.firstRequestPending) {
|
|
80
|
+
return handler();
|
|
81
|
+
}
|
|
82
|
+
this.firstRequestPath = new URL(request.url).pathname;
|
|
83
|
+
this.markPhaseStart("first-request-ssr");
|
|
84
|
+
try {
|
|
85
|
+
return await handler();
|
|
86
|
+
} finally {
|
|
87
|
+
this.markPhaseEnd("first-request-ssr");
|
|
88
|
+
this.emitFirstRequestSummary();
|
|
89
|
+
this.firstRequestPending = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
emitFirstRequestSummary() {
|
|
93
|
+
if (!isStartupTraceEnabled() || this.summaryEmitted) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.summaryEmitted = true;
|
|
97
|
+
writeTraceLine(
|
|
98
|
+
[
|
|
99
|
+
"summary",
|
|
100
|
+
`path=${this.firstRequestPath ?? "unknown"}`,
|
|
101
|
+
`bundleCount=${this.firstRequestBundleCount}`,
|
|
102
|
+
`clientBundleBytes=${this.firstRequestClientBytes}`,
|
|
103
|
+
`wallMs=${wallMsSinceProcessStart()}`
|
|
104
|
+
].join(" ")
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
/** Resets mutable trace state for unit tests. */
|
|
108
|
+
resetForTests() {
|
|
109
|
+
this.phaseStarts.clear();
|
|
110
|
+
this.phases.clear();
|
|
111
|
+
this.firstRequestPending = true;
|
|
112
|
+
this.firstRequestPath = void 0;
|
|
113
|
+
this.firstRequestBundleCount = 0;
|
|
114
|
+
this.firstRequestClientBytes = 0;
|
|
115
|
+
this.summaryEmitted = false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const startupTrace = new StartupTrace();
|
|
119
|
+
export {
|
|
120
|
+
startupTrace
|
|
121
|
+
};
|
package/src/env.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ interface EcopagesEnv {
|
|
|
3
3
|
ECOPAGES_HOSTNAME: string;
|
|
4
4
|
ECOPAGES_PORT: string;
|
|
5
5
|
ECOPAGES_LOGGER_DEBUG: 'true' | 'false';
|
|
6
|
+
/** When `true`, emits startup phase timings to stderr. Also enabled when `ECOPAGES_LOGGER_DEBUG=true`. */
|
|
7
|
+
ECOPAGES_STARTUP_TRACE?: 'true' | 'false';
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
declare global {
|
package/src/index.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export { eco } from './eco/eco.js';
|
|
|
4
4
|
export { defineApiHandler, defineGroupHandler, type GroupHandler } from './adapters/shared/define-api-handler.js';
|
|
5
5
|
export { createEcoBuildPluginFromSourceTransform, createVitePluginsFromAppSourceTransforms, getAppSourceTransforms, createVitePluginFromSourceTransform, normalizeTransformId, type EcoSourceTransform, type EcoSourceTransformResult, type EcoViteCompatiblePlugin, } from './plugins/source-transform.js';
|
|
6
6
|
export { createEcoComponentMetaTransform } from './plugins/eco-component-meta-plugin.js';
|
|
7
|
+
export { SchemaError, validateStandardSchema } from './services/validation/validate-standard-schema.js';
|
package/src/index.js
CHANGED
|
@@ -8,7 +8,9 @@ import {
|
|
|
8
8
|
normalizeTransformId
|
|
9
9
|
} from "./plugins/source-transform.js";
|
|
10
10
|
import { createEcoComponentMetaTransform } from "./plugins/eco-component-meta-plugin.js";
|
|
11
|
+
import { SchemaError, validateStandardSchema } from "./services/validation/validate-standard-schema.js";
|
|
11
12
|
export {
|
|
13
|
+
SchemaError,
|
|
12
14
|
createEcoBuildPluginFromSourceTransform,
|
|
13
15
|
createEcoComponentMetaTransform,
|
|
14
16
|
createVitePluginFromSourceTransform,
|
|
@@ -17,5 +19,6 @@ export {
|
|
|
17
19
|
defineGroupHandler,
|
|
18
20
|
eco,
|
|
19
21
|
getAppSourceTransforms,
|
|
20
|
-
normalizeTransformId
|
|
22
|
+
normalizeTransformId,
|
|
23
|
+
validateStandardSchema
|
|
21
24
|
};
|
|
@@ -197,6 +197,14 @@ export declare abstract class IntegrationPlugin<C = EcoPagesElement> {
|
|
|
197
197
|
initializeRenderer(options?: {
|
|
198
198
|
rendererModules?: unknown;
|
|
199
199
|
}): IntegrationRenderer<C>;
|
|
200
|
+
/**
|
|
201
|
+
* Shapes one dependency batch before core asset processing runs.
|
|
202
|
+
*
|
|
203
|
+
* @remarks
|
|
204
|
+
* Integrations use this to assign grouped-build metadata or other batch-level
|
|
205
|
+
* policy without teaching core about integration-specific asset graphs.
|
|
206
|
+
*/
|
|
207
|
+
prepareAssetDependencies?(dependencies: AssetDefinition[]): AssetDefinition[];
|
|
200
208
|
/**
|
|
201
209
|
* Prepares build-facing contributions before the app build manifest is sealed.
|
|
202
210
|
*
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { EcoBuildPlugin } from '../build/build-types.js';
|
|
2
2
|
import type { EcoPagesAppConfig, IClientBridge } from '../types/internal-types.js';
|
|
3
|
+
import { GENERATED_BASE_PATHS } from '../config/constants.js';
|
|
3
4
|
import type { RuntimeCapabilityDeclaration } from './runtime-capability.js';
|
|
4
5
|
export type { RuntimeCapabilityDeclaration, RuntimeCapabilityTag } from './runtime-capability.js';
|
|
5
6
|
export type { EcoBuildLoader, EcoBuildOnLoadArgs, EcoBuildOnLoadResult, EcoBuildOnResolveArgs, EcoBuildOnResolveResult, EcoBuildPlugin, EcoBuildPluginBuilder, } from '../build/build-types.js';
|
|
@@ -7,6 +8,11 @@ export declare const PROCESSOR_ERRORS: {
|
|
|
7
8
|
readonly CACHE_DIRECTORY_NOT_SET: "Cache directory not set in context";
|
|
8
9
|
};
|
|
9
10
|
export declare function mergeProcessorOptions<TDefaults, TOverrides>(defaults: TDefaults, overrides: TOverrides): TDefaults & TOverrides;
|
|
11
|
+
export declare function resolveGeneratedPath(type: keyof typeof GENERATED_BASE_PATHS, options: {
|
|
12
|
+
root: string;
|
|
13
|
+
module: string;
|
|
14
|
+
subPath?: string;
|
|
15
|
+
}): string;
|
|
10
16
|
export interface ProcessorWatchContext {
|
|
11
17
|
path: string;
|
|
12
18
|
bridge: IClientBridge;
|