@ecopages/core 0.2.0-beta.25 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ecopages/core",
3
- "version": "0.2.0-beta.25",
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.25",
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",
@@ -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);
@@ -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
- return processedResults.map((processed, index) => {
58
- const dep = bundleDeps[index];
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
- * Dev disk cache entries store only output paths. Callers must materialize the
8
- * full processed asset from the originating dependency so HTML and page-browser
9
- * graph assembly receive grouped-bundle metadata and script attributes.
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.inline ? dep.content : void 0,
7
+ content: resolveInlineContentScriptBody(dep, filepath),
7
8
  position: dep.position,
8
9
  attributes: dep.attributes,
9
10
  excludeFromHtml: dep.excludeFromHtml,
@@ -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
  }
@@ -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
- createBundleConfigHash(dep, shouldBundle) {
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.inline ? inlineContent : void 0,
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 cacheKey = this.createContentScriptCacheKey(dep, shouldBundle);
110
- return this.getOrProcess(cacheKey, async () => {
111
- const hash = this.generateHash(dep.content);
112
- const filename = dep.name ? `${dep.name}.js` : `script-${hash}.js`;
113
- const filepath = path.join(this.getAssetsDir(), "scripts", filename);
114
- if (!shouldBundle) {
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
- if (!dep.content) {
121
- throw new Error("No content found for script asset");
122
- }
123
- const entryPath = this.getContentScriptEntryPath(hash);
124
- fileSystem.write(entryPath, dep.content);
125
- try {
126
- const bundledFilePath = await this.bundleScript({
127
- entrypoint: entryPath,
128
- outdir: this.getAssetsDir(),
129
- minify: this.isProduction,
130
- naming: `${path.parse(filename).name}-[hash].[ext]`,
131
- ...this.getBundlerOptions(dep)
132
- });
133
- return this.toProcessedAsset(
134
- dep,
135
- bundledFilePath,
136
- dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0
137
- );
138
- } finally {
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 {