@ecopages/core 0.2.0-beta.6 → 0.2.0-beta.8
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 +26 -2
- package/src/adapters/abstract/application-adapter.d.ts +12 -1
- package/src/adapters/abstract/application-adapter.js +13 -0
- package/src/adapters/abstract/server-adapter.d.ts +3 -0
- package/src/adapters/bun/client-bridge.d.ts +1 -0
- package/src/adapters/bun/client-bridge.js +3 -0
- package/src/adapters/bun/create-app.d.ts +2 -0
- package/src/adapters/bun/create-app.js +28 -9
- package/src/adapters/bun/server-adapter.d.ts +13 -0
- package/src/adapters/bun/server-adapter.js +29 -4
- package/src/adapters/create-app.d.ts +13 -0
- package/src/adapters/create-app.js +10 -1
- package/src/adapters/node/create-app.d.ts +1 -0
- package/src/adapters/node/create-app.js +17 -7
- package/src/adapters/node/node-client-bridge.d.ts +1 -0
- package/src/adapters/node/node-client-bridge.js +3 -0
- package/src/adapters/node/server-adapter.d.ts +10 -13
- package/src/adapters/node/server-adapter.js +37 -82
- package/src/adapters/node/static-content-server.d.ts +1 -0
- package/src/adapters/node/static-content-server.js +3 -0
- package/src/adapters/shared/copy-runtime-public-dir.d.ts +6 -0
- package/src/adapters/shared/copy-runtime-public-dir.js +24 -0
- package/src/adapters/shared/runtime-app-bootstrap.d.ts +0 -1
- package/src/adapters/shared/runtime-app-bootstrap.js +1 -6
- package/src/adapters/shared/server-static-builder.d.ts +4 -7
- package/src/adapters/shared/server-static-builder.js +89 -49
- package/src/adapters/shared/shared-hmr-manager.d.ts +1 -0
- package/src/adapters/shared/shared-hmr-manager.js +15 -2
- package/src/build/README.md +44 -4
- package/src/build/build-adapter.d.ts +8 -2
- package/src/build/build-adapter.js +71 -33
- package/src/build/build-input-fingerprint.d.ts +28 -0
- package/src/build/build-input-fingerprint.js +31 -0
- package/src/build/jsx-ownership-plugins.d.ts +10 -0
- package/src/build/jsx-ownership-plugins.js +17 -0
- package/src/build/pages-unified-graph-build.d.ts +31 -0
- package/src/build/pages-unified-graph-build.js +208 -0
- package/src/build/parallel-build-executor.d.ts +21 -0
- package/src/build/parallel-build-executor.js +52 -0
- package/src/build/rolldown-adapter-helpers.js +47 -3
- package/src/build/rolldown-build-adapter.js +2 -0
- package/src/build/rolldown-build-invocation-metrics.d.ts +12 -0
- package/src/build/rolldown-build-invocation-metrics.js +43 -0
- package/src/build/rolldown-dev-build-adapter.d.ts +2 -2
- package/src/build/rolldown-dev-build-adapter.js +30 -14
- package/src/build/runtime-build-executor.d.ts +7 -13
- package/src/build/runtime-build-executor.js +31 -7
- package/src/build/runtime-build-output-normalizer.d.ts +7 -0
- package/src/build/runtime-build-output-normalizer.js +85 -1
- package/src/build/server-entry-build-cache.d.ts +44 -0
- package/src/build/server-entry-build-cache.js +169 -0
- package/src/hmr/hmr.test.e2e.js +15 -4
- package/src/plugins/integration-plugin.d.ts +19 -8
- package/src/plugins/integration-plugin.js +7 -2
- package/src/plugins/processor.d.ts +5 -0
- package/src/plugins/processor.js +7 -0
- package/src/route-renderer/GRAPH.md +4 -4
- package/src/route-renderer/README.md +6 -5
- package/src/route-renderer/orchestration/foreign-subtree-execution.service.js +1 -3
- package/src/route-renderer/orchestration/integration-renderer.d.ts +10 -2
- package/src/route-renderer/orchestration/integration-renderer.js +32 -12
- package/src/route-renderer/orchestration/render-output.utils.d.ts +2 -0
- package/src/route-renderer/orchestration/render-output.utils.js +4 -0
- package/src/route-renderer/orchestration/route-render-orchestrator.d.ts +2 -2
- package/src/route-renderer/orchestration/route-render-orchestrator.js +17 -16
- package/src/router/client/navigation-coordinator.js +10 -0
- package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.d.ts +1 -0
- package/src/services/assets/asset-processing-service/browser-runtime-asset.factory.js +1 -0
- package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.d.ts +4 -2
- package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +27 -7
- package/src/services/assets/asset-processing-service/processors/script/node-module-script.processor.js +17 -3
- package/src/services/assets/browser-bundle.service.d.ts +1 -0
- package/src/services/assets/browser-bundle.service.js +9 -3
- package/src/services/html/html-transformer.service.d.ts +1 -1
- package/src/services/html/html-transformer.service.js +4 -4
- package/src/services/invalidation/development-invalidation.service.d.ts +10 -1
- package/src/services/invalidation/development-invalidation.service.js +29 -0
- package/src/services/module-loading/README.md +49 -0
- package/src/services/module-loading/app-server-module-transpiler.service.js +4 -17
- package/src/services/module-loading/page-module-import.service.d.ts +4 -1
- package/src/services/module-loading/page-module-import.service.js +52 -29
- package/src/services/module-loading/route-module-build-cache-registry.d.ts +24 -0
- package/src/services/module-loading/route-module-build-cache-registry.js +37 -0
- package/src/services/module-loading/route-module-build-cache.d.ts +4 -0
- package/src/services/module-loading/route-module-build-cache.js +38 -0
- package/src/services/module-loading/route-module-build-cache.store.d.ts +57 -0
- package/src/services/module-loading/route-module-build-cache.store.js +203 -0
- package/src/services/module-loading/route-module-build-manifest.d.ts +61 -0
- package/src/services/module-loading/route-module-build-manifest.js +124 -0
- package/src/services/module-loading/route-module-dependency-hasher.d.ts +81 -0
- package/src/services/module-loading/route-module-dependency-hasher.js +121 -0
- package/src/services/module-loading/server-module-transpiler.service.js +1 -1
- package/src/static-site-generator/README.md +33 -0
- package/src/static-site-generator/static-build-invalidation.d.ts +10 -0
- package/src/static-site-generator/static-build-invalidation.js +78 -0
- package/src/static-site-generator/static-export-context.d.ts +22 -0
- package/src/static-site-generator/static-export-context.js +0 -0
- package/src/static-site-generator/static-site-generator.d.ts +17 -20
- package/src/static-site-generator/static-site-generator.js +188 -85
- package/src/types/internal-types.d.ts +8 -0
- package/src/utils/parse-cli-args.d.ts +1 -0
- package/src/utils/parse-cli-args.js +3 -0
- package/src/watchers/project-watcher.d.ts +8 -0
- package/src/watchers/project-watcher.js +32 -12
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide Rolldown build invocation counters for benchmarks and spikes.
|
|
3
|
+
*
|
|
4
|
+
* Gated behind {@link isRolldownBuildMetricsEnabled} so production paths stay quiet.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isRolldownBuildMetricsEnabled(): boolean;
|
|
7
|
+
export declare function recordRolldownBuildInvocation(adapter?: 'rolldown' | 'rolldown-dev'): void;
|
|
8
|
+
export declare function getRolldownBuildInvocationCounts(): Readonly<Record<string, number>>;
|
|
9
|
+
export declare function resetRolldownBuildInvocationCounts(): void;
|
|
10
|
+
export declare function recordPageModuleBuildInvocation(): void;
|
|
11
|
+
export declare function getPageModuleRolldownBuildInvocations(): number;
|
|
12
|
+
export declare function getTotalRolldownBuildInvocations(): number;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const invocationCounts = /* @__PURE__ */ new Map();
|
|
2
|
+
let pageModuleBuildInvocations = 0;
|
|
3
|
+
function isRolldownBuildMetricsEnabled() {
|
|
4
|
+
return process.env.ECOPAGES_ROLLDOWN_BUILD_METRICS === "1" || process.env.ECOPAGES_LOGGER_DEBUG === "true";
|
|
5
|
+
}
|
|
6
|
+
function recordRolldownBuildInvocation(adapter = "rolldown") {
|
|
7
|
+
if (!isRolldownBuildMetricsEnabled()) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
invocationCounts.set(adapter, (invocationCounts.get(adapter) ?? 0) + 1);
|
|
11
|
+
}
|
|
12
|
+
function getRolldownBuildInvocationCounts() {
|
|
13
|
+
return Object.fromEntries(invocationCounts);
|
|
14
|
+
}
|
|
15
|
+
function resetRolldownBuildInvocationCounts() {
|
|
16
|
+
invocationCounts.clear();
|
|
17
|
+
pageModuleBuildInvocations = 0;
|
|
18
|
+
}
|
|
19
|
+
function recordPageModuleBuildInvocation() {
|
|
20
|
+
if (!isRolldownBuildMetricsEnabled()) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
pageModuleBuildInvocations += 1;
|
|
24
|
+
}
|
|
25
|
+
function getPageModuleRolldownBuildInvocations() {
|
|
26
|
+
return pageModuleBuildInvocations;
|
|
27
|
+
}
|
|
28
|
+
function getTotalRolldownBuildInvocations() {
|
|
29
|
+
let total = 0;
|
|
30
|
+
for (const count of invocationCounts.values()) {
|
|
31
|
+
total += count;
|
|
32
|
+
}
|
|
33
|
+
return total;
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
getPageModuleRolldownBuildInvocations,
|
|
37
|
+
getRolldownBuildInvocationCounts,
|
|
38
|
+
getTotalRolldownBuildInvocations,
|
|
39
|
+
isRolldownBuildMetricsEnabled,
|
|
40
|
+
recordPageModuleBuildInvocation,
|
|
41
|
+
recordRolldownBuildInvocation,
|
|
42
|
+
resetRolldownBuildInvocationCounts
|
|
43
|
+
};
|
|
@@ -39,8 +39,8 @@ export declare class RolldownDevBuildAdapter implements BuildAdapter {
|
|
|
39
39
|
private readonly appRootRequireCache;
|
|
40
40
|
/**
|
|
41
41
|
* Stable key over the inputs that affect the engine configuration.
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Output paths and entrypoints must participate — HMR registers many
|
|
43
|
+
* distinct script entrypoints with literal naming under the same outdir.
|
|
44
44
|
*/
|
|
45
45
|
private getCacheKey;
|
|
46
46
|
/**
|
|
@@ -8,7 +8,17 @@ import {
|
|
|
8
8
|
toBuildLogs,
|
|
9
9
|
transpileProfileToOptions
|
|
10
10
|
} from "./rolldown-adapter-helpers.js";
|
|
11
|
+
import { recordRolldownBuildInvocation } from "./rolldown-build-invocation-metrics.js";
|
|
11
12
|
const moduleRequire = createRequire(import.meta.url);
|
|
13
|
+
function normalizeEntrypointList(entrypoints) {
|
|
14
|
+
if (Array.isArray(entrypoints)) {
|
|
15
|
+
return entrypoints.map((entrypoint) => path.resolve(entrypoint)).sort();
|
|
16
|
+
}
|
|
17
|
+
if (entrypoints && typeof entrypoints === "object") {
|
|
18
|
+
return Object.values(entrypoints).map((entrypoint) => path.resolve(entrypoint)).sort();
|
|
19
|
+
}
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
12
22
|
class RolldownDevBuildAdapter {
|
|
13
23
|
/**
|
|
14
24
|
* `'rolldown-dev'` (not `'rolldown'`) so {@link getBuildAdapterOwnership}
|
|
@@ -22,12 +32,22 @@ class RolldownDevBuildAdapter {
|
|
|
22
32
|
appRootRequireCache = /* @__PURE__ */ new Map();
|
|
23
33
|
/**
|
|
24
34
|
* Stable key over the inputs that affect the engine configuration.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
35
|
+
* Output paths and entrypoints must participate — HMR registers many
|
|
36
|
+
* distinct script entrypoints with literal naming under the same outdir.
|
|
27
37
|
*/
|
|
28
|
-
getCacheKey(
|
|
29
|
-
const pluginNames = plugins.map((
|
|
30
|
-
|
|
38
|
+
getCacheKey(options, contextRoot) {
|
|
39
|
+
const pluginNames = (options.plugins ?? []).map((plugin) => plugin.name).sort().join(":");
|
|
40
|
+
const outdir = path.resolve(options.outdir ?? "dist/assets");
|
|
41
|
+
const entrypoints = normalizeEntrypointList(options.entrypoints).join("|");
|
|
42
|
+
return [
|
|
43
|
+
contextRoot,
|
|
44
|
+
pluginNames,
|
|
45
|
+
options.target ?? "default",
|
|
46
|
+
options.format ?? "esm",
|
|
47
|
+
outdir,
|
|
48
|
+
options.naming ?? "[name]",
|
|
49
|
+
entrypoints
|
|
50
|
+
].join("::");
|
|
31
51
|
}
|
|
32
52
|
/**
|
|
33
53
|
* Returns the cached engine, creating a new one when the cache
|
|
@@ -36,15 +56,9 @@ class RolldownDevBuildAdapter {
|
|
|
36
56
|
*/
|
|
37
57
|
async getOrCreateEngine(options, contextRoot) {
|
|
38
58
|
const bundlePlugins = options.plugins ?? [];
|
|
39
|
-
const cacheKey = this.getCacheKey(
|
|
59
|
+
const cacheKey = this.getCacheKey(options, contextRoot);
|
|
40
60
|
if (this.cachedEngine) {
|
|
41
|
-
|
|
42
|
-
this.cachedEngine.bundlePlugins,
|
|
43
|
-
this.cachedEngine.contextRoot,
|
|
44
|
-
options.target,
|
|
45
|
-
options.format
|
|
46
|
-
);
|
|
47
|
-
if (existingKey === cacheKey) {
|
|
61
|
+
if (this.cachedEngine.cacheKey === cacheKey) {
|
|
48
62
|
return this.cachedEngine;
|
|
49
63
|
}
|
|
50
64
|
await this.cachedEngine.engine.close();
|
|
@@ -73,7 +87,8 @@ class RolldownDevBuildAdapter {
|
|
|
73
87
|
engine,
|
|
74
88
|
engineStarted: false,
|
|
75
89
|
contextRoot,
|
|
76
|
-
bundlePlugins
|
|
90
|
+
bundlePlugins,
|
|
91
|
+
cacheKey
|
|
77
92
|
};
|
|
78
93
|
this.engineInstanceCount += 1;
|
|
79
94
|
return this.cachedEngine;
|
|
@@ -108,6 +123,7 @@ class RolldownDevBuildAdapter {
|
|
|
108
123
|
}
|
|
109
124
|
/** Throws on error; see {@link build} for the no-throw variant. */
|
|
110
125
|
async buildOrThrow(options) {
|
|
126
|
+
recordRolldownBuildInvocation("rolldown-dev");
|
|
111
127
|
const contextRoot = options.root ? path.resolve(options.root) : process.cwd();
|
|
112
128
|
const outdir = path.resolve(options.outdir ?? "dist/assets");
|
|
113
129
|
const plugins = options.plugins ?? [];
|
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
2
2
|
import { type BuildExecutor } from './build-adapter.js';
|
|
3
3
|
/**
|
|
4
|
-
* Installs the app-owned runtime build
|
|
4
|
+
* Installs the app-owned runtime build executors for one app instance.
|
|
5
5
|
*
|
|
6
6
|
* @remarks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* so app-owned plugins are merged into every rebuild without callers
|
|
11
|
-
* having to know about the manifest.
|
|
7
|
+
* Route-module and HMR browser builds run through a {@link ParallelBuildExecutor}
|
|
8
|
+
* so independent compiles can overlap. Server-entry bundling continues to call
|
|
9
|
+
* the raw adapter directly and stays single-flight by design.
|
|
12
10
|
*
|
|
13
|
-
* Idempotent across calls: re-invoking replaces the existing
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* @param appConfig - The app config whose runtime state is updated.
|
|
17
|
-
* The function reads the existing executor (falling back to the
|
|
18
|
-
* app-owned adapter) and writes the wrapped executor back.
|
|
19
|
-
* @returns The installed {@link BuildExecutor}.
|
|
11
|
+
* Idempotent across calls: re-invoking replaces the existing executors on
|
|
12
|
+
* `appConfig.runtime` with fresh wrappers.
|
|
20
13
|
*/
|
|
21
14
|
export declare function installAppRuntimeBuildExecutor(appConfig: EcoPagesAppConfig): BuildExecutor;
|
|
15
|
+
export declare function getInstalledServerEntryBuildExecutor(appConfig: EcoPagesAppConfig): BuildExecutor;
|
|
@@ -1,19 +1,43 @@
|
|
|
1
|
+
import { availableParallelism } from "node:os";
|
|
1
2
|
import {
|
|
2
3
|
getAppBuildAdapter,
|
|
3
|
-
getAppBuildExecutor,
|
|
4
4
|
getAppServerBuildPlugins,
|
|
5
5
|
setAppBuildExecutor,
|
|
6
|
+
setAppHmrBuildExecutor,
|
|
7
|
+
setAppRouteModuleBuildExecutor,
|
|
6
8
|
withBuildExecutorPlugins
|
|
7
9
|
} from "./build-adapter.js";
|
|
10
|
+
import { ParallelBuildExecutor } from "./parallel-build-executor.js";
|
|
8
11
|
import { SerializedBuildExecutor } from "./serialized-build-executor.js";
|
|
12
|
+
function createPluginWrappedExecutor(appConfig) {
|
|
13
|
+
return withBuildExecutorPlugins(getAppBuildAdapter(appConfig), () => getAppServerBuildPlugins(appConfig));
|
|
14
|
+
}
|
|
15
|
+
function resolveParallelismLimit() {
|
|
16
|
+
return Math.max(1, availableParallelism() - 1);
|
|
17
|
+
}
|
|
9
18
|
function installAppRuntimeBuildExecutor(appConfig) {
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
);
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
const pluginWrappedExecutor = createPluginWrappedExecutor(appConfig);
|
|
20
|
+
const parallelismLimit = resolveParallelismLimit();
|
|
21
|
+
const routeModuleBuildExecutor = new ParallelBuildExecutor(pluginWrappedExecutor, parallelismLimit);
|
|
22
|
+
const hmrBuildExecutor = new ParallelBuildExecutor(pluginWrappedExecutor, Math.min(3, parallelismLimit));
|
|
23
|
+
setAppRouteModuleBuildExecutor(appConfig, routeModuleBuildExecutor);
|
|
24
|
+
setAppHmrBuildExecutor(appConfig, hmrBuildExecutor);
|
|
25
|
+
setAppBuildExecutor(appConfig, routeModuleBuildExecutor);
|
|
26
|
+
return routeModuleBuildExecutor;
|
|
27
|
+
}
|
|
28
|
+
function getInstalledServerEntryBuildExecutor(appConfig) {
|
|
29
|
+
const existing = appConfig.runtime?.serverEntryBuildExecutor;
|
|
30
|
+
if (existing) {
|
|
31
|
+
return existing;
|
|
32
|
+
}
|
|
33
|
+
const serverEntryBuildExecutor = new SerializedBuildExecutor(createPluginWrappedExecutor(appConfig));
|
|
34
|
+
appConfig.runtime = {
|
|
35
|
+
...appConfig.runtime ?? {},
|
|
36
|
+
serverEntryBuildExecutor
|
|
37
|
+
};
|
|
38
|
+
return serverEntryBuildExecutor;
|
|
16
39
|
}
|
|
17
40
|
export {
|
|
41
|
+
getInstalledServerEntryBuildExecutor,
|
|
18
42
|
installAppRuntimeBuildExecutor
|
|
19
43
|
};
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns true if the given specifier is a workspace package.
|
|
3
|
+
*
|
|
4
|
+
* Workspace packages are declared with the `workspace:` protocol and are
|
|
5
|
+
* typically source-only, requiring bundling rather than externalization.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isWorkspacePackageImport(specifier: string, rootDir: string): boolean;
|
|
1
8
|
export declare function isDeclaredAppPackageImport(specifier: string, rootDir: string): boolean;
|
|
2
9
|
export declare function normalizeNodeRuntimeBuildOutputFile(filePath: string, rootDir: string): void;
|
|
3
10
|
export declare function normalizeNodeRuntimeBuildOutputs(outputPaths: string[], rootDir: string): void;
|
|
@@ -4,6 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
const corePackageRequire = createRequire(new URL("../../package.json", import.meta.url));
|
|
6
6
|
const appDeclaredPackageCache = /* @__PURE__ */ new Map();
|
|
7
|
+
const appWorkspacePackageCache = /* @__PURE__ */ new Map();
|
|
7
8
|
const CORE_RUNTIME_BARE_SPECIFIER_PACKAGES = /* @__PURE__ */ new Set(["ws"]);
|
|
8
9
|
function tryResolveRuntimeImport(specifier, resolver) {
|
|
9
10
|
try {
|
|
@@ -15,6 +16,24 @@ function tryResolveRuntimeImport(specifier, resolver) {
|
|
|
15
16
|
function isBareRuntimeImport(specifier) {
|
|
16
17
|
return !specifier.startsWith(".") && !path.isAbsolute(specifier) && !specifier.startsWith("/") && !specifier.startsWith("node:") && !specifier.startsWith("@/") && !specifier.startsWith("~/") && !specifier.startsWith("#") && !specifier.includes(":");
|
|
17
18
|
}
|
|
19
|
+
function extractPackageNameFromFileUrl(specifier) {
|
|
20
|
+
if (!specifier.startsWith("file://")) {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
const lastNodeModulesIndex = specifier.lastIndexOf("/node_modules/");
|
|
24
|
+
if (lastNodeModulesIndex === -1) {
|
|
25
|
+
return void 0;
|
|
26
|
+
}
|
|
27
|
+
const afterNodeModules = specifier.slice(lastNodeModulesIndex + "/node_modules/".length);
|
|
28
|
+
const parts = afterNodeModules.split("/");
|
|
29
|
+
if (parts.length === 0) {
|
|
30
|
+
return void 0;
|
|
31
|
+
}
|
|
32
|
+
if (parts[0]?.startsWith("@") && parts.length > 1) {
|
|
33
|
+
return `${parts[0]}/${parts[1]}`;
|
|
34
|
+
}
|
|
35
|
+
return parts[0];
|
|
36
|
+
}
|
|
18
37
|
function getPackageNameFromSpecifier(specifier) {
|
|
19
38
|
if (specifier.startsWith("@")) {
|
|
20
39
|
return specifier.split("/").slice(0, 2).join("/");
|
|
@@ -47,6 +66,37 @@ function getDeclaredAppPackages(rootDir) {
|
|
|
47
66
|
appDeclaredPackageCache.set(cacheKey, declaredPackages);
|
|
48
67
|
return declaredPackages;
|
|
49
68
|
}
|
|
69
|
+
function getWorkspacePackages(rootDir) {
|
|
70
|
+
const cacheKey = path.resolve(rootDir);
|
|
71
|
+
const cached = appWorkspacePackageCache.get(cacheKey);
|
|
72
|
+
if (cached) {
|
|
73
|
+
return cached;
|
|
74
|
+
}
|
|
75
|
+
const packageJsonPath = path.resolve(rootDir, "package.json");
|
|
76
|
+
const workspacePackages = /* @__PURE__ */ new Set();
|
|
77
|
+
try {
|
|
78
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
79
|
+
for (const field of ["dependencies", "devDependencies", "peerDependencies", "optionalDependencies"]) {
|
|
80
|
+
const entries = packageJson[field];
|
|
81
|
+
if (!entries || typeof entries !== "object") {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
for (const [packageName, version] of Object.entries(entries)) {
|
|
85
|
+
if (typeof version === "string" && version.startsWith("workspace:")) {
|
|
86
|
+
workspacePackages.add(packageName);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
appWorkspacePackageCache.set(cacheKey, workspacePackages);
|
|
92
|
+
return workspacePackages;
|
|
93
|
+
}
|
|
94
|
+
appWorkspacePackageCache.set(cacheKey, workspacePackages);
|
|
95
|
+
return workspacePackages;
|
|
96
|
+
}
|
|
97
|
+
function isWorkspacePackageImport(specifier, rootDir) {
|
|
98
|
+
return getWorkspacePackages(rootDir).has(getPackageNameFromSpecifier(specifier));
|
|
99
|
+
}
|
|
50
100
|
function isDeclaredAppPackageImport(specifier, rootDir) {
|
|
51
101
|
return getDeclaredAppPackages(rootDir).has(getPackageNameFromSpecifier(specifier));
|
|
52
102
|
}
|
|
@@ -75,6 +125,23 @@ function rewriteRuntimeBuildOutputImports(code, rootDir) {
|
|
|
75
125
|
const rewrittenSpecifier = rewriteRuntimeImportSpecifier(specifier, quote, rootDir);
|
|
76
126
|
return `${prefix}${rewrittenSpecifier ?? `${quote}${specifier}${quote}`}`;
|
|
77
127
|
};
|
|
128
|
+
const rewriteFileUrl = (prefix, quote, specifier) => {
|
|
129
|
+
const packageName = extractPackageNameFromFileUrl(specifier);
|
|
130
|
+
if (!packageName) {
|
|
131
|
+
return `${prefix}${quote}${specifier}${quote}`;
|
|
132
|
+
}
|
|
133
|
+
if (!isDeclaredInResolutionChain(packageName, rootDir)) {
|
|
134
|
+
return `${prefix}${quote}${specifier}${quote}`;
|
|
135
|
+
}
|
|
136
|
+
const lastNodeModulesIndex = specifier.lastIndexOf("/node_modules/");
|
|
137
|
+
if (lastNodeModulesIndex === -1) {
|
|
138
|
+
return `${prefix}${quote}${specifier}${quote}`;
|
|
139
|
+
}
|
|
140
|
+
const afterNodeModules = specifier.slice(lastNodeModulesIndex + "/node_modules/".length);
|
|
141
|
+
const afterPackage = afterNodeModules.slice(packageName.length);
|
|
142
|
+
const bareSpecifier = packageName + afterPackage;
|
|
143
|
+
return `${prefix}${quote}${bareSpecifier}${quote}`;
|
|
144
|
+
};
|
|
78
145
|
const withResolvedPackages = code.replace(
|
|
79
146
|
/(\bfrom\s+)(['"])([^'"\\]+)\2/g,
|
|
80
147
|
(_match, prefix, quote, specifier) => rewriteSpecifier(prefix, quote, specifier)
|
|
@@ -85,7 +152,17 @@ function rewriteRuntimeBuildOutputImports(code, rootDir) {
|
|
|
85
152
|
/(\bimport\s*\(\s*)(['"])([^'"\\]+)\2/g,
|
|
86
153
|
(_match, prefix, quote, specifier) => rewriteSpecifier(prefix, quote, specifier)
|
|
87
154
|
);
|
|
88
|
-
|
|
155
|
+
const withBareSpecifiers = withResolvedPackages.replace(
|
|
156
|
+
/(\bfrom\s+)(['"])(file:\/\/[^'"\\]+)\2/g,
|
|
157
|
+
(_match, prefix, quote, specifier) => rewriteFileUrl(prefix, quote, specifier)
|
|
158
|
+
).replace(
|
|
159
|
+
/(\bimport\s+)(['"])(file:\/\/[^'"\\]+)\2/g,
|
|
160
|
+
(_match, prefix, quote, specifier) => rewriteFileUrl(prefix, quote, specifier)
|
|
161
|
+
).replace(
|
|
162
|
+
/(\bimport\s*\(\s*)(['"])(file:\/\/[^'"\\]+)\2/g,
|
|
163
|
+
(_match, prefix, quote, specifier) => rewriteFileUrl(prefix, quote, specifier)
|
|
164
|
+
);
|
|
165
|
+
return withBareSpecifiers.replace(
|
|
89
166
|
/(['"])(@oxc-project\/runtime\/(?:helpers(?:\/esm)?\/[^'"\\]+))\1/g,
|
|
90
167
|
(match, quote, specifier) => {
|
|
91
168
|
const resolvedPath = tryResolveRuntimeImport(specifier, corePackageRequire);
|
|
@@ -94,6 +171,9 @@ function rewriteRuntimeBuildOutputImports(code, rootDir) {
|
|
|
94
171
|
);
|
|
95
172
|
}
|
|
96
173
|
function normalizeNodeRuntimeBuildOutputFile(filePath, rootDir) {
|
|
174
|
+
if (process.env.ECOPAGES_LOGGER_DEBUG === "true") {
|
|
175
|
+
console.log(`[normalizeNodeRuntimeBuildOutputFile] Checking ${filePath}`);
|
|
176
|
+
}
|
|
97
177
|
if (!/\.(?:[cm]?js)$/u.test(filePath)) {
|
|
98
178
|
return;
|
|
99
179
|
}
|
|
@@ -104,6 +184,9 @@ function normalizeNodeRuntimeBuildOutputFile(filePath, rootDir) {
|
|
|
104
184
|
const code = fileSystem.readFileSync(filePath, "utf-8");
|
|
105
185
|
const rewritten = rewriteRuntimeBuildOutputImports(code, rootDir);
|
|
106
186
|
if (rewritten !== code) {
|
|
187
|
+
if (process.env.ECOPAGES_LOGGER_DEBUG === "true") {
|
|
188
|
+
console.log(`[normalizeNodeRuntimeBuildOutputFile] Rewriting ${filePath}`);
|
|
189
|
+
}
|
|
107
190
|
fileSystem.writeFileSync(filePath, rewritten);
|
|
108
191
|
}
|
|
109
192
|
}
|
|
@@ -114,6 +197,7 @@ function normalizeNodeRuntimeBuildOutputs(outputPaths, rootDir) {
|
|
|
114
197
|
}
|
|
115
198
|
export {
|
|
116
199
|
isDeclaredAppPackageImport,
|
|
200
|
+
isWorkspacePackageImport,
|
|
117
201
|
normalizeNodeRuntimeBuildOutputFile,
|
|
118
202
|
normalizeNodeRuntimeBuildOutputs
|
|
119
203
|
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { BuildResult } from './build-adapter.js';
|
|
2
|
+
import type { EcoPagesAppConfig } from '../types/internal-types.js';
|
|
3
|
+
/** @deprecated Use {@link ROUTE_MODULE_BUILD_CACHE_FILENAME}; both caches share the same manifest filename. */
|
|
4
|
+
export declare const SERVER_ENTRY_BUILD_CACHE_FILENAME = ".build-cache.json";
|
|
5
|
+
export declare const SERVER_BUNDLE_MANIFEST_FILENAME = "manifest.json";
|
|
6
|
+
export interface ServerBundleDeployManifest {
|
|
7
|
+
serverEntry: string;
|
|
8
|
+
distDir: string;
|
|
9
|
+
builtAt: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ServerEntryBuildCacheManifest {
|
|
12
|
+
invalidationVersion: string;
|
|
13
|
+
entryPath: string;
|
|
14
|
+
entryHash: string;
|
|
15
|
+
buildInputsFingerprint: string;
|
|
16
|
+
buildKey: string;
|
|
17
|
+
outputPaths: string[];
|
|
18
|
+
dependencyHashes: Record<string, string>;
|
|
19
|
+
builtAt: number;
|
|
20
|
+
}
|
|
21
|
+
export interface ServerEntryBuildCacheLookup {
|
|
22
|
+
manifest: ServerEntryBuildCacheManifest;
|
|
23
|
+
outputPaths: string[];
|
|
24
|
+
}
|
|
25
|
+
export declare function getServerBundleOutputPaths(appConfig: EcoPagesAppConfig): {
|
|
26
|
+
distDir: string;
|
|
27
|
+
serverOutdir: string;
|
|
28
|
+
serverEntryPath: string;
|
|
29
|
+
manifestPath: string;
|
|
30
|
+
};
|
|
31
|
+
export declare function readServerEntryBuildCacheManifest(appConfig: EcoPagesAppConfig): ServerEntryBuildCacheManifest | undefined;
|
|
32
|
+
export declare function writeServerBundleDeployManifest(appConfig: EcoPagesAppConfig, serverEntryPath: string): void;
|
|
33
|
+
export declare function lookupServerEntryBuildCache(options: {
|
|
34
|
+
appConfig: EcoPagesAppConfig;
|
|
35
|
+
entryPath: string;
|
|
36
|
+
force?: boolean;
|
|
37
|
+
}): ServerEntryBuildCacheLookup | undefined;
|
|
38
|
+
export declare function recordServerEntryBuildCache(options: {
|
|
39
|
+
appConfig: EcoPagesAppConfig;
|
|
40
|
+
entryPath: string;
|
|
41
|
+
buildResult: BuildResult;
|
|
42
|
+
outputPaths: string[];
|
|
43
|
+
}): void;
|
|
44
|
+
export declare function resolveProductionServerEntry(cwd?: string): string | undefined;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
import { getAppServerBuildPlugins } from "./build-adapter.js";
|
|
4
|
+
import { createBuildInputsFingerprint } from "./build-input-fingerprint.js";
|
|
5
|
+
import { getCorePackageVersion } from "../services/module-loading/route-module-build-manifest.js";
|
|
6
|
+
import {
|
|
7
|
+
RouteModuleDependencyHasher,
|
|
8
|
+
createRouteModuleDependencyHashes
|
|
9
|
+
} from "../services/module-loading/route-module-dependency-hasher.js";
|
|
10
|
+
import { resolveInternalExecutionDir } from "../utils/resolve-work-dir.js";
|
|
11
|
+
import { SERVER_BUNDLE_DIR, SERVER_BUNDLE_FILENAME } from "../utils/resolve-entry-file.js";
|
|
12
|
+
import { ROUTE_MODULE_BUILD_CACHE_FILENAME } from "../services/module-loading/route-module-build-manifest.js";
|
|
13
|
+
const SERVER_ENTRY_BUILD_CACHE_FILENAME = ROUTE_MODULE_BUILD_CACHE_FILENAME;
|
|
14
|
+
const SERVER_BUNDLE_MANIFEST_FILENAME = "manifest.json";
|
|
15
|
+
function getServerEntryCacheDir(appConfig) {
|
|
16
|
+
return path.join(resolveInternalExecutionDir(appConfig), ".server-entry");
|
|
17
|
+
}
|
|
18
|
+
function getServerEntryCacheManifestPath(appConfig) {
|
|
19
|
+
return path.join(getServerEntryCacheDir(appConfig), SERVER_ENTRY_BUILD_CACHE_FILENAME);
|
|
20
|
+
}
|
|
21
|
+
function hashDependencyGraph(entryPath, dependencyGraph, hasher, rootDir) {
|
|
22
|
+
if (!dependencyGraph) {
|
|
23
|
+
return { [path.resolve(entryPath)]: fileSystem.hash(entryPath) };
|
|
24
|
+
}
|
|
25
|
+
return createRouteModuleDependencyHashes(hasher, { dependencyGraph }, entryPath, rootDir);
|
|
26
|
+
}
|
|
27
|
+
function createServerEntryBuildKey(appConfig) {
|
|
28
|
+
try {
|
|
29
|
+
const plugins = getAppServerBuildPlugins(appConfig);
|
|
30
|
+
const pluginNames = plugins.map((plugin) => plugin.name).sort().join("|");
|
|
31
|
+
return ["node", "esm", "external-packages", pluginNames].join("::");
|
|
32
|
+
} catch {
|
|
33
|
+
return "node::esm::external-packages";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function getServerBundleOutputPaths(appConfig) {
|
|
37
|
+
const distDir = appConfig.absolutePaths?.distDir ?? path.join(appConfig.rootDir, appConfig.distDir);
|
|
38
|
+
const serverOutdir = path.join(distDir, SERVER_BUNDLE_DIR);
|
|
39
|
+
const serverEntryPath = path.join(serverOutdir, SERVER_BUNDLE_FILENAME);
|
|
40
|
+
const manifestPath = path.join(serverOutdir, SERVER_BUNDLE_MANIFEST_FILENAME);
|
|
41
|
+
return { distDir, serverOutdir, serverEntryPath, manifestPath };
|
|
42
|
+
}
|
|
43
|
+
function readServerEntryBuildCacheManifest(appConfig) {
|
|
44
|
+
const manifestPath = getServerEntryCacheManifestPath(appConfig);
|
|
45
|
+
if (!fileSystem.exists(manifestPath)) {
|
|
46
|
+
return void 0;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(fileSystem.readFileSync(manifestPath));
|
|
50
|
+
if (!parsed || typeof parsed !== "object") {
|
|
51
|
+
return void 0;
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
} catch {
|
|
55
|
+
return void 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function writeServerEntryBuildCacheManifest(appConfig, manifest) {
|
|
59
|
+
const cacheDir = getServerEntryCacheDir(appConfig);
|
|
60
|
+
fileSystem.ensureDir(cacheDir);
|
|
61
|
+
fileSystem.write(
|
|
62
|
+
path.join(cacheDir, SERVER_ENTRY_BUILD_CACHE_FILENAME),
|
|
63
|
+
`${JSON.stringify(manifest, null, " ")}
|
|
64
|
+
`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
function writeServerBundleDeployManifest(appConfig, serverEntryPath) {
|
|
68
|
+
const { distDir, manifestPath } = getServerBundleOutputPaths(appConfig);
|
|
69
|
+
fileSystem.ensureDir(path.dirname(manifestPath));
|
|
70
|
+
const payload = {
|
|
71
|
+
serverEntry: path.relative(path.dirname(manifestPath), serverEntryPath) || SERVER_BUNDLE_FILENAME,
|
|
72
|
+
distDir,
|
|
73
|
+
builtAt: Date.now()
|
|
74
|
+
};
|
|
75
|
+
fileSystem.write(manifestPath, `${JSON.stringify(payload, null, " ")}
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
function lookupServerEntryBuildCache(options) {
|
|
79
|
+
if (options.force) {
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
if (process.env.NODE_ENV !== "production") {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
const entryPath = path.resolve(options.entryPath);
|
|
86
|
+
const entryHash = fileSystem.hash(entryPath);
|
|
87
|
+
const buildInputsFingerprint = createBuildInputsFingerprint(options.appConfig);
|
|
88
|
+
const buildKey = createServerEntryBuildKey(options.appConfig);
|
|
89
|
+
const manifest = readServerEntryBuildCacheManifest(options.appConfig);
|
|
90
|
+
if (!manifest) {
|
|
91
|
+
return void 0;
|
|
92
|
+
}
|
|
93
|
+
if (manifest.invalidationVersion !== getCorePackageVersion()) {
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
if (manifest.entryPath !== entryPath || manifest.entryHash !== entryHash || manifest.buildInputsFingerprint !== buildInputsFingerprint || manifest.buildKey !== buildKey) {
|
|
97
|
+
return void 0;
|
|
98
|
+
}
|
|
99
|
+
const hasher = new RouteModuleDependencyHasher();
|
|
100
|
+
if (!hasher.matchesStoredHashes(manifest.dependencyHashes, entryPath, entryHash)) {
|
|
101
|
+
return void 0;
|
|
102
|
+
}
|
|
103
|
+
const existingOutputs = manifest.outputPaths.filter((outputPath) => fileSystem.exists(outputPath));
|
|
104
|
+
if (existingOutputs.length === 0) {
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
return {
|
|
108
|
+
manifest,
|
|
109
|
+
outputPaths: existingOutputs
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function recordServerEntryBuildCache(options) {
|
|
113
|
+
if (process.env.NODE_ENV !== "production") {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const entryPath = path.resolve(options.entryPath);
|
|
117
|
+
const entryHash = fileSystem.hash(entryPath);
|
|
118
|
+
const hasher = new RouteModuleDependencyHasher();
|
|
119
|
+
const dependencyHashes = hashDependencyGraph(
|
|
120
|
+
entryPath,
|
|
121
|
+
options.buildResult.dependencyGraph,
|
|
122
|
+
hasher,
|
|
123
|
+
options.appConfig.rootDir
|
|
124
|
+
);
|
|
125
|
+
const manifest = {
|
|
126
|
+
invalidationVersion: getCorePackageVersion(),
|
|
127
|
+
entryPath,
|
|
128
|
+
entryHash,
|
|
129
|
+
buildInputsFingerprint: createBuildInputsFingerprint(options.appConfig),
|
|
130
|
+
buildKey: createServerEntryBuildKey(options.appConfig),
|
|
131
|
+
outputPaths: options.outputPaths,
|
|
132
|
+
dependencyHashes,
|
|
133
|
+
builtAt: Date.now()
|
|
134
|
+
};
|
|
135
|
+
writeServerEntryBuildCacheManifest(options.appConfig, manifest);
|
|
136
|
+
}
|
|
137
|
+
function resolveProductionServerEntry(cwd = process.cwd()) {
|
|
138
|
+
const manifestPath = path.join(cwd, "dist", SERVER_BUNDLE_DIR, SERVER_BUNDLE_MANIFEST_FILENAME);
|
|
139
|
+
if (fileSystem.exists(manifestPath)) {
|
|
140
|
+
try {
|
|
141
|
+
const parsed = JSON.parse(fileSystem.readFileSync(manifestPath));
|
|
142
|
+
if (parsed?.serverEntry) {
|
|
143
|
+
const fromManifest = path.isAbsolute(parsed.serverEntry) ? parsed.serverEntry : path.join(path.dirname(manifestPath), parsed.serverEntry);
|
|
144
|
+
if (fileSystem.exists(fromManifest)) {
|
|
145
|
+
return fromManifest;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (parsed?.distDir) {
|
|
149
|
+
const fromDistDir = path.join(parsed.distDir, SERVER_BUNDLE_DIR, SERVER_BUNDLE_FILENAME);
|
|
150
|
+
if (fileSystem.exists(fromDistDir)) {
|
|
151
|
+
return fromDistDir;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const legacyPath = path.join(cwd, "dist", SERVER_BUNDLE_DIR, SERVER_BUNDLE_FILENAME);
|
|
158
|
+
return fileSystem.exists(legacyPath) ? legacyPath : void 0;
|
|
159
|
+
}
|
|
160
|
+
export {
|
|
161
|
+
SERVER_BUNDLE_MANIFEST_FILENAME,
|
|
162
|
+
SERVER_ENTRY_BUILD_CACHE_FILENAME,
|
|
163
|
+
getServerBundleOutputPaths,
|
|
164
|
+
lookupServerEntryBuildCache,
|
|
165
|
+
readServerEntryBuildCacheManifest,
|
|
166
|
+
recordServerEntryBuildCache,
|
|
167
|
+
resolveProductionServerEntry,
|
|
168
|
+
writeServerBundleDeployManifest
|
|
169
|
+
};
|
package/src/hmr/hmr.test.e2e.js
CHANGED
|
@@ -4,6 +4,15 @@ import { writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { resolve } from "node:path";
|
|
5
5
|
const FIXTURE_DIR = resolve(process.cwd(), "packages/core/__fixtures__/app");
|
|
6
6
|
const TEST_CSS_FILE = resolve(FIXTURE_DIR, "src/pages/index.css");
|
|
7
|
+
async function readMainTitleColor(page) {
|
|
8
|
+
return page.evaluate(() => {
|
|
9
|
+
const element = document.querySelector(".main-title");
|
|
10
|
+
if (!(element instanceof HTMLElement)) {
|
|
11
|
+
return "";
|
|
12
|
+
}
|
|
13
|
+
return getComputedStyle(element).color;
|
|
14
|
+
});
|
|
15
|
+
}
|
|
7
16
|
test.describe("HMR E2E", () => {
|
|
8
17
|
test("should load page with .main-title element", async ({ page }) => {
|
|
9
18
|
await page.goto("/", { waitUntil: "networkidle" });
|
|
@@ -23,9 +32,10 @@ test.describe("HMR E2E", () => {
|
|
|
23
32
|
const originalCss = readFileSync(TEST_CSS_FILE, "utf-8");
|
|
24
33
|
const title = page.locator(".main-title").first();
|
|
25
34
|
try {
|
|
26
|
-
await page.goto("/", { waitUntil: "
|
|
35
|
+
await page.goto("/", { waitUntil: "domcontentloaded" });
|
|
27
36
|
await expect(title).toBeVisible();
|
|
28
|
-
|
|
37
|
+
await expect.poll(async () => readMainTitleColor(page), { timeout: 1e4 }).not.toBe("");
|
|
38
|
+
const initialColor = await readMainTitleColor(page);
|
|
29
39
|
expect(initialColor).toBeTruthy();
|
|
30
40
|
const modifiedCss = originalCss.replace(".main-title {", ".main-title {\n color: rgb(255, 0, 0);");
|
|
31
41
|
const reloadPromise = page.waitForEvent("framenavigated", {
|
|
@@ -34,8 +44,9 @@ test.describe("HMR E2E", () => {
|
|
|
34
44
|
});
|
|
35
45
|
await writeFile(TEST_CSS_FILE, modifiedCss, { flush: true });
|
|
36
46
|
await reloadPromise;
|
|
37
|
-
await page.waitForLoadState("
|
|
38
|
-
await expect
|
|
47
|
+
await page.waitForLoadState("domcontentloaded");
|
|
48
|
+
await expect(title).toBeVisible();
|
|
49
|
+
await expect.poll(async () => readMainTitleColor(page), { timeout: 1e4 }).toBe("rgb(255, 0, 0)");
|
|
39
50
|
} finally {
|
|
40
51
|
await writeFile(TEST_CSS_FILE, originalCss, { flush: true });
|
|
41
52
|
}
|