@ecopages/core 0.2.0-beta.7 → 0.2.0-beta.9
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 +27 -3
- 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 -6
- package/src/adapters/shared/server-static-builder.js +90 -34
- 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 +27 -2
- package/src/build/build-adapter.js +75 -32
- 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 +7 -1
- 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/rolldown-plugin-bridge.d.ts +10 -1
- package/src/build/rolldown-plugin-bridge.js +17 -5
- package/src/build/rolldown-source-transform-pass.d.ts +15 -0
- package/src/build/rolldown-source-transform-pass.js +58 -0
- package/src/build/runtime-build-executor.d.ts +7 -13
- package/src/build/runtime-build-executor.js +31 -7
- package/src/build/server-entry-build-cache.d.ts +44 -0
- package/src/build/server-entry-build-cache.js +169 -0
- package/src/config/config-builder.d.ts +11 -1
- package/src/config/config-builder.js +11 -1
- 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/plugins/source-transform.d.ts +31 -0
- package/src/plugins/source-transform.js +23 -1
- 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 +3 -1
- package/src/services/assets/browser-bundle.service.js +14 -5
- package/src/services/html/html-transformer.service.d.ts +1 -1
- package/src/services/html/html-transformer.service.js +4 -4
- package/src/services/invalidation/development-invalidation.service.d.ts +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 -8
- package/src/static-site-generator/static-site-generator.js +176 -62
- 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
|
@@ -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 ?? [];
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
* on every source file.
|
|
41
41
|
*/
|
|
42
42
|
import type { Plugin } from 'rolldown';
|
|
43
|
+
import type { EcoSourceTransform } from '../plugins/source-transform.js';
|
|
43
44
|
import type { EcoBuildPlugin } from './build-types.js';
|
|
44
45
|
/**
|
|
45
46
|
* Creates a Rolldown `Plugin` array that drives the supplied
|
|
@@ -55,5 +56,13 @@ import type { EcoBuildPlugin } from './build-types.js';
|
|
|
55
56
|
* Plugin ordering is preserved: registrations from earlier eco plugins
|
|
56
57
|
* are checked before registrations from later ones, matching the
|
|
57
58
|
* original per-plugin priority semantics.
|
|
59
|
+
*
|
|
60
|
+
* @param plugins - `EcoBuildPlugin` instances registered for this build.
|
|
61
|
+
* @param contextRoot - Project root used to resolve relative load paths.
|
|
62
|
+
* @param sourceTransforms - Optional app-owned transforms applied after a matching
|
|
63
|
+
* `onLoad` handler returns module contents. Browser builds pass
|
|
64
|
+
* {@link getAppSourceTransforms | app source transforms} here so metadata injection
|
|
65
|
+
* still runs on output rewritten by boundary/runtime plugins. Virtual modules,
|
|
66
|
+
* CSS, and asset loads are skipped.
|
|
58
67
|
*/
|
|
59
|
-
export declare function createRolldownPluginBridge(plugins: EcoBuildPlugin[], contextRoot: string): Plugin[];
|
|
68
|
+
export declare function createRolldownPluginBridge(plugins: EcoBuildPlugin[], contextRoot: string, sourceTransforms?: readonly EcoSourceTransform[]): Plugin[];
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { finalizeLoadResultWithSourceTransforms } from "./rolldown-source-transform-pass.js";
|
|
2
3
|
import { escapeRegExp } from "./browser-runtime-plugin-helpers.js";
|
|
3
4
|
const NAMESPACE_SEPARATOR = ":";
|
|
4
5
|
function joinNamespace(namespace, value) {
|
|
@@ -124,7 +125,7 @@ function convertPluginOnResolveResult(result, importer, contextRoot) {
|
|
|
124
125
|
}
|
|
125
126
|
return partial;
|
|
126
127
|
}
|
|
127
|
-
function createRolldownPluginBridge(plugins, contextRoot) {
|
|
128
|
+
function createRolldownPluginBridge(plugins, contextRoot, sourceTransforms = []) {
|
|
128
129
|
if (plugins.length === 0) {
|
|
129
130
|
return [];
|
|
130
131
|
}
|
|
@@ -152,18 +153,29 @@ function createRolldownPluginBridge(plugins, contextRoot) {
|
|
|
152
153
|
return void 0;
|
|
153
154
|
};
|
|
154
155
|
const loadHandler = async (id) => {
|
|
156
|
+
let loadResult;
|
|
155
157
|
for (const { filter, callback } of loadRegistrations) {
|
|
156
158
|
if (!filter.test(id)) {
|
|
157
159
|
continue;
|
|
158
160
|
}
|
|
159
|
-
const { namespace, path:
|
|
160
|
-
const result = await callback({ path:
|
|
161
|
+
const { namespace: namespace2, path: sourcePath2 } = splitNamespace(id);
|
|
162
|
+
const result = await callback({ path: sourcePath2, namespace: namespace2 });
|
|
161
163
|
const converted = convertPluginOnLoadResult({ id }, result);
|
|
162
164
|
if (converted !== void 0) {
|
|
163
|
-
|
|
165
|
+
loadResult = converted;
|
|
166
|
+
break;
|
|
164
167
|
}
|
|
165
168
|
}
|
|
166
|
-
|
|
169
|
+
const { namespace, path: sourcePath } = splitNamespace(id);
|
|
170
|
+
return finalizeLoadResultWithSourceTransforms({
|
|
171
|
+
id,
|
|
172
|
+
namespace,
|
|
173
|
+
sourcePath,
|
|
174
|
+
loadResult,
|
|
175
|
+
sourceTransforms,
|
|
176
|
+
contextRoot,
|
|
177
|
+
inferModuleTypeFromPath: (filePath) => inferRolldownModuleTypeFromPath(filePath)
|
|
178
|
+
});
|
|
167
179
|
};
|
|
168
180
|
const plugin = {
|
|
169
181
|
name: "ecopages-plugin-bridge",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { LoadResult, SourceDescription } from 'rolldown';
|
|
2
|
+
import { type EcoSourceTransform } from '../plugins/source-transform.js';
|
|
3
|
+
/**
|
|
4
|
+
* Runs app-owned source transforms after first-wins `onLoad` plugins produce
|
|
5
|
+
* module contents for one Rolldown load request.
|
|
6
|
+
*/
|
|
7
|
+
export declare function finalizeLoadResultWithSourceTransforms(options: {
|
|
8
|
+
id: string;
|
|
9
|
+
namespace: string | undefined;
|
|
10
|
+
sourcePath: string;
|
|
11
|
+
loadResult: LoadResult | undefined;
|
|
12
|
+
sourceTransforms: readonly EcoSourceTransform[];
|
|
13
|
+
contextRoot: string;
|
|
14
|
+
inferModuleTypeFromPath: (filePath: string) => SourceDescription['moduleType'];
|
|
15
|
+
}): Promise<LoadResult | undefined>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
import { applySourceTransforms, normalizeTransformId } from "../plugins/source-transform.js";
|
|
4
|
+
const TRANSFORMABLE_SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mdx"]);
|
|
5
|
+
function shouldApplySourceTransforms(namespace, sourcePath) {
|
|
6
|
+
if (namespace !== void 0) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
const normalizedPath = normalizeTransformId(sourcePath);
|
|
10
|
+
return TRANSFORMABLE_SOURCE_EXTENSIONS.has(path.extname(normalizedPath).toLowerCase());
|
|
11
|
+
}
|
|
12
|
+
function shouldTransformLoadedModule(moduleType) {
|
|
13
|
+
return moduleType !== "css" && moduleType !== "asset";
|
|
14
|
+
}
|
|
15
|
+
function applyTransformsToLoadedSource(sourceTransforms, id, sourcePath, code) {
|
|
16
|
+
if (sourceTransforms.length === 0) {
|
|
17
|
+
return code;
|
|
18
|
+
}
|
|
19
|
+
return applySourceTransforms(sourceTransforms, code, sourcePath);
|
|
20
|
+
}
|
|
21
|
+
async function finalizeLoadResultWithSourceTransforms(options) {
|
|
22
|
+
const { id, namespace, sourcePath, loadResult, sourceTransforms, contextRoot, inferModuleTypeFromPath } = options;
|
|
23
|
+
if (!shouldApplySourceTransforms(namespace, sourcePath)) {
|
|
24
|
+
return loadResult;
|
|
25
|
+
}
|
|
26
|
+
if (typeof loadResult === "object" && loadResult !== null && "code" in loadResult && typeof loadResult.code === "string") {
|
|
27
|
+
if (!shouldTransformLoadedModule(loadResult.moduleType)) {
|
|
28
|
+
return loadResult;
|
|
29
|
+
}
|
|
30
|
+
const transformedCode2 = applyTransformsToLoadedSource(sourceTransforms, id, sourcePath, loadResult.code);
|
|
31
|
+
if (transformedCode2 === loadResult.code) {
|
|
32
|
+
return loadResult;
|
|
33
|
+
}
|
|
34
|
+
return {
|
|
35
|
+
code: transformedCode2,
|
|
36
|
+
moduleType: loadResult.moduleType
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (loadResult !== void 0 || sourceTransforms.length === 0) {
|
|
40
|
+
return loadResult;
|
|
41
|
+
}
|
|
42
|
+
const normalizedPath = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(contextRoot, sourcePath);
|
|
43
|
+
if (!fileSystem.exists(normalizedPath)) {
|
|
44
|
+
return loadResult;
|
|
45
|
+
}
|
|
46
|
+
const originalCode = fileSystem.readFileSync(normalizedPath);
|
|
47
|
+
const transformedCode = applyTransformsToLoadedSource(sourceTransforms, id, sourcePath, originalCode);
|
|
48
|
+
if (transformedCode === originalCode) {
|
|
49
|
+
return loadResult;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
code: transformedCode,
|
|
53
|
+
moduleType: inferModuleTypeFromPath(normalizedPath)
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
finalizeLoadResultWithSourceTransforms
|
|
58
|
+
};
|
|
@@ -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
|
};
|
|
@@ -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
|
+
};
|
|
@@ -250,7 +250,17 @@ export declare class ConfigBuilder {
|
|
|
250
250
|
private compareVersions;
|
|
251
251
|
/**
|
|
252
252
|
* Initializes default loaders that are required for EcoPages to function.
|
|
253
|
-
*
|
|
253
|
+
*
|
|
254
|
+
* @remarks
|
|
255
|
+
* `eco-component-meta` is registered twice on purpose:
|
|
256
|
+
*
|
|
257
|
+
* - `sourceTransforms` is the canonical browser/HMR path. The Rolldown bridge
|
|
258
|
+
* runs these after first-wins `onLoad` plugins rewrite module source.
|
|
259
|
+
* - `loaders` keeps the same transform available to server-oriented builds
|
|
260
|
+
* that still use competing `onLoad` handlers directly.
|
|
261
|
+
*
|
|
262
|
+
* Browser builds exclude the loader copy in {@link getAppBrowserBuildPlugins}
|
|
263
|
+
* when the transform name is already present in `sourceTransforms`.
|
|
254
264
|
*/
|
|
255
265
|
private initializeDefaultLoaders;
|
|
256
266
|
private reviewBaseUrl;
|
|
@@ -547,7 +547,17 @@ class ConfigBuilder {
|
|
|
547
547
|
}
|
|
548
548
|
/**
|
|
549
549
|
* Initializes default loaders that are required for EcoPages to function.
|
|
550
|
-
*
|
|
550
|
+
*
|
|
551
|
+
* @remarks
|
|
552
|
+
* `eco-component-meta` is registered twice on purpose:
|
|
553
|
+
*
|
|
554
|
+
* - `sourceTransforms` is the canonical browser/HMR path. The Rolldown bridge
|
|
555
|
+
* runs these after first-wins `onLoad` plugins rewrite module source.
|
|
556
|
+
* - `loaders` keeps the same transform available to server-oriented builds
|
|
557
|
+
* that still use competing `onLoad` handlers directly.
|
|
558
|
+
*
|
|
559
|
+
* Browser builds exclude the loader copy in {@link getAppBrowserBuildPlugins}
|
|
560
|
+
* when the transform name is already present in `sourceTransforms`.
|
|
551
561
|
*/
|
|
552
562
|
async initializeDefaultLoaders() {
|
|
553
563
|
const componentMetaTransform = createEcoComponentMetaTransform({ config: this.config });
|
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
|
}
|