@ecopages/core 0.2.0-alpha.53 → 0.2.0-alpha.55
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/README.md +2 -3
- package/package.json +34 -14
- package/src/adapters/bun/server-adapter.d.ts +7 -0
- package/src/adapters/bun/server-adapter.js +8 -3
- package/src/adapters/node/server-adapter.d.ts +1 -1
- package/src/adapters/node/server-adapter.js +2 -4
- package/src/build/README.md +57 -73
- package/src/build/browser-runtime-plugin-helpers.d.ts +26 -0
- package/src/build/browser-runtime-plugin-helpers.js +14 -0
- package/src/build/browser-runtime-plugin.d.ts +78 -0
- package/src/build/{browser-runtime-import-rewrite-plugin.js → browser-runtime-plugin.js} +49 -42
- package/src/build/build-adapter.d.ts +350 -93
- package/src/build/build-adapter.js +61 -492
- package/src/build/build-manifest.js +3 -6
- package/src/build/build-types.d.ts +2 -2
- package/src/build/rolldown-build-adapter.d.ts +32 -0
- package/src/build/rolldown-build-adapter.js +260 -0
- package/src/build/rolldown-plugin-bridge.d.ts +50 -0
- package/src/build/rolldown-plugin-bridge.js +194 -0
- package/src/build/runtime-build-executor.d.ts +14 -7
- package/src/build/runtime-build-executor.js +8 -11
- package/src/build/runtime-build-output-normalizer.d.ts +3 -0
- package/src/build/runtime-build-output-normalizer.js +111 -0
- package/src/build/serialized-build-executor.d.ts +64 -0
- package/src/build/serialized-build-executor.js +63 -0
- package/src/build/server-side-css-shim-plugin.d.ts +41 -0
- package/src/build/server-side-css-shim-plugin.js +35 -0
- package/src/cache/index.d.ts +6 -0
- package/src/cache/index.js +6 -0
- package/src/cache/module-parse-cache.d.ts +65 -0
- package/src/cache/module-parse-cache.js +75 -0
- package/src/config/README.md +1 -1
- package/src/config/config-builder.d.ts +3 -3
- package/src/config/config-builder.js +5 -14
- package/src/eco/eco.types.d.ts +2 -5
- package/src/hmr/strategies/js-hmr-strategy.d.ts +2 -2
- package/src/hmr/strategies/js-hmr-strategy.js +2 -2
- package/src/plugins/alias-resolver-cache.d.ts +68 -0
- package/src/plugins/alias-resolver-cache.js +106 -0
- package/src/plugins/alias-resolver-plugin.d.ts +4 -1
- package/src/plugins/alias-resolver-plugin.js +9 -5
- package/src/plugins/eco-component-meta-plugin.js +2 -2
- package/src/plugins/foreign-jsx-override-plugin.d.ts +1 -1
- package/src/route-renderer/orchestration/render-output.utils.d.ts +1 -1
- package/src/services/assets/browser-bundle.service.d.ts +1 -1
- package/src/services/module-loading/app-module-loader.service.d.ts +1 -1
- package/src/services/module-loading/app-server-module-transpiler.service.js +8 -29
- package/src/services/module-loading/page-module-import.service.js +2 -0
- package/src/types/internal-types.d.ts +1 -1
- package/src/build/browser-runtime-import-rewrite-plugin.d.ts +0 -26
- package/src/build/dev-build-coordinator.d.ts +0 -72
- package/src/build/dev-build-coordinator.js +0 -154
- package/src/build/esbuild-build-adapter.d.ts +0 -79
- package/src/build/esbuild-build-adapter.js +0 -521
- package/src/build/runtime-specifier-alias-plugin.d.ts +0 -15
- package/src/build/runtime-specifier-alias-plugin.js +0 -31
- package/src/services/module-loading/node-bootstrap-plugin.d.ts +0 -38
- package/src/services/module-loading/node-bootstrap-plugin.js +0 -215
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { BuildExecutor, BuildOptions, BuildResult } from './build-adapter.js';
|
|
2
|
+
/**
|
|
3
|
+
* FIFO-serialized build executor wrapper.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Wraps any `BuildExecutor` and ensures that no two builds run
|
|
7
|
+
* concurrently. The next build waits for the previous one to finish
|
|
8
|
+
* (success or failure) before starting.
|
|
9
|
+
*
|
|
10
|
+
* This is a bundler-agnostic primitive. It is unaware of bundler
|
|
11
|
+
* lifecycle events and exists purely to enforce one build at a time.
|
|
12
|
+
* Use it from the dev watch pipeline, the static preview path, or any
|
|
13
|
+
* caller that issues builds concurrently.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* FIFO-serialized wrapper around a {@link BuildExecutor}.
|
|
17
|
+
*
|
|
18
|
+
* Every `build()` call enqueues onto the wrapper's internal queue. A
|
|
19
|
+
* build only starts after the previous build's promise has settled.
|
|
20
|
+
*/
|
|
21
|
+
export declare class SerializedBuildExecutor implements BuildExecutor {
|
|
22
|
+
private readonly inner;
|
|
23
|
+
private tail;
|
|
24
|
+
constructor(inner: BuildExecutor);
|
|
25
|
+
/**
|
|
26
|
+
* Run a build through the serialized queue. The returned promise
|
|
27
|
+
* resolves with the inner executor's `BuildResult`. A rejected
|
|
28
|
+
* inner build does not block the queue — the next caller can
|
|
29
|
+
* proceed.
|
|
30
|
+
*/
|
|
31
|
+
build(options: BuildOptions): Promise<BuildResult>;
|
|
32
|
+
/**
|
|
33
|
+
* Run an arbitrary async operation through the serialized queue.
|
|
34
|
+
*
|
|
35
|
+
* Use this when the work is not a `build()` call against a wrapped
|
|
36
|
+
* executor (e.g. when composing with a coordinator that needs to
|
|
37
|
+
* inject recovery around the inner call). The returned promise
|
|
38
|
+
* resolves with the operation's result. A rejected operation does
|
|
39
|
+
* not block the queue.
|
|
40
|
+
*/
|
|
41
|
+
run<T>(operation: () => Promise<T>): Promise<T>;
|
|
42
|
+
/**
|
|
43
|
+
* Returns the wrapped executor. Tests use this to assert the
|
|
44
|
+
* delegation chain.
|
|
45
|
+
*/
|
|
46
|
+
unwrap(): BuildExecutor;
|
|
47
|
+
/**
|
|
48
|
+
* Reset the queue to an empty state. Tests use this to recover
|
|
49
|
+
* from a poisoned tail.
|
|
50
|
+
*/
|
|
51
|
+
resetForTests(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Overrides the internal queue tail for fault-recovery tests. The
|
|
54
|
+
* supplied promise is awaited before the next enqueued operation
|
|
55
|
+
* starts.
|
|
56
|
+
*/
|
|
57
|
+
setBuildQueueForTests(queue: Promise<unknown>): void;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the current internal queue tail for fault-recovery
|
|
60
|
+
* tests. Tests await this to assert that recovery has cleared a
|
|
61
|
+
* wedged queue.
|
|
62
|
+
*/
|
|
63
|
+
getBuildQueueForTests(): Promise<unknown>;
|
|
64
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
class SerializedBuildExecutor {
|
|
2
|
+
inner;
|
|
3
|
+
tail = Promise.resolve();
|
|
4
|
+
constructor(inner) {
|
|
5
|
+
this.inner = inner;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Run a build through the serialized queue. The returned promise
|
|
9
|
+
* resolves with the inner executor's `BuildResult`. A rejected
|
|
10
|
+
* inner build does not block the queue — the next caller can
|
|
11
|
+
* proceed.
|
|
12
|
+
*/
|
|
13
|
+
build(options) {
|
|
14
|
+
return this.run(() => this.inner.build(options));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run an arbitrary async operation through the serialized queue.
|
|
18
|
+
*
|
|
19
|
+
* Use this when the work is not a `build()` call against a wrapped
|
|
20
|
+
* executor (e.g. when composing with a coordinator that needs to
|
|
21
|
+
* inject recovery around the inner call). The returned promise
|
|
22
|
+
* resolves with the operation's result. A rejected operation does
|
|
23
|
+
* not block the queue.
|
|
24
|
+
*/
|
|
25
|
+
run(operation) {
|
|
26
|
+
const next = this.tail.catch(() => void 0).then(() => operation());
|
|
27
|
+
this.tail = next.catch(() => void 0);
|
|
28
|
+
return next;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Returns the wrapped executor. Tests use this to assert the
|
|
32
|
+
* delegation chain.
|
|
33
|
+
*/
|
|
34
|
+
unwrap() {
|
|
35
|
+
return this.inner;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Reset the queue to an empty state. Tests use this to recover
|
|
39
|
+
* from a poisoned tail.
|
|
40
|
+
*/
|
|
41
|
+
resetForTests() {
|
|
42
|
+
this.tail = Promise.resolve();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Overrides the internal queue tail for fault-recovery tests. The
|
|
46
|
+
* supplied promise is awaited before the next enqueued operation
|
|
47
|
+
* starts.
|
|
48
|
+
*/
|
|
49
|
+
setBuildQueueForTests(queue) {
|
|
50
|
+
this.tail = queue;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Returns the current internal queue tail for fault-recovery
|
|
54
|
+
* tests. Tests await this to assert that recovery has cleared a
|
|
55
|
+
* wedged queue.
|
|
56
|
+
*/
|
|
57
|
+
getBuildQueueForTests() {
|
|
58
|
+
return this.tail;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
SerializedBuildExecutor
|
|
63
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side CSS shim plugin for the bundler adapter.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Page modules imported by the server (so the framework can read
|
|
6
|
+
* `getStaticProps` / `staticPaths` / page metadata) commonly have
|
|
7
|
+
* `import './style.css'` at the top. The bundler does not bundle
|
|
8
|
+
* CSS, and the server only needs the JS exports — the CSS itself
|
|
9
|
+
* is delivered to the browser via the page's `dependencies.stylesheets`
|
|
10
|
+
* declarations. This plugin:
|
|
11
|
+
*
|
|
12
|
+
* - Resolves `.css` imports to the absolute on-disk path.
|
|
13
|
+
* - Loads them and returns an empty ESM module with
|
|
14
|
+
* `moduleSideEffects: true` so the bundler keeps the import in
|
|
15
|
+
* the dependency graph without trying to bundle the bytes.
|
|
16
|
+
*
|
|
17
|
+
* The plugin is added by the adapter on every server-side build.
|
|
18
|
+
* Browser-side builds that want real CSS bundling should keep using
|
|
19
|
+
* a dedicated CSS pipeline (e.g. the postcss processor's output)
|
|
20
|
+
* and continue to declare stylesheets in `dependencies.stylesheets`
|
|
21
|
+
* — the shim is intentionally inert for those flows.
|
|
22
|
+
*/
|
|
23
|
+
type RolldownPluginLike = {
|
|
24
|
+
name: string;
|
|
25
|
+
resolveId: (source: string, importer: string | undefined) => Promise<string | null> | string | null;
|
|
26
|
+
load: (id: string) => Promise<{
|
|
27
|
+
code: string;
|
|
28
|
+
moduleType: 'js';
|
|
29
|
+
moduleSideEffects: boolean;
|
|
30
|
+
} | null> | {
|
|
31
|
+
code: string;
|
|
32
|
+
moduleType: 'js';
|
|
33
|
+
moduleSideEffects: boolean;
|
|
34
|
+
} | null;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Builds a Rolldown plugin that turns `.css` imports into no-op ESM
|
|
38
|
+
* modules while preserving the dependency graph.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createServerSideCssShimPlugin(): RolldownPluginLike;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const CSS_PATH = /\.css$/u;
|
|
4
|
+
const CSS_QUERY = "?ecopages-css-shim";
|
|
5
|
+
function stripShimQuery(id) {
|
|
6
|
+
const queryIndex = id.indexOf("?");
|
|
7
|
+
return queryIndex === -1 ? id : id.slice(0, queryIndex);
|
|
8
|
+
}
|
|
9
|
+
function createServerSideCssShimPlugin() {
|
|
10
|
+
return {
|
|
11
|
+
name: "ecopages:server-side-css-shim",
|
|
12
|
+
async resolveId(source, importer) {
|
|
13
|
+
if (!CSS_PATH.test(source)) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const resolved = importer ? path.resolve(path.dirname(importer), source) : path.resolve(source);
|
|
17
|
+
return existsSync(resolved) ? `${resolved}${CSS_QUERY}` : null;
|
|
18
|
+
},
|
|
19
|
+
async load(id) {
|
|
20
|
+
if (!id.endsWith(CSS_QUERY)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const realPath = stripShimQuery(id);
|
|
24
|
+
readFileSync(realPath, "utf-8");
|
|
25
|
+
return {
|
|
26
|
+
code: 'export default "";',
|
|
27
|
+
moduleType: "js",
|
|
28
|
+
moduleSideEffects: true
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export {
|
|
34
|
+
createServerSideCssShimPlugin
|
|
35
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide parse cache for `oxc-parser.parseSync`.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* Several build plugins (eco-component-meta, client-graph-boundary,
|
|
6
|
+
* browser-runtime) each call `parseSync` on the same source file with
|
|
7
|
+
* overlapping options. This cache memoizes the parse result keyed by
|
|
8
|
+
* `(absolute path, source, options)`. When the source is unchanged,
|
|
9
|
+
* subsequent calls return the cached result without re-parsing.
|
|
10
|
+
*
|
|
11
|
+
* The cache is LRU-bounded (10 000 entries) and key-stable across a
|
|
12
|
+
* single HMR session. It is **content-hashed** rather than
|
|
13
|
+
* mtime-hashed so that `touch`/`utimes` does not invalidate a
|
|
14
|
+
* still-valid parse.
|
|
15
|
+
*/
|
|
16
|
+
import { type ParseResult, type ParserOptions } from 'oxc-parser';
|
|
17
|
+
export type ModuleParseOptions = ParserOptions & {
|
|
18
|
+
/**
|
|
19
|
+
* Optional parser language override. If omitted, derived from the file
|
|
20
|
+
* extension at lookup time. Set this explicitly if your caller already
|
|
21
|
+
* computed the language (avoids re-deriving inside the cache).
|
|
22
|
+
*/
|
|
23
|
+
lang?: ParserOptions['lang'];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* LRU-bounded module parse cache.
|
|
27
|
+
*
|
|
28
|
+
* Single instance shared across the process. Constructed lazily; use
|
|
29
|
+
* {@link moduleParseCache} for the default shared instance.
|
|
30
|
+
*/
|
|
31
|
+
export declare class ModuleParseCache {
|
|
32
|
+
private readonly entries;
|
|
33
|
+
private readonly maxEntries;
|
|
34
|
+
private hits;
|
|
35
|
+
private misses;
|
|
36
|
+
constructor(maxEntries?: number);
|
|
37
|
+
/**
|
|
38
|
+
* Parse `source` for `filePath`, memoizing by (filePath, source, options).
|
|
39
|
+
*
|
|
40
|
+
* @returns the {@link ParseResult} from `oxc-parser.parseSync`.
|
|
41
|
+
*/
|
|
42
|
+
getOrParse(filePath: string, source: string, options?: ModuleParseOptions): ParseResult;
|
|
43
|
+
/** Clear all cached entries. Useful in tests and on full-rebuild signals. */
|
|
44
|
+
clear(): void;
|
|
45
|
+
/** Current cache size (for observability). */
|
|
46
|
+
get size(): number;
|
|
47
|
+
/** Cumulative hit/miss counters (for observability). */
|
|
48
|
+
stats(): {
|
|
49
|
+
hits: number;
|
|
50
|
+
misses: number;
|
|
51
|
+
size: number;
|
|
52
|
+
hitRate: number;
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Default shared cache. Use this from plugin code so the cache is
|
|
57
|
+
* amortized across plugins and build invocations.
|
|
58
|
+
*/
|
|
59
|
+
export declare const moduleParseCache: ModuleParseCache;
|
|
60
|
+
/**
|
|
61
|
+
* Drop-in replacement for `oxc-parser.parseSync` that uses the shared
|
|
62
|
+
* {@link moduleParseCache}. Use everywhere we currently call `parseSync`
|
|
63
|
+
* on user/source files during a build.
|
|
64
|
+
*/
|
|
65
|
+
export declare function cachedParseSync(filePath: string, source: string, options?: ModuleParseOptions): ParseResult;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { parseSync } from "oxc-parser";
|
|
2
|
+
import { rapidhash } from "../utils/hash.js";
|
|
3
|
+
const DEFAULT_MAX_ENTRIES = 1e4;
|
|
4
|
+
class ModuleParseCache {
|
|
5
|
+
entries = /* @__PURE__ */ new Map();
|
|
6
|
+
maxEntries;
|
|
7
|
+
hits = 0;
|
|
8
|
+
misses = 0;
|
|
9
|
+
constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
10
|
+
if (maxEntries <= 0) {
|
|
11
|
+
throw new Error(`ModuleParseCache: maxEntries must be > 0, got ${maxEntries}`);
|
|
12
|
+
}
|
|
13
|
+
this.maxEntries = maxEntries;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Parse `source` for `filePath`, memoizing by (filePath, source, options).
|
|
17
|
+
*
|
|
18
|
+
* @returns the {@link ParseResult} from `oxc-parser.parseSync`.
|
|
19
|
+
*/
|
|
20
|
+
getOrParse(filePath, source, options = {}) {
|
|
21
|
+
const hash = rapidhash(source);
|
|
22
|
+
const key = makeKey(filePath, hash, options);
|
|
23
|
+
const existing = this.entries.get(key);
|
|
24
|
+
if (existing && existing.hash === hash) {
|
|
25
|
+
this.hits += 1;
|
|
26
|
+
this.entries.delete(key);
|
|
27
|
+
this.entries.set(key, existing);
|
|
28
|
+
return existing.result;
|
|
29
|
+
}
|
|
30
|
+
this.misses += 1;
|
|
31
|
+
const result = parseSync(filePath, source, options);
|
|
32
|
+
this.entries.set(key, { hash, result });
|
|
33
|
+
if (this.entries.size > this.maxEntries) {
|
|
34
|
+
const oldestKey = this.entries.keys().next().value;
|
|
35
|
+
if (oldestKey !== void 0) {
|
|
36
|
+
this.entries.delete(oldestKey);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return result;
|
|
40
|
+
}
|
|
41
|
+
/** Clear all cached entries. Useful in tests and on full-rebuild signals. */
|
|
42
|
+
clear() {
|
|
43
|
+
this.entries.clear();
|
|
44
|
+
this.hits = 0;
|
|
45
|
+
this.misses = 0;
|
|
46
|
+
}
|
|
47
|
+
/** Current cache size (for observability). */
|
|
48
|
+
get size() {
|
|
49
|
+
return this.entries.size;
|
|
50
|
+
}
|
|
51
|
+
/** Cumulative hit/miss counters (for observability). */
|
|
52
|
+
stats() {
|
|
53
|
+
const total = this.hits + this.misses;
|
|
54
|
+
return {
|
|
55
|
+
hits: this.hits,
|
|
56
|
+
misses: this.misses,
|
|
57
|
+
size: this.entries.size,
|
|
58
|
+
hitRate: total === 0 ? 0 : this.hits / total
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
const moduleParseCache = new ModuleParseCache();
|
|
63
|
+
function cachedParseSync(filePath, source, options = {}) {
|
|
64
|
+
return moduleParseCache.getOrParse(filePath, source, options);
|
|
65
|
+
}
|
|
66
|
+
function makeKey(filePath, sourceHash, options) {
|
|
67
|
+
const lang = options.lang ?? "";
|
|
68
|
+
const sourceType = options.sourceType ?? "";
|
|
69
|
+
return `${filePath} ${sourceHash.toString(36)} ${lang} ${sourceType}`;
|
|
70
|
+
}
|
|
71
|
+
export {
|
|
72
|
+
ModuleParseCache,
|
|
73
|
+
cachedParseSync,
|
|
74
|
+
moduleParseCache
|
|
75
|
+
};
|
package/src/config/README.md
CHANGED
|
@@ -27,7 +27,7 @@ It is responsible for:
|
|
|
27
27
|
- `ConfigBuilder.build()` decides ordering, validates compatibility, and seals build ownership for the finalized app config.
|
|
28
28
|
- Runtime startup reuses finalized config/build state; it should not recompute manifest ownership.
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
App-owned is the default ownership path. Host-owned is explicit and should be selected during config construction when a host-driven compatibility flow must avoid silently falling back to app build execution.
|
|
31
31
|
|
|
32
32
|
## Output
|
|
33
33
|
|
|
@@ -66,9 +66,9 @@ export declare class ConfigBuilder {
|
|
|
66
66
|
* Sets which runtime path owns build execution for the finalized app config.
|
|
67
67
|
*
|
|
68
68
|
* @remarks
|
|
69
|
-
*
|
|
70
|
-
* for host-driven compatibility flows where core must not
|
|
71
|
-
*
|
|
69
|
+
* The app-owned build path is the default. The host-owned path should be
|
|
70
|
+
* selected only for host-driven compatibility flows where core must not
|
|
71
|
+
* silently fall back to app build execution.
|
|
72
72
|
*/
|
|
73
73
|
setBuildOwnership(buildOwnership: BuildOwnership): this;
|
|
74
74
|
/**
|
|
@@ -8,12 +8,10 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
collectConfiguredAppBuildManifestContributions,
|
|
10
10
|
createBuildAdapter,
|
|
11
|
-
getAppServerBuildPlugins,
|
|
12
11
|
setAppBuildAdapter,
|
|
13
12
|
setAppBuildExecutor,
|
|
14
13
|
updateAppBuildManifest
|
|
15
14
|
} from "../build/build-adapter.js";
|
|
16
|
-
import { createAppBuildExecutor } from "../build/dev-build-coordinator.js";
|
|
17
15
|
import { GHTML_PLUGIN_NAME } from "../integrations/ghtml/ghtml.constants.js";
|
|
18
16
|
import { ghtmlPlugin } from "../integrations/ghtml/ghtml.plugin.js";
|
|
19
17
|
import { createEcoComponentMetaPlugin } from "../plugins/eco-component-meta-plugin.js";
|
|
@@ -41,7 +39,7 @@ const CONFIG_BUILDER_ERRORS = {
|
|
|
41
39
|
invalidRuntimeVersion: (kind, name, version) => `Cannot validate ${kind} "${name}" runtimeCapability.minRuntimeVersion "${version}" because it is not a dot-separated numeric version`
|
|
42
40
|
};
|
|
43
41
|
class ConfigBuilder {
|
|
44
|
-
buildOwnership = "
|
|
42
|
+
buildOwnership = "rolldown";
|
|
45
43
|
config = {
|
|
46
44
|
baseUrl: "",
|
|
47
45
|
rootDir: ".",
|
|
@@ -110,9 +108,9 @@ class ConfigBuilder {
|
|
|
110
108
|
* Sets which runtime path owns build execution for the finalized app config.
|
|
111
109
|
*
|
|
112
110
|
* @remarks
|
|
113
|
-
*
|
|
114
|
-
* for host-driven compatibility flows where core must not
|
|
115
|
-
*
|
|
111
|
+
* The app-owned build path is the default. The host-owned path should be
|
|
112
|
+
* selected only for host-driven compatibility flows where core must not
|
|
113
|
+
* silently fall back to app build execution.
|
|
116
114
|
*/
|
|
117
115
|
setBuildOwnership(buildOwnership) {
|
|
118
116
|
this.buildOwnership = buildOwnership;
|
|
@@ -581,14 +579,7 @@ class ConfigBuilder {
|
|
|
581
579
|
updateAppBuildManifest(this.config, await collectConfiguredAppBuildManifestContributions(this.config));
|
|
582
580
|
setAppServerInvalidationState(this.config, new CounterServerInvalidationState());
|
|
583
581
|
setAppEntrypointDependencyGraph(this.config, new NoopEntrypointDependencyGraph());
|
|
584
|
-
setAppBuildExecutor(
|
|
585
|
-
this.config,
|
|
586
|
-
createAppBuildExecutor({
|
|
587
|
-
development: false,
|
|
588
|
-
adapter: buildAdapter,
|
|
589
|
-
getPlugins: () => getAppServerBuildPlugins(this.config)
|
|
590
|
-
})
|
|
591
|
-
);
|
|
582
|
+
setAppBuildExecutor(this.config, buildAdapter);
|
|
592
583
|
return this.config;
|
|
593
584
|
}
|
|
594
585
|
}
|
package/src/eco/eco.types.d.ts
CHANGED
|
@@ -136,11 +136,8 @@ interface PageOptionsWithMiddleware<T, E = EcoPagesElement> extends PageOptionsB
|
|
|
136
136
|
/**
|
|
137
137
|
* Options for creating a page with eco.page()
|
|
138
138
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
* 2. **Separate exports** (legacy): Export getStaticPaths, getStaticProps, getMetadata separately
|
|
142
|
-
*
|
|
143
|
-
* When using `middleware`, `cache` must be set to `'dynamic'` because middleware
|
|
139
|
+
* Define staticPaths, staticProps, and metadata inline. When using
|
|
140
|
+
* `middleware`, `cache` must be set to `'dynamic'` because middleware
|
|
144
141
|
* runs on every request and caching would bypass middleware effects.
|
|
145
142
|
*
|
|
146
143
|
* @template T - The props type for the page
|
|
@@ -111,13 +111,13 @@ export declare class JsHmrStrategy extends HmrStrategy {
|
|
|
111
111
|
* @remarks
|
|
112
112
|
* If runtime-specific dependency graph hooks are unavailable, this strategy
|
|
113
113
|
* falls back to rebuilding all watched entrypoints.
|
|
114
|
-
* When multiple entrypoints are impacted they are bundled in a single
|
|
114
|
+
* When multiple entrypoints are impacted they are bundled in a single
|
|
115
115
|
* invocation to share AST parsing and chunk deduplication.
|
|
116
116
|
* @returns Action to broadcast update events
|
|
117
117
|
*/
|
|
118
118
|
process(filePath: string): Promise<HmrAction>;
|
|
119
119
|
/**
|
|
120
|
-
* Bundles one or more entrypoints in a single
|
|
120
|
+
* Bundles one or more entrypoints in a single build invocation.
|
|
121
121
|
* Uses the source directory as the output base so that the directory structure
|
|
122
122
|
* is preserved under the HMR dist folder.
|
|
123
123
|
*/
|
|
@@ -46,7 +46,7 @@ class JsHmrStrategy extends HmrStrategy {
|
|
|
46
46
|
* @remarks
|
|
47
47
|
* If runtime-specific dependency graph hooks are unavailable, this strategy
|
|
48
48
|
* falls back to rebuilding all watched entrypoints.
|
|
49
|
-
* When multiple entrypoints are impacted they are bundled in a single
|
|
49
|
+
* When multiple entrypoints are impacted they are bundled in a single
|
|
50
50
|
* invocation to share AST parsing and chunk deduplication.
|
|
51
51
|
* @returns Action to broadcast update events
|
|
52
52
|
*/
|
|
@@ -114,7 +114,7 @@ class JsHmrStrategy extends HmrStrategy {
|
|
|
114
114
|
return { type: "none" };
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
|
-
* Bundles one or more entrypoints in a single
|
|
117
|
+
* Bundles one or more entrypoints in a single build invocation.
|
|
118
118
|
* Uses the source directory as the output base so that the directory structure
|
|
119
119
|
* is preserved under the HMR dist folder.
|
|
120
120
|
*/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-plugin cache for `@/...` alias resolution.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The `ecopages-alias-resolver` plugin resolves project aliases like
|
|
6
|
+
* `@/components/Button` to concrete file paths under the app's `srcDir`.
|
|
7
|
+
* Each resolution can trigger up to 22 `existsSync` calls and (when
|
|
8
|
+
* the resolved path is a barrel) a `readFileSync` to detect
|
|
9
|
+
* `export * from './...'` forwarding.
|
|
10
|
+
*
|
|
11
|
+
* This cache memoizes the result keyed by `(srcDir, specifier)`. It is
|
|
12
|
+
* process-local and not currently invalidated by the file watcher —
|
|
13
|
+
* `srcDir` contents are assumed to be stable for the lifetime of the
|
|
14
|
+
* process.
|
|
15
|
+
*/
|
|
16
|
+
export declare class AliasResolverCache {
|
|
17
|
+
private readonly entries;
|
|
18
|
+
private readonly maxEntries;
|
|
19
|
+
private hits;
|
|
20
|
+
private misses;
|
|
21
|
+
constructor(maxEntries?: number);
|
|
22
|
+
/**
|
|
23
|
+
* Look up a previously-computed resolution for `(srcDir, specifier)`.
|
|
24
|
+
*
|
|
25
|
+
* Returns `{ hit: true, resolved }` on cache hit (resolved may be
|
|
26
|
+
* `undefined` for an unresolvable specifier), or `{ hit: false }` on
|
|
27
|
+
* miss. The caller is responsible for re-running the resolver and
|
|
28
|
+
* calling `set` to store the new entry.
|
|
29
|
+
*/
|
|
30
|
+
get(srcDir: string, specifier: string): {
|
|
31
|
+
hit: true;
|
|
32
|
+
resolved: string | undefined;
|
|
33
|
+
} | {
|
|
34
|
+
hit: false;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Store a resolution result.
|
|
38
|
+
*
|
|
39
|
+
* Pass `undefined` for `resolved` to memoize the negative case
|
|
40
|
+
* (specifier did not resolve) and avoid repeating the FS walk on
|
|
41
|
+
* every `@/...` import.
|
|
42
|
+
*/
|
|
43
|
+
set(srcDir: string, specifier: string, resolved: string | undefined): void;
|
|
44
|
+
/**
|
|
45
|
+
* Drop all entries whose `srcDir` is under `rootDir`. Use this from
|
|
46
|
+
* the file watcher when files are added or removed under the app's
|
|
47
|
+
* source tree.
|
|
48
|
+
*
|
|
49
|
+
* Path comparison is path-aware (handles both POSIX `/` and
|
|
50
|
+
* Windows `\` separators) via `path.relative` so cached entries
|
|
51
|
+
* normalized on one platform still match the watcher's `rootDir`
|
|
52
|
+
* on another. Without this, a Windows build that sees
|
|
53
|
+
* `entry.srcDir === 'C:\app\src'` would miss
|
|
54
|
+
* `entry.srcDir.startsWith('C:/app/src/')` and leave stale entries.
|
|
55
|
+
*/
|
|
56
|
+
invalidateUnder(rootDir: string): number;
|
|
57
|
+
/** Clear all entries. */
|
|
58
|
+
clear(): void;
|
|
59
|
+
/** Current cache size. */
|
|
60
|
+
get size(): number;
|
|
61
|
+
/** Hit/miss counters for observability. */
|
|
62
|
+
stats(): {
|
|
63
|
+
hits: number;
|
|
64
|
+
misses: number;
|
|
65
|
+
size: number;
|
|
66
|
+
hitRate: number;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
const DEFAULT_MAX_ENTRIES = 2e3;
|
|
3
|
+
class AliasResolverCache {
|
|
4
|
+
entries = /* @__PURE__ */ new Map();
|
|
5
|
+
maxEntries;
|
|
6
|
+
hits = 0;
|
|
7
|
+
misses = 0;
|
|
8
|
+
constructor(maxEntries = DEFAULT_MAX_ENTRIES) {
|
|
9
|
+
if (maxEntries <= 0) {
|
|
10
|
+
throw new Error(`AliasResolverCache: maxEntries must be > 0, got ${maxEntries}`);
|
|
11
|
+
}
|
|
12
|
+
this.maxEntries = maxEntries;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Look up a previously-computed resolution for `(srcDir, specifier)`.
|
|
16
|
+
*
|
|
17
|
+
* Returns `{ hit: true, resolved }` on cache hit (resolved may be
|
|
18
|
+
* `undefined` for an unresolvable specifier), or `{ hit: false }` on
|
|
19
|
+
* miss. The caller is responsible for re-running the resolver and
|
|
20
|
+
* calling `set` to store the new entry.
|
|
21
|
+
*/
|
|
22
|
+
get(srcDir, specifier) {
|
|
23
|
+
const key = makeKey(srcDir, specifier);
|
|
24
|
+
const existing = this.entries.get(key);
|
|
25
|
+
if (existing) {
|
|
26
|
+
this.hits += 1;
|
|
27
|
+
this.entries.delete(key);
|
|
28
|
+
this.entries.set(key, existing);
|
|
29
|
+
return { hit: true, resolved: existing.resolved };
|
|
30
|
+
}
|
|
31
|
+
this.misses += 1;
|
|
32
|
+
return { hit: false };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Store a resolution result.
|
|
36
|
+
*
|
|
37
|
+
* Pass `undefined` for `resolved` to memoize the negative case
|
|
38
|
+
* (specifier did not resolve) and avoid repeating the FS walk on
|
|
39
|
+
* every `@/...` import.
|
|
40
|
+
*/
|
|
41
|
+
set(srcDir, specifier, resolved) {
|
|
42
|
+
const key = makeKey(srcDir, specifier);
|
|
43
|
+
this.entries.set(key, { srcDir, resolved });
|
|
44
|
+
if (this.entries.size > this.maxEntries) {
|
|
45
|
+
const oldestKey = this.entries.keys().next().value;
|
|
46
|
+
if (oldestKey !== void 0) {
|
|
47
|
+
this.entries.delete(oldestKey);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Drop all entries whose `srcDir` is under `rootDir`. Use this from
|
|
53
|
+
* the file watcher when files are added or removed under the app's
|
|
54
|
+
* source tree.
|
|
55
|
+
*
|
|
56
|
+
* Path comparison is path-aware (handles both POSIX `/` and
|
|
57
|
+
* Windows `\` separators) via `path.relative` so cached entries
|
|
58
|
+
* normalized on one platform still match the watcher's `rootDir`
|
|
59
|
+
* on another. Without this, a Windows build that sees
|
|
60
|
+
* `entry.srcDir === 'C:\app\src'` would miss
|
|
61
|
+
* `entry.srcDir.startsWith('C:/app/src/')` and leave stale entries.
|
|
62
|
+
*/
|
|
63
|
+
invalidateUnder(rootDir) {
|
|
64
|
+
const normalizedRoot = path.resolve(rootDir);
|
|
65
|
+
let removed = 0;
|
|
66
|
+
for (const [key, entry] of this.entries) {
|
|
67
|
+
if (isUnderDirectory(path.resolve(entry.srcDir), normalizedRoot)) {
|
|
68
|
+
this.entries.delete(key);
|
|
69
|
+
removed += 1;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return removed;
|
|
73
|
+
}
|
|
74
|
+
/** Clear all entries. */
|
|
75
|
+
clear() {
|
|
76
|
+
this.entries.clear();
|
|
77
|
+
this.hits = 0;
|
|
78
|
+
this.misses = 0;
|
|
79
|
+
}
|
|
80
|
+
/** Current cache size. */
|
|
81
|
+
get size() {
|
|
82
|
+
return this.entries.size;
|
|
83
|
+
}
|
|
84
|
+
/** Hit/miss counters for observability. */
|
|
85
|
+
stats() {
|
|
86
|
+
const total = this.hits + this.misses;
|
|
87
|
+
return {
|
|
88
|
+
hits: this.hits,
|
|
89
|
+
misses: this.misses,
|
|
90
|
+
size: this.entries.size,
|
|
91
|
+
hitRate: total === 0 ? 0 : this.hits / total
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function makeKey(srcDir, specifier) {
|
|
96
|
+
return `${srcDir} ${specifier}`;
|
|
97
|
+
}
|
|
98
|
+
function isUnderDirectory(candidate, parent) {
|
|
99
|
+
if (candidate === parent) return true;
|
|
100
|
+
const rel = path.relative(parent, candidate);
|
|
101
|
+
if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return false;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
export {
|
|
105
|
+
AliasResolverCache
|
|
106
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
import type { EcoBuildPlugin } from '../build/build-types.js';
|
|
2
|
+
import { AliasResolverCache } from './alias-resolver-cache.js';
|
|
2
3
|
export declare function resolveAppSourceAliasPath(srcDir: string, specifier: string): string | undefined;
|
|
3
|
-
export declare function createAliasResolverPlugin(srcDir: string
|
|
4
|
+
export declare function createAliasResolverPlugin(srcDir: string, options?: {
|
|
5
|
+
cache?: AliasResolverCache;
|
|
6
|
+
}): EcoBuildPlugin;
|