@ecopages/core 0.2.0-beta.24 → 0.2.0-beta.26
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 +19 -2
- package/src/build/build-adapter.js +4 -4
- package/src/build/rolldown-adapter-helpers.js +4 -3
- package/src/build/runtime-build-output-normalizer.js +2 -4
- package/src/eco/eco.types.d.ts +12 -1
- 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/tsconfig-import-resolver.d.ts +18 -0
- package/src/plugins/tsconfig-import-resolver.js +159 -0
- package/src/services/assets/asset-processing-service/asset-dependency-keys.d.ts +13 -0
- package/src/services/assets/asset-processing-service/asset-dependency-keys.js +3 -0
- package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +3 -1
- package/src/services/assets/asset-processing-service/asset-processing.service.js +3 -1
- 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/grouped-content-bundles.js +19 -3
- package/src/services/assets/asset-processing-service/index.d.ts +1 -0
- package/src/services/assets/asset-processing-service/index.js +1 -0
- package/src/services/assets/asset-processing-service/inline-content-script-body.d.ts +11 -0
- package/src/services/assets/asset-processing-service/inline-content-script-body.js +17 -0
- package/src/services/assets/asset-processing-service/materialize-content-script-asset.d.ts +4 -6
- package/src/services/assets/asset-processing-service/materialize-content-script-asset.js +2 -1
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts +3 -2
- package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +32 -53
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ecopages/core",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.26",
|
|
4
4
|
"description": "Core package for Ecopages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ecopages",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"directory": "packages/core"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@ecopages/file-system": "0.2.0-beta.
|
|
20
|
+
"@ecopages/file-system": "0.2.0-beta.26",
|
|
21
21
|
"@ecopages/logger": "^0.2.3",
|
|
22
22
|
"@ecopages/scripts-injector": "^0.1.5",
|
|
23
23
|
"@oxc-project/runtime": "0.134.0",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"esbuild": "^0.28.0",
|
|
27
27
|
"ghtml": "^4.0.2",
|
|
28
28
|
"oxc-parser": "^0.124.0",
|
|
29
|
+
"oxc-resolver": "^11.24.2",
|
|
29
30
|
"rolldown": "^1.1.0",
|
|
30
31
|
"ws": "^8.20.1",
|
|
31
32
|
"@standard-schema/spec": "^1.1.0",
|
|
@@ -219,6 +220,14 @@
|
|
|
219
220
|
"types": "./src/plugins/foreign-jsx-override-plugin.d.ts",
|
|
220
221
|
"default": "./src/plugins/foreign-jsx-override-plugin.js"
|
|
221
222
|
},
|
|
223
|
+
"./plugins/alias-resolver-plugin": {
|
|
224
|
+
"types": "./src/plugins/alias-resolver-plugin.d.ts",
|
|
225
|
+
"default": "./src/plugins/alias-resolver-plugin.js"
|
|
226
|
+
},
|
|
227
|
+
"./plugins/tsconfig-import-resolver": {
|
|
228
|
+
"types": "./src/plugins/tsconfig-import-resolver.d.ts",
|
|
229
|
+
"default": "./src/plugins/tsconfig-import-resolver.js"
|
|
230
|
+
},
|
|
222
231
|
"./plugins/jsx-import-source.utils": {
|
|
223
232
|
"types": "./src/plugins/jsx-import-source.utils.d.ts",
|
|
224
233
|
"default": "./src/plugins/jsx-import-source.utils.js"
|
|
@@ -432,6 +441,14 @@
|
|
|
432
441
|
"types": "./src/plugins/foreign-jsx-override-plugin.d.ts",
|
|
433
442
|
"default": "./src/plugins/foreign-jsx-override-plugin.js"
|
|
434
443
|
},
|
|
444
|
+
"./plugins/alias-resolver-plugin.ts": {
|
|
445
|
+
"types": "./src/plugins/alias-resolver-plugin.d.ts",
|
|
446
|
+
"default": "./src/plugins/alias-resolver-plugin.js"
|
|
447
|
+
},
|
|
448
|
+
"./plugins/tsconfig-import-resolver.ts": {
|
|
449
|
+
"types": "./src/plugins/tsconfig-import-resolver.d.ts",
|
|
450
|
+
"default": "./src/plugins/tsconfig-import-resolver.js"
|
|
451
|
+
},
|
|
435
452
|
"./plugins/jsx-import-source.utils.ts": {
|
|
436
453
|
"types": "./src/plugins/jsx-import-source.utils.d.ts",
|
|
437
454
|
"default": "./src/plugins/jsx-import-source.utils.js"
|
|
@@ -120,16 +120,16 @@ import {
|
|
|
120
120
|
setupAppRuntimePlugins
|
|
121
121
|
} from "./app-build-manifest-runtime.js";
|
|
122
122
|
function getAppServerBuildPlugins(appConfig) {
|
|
123
|
-
const
|
|
124
|
-
const aliasPlugin =
|
|
123
|
+
const projectDir = appConfig.absolutePaths?.projectDir;
|
|
124
|
+
const aliasPlugin = projectDir ? [createAliasResolverPlugin(projectDir)] : [];
|
|
125
125
|
return [...aliasPlugin, ...getServerBuildPlugins(getAppBuildManifest(appConfig))];
|
|
126
126
|
}
|
|
127
127
|
function getAppBrowserBuildPlugins(appConfig) {
|
|
128
128
|
const manifest = getAppBuildManifest(appConfig);
|
|
129
129
|
const sourceTransformNames = new Set(getAppSourceTransforms(appConfig).map((transform) => transform.name));
|
|
130
130
|
const browserPlugins = getBrowserBuildPlugins(manifest).filter((plugin) => !sourceTransformNames.has(plugin.name));
|
|
131
|
-
const
|
|
132
|
-
const aliasPlugin =
|
|
131
|
+
const projectDir = appConfig.absolutePaths?.projectDir;
|
|
132
|
+
const aliasPlugin = projectDir ? [createAliasResolverPlugin(projectDir)] : [];
|
|
133
133
|
return [...aliasPlugin, ...browserPlugins, ...getJsxOwnershipPlugins(appConfig)];
|
|
134
134
|
}
|
|
135
135
|
function build(options, executor = defaultRolldownBuildAdapter) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { isBarePackageImportSpecifier } from "../plugins/tsconfig-import-resolver.js";
|
|
4
5
|
import { collectBrowserRuntimeImportRewriteMap, rewriteBrowserRuntimeImports } from "./browser-runtime-plugin.js";
|
|
5
6
|
import { createServerSideCssShimPlugin } from "./server-side-css-shim-plugin.js";
|
|
6
7
|
import { createRolldownPluginBridge } from "./rolldown-plugin-bridge.js";
|
|
@@ -48,8 +49,8 @@ function transpileProfileToOptions(profile) {
|
|
|
48
49
|
};
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
|
-
function isPackageImport(id) {
|
|
52
|
-
return
|
|
52
|
+
function isPackageImport(id, contextRoot) {
|
|
53
|
+
return isBarePackageImportSpecifier(id, contextRoot);
|
|
53
54
|
}
|
|
54
55
|
function tryResolveModule(id, resolver) {
|
|
55
56
|
try {
|
|
@@ -92,7 +93,7 @@ function createExternalMatcher(options, appRootRequireCache) {
|
|
|
92
93
|
if (explicitExternals.has(id)) {
|
|
93
94
|
return true;
|
|
94
95
|
}
|
|
95
|
-
if (!externalPackages || !isPackageImport(id)) {
|
|
96
|
+
if (!externalPackages || !isPackageImport(id, contextRoot)) {
|
|
96
97
|
return false;
|
|
97
98
|
}
|
|
98
99
|
return !shouldBundlePackageImport(id, contextRoot, appRootRequireCache);
|
|
@@ -2,6 +2,7 @@ import { createRequire } from "node:module";
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { isBarePackageImportSpecifier } from "../plugins/tsconfig-import-resolver.js";
|
|
5
6
|
const corePackageRequire = createRequire(new URL("../../package.json", import.meta.url));
|
|
6
7
|
const appDeclaredPackageCache = /* @__PURE__ */ new Map();
|
|
7
8
|
const appWorkspacePackageCache = /* @__PURE__ */ new Map();
|
|
@@ -13,9 +14,6 @@ function tryResolveRuntimeImport(specifier, resolver) {
|
|
|
13
14
|
return void 0;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
16
|
-
function isBareRuntimeImport(specifier) {
|
|
17
|
-
return !specifier.startsWith(".") && !path.isAbsolute(specifier) && !specifier.startsWith("/") && !specifier.startsWith("node:") && !specifier.startsWith("@/") && !specifier.startsWith("~/") && !specifier.startsWith("#") && !specifier.includes(":");
|
|
18
|
-
}
|
|
19
17
|
function extractPackageNameFromFileUrl(specifier) {
|
|
20
18
|
if (!specifier.startsWith("file://")) {
|
|
21
19
|
return void 0;
|
|
@@ -108,7 +106,7 @@ function isDeclaredInResolutionChain(specifier, rootDir) {
|
|
|
108
106
|
return isDeclaredAppPackageImport(specifier, rootDir);
|
|
109
107
|
}
|
|
110
108
|
function rewriteRuntimeImportSpecifier(specifier, quote, rootDir) {
|
|
111
|
-
if (!
|
|
109
|
+
if (!isBarePackageImportSpecifier(specifier, rootDir)) {
|
|
112
110
|
return void 0;
|
|
113
111
|
}
|
|
114
112
|
if (isDeclaredInResolutionChain(specifier, rootDir)) {
|
package/src/eco/eco.types.d.ts
CHANGED
|
@@ -72,7 +72,18 @@ export interface ComponentOptions<P, E = EcoPagesElement> {
|
|
|
72
72
|
render: (props: P) => E | Promise<E>;
|
|
73
73
|
}
|
|
74
74
|
export type HtmlOptions<E = EcoPagesElement> = ComponentOptions<HtmlTemplateProps, E>;
|
|
75
|
-
export type LayoutOptions<E = EcoPagesElement> = ComponentOptions<LayoutProps<E>, E
|
|
75
|
+
export type LayoutOptions<E = EcoPagesElement> = ComponentOptions<LayoutProps<E>, E> & {
|
|
76
|
+
/**
|
|
77
|
+
* Marks this layout as mounting shared client runtime state such as React context
|
|
78
|
+
* providers or query clients.
|
|
79
|
+
*
|
|
80
|
+
* @remarks
|
|
81
|
+
* When any layout in the app sets this flag, only flagged layouts participate in
|
|
82
|
+
* React auto-vendoring discovery. This keeps shell-only layouts out of provider
|
|
83
|
+
* scans while persisted SPA navigation stays on one shared vendor instance.
|
|
84
|
+
*/
|
|
85
|
+
runtimeProvider?: boolean;
|
|
86
|
+
};
|
|
76
87
|
/**
|
|
77
88
|
* Base options shared by all page variants
|
|
78
89
|
*/
|
|
@@ -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
|
};
|
|
@@ -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
|
+
};
|
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
import type { AssetDefinition } from './assets.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Stable identity for dependency deduplication and {@link AssetProcessingService} cache lookups.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* Script dependencies include a build signature derived from bundle flags, grouped-bundle
|
|
7
|
+
* metadata, and selected `bundleOptions` fields (`naming`, `external`, `minify`, plugin names).
|
|
8
|
+
* HTML-affecting script `attributes` are hashed into the key so dedupe and cache reuse do not
|
|
9
|
+
* collapse declarations that would emit different `<script>` tags. Full plugin objects and
|
|
10
|
+
* `NODE_ENV` are excluded from the key.
|
|
11
|
+
*
|
|
12
|
+
* Runtime minify in {@link ContentScriptProcessor} still follows `NODE_ENV` via
|
|
13
|
+
* `isProduction`, not `bundleOptions.minify`.
|
|
14
|
+
*/
|
|
2
15
|
export declare function getAssetDependencyKey(dep: AssetDefinition): string;
|
|
3
16
|
export declare function deduplicateAssetDependencies(deps: AssetDefinition[]): AssetDefinition[];
|
|
@@ -34,6 +34,9 @@ function getAssetDependencyKey(dep) {
|
|
|
34
34
|
if ("packageRole" in dep && dep.packageRole) {
|
|
35
35
|
parts.push(`package:${dep.packageRole}`);
|
|
36
36
|
}
|
|
37
|
+
if (dep.kind === "script" && dep.attributes) {
|
|
38
|
+
parts.push(`attrs:${generateHash(JSON.stringify(dep.attributes))}`);
|
|
39
|
+
}
|
|
37
40
|
const scriptBuildSignature = getScriptDependencyBuildSignature(dep);
|
|
38
41
|
if (scriptBuildSignature) {
|
|
39
42
|
parts.push(`build:${scriptBuildSignature}`);
|
|
@@ -39,7 +39,9 @@ export declare class AssetProcessingService {
|
|
|
39
39
|
* @remarks
|
|
40
40
|
* Dependencies are deduplicated before processor execution so repeated
|
|
41
41
|
* declarations across the render tree reuse the same emitted outputs and cache
|
|
42
|
-
* entries.
|
|
42
|
+
* entries. Returned asset order is unspecified — consumers must re-associate
|
|
43
|
+
* outputs with inputs via dependency metadata such as `groupedBundle`, not by
|
|
44
|
+
* array index alignment with the input list.
|
|
43
45
|
*/
|
|
44
46
|
processDependencies(deps: AssetDefinition[], key: string): Promise<ProcessedAsset[]>;
|
|
45
47
|
private prepareDependenciesForProcessing;
|
|
@@ -69,7 +69,9 @@ class AssetProcessingService {
|
|
|
69
69
|
* @remarks
|
|
70
70
|
* Dependencies are deduplicated before processor execution so repeated
|
|
71
71
|
* declarations across the render tree reuse the same emitted outputs and cache
|
|
72
|
-
* entries.
|
|
72
|
+
* entries. Returned asset order is unspecified — consumers must re-associate
|
|
73
|
+
* outputs with inputs via dependency metadata such as `groupedBundle`, not by
|
|
74
|
+
* array index alignment with the input list.
|
|
73
75
|
*/
|
|
74
76
|
async processDependencies(deps, key) {
|
|
75
77
|
const depsDir = path.join(this.config.absolutePaths.distDir, RESOLVED_ASSETS_DIR);
|
|
@@ -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 {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL, fileURLToPath } from "node:url";
|
|
4
|
+
import { isBarePackageImportSpecifier } from "../../../plugins/tsconfig-import-resolver.js";
|
|
5
|
+
function resolvePackageEsmEntryPath(specifier, rootDir) {
|
|
6
|
+
try {
|
|
7
|
+
return fileURLToPath(import.meta.resolve(specifier, pathToFileURL(path.join(rootDir, "package.json")).href));
|
|
8
|
+
} catch {
|
|
9
|
+
return void 0;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function toRelativeEntryImport(entryDir, resolvedPath) {
|
|
13
|
+
let relativePath = path.relative(entryDir, resolvedPath).replace(/\\/g, "/");
|
|
14
|
+
if (!relativePath.startsWith(".")) {
|
|
15
|
+
relativePath = `./${relativePath}`;
|
|
16
|
+
}
|
|
17
|
+
return relativePath;
|
|
18
|
+
}
|
|
19
|
+
function resolveBrowserRuntimeEntryImport(options) {
|
|
20
|
+
const { specifier, requireFromRoot, entryDir, rootDir } = options;
|
|
21
|
+
if (specifier.startsWith("node:") || specifier.startsWith("file:")) {
|
|
22
|
+
return specifier;
|
|
23
|
+
}
|
|
24
|
+
if (specifier.startsWith(".")) {
|
|
25
|
+
const resolvedPath2 = requireFromRoot.resolve(specifier);
|
|
26
|
+
return toRelativeEntryImport(entryDir, resolvedPath2);
|
|
27
|
+
}
|
|
28
|
+
if (isBarePackageImportSpecifier(specifier, rootDir)) {
|
|
29
|
+
const esmResolvedPath = resolvePackageEsmEntryPath(specifier, rootDir);
|
|
30
|
+
if (esmResolvedPath) {
|
|
31
|
+
return toRelativeEntryImport(entryDir, esmResolvedPath);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const resolvedPath = requireFromRoot.resolve(specifier);
|
|
35
|
+
const esmSibling = resolvedPath.endsWith(".cjs") ? `${resolvedPath.slice(0, -4)}.js` : resolvedPath.endsWith(".cts") ? `${resolvedPath.slice(0, -4)}.ts` : void 0;
|
|
36
|
+
if (esmSibling && fs.existsSync(esmSibling)) {
|
|
37
|
+
return toRelativeEntryImport(entryDir, esmSibling);
|
|
38
|
+
}
|
|
39
|
+
return toRelativeEntryImport(entryDir, resolvedPath);
|
|
40
|
+
}
|
|
41
|
+
function inferBrowserRuntimeDefaultExportPolicy(options) {
|
|
42
|
+
const { specifier, requireFromRoot, rootDir } = options;
|
|
43
|
+
const esmPath = resolvePackageEsmEntryPath(specifier, rootDir);
|
|
44
|
+
if (esmPath && fs.existsSync(esmPath)) {
|
|
45
|
+
const source = fs.readFileSync(esmPath, "utf8");
|
|
46
|
+
if (/\bexport\s+default\b/.test(source)) {
|
|
47
|
+
return "emit-default";
|
|
48
|
+
}
|
|
49
|
+
if (/\bexport\s+(?:[\w*{]|const|let|var|function|class)/.test(source)) {
|
|
50
|
+
return "skip-default";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
try {
|
|
54
|
+
const moduleExports = requireFromRoot(specifier);
|
|
55
|
+
if (moduleExports.default !== void 0) {
|
|
56
|
+
return "emit-default";
|
|
57
|
+
}
|
|
58
|
+
return moduleExports.__esModule === true ? "skip-default" : "emit-default";
|
|
59
|
+
} catch {
|
|
60
|
+
return "skip-default";
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function listBrowserRuntimeModuleExportNames(specifier, requireFromRoot) {
|
|
64
|
+
let moduleExports;
|
|
65
|
+
try {
|
|
66
|
+
moduleExports = requireFromRoot(specifier);
|
|
67
|
+
} catch {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
return Object.keys(moduleExports).filter((name) => name !== "__esModule" && name !== "default").filter((name) => /^[$A-Z_a-z][$\w]*$/.test(name)).sort();
|
|
71
|
+
}
|
|
72
|
+
export {
|
|
73
|
+
inferBrowserRuntimeDefaultExportPolicy,
|
|
74
|
+
listBrowserRuntimeModuleExportNames,
|
|
75
|
+
resolveBrowserRuntimeEntryImport,
|
|
76
|
+
resolvePackageEsmEntryPath,
|
|
77
|
+
toRelativeEntryImport
|
|
78
|
+
};
|
|
@@ -2,6 +2,11 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { DEFAULT_ECOPAGES_WORK_DIR } from "../../../config/constants.js";
|
|
5
|
+
import {
|
|
6
|
+
inferBrowserRuntimeDefaultExportPolicy,
|
|
7
|
+
listBrowserRuntimeModuleExportNames,
|
|
8
|
+
resolveBrowserRuntimeEntryImport
|
|
9
|
+
} from "./browser-runtime-entry-resolution.js";
|
|
5
10
|
function createBrowserRuntimeEntryModule(options) {
|
|
6
11
|
if (options.modules.some((module) => !module.specifier.startsWith("node:")) && !options.rootDir) {
|
|
7
12
|
throw new Error("createBrowserRuntimeEntryModule requires rootDir to resolve package specifiers");
|
|
@@ -20,12 +25,17 @@ function createBrowserRuntimeEntryModule(options) {
|
|
|
20
25
|
const filePath = path.join(artifactsDir, options.fileName);
|
|
21
26
|
const entryDir = path.dirname(filePath);
|
|
22
27
|
for (const module of options.modules) {
|
|
23
|
-
const importSpecifier =
|
|
24
|
-
|
|
28
|
+
const importSpecifier = resolveBrowserRuntimeEntryImport({
|
|
29
|
+
specifier: module.specifier,
|
|
30
|
+
requireFromRoot,
|
|
31
|
+
entryDir,
|
|
32
|
+
rootDir
|
|
33
|
+
});
|
|
34
|
+
if (module.defaultExport && inferBrowserRuntimeDefaultExportPolicy({ specifier: module.specifier, requireFromRoot, rootDir }) === "emit-default") {
|
|
25
35
|
statements.push(`import __ecopages_default_export__ from '${importSpecifier}';`);
|
|
26
36
|
statements.push("export default __ecopages_default_export__;");
|
|
27
37
|
}
|
|
28
|
-
const exportNames =
|
|
38
|
+
const exportNames = listBrowserRuntimeModuleExportNames(module.specifier, requireFromRoot).filter(
|
|
29
39
|
(name) => !seenExports.has(name)
|
|
30
40
|
);
|
|
31
41
|
if (exportNames.length > 0) {
|
|
@@ -41,21 +51,6 @@ function createBrowserRuntimeEntryModule(options) {
|
|
|
41
51
|
}
|
|
42
52
|
return filePath;
|
|
43
53
|
}
|
|
44
|
-
function resolveEntryImportSpecifier(specifier, requireFromRoot, entryDir) {
|
|
45
|
-
if (specifier.startsWith("node:") || specifier.startsWith("file:")) {
|
|
46
|
-
return specifier;
|
|
47
|
-
}
|
|
48
|
-
const resolvedPath = requireFromRoot.resolve(specifier);
|
|
49
|
-
let relativePath = path.relative(entryDir, resolvedPath).replace(/\\/g, "/");
|
|
50
|
-
if (!relativePath.startsWith(".")) {
|
|
51
|
-
relativePath = `./${relativePath}`;
|
|
52
|
-
}
|
|
53
|
-
return relativePath;
|
|
54
|
-
}
|
|
55
|
-
function getModuleExportNames(specifier, requireFromRoot) {
|
|
56
|
-
const moduleExports = requireFromRoot(specifier);
|
|
57
|
-
return Object.keys(moduleExports).filter((name) => name !== "__esModule" && name !== "default").filter((name) => /^[$A-Z_a-z][$\w]*$/.test(name)).sort();
|
|
58
|
-
}
|
|
59
54
|
export {
|
|
60
55
|
createBrowserRuntimeEntryModule
|
|
61
56
|
};
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { isDevelopmentRuntime } from "../../../utils/runtime.js";
|
|
2
2
|
import { finalizeProcessedAsset } from "./finalize-processed-asset.js";
|
|
3
|
+
function getGroupedBundleAssetKey(groupedBundle) {
|
|
4
|
+
return `${groupedBundle.id}:${groupedBundle.entryName}`;
|
|
5
|
+
}
|
|
3
6
|
function ensureGroupedContentScriptsBundle(dependencies) {
|
|
4
7
|
if (isDevelopmentRuntime()) {
|
|
5
8
|
return;
|
|
@@ -54,12 +57,25 @@ async function processGroupedDependencyBundles(options) {
|
|
|
54
57
|
}
|
|
55
58
|
try {
|
|
56
59
|
const processedResults = await processor.processGrouped(bundleDeps);
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
const processedByEntryKey = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const processed of processedResults) {
|
|
62
|
+
if (!processed.groupedBundle) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
processedByEntryKey.set(getGroupedBundleAssetKey(processed.groupedBundle), processed);
|
|
66
|
+
}
|
|
67
|
+
return bundleDeps.flatMap((dep) => {
|
|
68
|
+
if (dep.kind !== "script" || dep.source !== "content" || !dep.groupedBundle) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
const processed = processedByEntryKey.get(getGroupedBundleAssetKey(dep.groupedBundle));
|
|
72
|
+
if (!processed) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
59
75
|
const depKey = getDependencyKey(dep);
|
|
60
76
|
const finalized = finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl);
|
|
61
77
|
setCachedAsset(dep, depKey, finalized);
|
|
62
|
-
return finalized;
|
|
78
|
+
return [finalized];
|
|
63
79
|
});
|
|
64
80
|
} catch (error) {
|
|
65
81
|
logError(error);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ContentScriptAsset } from './assets.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves the script body to embed when a content script is marked inline.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* `inline` means embed in HTML, not "use declaration source." When bundling has
|
|
7
|
+
* already run, only the output file is valid browser script; `dep.content` may
|
|
8
|
+
* still contain build-time import paths. Processor emission and cache
|
|
9
|
+
* materialization must share this rule so they cannot drift.
|
|
10
|
+
*/
|
|
11
|
+
export declare function resolveInlineContentScriptBody(dep: Pick<ContentScriptAsset, 'inline' | 'bundle' | 'content'>, filepath: string): string | undefined;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { fileSystem } from "@ecopages/file-system";
|
|
2
|
+
import { appLogger } from "../../../global/app-logger.js";
|
|
3
|
+
function resolveInlineContentScriptBody(dep, filepath) {
|
|
4
|
+
if (!dep.inline) {
|
|
5
|
+
return void 0;
|
|
6
|
+
}
|
|
7
|
+
if (dep.bundle !== false && fileSystem.exists(filepath)) {
|
|
8
|
+
return fileSystem.readFileSync(filepath);
|
|
9
|
+
}
|
|
10
|
+
if (dep.bundle !== false) {
|
|
11
|
+
appLogger.warn(`Missing bundled inline script output at ${filepath}; falling back to declaration source.`);
|
|
12
|
+
}
|
|
13
|
+
return dep.content;
|
|
14
|
+
}
|
|
15
|
+
export {
|
|
16
|
+
resolveInlineContentScriptBody
|
|
17
|
+
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import type { ContentScriptAsset, ProcessedAsset } from './assets.types.js';
|
|
2
2
|
/**
|
|
3
|
-
* Builds a processed content-script asset from one dependency declaration and a
|
|
4
|
-
* previously emitted output file.
|
|
5
|
-
*
|
|
6
3
|
* @remarks
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* graph assembly
|
|
4
|
+
* {@link AssetProcessingService} cache and dev disk cache entries usually keep
|
|
5
|
+
* only an output path. Dependency declarations still carry the metadata HTML and
|
|
6
|
+
* page-browser graph assembly need (`groupedBundle`, attributes, roles), so cache
|
|
7
|
+
* hits must be rehydrated from both sources rather than returned verbatim.
|
|
10
8
|
*/
|
|
11
9
|
export declare function materializeContentScriptAsset(dep: ContentScriptAsset, filepath: string): ProcessedAsset;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { resolveInlineContentScriptBody } from "./inline-content-script-body.js";
|
|
1
2
|
function materializeContentScriptAsset(dep, filepath) {
|
|
2
3
|
return {
|
|
3
4
|
filepath,
|
|
4
5
|
kind: "script",
|
|
5
6
|
inline: dep.inline ?? false,
|
|
6
|
-
content: dep
|
|
7
|
+
content: resolveInlineContentScriptBody(dep, filepath),
|
|
7
8
|
position: dep.position,
|
|
8
9
|
attributes: dep.attributes,
|
|
9
10
|
excludeFromHtml: dep.excludeFromHtml,
|
package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts
CHANGED
|
@@ -3,11 +3,12 @@ import { BaseScriptProcessor } from '../base/base-script-processor.js';
|
|
|
3
3
|
export declare class ContentScriptProcessor extends BaseScriptProcessor<ContentScriptAsset> {
|
|
4
4
|
private getContentScriptEntryDir;
|
|
5
5
|
private getContentScriptEntryPath;
|
|
6
|
-
private createBundleConfigHash;
|
|
7
|
-
private createContentScriptCacheKey;
|
|
8
6
|
private toProcessedAsset;
|
|
9
7
|
private removeContentScriptEntry;
|
|
10
8
|
processGrouped(deps: ContentScriptAsset[]): Promise<ProcessedAsset[]>;
|
|
11
9
|
private getGroupedBundlerOptions;
|
|
10
|
+
/**
|
|
11
|
+
* Emits one content script asset. Cache reuse is owned by {@link AssetProcessingService}.
|
|
12
|
+
*/
|
|
12
13
|
process(dep: ContentScriptAsset): Promise<ProcessedAsset>;
|
|
13
14
|
}
|
package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { fileSystem } from "@ecopages/file-system";
|
|
3
|
+
import { resolveInlineContentScriptBody } from "../../inline-content-script-body.js";
|
|
3
4
|
import { shouldUseDevBrowserScriptCache } from "../../../../../build/dev-browser-script-cache.js";
|
|
4
5
|
import { BaseScriptProcessor } from "../base/base-script-processor.js";
|
|
5
6
|
class ContentScriptProcessor extends BaseScriptProcessor {
|
|
@@ -11,24 +12,10 @@ class ContentScriptProcessor extends BaseScriptProcessor {
|
|
|
11
12
|
getContentScriptEntryPath(contentHash) {
|
|
12
13
|
return path.join(this.getContentScriptEntryDir(), `${contentHash}.js`);
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
-
return this.generateHash(
|
|
16
|
-
JSON.stringify({
|
|
17
|
-
bundle: shouldBundle,
|
|
18
|
-
minify: shouldBundle && this.isProduction,
|
|
19
|
-
opts: dep.bundleOptions
|
|
20
|
-
})
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
createContentScriptCacheKey(dep, shouldBundle) {
|
|
24
|
-
const contentHash = this.generateHash(dep.content);
|
|
25
|
-
const configHash = this.createBundleConfigHash(dep, shouldBundle);
|
|
26
|
-
return `${this.buildCacheKey(`content-script:${contentHash}`, contentHash, dep)}:${configHash}`;
|
|
27
|
-
}
|
|
28
|
-
toProcessedAsset(dep, filepath, inlineContent) {
|
|
15
|
+
toProcessedAsset(dep, filepath) {
|
|
29
16
|
return {
|
|
30
17
|
filepath,
|
|
31
|
-
content: dep
|
|
18
|
+
content: resolveInlineContentScriptBody(dep, filepath),
|
|
32
19
|
kind: "script",
|
|
33
20
|
position: dep.position,
|
|
34
21
|
attributes: dep.attributes,
|
|
@@ -81,11 +68,7 @@ class ContentScriptProcessor extends BaseScriptProcessor {
|
|
|
81
68
|
if (!bundledFilePath) {
|
|
82
69
|
throw new Error(`Missing grouped bundle output for ${entryName}`);
|
|
83
70
|
}
|
|
84
|
-
return this.toProcessedAsset(
|
|
85
|
-
dep,
|
|
86
|
-
bundledFilePath,
|
|
87
|
-
dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0
|
|
88
|
-
);
|
|
71
|
+
return this.toProcessedAsset(dep, bundledFilePath);
|
|
89
72
|
});
|
|
90
73
|
} finally {
|
|
91
74
|
for (const { contentHash } of tempEntries) {
|
|
@@ -104,41 +87,37 @@ class ContentScriptProcessor extends BaseScriptProcessor {
|
|
|
104
87
|
}
|
|
105
88
|
return options;
|
|
106
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* Emits one content script asset. Cache reuse is owned by {@link AssetProcessingService}.
|
|
92
|
+
*/
|
|
107
93
|
async process(dep) {
|
|
108
94
|
const shouldBundle = this.shouldBundle(dep);
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
if (!dep.inline) {
|
|
116
|
-
fileSystem.write(filepath, dep.content);
|
|
117
|
-
}
|
|
118
|
-
return this.toProcessedAsset(dep, filepath, dep.inline ? dep.content : void 0);
|
|
95
|
+
const hash = this.generateHash(dep.content);
|
|
96
|
+
const filename = dep.name ? `${dep.name}.js` : `script-${hash}.js`;
|
|
97
|
+
const filepath = path.join(this.getAssetsDir(), "scripts", filename);
|
|
98
|
+
if (!shouldBundle) {
|
|
99
|
+
if (!dep.inline) {
|
|
100
|
+
fileSystem.write(filepath, dep.content);
|
|
119
101
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
this.removeContentScriptEntry(hash);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
102
|
+
return this.toProcessedAsset(dep, filepath);
|
|
103
|
+
}
|
|
104
|
+
if (!dep.content) {
|
|
105
|
+
throw new Error("No content found for script asset");
|
|
106
|
+
}
|
|
107
|
+
const entryPath = this.getContentScriptEntryPath(hash);
|
|
108
|
+
fileSystem.write(entryPath, dep.content);
|
|
109
|
+
try {
|
|
110
|
+
const bundledFilePath = await this.bundleScript({
|
|
111
|
+
entrypoint: entryPath,
|
|
112
|
+
outdir: this.getAssetsDir(),
|
|
113
|
+
minify: this.isProduction,
|
|
114
|
+
naming: `${path.parse(filename).name}-[hash].[ext]`,
|
|
115
|
+
...this.getBundlerOptions(dep)
|
|
116
|
+
});
|
|
117
|
+
return this.toProcessedAsset(dep, bundledFilePath);
|
|
118
|
+
} finally {
|
|
119
|
+
this.removeContentScriptEntry(hash);
|
|
120
|
+
}
|
|
142
121
|
}
|
|
143
122
|
}
|
|
144
123
|
export {
|