@ecopages/core 0.2.0-beta.23 → 0.2.0-beta.25
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 -2
- package/src/adapters/abstract/application-adapter.js +3 -0
- package/src/adapters/bun/create-app.js +2 -0
- package/src/adapters/node/create-app.js +2 -0
- package/src/adapters/shared/server-adapter.js +17 -9
- package/src/build/app-build-manifest-runtime.js +12 -1
- package/src/build/build-adapter.js +4 -4
- package/src/build/dev-browser-script-cache.d.ts +34 -0
- package/src/build/dev-browser-script-cache.js +90 -0
- package/src/build/lit-static-render-worker-context.d.ts +2 -0
- package/src/build/lit-static-render-worker-context.js +6 -0
- package/src/build/rolldown-adapter-helpers.js +4 -3
- package/src/build/runtime-build-output-normalizer.js +2 -4
- package/src/diagnostics/request-build-dedupe.d.ts +18 -0
- package/src/diagnostics/request-build-dedupe.js +33 -0
- package/src/diagnostics/startup-trace.d.ts +25 -0
- package/src/diagnostics/startup-trace.js +121 -0
- package/src/eco/eco.types.d.ts +12 -1
- package/src/env.d.ts +2 -0
- package/src/plugins/alias-resolver-cache.d.ts +6 -9
- package/src/plugins/alias-resolver-plugin.d.ts +2 -2
- package/src/plugins/alias-resolver-plugin.js +38 -51
- package/src/plugins/integration-plugin.d.ts +8 -0
- package/src/plugins/tsconfig-import-resolver.d.ts +18 -0
- package/src/plugins/tsconfig-import-resolver.js +159 -0
- package/src/route-renderer/orchestration/page-browser-graph.service.js +15 -9
- package/src/route-renderer/page-loading/ecopages-virtual-imports.d.ts +6 -0
- package/src/route-renderer/page-loading/ecopages-virtual-imports.js +7 -2
- package/src/route-renderer/page-loading/module-declaration-aggregation.js +4 -0
- package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +3 -0
- package/src/services/assets/asset-processing-service/asset-processing.service.js +41 -4
- package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.d.ts +52 -0
- package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.js +78 -0
- package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +13 -18
- package/src/services/assets/asset-processing-service/finalize-processed-asset.d.ts +3 -0
- package/src/services/assets/asset-processing-service/finalize-processed-asset.js +7 -0
- package/src/services/assets/asset-processing-service/grouped-content-bundles.d.ts +2 -1
- package/src/services/assets/asset-processing-service/grouped-content-bundles.js +20 -10
- package/src/services/assets/asset-processing-service/index.d.ts +2 -0
- package/src/services/assets/asset-processing-service/index.js +2 -0
- package/src/services/assets/asset-processing-service/materialize-content-script-asset.d.ts +11 -0
- package/src/services/assets/asset-processing-service/materialize-content-script-asset.js +17 -0
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts +7 -0
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +105 -79
- package/src/services/assets/asset-processing-service/resolve-integration-plugin.d.ts +4 -0
- package/src/services/assets/asset-processing-service/resolve-integration-plugin.js +17 -0
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.d.ts +0 -1
- package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.js +5 -10
- package/src/services/assets/browser-bundle.service.js +10 -2
package/src/env.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ interface EcopagesEnv {
|
|
|
3
3
|
ECOPAGES_HOSTNAME: string;
|
|
4
4
|
ECOPAGES_PORT: string;
|
|
5
5
|
ECOPAGES_LOGGER_DEBUG: 'true' | 'false';
|
|
6
|
+
/** When `true`, emits startup phase timings to stderr. Also enabled when `ECOPAGES_LOGGER_DEBUG=true`. */
|
|
7
|
+
ECOPAGES_STARTUP_TRACE?: 'true' | 'false';
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
declare global {
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-plugin cache for
|
|
2
|
+
* Per-plugin cache for tsconfig path alias resolution.
|
|
3
3
|
*
|
|
4
4
|
* @remarks
|
|
5
|
-
* The `ecopages-alias-resolver` plugin resolves project aliases
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* the resolved path is a barrel) a `readFileSync` to detect
|
|
9
|
-
* `export * from './...'` forwarding.
|
|
5
|
+
* The `ecopages-alias-resolver` plugin resolves project path aliases to
|
|
6
|
+
* concrete file paths using the app's tsconfig `paths`. Each resolution goes
|
|
7
|
+
* through oxc-resolver and may read barrel re-export targets.
|
|
10
8
|
*
|
|
11
|
-
* This cache memoizes the result keyed by `(
|
|
9
|
+
* This cache memoizes the result keyed by `(projectRoot, specifier)`. It is
|
|
12
10
|
* process-local and not currently invalidated by the file watcher —
|
|
13
|
-
*
|
|
14
|
-
* process.
|
|
11
|
+
* project contents are assumed to be stable for the lifetime of the process.
|
|
15
12
|
*/
|
|
16
13
|
export declare class AliasResolverCache {
|
|
17
14
|
private readonly entries;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { EcoBuildPlugin } from '../build/build-types.js';
|
|
2
2
|
import { AliasResolverCache } from './alias-resolver-cache.js';
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function createAliasResolverPlugin(srcDir: string, options?: {
|
|
3
|
+
export declare function createAliasResolverPlugin(projectRoot: string, options?: {
|
|
5
4
|
cache?: AliasResolverCache;
|
|
6
5
|
}): EcoBuildPlugin;
|
|
6
|
+
export { isBarePackageImportSpecifier, matchesTsconfigPathPrefix, loadTsconfigPathPrefixes, resolveProjectImportPath, resolveProjectModulePath, } from './tsconfig-import-resolver.js';
|
|
@@ -1,65 +1,52 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { AliasResolverCache } from "./alias-resolver-cache.js";
|
|
4
|
-
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
if (existsSync(candidate)) {
|
|
8
|
-
return candidate;
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
12
|
-
const fileCandidate = `${candidate}${extension}`;
|
|
13
|
-
if (existsSync(fileCandidate)) {
|
|
14
|
-
return fileCandidate;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
18
|
-
const indexCandidate = path.join(candidate, `index${extension}`);
|
|
19
|
-
if (existsSync(indexCandidate)) {
|
|
20
|
-
return indexCandidate;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return void 0;
|
|
24
|
-
}
|
|
25
|
-
function resolveAliasedBarrelTarget(resolvedPath) {
|
|
26
|
-
if (!path.basename(resolvedPath).startsWith("index.")) {
|
|
27
|
-
return resolvedPath;
|
|
28
|
-
}
|
|
29
|
-
const source = readFileSync(resolvedPath, "utf8").trim();
|
|
30
|
-
const match = source.match(/^export\s+\*\s+from\s+['"]([^'"]+)['"]\s*;?$/);
|
|
31
|
-
if (!match?.[1]?.startsWith(".")) {
|
|
32
|
-
return resolvedPath;
|
|
33
|
-
}
|
|
34
|
-
const target = findResolvablePath(path.resolve(path.dirname(resolvedPath), match[1]));
|
|
35
|
-
return target ?? resolvedPath;
|
|
2
|
+
import { loadTsconfigPathPrefixes, resolveProjectImportPath } from "./tsconfig-import-resolver.js";
|
|
3
|
+
function escapeRegExp(value) {
|
|
4
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
36
5
|
}
|
|
37
|
-
function
|
|
38
|
-
if (
|
|
39
|
-
return
|
|
6
|
+
function buildPathPrefixFilter(prefix) {
|
|
7
|
+
if (prefix.endsWith("/")) {
|
|
8
|
+
return new RegExp(`^${escapeRegExp(prefix)}`);
|
|
40
9
|
}
|
|
41
|
-
|
|
42
|
-
const resolved = findResolvablePath(candidate);
|
|
43
|
-
return resolved ? resolveAliasedBarrelTarget(resolved) : void 0;
|
|
10
|
+
return new RegExp(`^${escapeRegExp(prefix)}(?:/|$)`);
|
|
44
11
|
}
|
|
45
|
-
function createAliasResolverPlugin(
|
|
12
|
+
function createAliasResolverPlugin(projectRoot, options) {
|
|
46
13
|
const cache = options?.cache ?? new AliasResolverCache();
|
|
14
|
+
const pathPrefixes = loadTsconfigPathPrefixes(projectRoot);
|
|
47
15
|
return {
|
|
48
16
|
name: "ecopages-alias-resolver",
|
|
49
17
|
setup(build) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
18
|
+
if (pathPrefixes.length === 0) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
for (const prefix of pathPrefixes) {
|
|
22
|
+
build.onResolve({ filter: buildPathPrefixFilter(prefix) }, (args) => {
|
|
23
|
+
if (!args.importer) {
|
|
24
|
+
return void 0;
|
|
25
|
+
}
|
|
26
|
+
const cached = cache.get(projectRoot, args.path);
|
|
27
|
+
if (cached.hit) {
|
|
28
|
+
return cached.resolved ? { path: cached.resolved } : void 0;
|
|
29
|
+
}
|
|
30
|
+
const resolved = resolveProjectImportPath(projectRoot, args.importer, args.path);
|
|
31
|
+
cache.set(projectRoot, args.path, resolved);
|
|
32
|
+
return resolved ? { path: resolved } : void 0;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
59
35
|
}
|
|
60
36
|
};
|
|
61
37
|
}
|
|
38
|
+
import {
|
|
39
|
+
isBarePackageImportSpecifier,
|
|
40
|
+
matchesTsconfigPathPrefix,
|
|
41
|
+
loadTsconfigPathPrefixes as loadTsconfigPathPrefixes2,
|
|
42
|
+
resolveProjectImportPath as resolveProjectImportPath2,
|
|
43
|
+
resolveProjectModulePath
|
|
44
|
+
} from "./tsconfig-import-resolver.js";
|
|
62
45
|
export {
|
|
63
46
|
createAliasResolverPlugin,
|
|
64
|
-
|
|
47
|
+
isBarePackageImportSpecifier,
|
|
48
|
+
loadTsconfigPathPrefixes2 as loadTsconfigPathPrefixes,
|
|
49
|
+
matchesTsconfigPathPrefix,
|
|
50
|
+
resolveProjectImportPath2 as resolveProjectImportPath,
|
|
51
|
+
resolveProjectModulePath
|
|
65
52
|
};
|
|
@@ -197,6 +197,14 @@ export declare abstract class IntegrationPlugin<C = EcoPagesElement> {
|
|
|
197
197
|
initializeRenderer(options?: {
|
|
198
198
|
rendererModules?: unknown;
|
|
199
199
|
}): IntegrationRenderer<C>;
|
|
200
|
+
/**
|
|
201
|
+
* Shapes one dependency batch before core asset processing runs.
|
|
202
|
+
*
|
|
203
|
+
* @remarks
|
|
204
|
+
* Integrations use this to assign grouped-build metadata or other batch-level
|
|
205
|
+
* policy without teaching core about integration-specific asset graphs.
|
|
206
|
+
*/
|
|
207
|
+
prepareAssetDependencies?(dependencies: AssetDefinition[]): AssetDefinition[];
|
|
200
208
|
/**
|
|
201
209
|
* Prepares build-facing contributions before the app build manifest is sealed.
|
|
202
210
|
*
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loads `compilerOptions.paths` keys from tsconfig, following `extends` recursively.
|
|
3
|
+
*/
|
|
4
|
+
export declare function loadTsconfigPathPrefixes(projectRoot: string): string[];
|
|
5
|
+
export declare function matchesTsconfigPathPrefix(specifier: string, prefixes: readonly string[]): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Returns true when `specifier` looks like a bare npm package import rather than
|
|
8
|
+
* a relative path, absolute path, node built-in, tsconfig alias, or `#` import map.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isBarePackageImportSpecifier(specifier: string, projectRoot?: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Resolves a relative or tsconfig path alias import (via oxc-resolver).
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveProjectModulePath(projectRoot: string, fromFile: string, specifier: string): string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Resolves a TS path alias import using the app's tsconfig `paths` (via oxc-resolver).
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveProjectImportPath(projectRoot: string, fromFile: string, specifier: string): string | undefined;
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ResolverFactory } from "oxc-resolver";
|
|
4
|
+
const RESOLVABLE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mdx"];
|
|
5
|
+
const resolverCache = /* @__PURE__ */ new Map();
|
|
6
|
+
const pathPrefixCache = /* @__PURE__ */ new Map();
|
|
7
|
+
function stripJsonComments(source) {
|
|
8
|
+
return source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
|
|
9
|
+
}
|
|
10
|
+
function findTsconfigPath(projectRoot) {
|
|
11
|
+
const candidate = path.join(projectRoot, "tsconfig.json");
|
|
12
|
+
return existsSync(candidate) ? candidate : void 0;
|
|
13
|
+
}
|
|
14
|
+
function readJsonFile(filePath) {
|
|
15
|
+
const source = readFileSync(filePath, "utf8");
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(source);
|
|
18
|
+
} catch {
|
|
19
|
+
return JSON.parse(stripJsonComments(source));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function loadTsconfigPathPrefixes(projectRoot) {
|
|
23
|
+
const cached = pathPrefixCache.get(projectRoot);
|
|
24
|
+
if (cached) {
|
|
25
|
+
return cached;
|
|
26
|
+
}
|
|
27
|
+
const tsconfigPath = findTsconfigPath(projectRoot);
|
|
28
|
+
if (!tsconfigPath) {
|
|
29
|
+
pathPrefixCache.set(projectRoot, []);
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
const prefixes = /* @__PURE__ */ new Set();
|
|
33
|
+
const visited = /* @__PURE__ */ new Set();
|
|
34
|
+
const collectFromConfig = (configPath) => {
|
|
35
|
+
const normalized = path.resolve(configPath);
|
|
36
|
+
if (visited.has(normalized)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
visited.add(normalized);
|
|
40
|
+
let config;
|
|
41
|
+
try {
|
|
42
|
+
config = readJsonFile(normalized);
|
|
43
|
+
} catch {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (typeof config.extends === "string") {
|
|
47
|
+
const parentPath = path.resolve(path.dirname(normalized), config.extends);
|
|
48
|
+
collectFromConfig(parentPath);
|
|
49
|
+
}
|
|
50
|
+
for (const key of Object.keys(config.compilerOptions?.paths ?? {})) {
|
|
51
|
+
if (key.endsWith("/*")) {
|
|
52
|
+
prefixes.add(key.slice(0, -1));
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (key.endsWith("*")) {
|
|
56
|
+
prefixes.add(key.slice(0, -1));
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
prefixes.add(key);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
collectFromConfig(tsconfigPath);
|
|
63
|
+
const resolvedPrefixes = [...prefixes];
|
|
64
|
+
pathPrefixCache.set(projectRoot, resolvedPrefixes);
|
|
65
|
+
return resolvedPrefixes;
|
|
66
|
+
}
|
|
67
|
+
function matchesTsconfigPathPrefix(specifier, prefixes) {
|
|
68
|
+
return prefixes.some((prefix) => {
|
|
69
|
+
if (prefix.endsWith("/")) {
|
|
70
|
+
return specifier.startsWith(prefix);
|
|
71
|
+
}
|
|
72
|
+
return specifier === prefix || specifier.startsWith(`${prefix}/`);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function isBarePackageImportSpecifier(specifier, projectRoot) {
|
|
76
|
+
if (specifier.startsWith(".") || path.isAbsolute(specifier) || specifier.startsWith("/")) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (specifier.startsWith("node:") || specifier.startsWith("#") || specifier.includes(":")) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
if (projectRoot && matchesTsconfigPathPrefix(specifier, loadTsconfigPathPrefixes(projectRoot))) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
function getResolverFactory(projectRoot) {
|
|
88
|
+
const cached = resolverCache.get(projectRoot);
|
|
89
|
+
if (cached) {
|
|
90
|
+
return cached;
|
|
91
|
+
}
|
|
92
|
+
const tsconfigPath = findTsconfigPath(projectRoot);
|
|
93
|
+
if (!tsconfigPath) {
|
|
94
|
+
return void 0;
|
|
95
|
+
}
|
|
96
|
+
const resolver = new ResolverFactory({
|
|
97
|
+
tsconfig: {
|
|
98
|
+
configFile: tsconfigPath
|
|
99
|
+
},
|
|
100
|
+
extensions: [...RESOLVABLE_EXTENSIONS]
|
|
101
|
+
});
|
|
102
|
+
resolverCache.set(projectRoot, resolver);
|
|
103
|
+
return resolver;
|
|
104
|
+
}
|
|
105
|
+
function resolveAliasedBarrelTarget(resolvedPath) {
|
|
106
|
+
if (!path.basename(resolvedPath).startsWith("index.")) {
|
|
107
|
+
return resolvedPath;
|
|
108
|
+
}
|
|
109
|
+
const source = readFileSync(resolvedPath, "utf8").trim();
|
|
110
|
+
const match = source.match(/^export\s+\*\s+from\s+['"]([^'"]+)['"]\s*;?$/);
|
|
111
|
+
if (!match?.[1]?.startsWith(".")) {
|
|
112
|
+
return resolvedPath;
|
|
113
|
+
}
|
|
114
|
+
const targetDir = path.dirname(resolvedPath);
|
|
115
|
+
const targetBase = path.resolve(targetDir, match[1]);
|
|
116
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
117
|
+
const candidate = `${targetBase}${extension}`;
|
|
118
|
+
if (existsSync(candidate)) {
|
|
119
|
+
return candidate;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
for (const extension of RESOLVABLE_EXTENSIONS) {
|
|
123
|
+
const candidate = path.join(targetBase, `index${extension}`);
|
|
124
|
+
if (existsSync(candidate)) {
|
|
125
|
+
return candidate;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return resolvedPath;
|
|
129
|
+
}
|
|
130
|
+
function resolveProjectModulePath(projectRoot, fromFile, specifier) {
|
|
131
|
+
const prefixes = loadTsconfigPathPrefixes(projectRoot);
|
|
132
|
+
const isRelative = specifier.startsWith(".");
|
|
133
|
+
const isPathAlias = matchesTsconfigPathPrefix(specifier, prefixes);
|
|
134
|
+
if (!isRelative && !isPathAlias) {
|
|
135
|
+
return void 0;
|
|
136
|
+
}
|
|
137
|
+
const resolver = getResolverFactory(projectRoot);
|
|
138
|
+
if (!resolver) {
|
|
139
|
+
return void 0;
|
|
140
|
+
}
|
|
141
|
+
const result = resolver.sync(path.dirname(fromFile), specifier);
|
|
142
|
+
if (!result.path) {
|
|
143
|
+
return void 0;
|
|
144
|
+
}
|
|
145
|
+
return resolveAliasedBarrelTarget(realpathSync(result.path));
|
|
146
|
+
}
|
|
147
|
+
function resolveProjectImportPath(projectRoot, fromFile, specifier) {
|
|
148
|
+
if (!matchesTsconfigPathPrefix(specifier, loadTsconfigPathPrefixes(projectRoot))) {
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
return resolveProjectModulePath(projectRoot, fromFile, specifier);
|
|
152
|
+
}
|
|
153
|
+
export {
|
|
154
|
+
isBarePackageImportSpecifier,
|
|
155
|
+
loadTsconfigPathPrefixes,
|
|
156
|
+
matchesTsconfigPathPrefix,
|
|
157
|
+
resolveProjectImportPath,
|
|
158
|
+
resolveProjectModulePath
|
|
159
|
+
};
|
|
@@ -103,6 +103,9 @@ class PageBrowserGraphService {
|
|
|
103
103
|
);
|
|
104
104
|
continue;
|
|
105
105
|
}
|
|
106
|
+
if (this.isHmrEnabled()) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
106
109
|
if (!contribution?.dependencies?.length) {
|
|
107
110
|
continue;
|
|
108
111
|
}
|
|
@@ -130,15 +133,18 @@ class PageBrowserGraphService {
|
|
|
130
133
|
);
|
|
131
134
|
const groupedAssetsByRoute = /* @__PURE__ */ new Map();
|
|
132
135
|
for (const [routeFile, groupedAssetKeys] of groupedAssetKeysByRoute) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
136
|
+
const matchedAssets = processedGroupedDependencies.filter((asset) => {
|
|
137
|
+
if (!asset.groupedBundle) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return groupedAssetKeys.has(getGroupedBundleAssetKey(asset.groupedBundle));
|
|
141
|
+
});
|
|
142
|
+
if (groupedAssetKeys.size > 0 && matchedAssets.length === 0) {
|
|
143
|
+
appLogger.warn(
|
|
144
|
+
`Grouped page-browser assets for ${routeFile} are missing groupedBundle metadata after processing. Hydration scripts may be omitted from HTML.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
groupedAssetsByRoute.set(routeFile, matchedAssets);
|
|
142
148
|
}
|
|
143
149
|
return {
|
|
144
150
|
assetsByRoute: groupedAssetsByRoute,
|
|
@@ -2,6 +2,12 @@ export type EcopagesVirtualImport = {
|
|
|
2
2
|
from: string;
|
|
3
3
|
imports: string[] | undefined;
|
|
4
4
|
};
|
|
5
|
+
/**
|
|
6
|
+
* @remarks
|
|
7
|
+
* Content server modules static-import every MDX entry. They must never become
|
|
8
|
+
* browser module-script dependencies discovered from page or component sources.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isBrowserEcopagesVirtualImport(specifier: string): boolean;
|
|
5
11
|
/**
|
|
6
12
|
* Extracts runtime `ecopages:` virtual-module imports from a component source file.
|
|
7
13
|
*
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { parseSync } from "oxc-parser";
|
|
3
|
+
const CONTENT_SERVER_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]+\/server$/;
|
|
4
|
+
function isBrowserEcopagesVirtualImport(specifier) {
|
|
5
|
+
return specifier.startsWith("ecopages:") && !CONTENT_SERVER_VIRTUAL_MODULE_PATTERN.test(specifier);
|
|
6
|
+
}
|
|
3
7
|
function extractEcopagesVirtualImports(file) {
|
|
4
8
|
let source;
|
|
5
9
|
try {
|
|
@@ -18,7 +22,7 @@ function extractEcopagesVirtualImports(file) {
|
|
|
18
22
|
if (node.type !== "ImportDeclaration") continue;
|
|
19
23
|
if (node.importKind === "type") continue;
|
|
20
24
|
const specifier = node.source?.value ?? "";
|
|
21
|
-
if (!specifier
|
|
25
|
+
if (!isBrowserEcopagesVirtualImport(specifier)) continue;
|
|
22
26
|
if (found.get(specifier) === null) {
|
|
23
27
|
continue;
|
|
24
28
|
}
|
|
@@ -53,5 +57,6 @@ function extractEcopagesVirtualImports(file) {
|
|
|
53
57
|
}));
|
|
54
58
|
}
|
|
55
59
|
export {
|
|
56
|
-
extractEcopagesVirtualImports
|
|
60
|
+
extractEcopagesVirtualImports,
|
|
61
|
+
isBrowserEcopagesVirtualImport
|
|
57
62
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { normalizeModuleDeclarations } from "../../eco/module-dependencies.js";
|
|
2
|
+
import { isBrowserEcopagesVirtualImport } from "./ecopages-virtual-imports.js";
|
|
2
3
|
function getDeclaredModules(value) {
|
|
3
4
|
if (!Array.isArray(value)) {
|
|
4
5
|
return void 0;
|
|
@@ -22,6 +23,9 @@ function mergeModuleDeclaration(modulesMap, declaration) {
|
|
|
22
23
|
}
|
|
23
24
|
function collectModuleDeclarations(modulesMap, declaredModules, autoVirtualImports) {
|
|
24
25
|
for (const declaration of normalizeModuleDeclarations(getDeclaredModules(declaredModules))) {
|
|
26
|
+
if (declaration.from.startsWith("ecopages:") && !isBrowserEcopagesVirtualImport(declaration.from)) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
25
29
|
mergeModuleDeclaration(modulesMap, declaration);
|
|
26
30
|
}
|
|
27
31
|
for (const declaration of autoVirtualImports) {
|
|
@@ -42,6 +42,7 @@ export declare class AssetProcessingService {
|
|
|
42
42
|
* entries.
|
|
43
43
|
*/
|
|
44
44
|
processDependencies(deps: AssetDefinition[], key: string): Promise<ProcessedAsset[]>;
|
|
45
|
+
private prepareDependenciesForProcessing;
|
|
45
46
|
/**
|
|
46
47
|
* Processes deduplicated dependencies grouped by processor type.
|
|
47
48
|
*
|
|
@@ -80,6 +81,8 @@ export declare class AssetProcessingService {
|
|
|
80
81
|
* Returns the cached processed asset for a dependency key when available.
|
|
81
82
|
*/
|
|
82
83
|
private getCachedAsset;
|
|
84
|
+
private getCachedContentScriptAsset;
|
|
85
|
+
private resolveCachedContentScriptFilepath;
|
|
83
86
|
/**
|
|
84
87
|
* Stores one processed asset in the dependency cache.
|
|
85
88
|
*/
|
|
@@ -4,12 +4,19 @@ import { appLogger } from "../../../global/app-logger.js";
|
|
|
4
4
|
import { fileSystem } from "@ecopages/file-system";
|
|
5
5
|
import { deduplicateAssetDependencies, getAssetDependencyKey } from "./asset-dependency-keys.js";
|
|
6
6
|
import {
|
|
7
|
+
ensureGroupedContentScriptsBundle,
|
|
7
8
|
partitionGroupedContentScriptDependencies,
|
|
8
9
|
processGroupedDependencyBundles
|
|
9
10
|
} from "./grouped-content-bundles.js";
|
|
11
|
+
import { resolveIntegrationPluginForProcessingKey } from "./resolve-integration-plugin.js";
|
|
10
12
|
import { isHmrAware } from "./processor.interface.js";
|
|
11
13
|
import { ProcessorRegistry } from "./processor.registry.js";
|
|
12
14
|
import { processUngroupedDependency } from "./ungrouped-dependency-processing.js";
|
|
15
|
+
import { materializeContentScriptAsset } from "./materialize-content-script-asset.js";
|
|
16
|
+
import {
|
|
17
|
+
getDevBrowserScriptCacheEntry,
|
|
18
|
+
setDevBrowserScriptCacheEntry
|
|
19
|
+
} from "../../../build/dev-browser-script-cache.js";
|
|
13
20
|
import {
|
|
14
21
|
ContentScriptProcessor,
|
|
15
22
|
ContentStylesheetProcessor,
|
|
@@ -68,10 +75,16 @@ class AssetProcessingService {
|
|
|
68
75
|
const depsDir = path.join(this.config.absolutePaths.distDir, RESOLVED_ASSETS_DIR);
|
|
69
76
|
fileSystem.ensureDir(depsDir);
|
|
70
77
|
const dedupedDeps = deduplicateAssetDependencies(deps);
|
|
71
|
-
const
|
|
78
|
+
const preparedDeps = this.prepareDependenciesForProcessing(dedupedDeps, key);
|
|
79
|
+
ensureGroupedContentScriptsBundle(preparedDeps);
|
|
80
|
+
const results = await this.processDependenciesParallel(preparedDeps);
|
|
72
81
|
await this.optimizeDependencies(results);
|
|
73
82
|
return results;
|
|
74
83
|
}
|
|
84
|
+
prepareDependenciesForProcessing(deps, processingKey) {
|
|
85
|
+
const plugin = resolveIntegrationPluginForProcessingKey(this.config, processingKey);
|
|
86
|
+
return plugin?.prepareAssetDependencies?.(deps) ?? deps;
|
|
87
|
+
}
|
|
75
88
|
/**
|
|
76
89
|
* Processes deduplicated dependencies grouped by processor type.
|
|
77
90
|
*
|
|
@@ -80,14 +93,13 @@ class AssetProcessingService {
|
|
|
80
93
|
* pair, while still allowing the overall dependency set to resolve in
|
|
81
94
|
* parallel.
|
|
82
95
|
*/
|
|
83
|
-
async processDependenciesParallel(deps
|
|
96
|
+
async processDependenciesParallel(deps) {
|
|
84
97
|
const grouped = this.groupDependenciesByType(deps);
|
|
85
98
|
const groupPromises = Object.entries(grouped).map(async ([, typeDeps]) => {
|
|
86
99
|
const { groupedBundleDeps, ungroupedDeps } = partitionGroupedContentScriptDependencies(typeDeps);
|
|
87
100
|
const typePromises = ungroupedDeps.map(
|
|
88
101
|
(dep) => processUngroupedDependency({
|
|
89
102
|
dep,
|
|
90
|
-
key,
|
|
91
103
|
depKey: getAssetDependencyKey(dep),
|
|
92
104
|
getCachedAsset: (assetDep, depKey) => this.getCachedAsset(assetDep, depKey),
|
|
93
105
|
getProcessor: (assetDep) => this.registry.getProcessor(assetDep.kind, assetDep.source),
|
|
@@ -109,7 +121,6 @@ class AssetProcessingService {
|
|
|
109
121
|
);
|
|
110
122
|
const groupedResults = await processGroupedDependencyBundles({
|
|
111
123
|
bundles: Array.from(groupedBundleDeps.values()),
|
|
112
|
-
key,
|
|
113
124
|
getCachedAsset: (dep, depKey) => this.getCachedAsset(dep, depKey),
|
|
114
125
|
getDependencyKey: getAssetDependencyKey,
|
|
115
126
|
getGroupedProcessor: () => this.registry.getProcessor("script", "content"),
|
|
@@ -229,6 +240,9 @@ class AssetProcessingService {
|
|
|
229
240
|
if (process.env.NODE_ENV !== "production" && dep.source === "file" && dep.kind === "stylesheet") {
|
|
230
241
|
return null;
|
|
231
242
|
}
|
|
243
|
+
if (dep.kind === "script" && dep.source === "content") {
|
|
244
|
+
return this.getCachedContentScriptAsset(dep, depKey);
|
|
245
|
+
}
|
|
232
246
|
const cached = this.cache.get(depKey);
|
|
233
247
|
if (!cached) {
|
|
234
248
|
return null;
|
|
@@ -239,11 +253,34 @@ class AssetProcessingService {
|
|
|
239
253
|
}
|
|
240
254
|
return cached.asset;
|
|
241
255
|
}
|
|
256
|
+
getCachedContentScriptAsset(dep, depKey) {
|
|
257
|
+
const filepath = this.resolveCachedContentScriptFilepath(depKey) ?? getDevBrowserScriptCacheEntry(this.config, depKey)?.filepath;
|
|
258
|
+
if (!filepath) {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
const materialized = materializeContentScriptAsset(dep, filepath);
|
|
262
|
+
this.cache.set(depKey, { asset: materialized });
|
|
263
|
+
return materialized;
|
|
264
|
+
}
|
|
265
|
+
resolveCachedContentScriptFilepath(depKey) {
|
|
266
|
+
const cached = this.cache.get(depKey);
|
|
267
|
+
if (!cached?.asset.filepath) {
|
|
268
|
+
return void 0;
|
|
269
|
+
}
|
|
270
|
+
if (!fileSystem.exists(cached.asset.filepath)) {
|
|
271
|
+
this.cache.delete(depKey);
|
|
272
|
+
return void 0;
|
|
273
|
+
}
|
|
274
|
+
return cached.asset.filepath;
|
|
275
|
+
}
|
|
242
276
|
/**
|
|
243
277
|
* Stores one processed asset in the dependency cache.
|
|
244
278
|
*/
|
|
245
279
|
setCachedAsset(dep, depKey, asset) {
|
|
246
280
|
this.cache.set(depKey, { asset });
|
|
281
|
+
if (dep.kind === "script" && dep.source === "content") {
|
|
282
|
+
setDevBrowserScriptCacheEntry(this.config, depKey, asset);
|
|
283
|
+
}
|
|
247
284
|
}
|
|
248
285
|
/**
|
|
249
286
|
* Clears all cached processed assets.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { createRequire } from 'node:module';
|
|
2
|
+
export type BrowserRuntimeDefaultExportPolicy = 'emit-default' | 'skip-default';
|
|
3
|
+
type RequireFromRoot = ReturnType<typeof createRequire>;
|
|
4
|
+
/**
|
|
5
|
+
* Resolves a package specifier to its ESM entry file path from the app root.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolvePackageEsmEntryPath(specifier: string, rootDir: string): string | undefined;
|
|
8
|
+
/**
|
|
9
|
+
* Builds a relative import path from a generated runtime entry file to a resolved module path.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toRelativeEntryImport(entryDir: string, resolvedPath: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Resolves a browser runtime entry import to an ESM file path when possible.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* `createRequire().resolve()` follows the `require` export condition and can
|
|
17
|
+
* land on `.cjs` entrypoints. Browser vendor bundles then emit runtime
|
|
18
|
+
* `require()` calls for React externals. Prefer Node's ESM resolver first, then
|
|
19
|
+
* a `.cjs` → `.js` sibling fallback.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveBrowserRuntimeEntryImport(options: {
|
|
22
|
+
specifier: string;
|
|
23
|
+
requireFromRoot: RequireFromRoot;
|
|
24
|
+
entryDir: string;
|
|
25
|
+
rootDir: string;
|
|
26
|
+
}): string;
|
|
27
|
+
/**
|
|
28
|
+
* Decides whether a generated runtime entry should re-export a default binding.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* ESM-only packages such as `@tanstack/react-query` expose named exports only.
|
|
32
|
+
* Legacy CJS packages such as `react` assign `module.exports` directly and have
|
|
33
|
+
* no `.default` under `require()`, but still need a default re-export for
|
|
34
|
+
* `import React from 'react'` in browser bundles.
|
|
35
|
+
*
|
|
36
|
+
* ESM source inspection is a best-effort fast path; when it is inconclusive the
|
|
37
|
+
* policy falls back to the shape returned by `require()`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function inferBrowserRuntimeDefaultExportPolicy(options: {
|
|
40
|
+
specifier: string;
|
|
41
|
+
requireFromRoot: RequireFromRoot;
|
|
42
|
+
rootDir: string;
|
|
43
|
+
}): BrowserRuntimeDefaultExportPolicy;
|
|
44
|
+
/**
|
|
45
|
+
* Reads the named runtime exports that should be re-exported from a generated runtime entry module.
|
|
46
|
+
*
|
|
47
|
+
* @remarks
|
|
48
|
+
* Default exports are handled separately because generated runtime entry files need to emit a
|
|
49
|
+
* synthetic default binding only when the caller explicitly asks for it.
|
|
50
|
+
*/
|
|
51
|
+
export declare function listBrowserRuntimeModuleExportNames(specifier: string, requireFromRoot: RequireFromRoot): string[];
|
|
52
|
+
export {};
|