@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.
Files changed (49) hide show
  1. package/package.json +27 -2
  2. package/src/adapters/abstract/application-adapter.js +3 -0
  3. package/src/adapters/bun/create-app.js +2 -0
  4. package/src/adapters/node/create-app.js +2 -0
  5. package/src/adapters/shared/server-adapter.js +17 -9
  6. package/src/build/app-build-manifest-runtime.js +12 -1
  7. package/src/build/build-adapter.js +4 -4
  8. package/src/build/dev-browser-script-cache.d.ts +34 -0
  9. package/src/build/dev-browser-script-cache.js +90 -0
  10. package/src/build/lit-static-render-worker-context.d.ts +2 -0
  11. package/src/build/lit-static-render-worker-context.js +6 -0
  12. package/src/build/rolldown-adapter-helpers.js +4 -3
  13. package/src/build/runtime-build-output-normalizer.js +2 -4
  14. package/src/diagnostics/request-build-dedupe.d.ts +18 -0
  15. package/src/diagnostics/request-build-dedupe.js +33 -0
  16. package/src/diagnostics/startup-trace.d.ts +25 -0
  17. package/src/diagnostics/startup-trace.js +121 -0
  18. package/src/eco/eco.types.d.ts +12 -1
  19. package/src/env.d.ts +2 -0
  20. package/src/plugins/alias-resolver-cache.d.ts +6 -9
  21. package/src/plugins/alias-resolver-plugin.d.ts +2 -2
  22. package/src/plugins/alias-resolver-plugin.js +38 -51
  23. package/src/plugins/integration-plugin.d.ts +8 -0
  24. package/src/plugins/tsconfig-import-resolver.d.ts +18 -0
  25. package/src/plugins/tsconfig-import-resolver.js +159 -0
  26. package/src/route-renderer/orchestration/page-browser-graph.service.js +15 -9
  27. package/src/route-renderer/page-loading/ecopages-virtual-imports.d.ts +6 -0
  28. package/src/route-renderer/page-loading/ecopages-virtual-imports.js +7 -2
  29. package/src/route-renderer/page-loading/module-declaration-aggregation.js +4 -0
  30. package/src/services/assets/asset-processing-service/asset-processing.service.d.ts +3 -0
  31. package/src/services/assets/asset-processing-service/asset-processing.service.js +41 -4
  32. package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.d.ts +52 -0
  33. package/src/services/assets/asset-processing-service/browser-runtime-entry-resolution.js +78 -0
  34. package/src/services/assets/asset-processing-service/browser-runtime-entry.factory.js +13 -18
  35. package/src/services/assets/asset-processing-service/finalize-processed-asset.d.ts +3 -0
  36. package/src/services/assets/asset-processing-service/finalize-processed-asset.js +7 -0
  37. package/src/services/assets/asset-processing-service/grouped-content-bundles.d.ts +2 -1
  38. package/src/services/assets/asset-processing-service/grouped-content-bundles.js +20 -10
  39. package/src/services/assets/asset-processing-service/index.d.ts +2 -0
  40. package/src/services/assets/asset-processing-service/index.js +2 -0
  41. package/src/services/assets/asset-processing-service/materialize-content-script-asset.d.ts +11 -0
  42. package/src/services/assets/asset-processing-service/materialize-content-script-asset.js +17 -0
  43. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.d.ts +7 -0
  44. package/src/services/assets/asset-processing-service/processors/script/content-script.processor.js +105 -79
  45. package/src/services/assets/asset-processing-service/resolve-integration-plugin.d.ts +4 -0
  46. package/src/services/assets/asset-processing-service/resolve-integration-plugin.js +17 -0
  47. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.d.ts +0 -1
  48. package/src/services/assets/asset-processing-service/ungrouped-dependency-processing.js +5 -10
  49. package/src/services/assets/browser-bundle.service.js +10 -2
@@ -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 = resolveEntryImportSpecifier(module.specifier, requireFromRoot, entryDir);
24
- if (module.defaultExport) {
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 = getModuleExportNames(module.specifier, requireFromRoot).filter(
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
  };
@@ -0,0 +1,3 @@
1
+ import type { ProcessedAsset } from './assets.types.js';
2
+ /** Applies the public source URL to one processed asset when available. */
3
+ export declare function finalizeProcessedAsset(processed: ProcessedAsset, resolveProcessedAssetSrcUrl: (processed: ProcessedAsset) => string | undefined): ProcessedAsset;
@@ -0,0 +1,7 @@
1
+ function finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl) {
2
+ const srcUrl = resolveProcessedAssetSrcUrl(processed);
3
+ return srcUrl ? { ...processed, srcUrl } : processed;
4
+ }
5
+ export {
6
+ finalizeProcessedAsset
7
+ };
@@ -1,4 +1,6 @@
1
1
  import type { AssetDefinition, ProcessedAsset } from './assets.types.js';
2
+ /** Forces grouped content scripts to run through the bundler in production builds. */
3
+ export declare function ensureGroupedContentScriptsBundle(dependencies: AssetDefinition[]): void;
2
4
  /**
3
5
  * Splits grouped content-script dependencies from ordinary dependencies so callers can
4
6
  * route them through `processGrouped` without changing ordering for the remaining assets.
@@ -12,7 +14,6 @@ type GroupedBundleProcessor = {
12
14
  };
13
15
  type ProcessGroupedDependencyBundlesOptions = {
14
16
  bundles: AssetDefinition[][];
15
- key: string;
16
17
  getCachedAsset: (dep: AssetDefinition, depKey: string) => ProcessedAsset | null;
17
18
  getDependencyKey: (dep: AssetDefinition) => string;
18
19
  getGroupedProcessor: () => GroupedBundleProcessor | undefined;
@@ -1,3 +1,18 @@
1
+ import { isDevelopmentRuntime } from "../../../utils/runtime.js";
2
+ import { finalizeProcessedAsset } from "./finalize-processed-asset.js";
3
+ function ensureGroupedContentScriptsBundle(dependencies) {
4
+ if (isDevelopmentRuntime()) {
5
+ return;
6
+ }
7
+ for (const dependency of dependencies) {
8
+ if (dependency.kind !== "script" || dependency.source !== "content" || !dependency.groupedBundle?.id) {
9
+ continue;
10
+ }
11
+ if (dependency.bundle === false) {
12
+ dependency.bundle = true;
13
+ }
14
+ }
15
+ }
1
16
  function partitionGroupedContentScriptDependencies(typeDeps) {
2
17
  const groupedBundleDeps = /* @__PURE__ */ new Map();
3
18
  const ungroupedDeps = [];
@@ -18,7 +33,6 @@ function partitionGroupedContentScriptDependencies(typeDeps) {
18
33
  async function processGroupedDependencyBundles(options) {
19
34
  const {
20
35
  bundles,
21
- key,
22
36
  getCachedAsset,
23
37
  getDependencyKey,
24
38
  getGroupedProcessor,
@@ -29,7 +43,7 @@ async function processGroupedDependencyBundles(options) {
29
43
  const groupedPromises = bundles.map(async (bundleDeps) => {
30
44
  const cachedResults = bundleDeps.map((dep) => {
31
45
  const cached = getCachedAsset(dep, getDependencyKey(dep));
32
- return cached ? { key, ...cached } : null;
46
+ return cached ? finalizeProcessedAsset(cached, resolveProcessedAssetSrcUrl) : null;
33
47
  });
34
48
  if (cachedResults.every((result) => result !== null)) {
35
49
  return cachedResults.filter((result) => result !== null);
@@ -43,14 +57,9 @@ async function processGroupedDependencyBundles(options) {
43
57
  return processedResults.map((processed, index) => {
44
58
  const dep = bundleDeps[index];
45
59
  const depKey = getDependencyKey(dep);
46
- const srcUrl = resolveProcessedAssetSrcUrl(processed);
47
- const processedWithKey = {
48
- key,
49
- ...processed,
50
- srcUrl
51
- };
52
- setCachedAsset(dep, depKey, processedWithKey);
53
- return processedWithKey;
60
+ const finalized = finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl);
61
+ setCachedAsset(dep, depKey, finalized);
62
+ return finalized;
54
63
  });
55
64
  } catch (error) {
56
65
  logError(error);
@@ -60,6 +69,7 @@ async function processGroupedDependencyBundles(options) {
60
69
  return (await Promise.all(groupedPromises)).flat();
61
70
  }
62
71
  export {
72
+ ensureGroupedContentScriptsBundle,
63
73
  partitionGroupedContentScriptDependencies,
64
74
  processGroupedDependencyBundles
65
75
  };
@@ -1,6 +1,8 @@
1
1
  export * from './asset.factory.js';
2
+ export * from './grouped-content-bundles.js';
2
3
  export * from './page-package.js';
3
4
  export * from './asset-processing.service.js';
4
5
  export * from './assets.types.js';
5
6
  export * from './browser-runtime-asset.factory.js';
6
7
  export * from './browser-runtime-entry.factory.js';
8
+ export * from './browser-runtime-entry-resolution.js';
@@ -1,6 +1,8 @@
1
1
  export * from "./asset.factory.js";
2
+ export * from "./grouped-content-bundles.js";
2
3
  export * from "./page-package.js";
3
4
  export * from "./asset-processing.service.js";
4
5
  export * from "./assets.types.js";
5
6
  export * from "./browser-runtime-asset.factory.js";
6
7
  export * from "./browser-runtime-entry.factory.js";
8
+ export * from "./browser-runtime-entry-resolution.js";
@@ -0,0 +1,11 @@
1
+ import type { ContentScriptAsset, ProcessedAsset } from './assets.types.js';
2
+ /**
3
+ * Builds a processed content-script asset from one dependency declaration and a
4
+ * previously emitted output file.
5
+ *
6
+ * @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.
10
+ */
11
+ export declare function materializeContentScriptAsset(dep: ContentScriptAsset, filepath: string): ProcessedAsset;
@@ -0,0 +1,17 @@
1
+ function materializeContentScriptAsset(dep, filepath) {
2
+ return {
3
+ filepath,
4
+ kind: "script",
5
+ inline: dep.inline ?? false,
6
+ content: dep.inline ? dep.content : void 0,
7
+ position: dep.position,
8
+ attributes: dep.attributes,
9
+ excludeFromHtml: dep.excludeFromHtml,
10
+ packageRole: dep.packageRole,
11
+ groupedBundle: dep.groupedBundle,
12
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
13
+ };
14
+ }
15
+ export {
16
+ materializeContentScriptAsset
17
+ };
@@ -1,6 +1,13 @@
1
1
  import type { ContentScriptAsset, ProcessedAsset } from '../../assets.types.js';
2
2
  import { BaseScriptProcessor } from '../base/base-script-processor.js';
3
3
  export declare class ContentScriptProcessor extends BaseScriptProcessor<ContentScriptAsset> {
4
+ private getContentScriptEntryDir;
5
+ private getContentScriptEntryPath;
6
+ private createBundleConfigHash;
7
+ private createContentScriptCacheKey;
8
+ private toProcessedAsset;
9
+ private removeContentScriptEntry;
4
10
  processGrouped(deps: ContentScriptAsset[]): Promise<ProcessedAsset[]>;
11
+ private getGroupedBundlerOptions;
5
12
  process(dep: ContentScriptAsset): Promise<ProcessedAsset>;
6
13
  }
@@ -1,7 +1,50 @@
1
1
  import path from "node:path";
2
2
  import { fileSystem } from "@ecopages/file-system";
3
+ import { shouldUseDevBrowserScriptCache } from "../../../../../build/dev-browser-script-cache.js";
3
4
  import { BaseScriptProcessor } from "../base/base-script-processor.js";
4
5
  class ContentScriptProcessor extends BaseScriptProcessor {
6
+ getContentScriptEntryDir() {
7
+ const dir = path.join(this.appConfig.absolutePaths.workDir, "content-script-entries");
8
+ fileSystem.ensureDir(dir);
9
+ return dir;
10
+ }
11
+ getContentScriptEntryPath(contentHash) {
12
+ return path.join(this.getContentScriptEntryDir(), `${contentHash}.js`);
13
+ }
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) {
29
+ return {
30
+ filepath,
31
+ content: dep.inline ? inlineContent : void 0,
32
+ kind: "script",
33
+ position: dep.position,
34
+ attributes: dep.attributes,
35
+ inline: dep.inline,
36
+ excludeFromHtml: dep.excludeFromHtml,
37
+ packageRole: dep.packageRole,
38
+ groupedBundle: dep.groupedBundle,
39
+ bundledSourceFilepaths: dep.bundledSourceFilepaths
40
+ };
41
+ }
42
+ removeContentScriptEntry(contentHash) {
43
+ if (shouldUseDevBrowserScriptCache()) {
44
+ return;
45
+ }
46
+ fileSystem.remove(this.getContentScriptEntryPath(contentHash));
47
+ }
5
48
  async processGrouped(deps) {
6
49
  if (deps.length === 0) {
7
50
  return [];
@@ -10,109 +53,92 @@ class ContentScriptProcessor extends BaseScriptProcessor {
10
53
  if (!shouldBundle || deps.some((dep) => dep.inline)) {
11
54
  return Promise.all(deps.map((dep) => this.process(dep)));
12
55
  }
13
- const tempDir = path.join(
14
- this.appConfig.absolutePaths.distDir,
15
- `grouped-script-entries-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`
16
- );
17
- fileSystem.ensureDir(tempDir);
56
+ let tempEntries = [];
18
57
  try {
19
- const tempEntries = deps.map((dep, index) => {
20
- const entryName = dep.groupedBundle?.entryName ?? dep.name ?? `grouped-script-${index}`;
21
- const tempFilepath = path.join(tempDir, `${entryName}.js`);
58
+ tempEntries = deps.map((dep) => {
59
+ const contentHash = this.generateHash(dep.content);
60
+ const tempFilepath = this.getContentScriptEntryPath(contentHash);
22
61
  fileSystem.write(tempFilepath, dep.content);
23
62
  return {
24
63
  dep,
25
- entryName,
64
+ contentHash,
26
65
  tempFilepath
27
66
  };
28
67
  });
29
- const primaryDep = deps[0];
30
68
  const outputPaths = await this.bundleScripts({
31
- ...this.getBundlerOptions(primaryDep),
32
- entries: tempEntries.map(({ entryName, tempFilepath }) => ({
33
- entryName,
69
+ ...this.getGroupedBundlerOptions(deps),
70
+ entries: tempEntries.map(({ dep, contentHash, tempFilepath }) => ({
71
+ entryName: dep.groupedBundle?.entryName ?? dep.name ?? contentHash,
34
72
  entrypoint: tempFilepath
35
73
  })),
36
74
  outdir: this.getAssetsDir(),
37
75
  minify: this.isProduction,
38
76
  naming: "[name]-[hash].[ext]"
39
77
  });
40
- return tempEntries.map(({ dep, entryName }) => {
78
+ return tempEntries.map(({ dep, contentHash }) => {
79
+ const entryName = dep.groupedBundle?.entryName ?? dep.name ?? contentHash;
41
80
  const bundledFilePath = outputPaths.get(entryName);
42
81
  if (!bundledFilePath) {
43
82
  throw new Error(`Missing grouped bundle output for ${entryName}`);
44
83
  }
45
- return {
46
- filepath: bundledFilePath,
47
- content: dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0,
48
- kind: "script",
49
- position: dep.position,
50
- attributes: dep.attributes,
51
- inline: dep.inline,
52
- excludeFromHtml: dep.excludeFromHtml,
53
- packageRole: dep.packageRole,
54
- groupedBundle: dep.groupedBundle,
55
- bundledSourceFilepaths: dep.bundledSourceFilepaths
56
- };
84
+ return this.toProcessedAsset(
85
+ dep,
86
+ bundledFilePath,
87
+ dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0
88
+ );
57
89
  });
58
90
  } finally {
59
- fileSystem.remove(tempDir);
91
+ for (const { contentHash } of tempEntries) {
92
+ this.removeContentScriptEntry(contentHash);
93
+ }
60
94
  }
61
95
  }
62
- async process(dep) {
63
- const hash = this.generateHash(dep.content);
64
- const filename = dep.name ? `${dep.name}.js` : `script-${hash}.js`;
65
- const shouldBundle = this.shouldBundle(dep);
66
- const filepath = path.join(this.getAssetsDir(), "scripts", filename);
67
- if (!shouldBundle) {
68
- if (!dep.inline) fileSystem.write(filepath, dep.content);
69
- const unbundledProcessedAsset = {
70
- filepath,
71
- content: dep.inline ? dep.content : void 0,
72
- kind: "script",
73
- position: dep.position,
74
- attributes: dep.attributes,
75
- inline: dep.inline,
76
- excludeFromHtml: dep.excludeFromHtml,
77
- packageRole: dep.packageRole,
78
- groupedBundle: dep.groupedBundle,
79
- bundledSourceFilepaths: dep.bundledSourceFilepaths
80
- };
81
- this.writeCacheFile(filename, unbundledProcessedAsset);
82
- return unbundledProcessedAsset;
83
- }
84
- if (dep.content) {
85
- const tempDir = this.appConfig.absolutePaths.distDir;
86
- fileSystem.ensureDir(tempDir);
87
- const tempFileName = path.join(
88
- tempDir,
89
- `${path.parse(filename).name}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp.js`
90
- );
91
- fileSystem.write(tempFileName, dep.content);
92
- const bundledFilePath = await this.bundleScript({
93
- entrypoint: tempFileName,
94
- outdir: this.getAssetsDir(),
95
- minify: this.isProduction,
96
- naming: `${path.parse(filename).name}-[hash].[ext]`,
97
- ...this.getBundlerOptions(dep)
98
- });
99
- const processedAsset = {
100
- filepath: bundledFilePath,
101
- content: dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0,
102
- kind: "script",
103
- position: dep.position,
104
- attributes: dep.attributes,
105
- inline: dep.inline,
106
- excludeFromHtml: dep.excludeFromHtml,
107
- packageRole: dep.packageRole,
108
- groupedBundle: dep.groupedBundle,
109
- bundledSourceFilepaths: dep.bundledSourceFilepaths
96
+ getGroupedBundlerOptions(deps) {
97
+ const primaryDep = deps[0];
98
+ const options = this.getBundlerOptions(primaryDep);
99
+ if (deps.some((dep) => dep.bundleOptions?.splitting === false)) {
100
+ return {
101
+ ...options,
102
+ splitting: false
110
103
  };
111
- fileSystem.remove(tempFileName);
112
- this.writeCacheFile(filename, processedAsset);
113
- return processedAsset;
114
104
  }
115
- throw new Error("No content found for script asset");
105
+ return options;
106
+ }
107
+ async process(dep) {
108
+ 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);
119
+ }
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
+ });
116
142
  }
117
143
  }
118
144
  export {
@@ -0,0 +1,4 @@
1
+ import type { AnyIntegrationPlugin } from '../../../plugins/integration-plugin.js';
2
+ import type { EcoPagesAppConfig } from '../../../types/internal-types.js';
3
+ /** Resolves the integration plugin that owns one asset-processing batch key. */
4
+ export declare function resolveIntegrationPluginForProcessingKey(appConfig: EcoPagesAppConfig, processingKey: string): AnyIntegrationPlugin | undefined;
@@ -0,0 +1,17 @@
1
+ function resolveIntegrationPluginForProcessingKey(appConfig, processingKey) {
2
+ if (!appConfig.integrations?.length) {
3
+ return void 0;
4
+ }
5
+ const exactMatch = appConfig.integrations.find((integration) => integration.name === processingKey);
6
+ if (exactMatch) {
7
+ return exactMatch;
8
+ }
9
+ const baseName = processingKey.split(":")[0];
10
+ if (!baseName || baseName === processingKey) {
11
+ return void 0;
12
+ }
13
+ return appConfig.integrations.find((integration) => integration.name === baseName);
14
+ }
15
+ export {
16
+ resolveIntegrationPluginForProcessingKey
17
+ };
@@ -2,7 +2,6 @@ import type { AssetProcessor } from './processor.interface.js';
2
2
  import type { AssetDefinition, ProcessedAsset } from './assets.types.js';
3
3
  type ProcessUngroupedDependencyOptions = {
4
4
  dep: AssetDefinition;
5
- key: string;
6
5
  depKey: string;
7
6
  getCachedAsset: (dep: AssetDefinition, depKey: string) => ProcessedAsset | null;
8
7
  getProcessor: (dep: AssetDefinition) => AssetProcessor | undefined;
@@ -1,8 +1,8 @@
1
1
  import { fileSystem } from "@ecopages/file-system";
2
+ import { finalizeProcessedAsset } from "./finalize-processed-asset.js";
2
3
  async function processUngroupedDependency(options) {
3
4
  const {
4
5
  dep,
5
- key,
6
6
  depKey,
7
7
  getCachedAsset,
8
8
  getProcessor,
@@ -14,7 +14,7 @@ async function processUngroupedDependency(options) {
14
14
  } = options;
15
15
  const cached = getCachedAsset(dep, depKey);
16
16
  if (cached) {
17
- return { key, ...cached };
17
+ return finalizeProcessedAsset(cached, resolveProcessedAssetSrcUrl);
18
18
  }
19
19
  const processor = getProcessor(dep);
20
20
  if (!processor) {
@@ -27,14 +27,9 @@ async function processUngroupedDependency(options) {
27
27
  }
28
28
  try {
29
29
  const processed = await processor.process(dep);
30
- const srcUrl = resolveProcessedAssetSrcUrl(processed);
31
- const processedWithKey = {
32
- key,
33
- ...processed,
34
- srcUrl
35
- };
36
- setCachedAsset(dep, depKey, processedWithKey);
37
- return processedWithKey;
30
+ const finalized = finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl);
31
+ setCachedAsset(dep, depKey, finalized);
32
+ return finalized;
38
33
  } catch (error) {
39
34
  logProcessingError(dep, error);
40
35
  return null;
@@ -2,6 +2,9 @@ import { getAppBrowserBuildPlugins, getAppTranspileOptions } from "../../build/b
2
2
  import { requireBuildRuntime } from "../../build/build-runtime.js";
3
3
  import { mergeEcoBuildPlugins } from "../../build/build-manifest.js";
4
4
  import { getAppSourceTransforms } from "../../plugins/source-transform.js";
5
+ import { startupTrace } from "../../diagnostics/startup-trace.js";
6
+ import { requestBuildDedupe } from "../../diagnostics/request-build-dedupe.js";
7
+ import { createBuildOptionsDedupeKey } from "../../build/deduping-build-executor.js";
5
8
  function resolveBrowserBundleExecutor(appConfig, profile, executor) {
6
9
  const buildRuntime = requireBuildRuntime(appConfig);
7
10
  if (executor === "hmr" && (profile === "hmr-entrypoint" || profile === "hmr-runtime")) {
@@ -38,12 +41,17 @@ class BrowserBundleService {
38
41
  sourceTransforms: getAppSourceTransforms(this.appConfig)
39
42
  };
40
43
  const buildExecutor = resolveBrowserBundleExecutor(this.appConfig, profile, executor);
41
- return await buildExecutor.build(request);
44
+ const dedupeKey = createBuildOptionsDedupeKey(request);
45
+ return requestBuildDedupe.dedupeBuild(dedupeKey, async () => {
46
+ const result = await buildExecutor.build(request);
47
+ startupTrace.recordBrowserBundle(result.outputs);
48
+ return result;
49
+ });
42
50
  }
43
51
  async bundleGroupedEntries(entries, options) {
44
52
  const request = {
45
53
  ...options,
46
- entrypoints: entries.map((entry) => entry.entrypoint)
54
+ entrypoints: Object.fromEntries(entries.map((entry) => [entry.entryName, entry.entrypoint]))
47
55
  };
48
56
  return this.bundle(request);
49
57
  }