@ecopages/core
Advanced tools
| import type { EcoPagesAppConfig } from '../types/internal-types.js'; | ||
| import type { ProcessedAsset } from '../services/assets/asset-processing-service/assets.types.js'; | ||
| export declare const DEV_BROWSER_SCRIPT_CACHE_DIR = ".browser-script-bundles"; | ||
| export declare const DEV_BROWSER_SCRIPT_CACHE_FILENAME = ".build-cache.json"; | ||
| /** Output path restored from the dev disk cache before dependency metadata is merged. */ | ||
| export type DevBrowserScriptCacheHit = { | ||
| filepath: string; | ||
| }; | ||
| /** | ||
| * @remarks | ||
| * Dev asset caching spans three scopes that should not be conflated: | ||
| * - request dedupe (`request-build-dedupe.ts`) — one in-flight build per request key | ||
| * - in-memory service cache (`AssetProcessingService`) — per-process reuse within one dev server | ||
| * - this disk manifest (`.eco/.browser-script-bundles`) — cross-request reuse of content-script bundles | ||
| * | ||
| * Disk entries intentionally store only output file paths. Declarative fields such as | ||
| * `groupedBundle`, `attributes`, and `packageRole` live on the originating | ||
| * `ContentScriptAsset` and are merged via `materializeContentScriptAsset()` on lookup. | ||
| */ | ||
| export interface DevBrowserScriptCacheEntry { | ||
| filepath: string; | ||
| builtAt: number; | ||
| } | ||
| export interface DevBrowserScriptCacheManifest { | ||
| invalidationVersion: string; | ||
| buildInputsFingerprint: string; | ||
| entries: Record<string, DevBrowserScriptCacheEntry>; | ||
| } | ||
| /** Returns whether dev-only cross-request content-script bundle cache is active. */ | ||
| export declare function shouldUseDevBrowserScriptCache(): boolean; | ||
| /** Looks up one persisted dev content-script output path by dependency cache key. */ | ||
| export declare function getDevBrowserScriptCacheEntry(appConfig: EcoPagesAppConfig, depKey: string): DevBrowserScriptCacheHit | null; | ||
| /** Persists one dev content-script bundle output for cross-request reuse. */ | ||
| export declare function setDevBrowserScriptCacheEntry(appConfig: EcoPagesAppConfig, depKey: string, asset: ProcessedAsset): void; |
| import path from "node:path"; | ||
| import { fileSystem } from "@ecopages/file-system"; | ||
| import { getAppBrowserBuildPlugins } from "./build-adapter.js"; | ||
| import { createBuildInputsFingerprint, hashAppConfigFile } from "./build-input-fingerprint.js"; | ||
| import { | ||
| readProductionCacheManifest, | ||
| writeProductionCacheManifest, | ||
| isProductionCacheManifestCurrent, | ||
| matchesProductionCacheFingerprint | ||
| } from "./production-build-cache.js"; | ||
| import { getCorePackageVersion } from "../services/module-loading/route-module-build-manifest.js"; | ||
| import { resolveInternalExecutionDir } from "../utils/resolve-work-dir.js"; | ||
| import { isDevelopmentRuntime } from "../utils/runtime.js"; | ||
| const DEV_BROWSER_SCRIPT_CACHE_DIR = ".browser-script-bundles"; | ||
| const DEV_BROWSER_SCRIPT_CACHE_FILENAME = ".build-cache.json"; | ||
| function getDevBrowserScriptCacheDir(appConfig) { | ||
| return path.join(resolveInternalExecutionDir(appConfig), DEV_BROWSER_SCRIPT_CACHE_DIR); | ||
| } | ||
| function getDevBrowserScriptCacheManifestPath(appConfig) { | ||
| return path.join(getDevBrowserScriptCacheDir(appConfig), DEV_BROWSER_SCRIPT_CACHE_FILENAME); | ||
| } | ||
| function createBrowserBuildKey(appConfig) { | ||
| try { | ||
| const pluginNames = getAppBrowserBuildPlugins(appConfig).map((plugin) => plugin.name).sort().join("|"); | ||
| return pluginNames || "default"; | ||
| } catch { | ||
| return "default"; | ||
| } | ||
| } | ||
| function createDevBrowserScriptCacheInvalidationVersion(appConfig) { | ||
| return [getCorePackageVersion(), hashAppConfigFile(appConfig), createBrowserBuildKey(appConfig)].join("::"); | ||
| } | ||
| function readManifest(appConfig) { | ||
| const manifest = readProductionCacheManifest( | ||
| getDevBrowserScriptCacheManifestPath(appConfig) | ||
| ); | ||
| if (!manifest) { | ||
| return void 0; | ||
| } | ||
| const invalidationVersion = createDevBrowserScriptCacheInvalidationVersion(appConfig); | ||
| const buildInputsFingerprint = createBuildInputsFingerprint(appConfig); | ||
| if (!isProductionCacheManifestCurrent(manifest, invalidationVersion) || !matchesProductionCacheFingerprint(manifest, buildInputsFingerprint)) { | ||
| return void 0; | ||
| } | ||
| return manifest; | ||
| } | ||
| function writeManifest(appConfig, manifest) { | ||
| writeProductionCacheManifest(getDevBrowserScriptCacheManifestPath(appConfig), manifest); | ||
| } | ||
| function shouldUseDevBrowserScriptCache() { | ||
| return isDevelopmentRuntime(); | ||
| } | ||
| function getDevBrowserScriptCacheEntry(appConfig, depKey) { | ||
| if (!shouldUseDevBrowserScriptCache()) { | ||
| return null; | ||
| } | ||
| const manifest = readManifest(appConfig); | ||
| const entry = manifest?.entries[depKey]; | ||
| if (!entry?.filepath || !fileSystem.exists(entry.filepath)) { | ||
| return null; | ||
| } | ||
| return { filepath: entry.filepath }; | ||
| } | ||
| function setDevBrowserScriptCacheEntry(appConfig, depKey, asset) { | ||
| if (!shouldUseDevBrowserScriptCache() || !asset.filepath) { | ||
| return; | ||
| } | ||
| const invalidationVersion = createDevBrowserScriptCacheInvalidationVersion(appConfig); | ||
| const buildInputsFingerprint = createBuildInputsFingerprint(appConfig); | ||
| const existing = readManifest(appConfig); | ||
| const manifest = existing ?? { | ||
| invalidationVersion, | ||
| buildInputsFingerprint, | ||
| entries: {} | ||
| }; | ||
| manifest.invalidationVersion = invalidationVersion; | ||
| manifest.buildInputsFingerprint = buildInputsFingerprint; | ||
| manifest.entries[depKey] = { | ||
| filepath: asset.filepath, | ||
| builtAt: Date.now() | ||
| }; | ||
| writeManifest(appConfig, manifest); | ||
| } | ||
| export { | ||
| DEV_BROWSER_SCRIPT_CACHE_DIR, | ||
| DEV_BROWSER_SCRIPT_CACHE_FILENAME, | ||
| getDevBrowserScriptCacheEntry, | ||
| setDevBrowserScriptCacheEntry, | ||
| shouldUseDevBrowserScriptCache | ||
| }; |
| /** Returns whether the current thread is the Lit static-render worker. */ | ||
| export declare function isLitStaticRenderWorkerThread(): boolean; |
| function isLitStaticRenderWorkerThread() { | ||
| return process.env.ECOPAGES_LIT_STATIC_RENDER_WORKER === "true"; | ||
| } | ||
| export { | ||
| isLitStaticRenderWorkerThread | ||
| }; |
| import type { BuildResult } from '../build/build-adapter.js'; | ||
| /** | ||
| * Per-request browser build dedupe cache. | ||
| * | ||
| * @remarks | ||
| * `DedupingBuildExecutor` only coalesces concurrent identical builds. Asset | ||
| * processing can call the same logical bundle several times in one request | ||
| * (page graph, HMR, content scripts) after the first build settles. This cache | ||
| * reuses the settled result until the request scope ends. | ||
| */ | ||
| declare class RequestBuildDedupe { | ||
| private readonly storage; | ||
| run<T>(fn: () => Promise<T>): Promise<T>; | ||
| dedupeBuild(key: string, build: () => Promise<BuildResult>): Promise<BuildResult>; | ||
| getCachedCountForTests(): number; | ||
| } | ||
| export declare const requestBuildDedupe: RequestBuildDedupe; | ||
| export {}; |
| import { AsyncLocalStorage } from "node:async_hooks"; | ||
| class RequestBuildDedupe { | ||
| storage = new AsyncLocalStorage(); | ||
| run(fn) { | ||
| if (this.storage.getStore()) { | ||
| return fn(); | ||
| } | ||
| return this.storage.run(/* @__PURE__ */ new Map(), fn); | ||
| } | ||
| dedupeBuild(key, build) { | ||
| const store = this.storage.getStore(); | ||
| if (!store) { | ||
| return build(); | ||
| } | ||
| const existing = store.get(key); | ||
| if (existing) { | ||
| return existing; | ||
| } | ||
| const buildPromise = build().catch((error) => { | ||
| store.delete(key); | ||
| throw error; | ||
| }); | ||
| store.set(key, buildPromise); | ||
| return buildPromise; | ||
| } | ||
| getCachedCountForTests() { | ||
| return this.storage.getStore()?.size ?? 0; | ||
| } | ||
| } | ||
| const requestBuildDedupe = new RequestBuildDedupe(); | ||
| export { | ||
| requestBuildDedupe | ||
| }; |
| export type StartupTracePhase = 'config-ready' | 'setupAppRuntimePlugins' | 'route-registry' | 'server-listen' | 'first-request-ssr'; | ||
| declare class StartupTrace { | ||
| private readonly phaseStarts; | ||
| private readonly phases; | ||
| private firstRequestPending; | ||
| private firstRequestPath; | ||
| private firstRequestBundleCount; | ||
| private firstRequestClientBytes; | ||
| private summaryEmitted; | ||
| isEnabled(): boolean; | ||
| markPhaseStart(phase: StartupTracePhase): void; | ||
| markPhaseEnd(phase: StartupTracePhase): void; | ||
| markConfigReady(): void; | ||
| markServerListening(): void; | ||
| beginServerListen(): void; | ||
| recordBrowserBundle(outputs: Array<{ | ||
| path: string; | ||
| }>): void; | ||
| traceFirstRequest<T>(request: Request, handler: () => Promise<T>): Promise<T>; | ||
| private emitFirstRequestSummary; | ||
| /** Resets mutable trace state for unit tests. */ | ||
| resetForTests(): void; | ||
| } | ||
| export declare const startupTrace: StartupTrace; | ||
| export {}; |
| import { statSync } from "node:fs"; | ||
| import { fileSystem } from "@ecopages/file-system"; | ||
| const LOG_PREFIX = "[ecopages:startup-trace]"; | ||
| function isStartupTraceEnabled() { | ||
| return process.env.ECOPAGES_STARTUP_TRACE === "true" || process.env.ECOPAGES_LOGGER_DEBUG === "true"; | ||
| } | ||
| function wallMsSinceProcessStart() { | ||
| return Math.round(process.uptime() * 1e3); | ||
| } | ||
| function writeTraceLine(message) { | ||
| process.stderr.write(`${LOG_PREFIX} ${message} | ||
| `); | ||
| } | ||
| class StartupTrace { | ||
| phaseStarts = /* @__PURE__ */ new Map(); | ||
| phases = /* @__PURE__ */ new Map(); | ||
| firstRequestPending = true; | ||
| firstRequestPath; | ||
| firstRequestBundleCount = 0; | ||
| firstRequestClientBytes = 0; | ||
| summaryEmitted = false; | ||
| isEnabled() { | ||
| return isStartupTraceEnabled(); | ||
| } | ||
| markPhaseStart(phase) { | ||
| if (!isStartupTraceEnabled() || this.phases.has(phase)) { | ||
| return; | ||
| } | ||
| this.phaseStarts.set(phase, performance.now()); | ||
| } | ||
| markPhaseEnd(phase) { | ||
| if (!isStartupTraceEnabled() || this.phases.has(phase)) { | ||
| return; | ||
| } | ||
| const startedAt = this.phaseStarts.get(phase); | ||
| if (startedAt === void 0) { | ||
| return; | ||
| } | ||
| const durationMs = Math.round(performance.now() - startedAt); | ||
| const record = { | ||
| durationMs, | ||
| wallMs: wallMsSinceProcessStart() | ||
| }; | ||
| this.phases.set(phase, record); | ||
| writeTraceLine(`phase=${phase} durationMs=${durationMs} wallMs=${record.wallMs}`); | ||
| } | ||
| markConfigReady() { | ||
| if (!isStartupTraceEnabled() || this.phases.has("config-ready")) { | ||
| return; | ||
| } | ||
| const record = { | ||
| durationMs: 0, | ||
| wallMs: wallMsSinceProcessStart() | ||
| }; | ||
| this.phases.set("config-ready", record); | ||
| writeTraceLine(`phase=config-ready wallMs=${record.wallMs}`); | ||
| } | ||
| markServerListening() { | ||
| this.markPhaseEnd("server-listen"); | ||
| } | ||
| beginServerListen() { | ||
| this.markPhaseStart("server-listen"); | ||
| } | ||
| recordBrowserBundle(outputs) { | ||
| if (!isStartupTraceEnabled() || !this.firstRequestPending) { | ||
| return; | ||
| } | ||
| this.firstRequestBundleCount += 1; | ||
| for (const output of outputs) { | ||
| try { | ||
| if (fileSystem.exists(output.path)) { | ||
| this.firstRequestClientBytes += statSync(output.path).size; | ||
| } | ||
| } catch { | ||
| } | ||
| } | ||
| } | ||
| async traceFirstRequest(request, handler) { | ||
| if (!isStartupTraceEnabled() || !this.firstRequestPending) { | ||
| return handler(); | ||
| } | ||
| this.firstRequestPath = new URL(request.url).pathname; | ||
| this.markPhaseStart("first-request-ssr"); | ||
| try { | ||
| return await handler(); | ||
| } finally { | ||
| this.markPhaseEnd("first-request-ssr"); | ||
| this.emitFirstRequestSummary(); | ||
| this.firstRequestPending = false; | ||
| } | ||
| } | ||
| emitFirstRequestSummary() { | ||
| if (!isStartupTraceEnabled() || this.summaryEmitted) { | ||
| return; | ||
| } | ||
| this.summaryEmitted = true; | ||
| writeTraceLine( | ||
| [ | ||
| "summary", | ||
| `path=${this.firstRequestPath ?? "unknown"}`, | ||
| `bundleCount=${this.firstRequestBundleCount}`, | ||
| `clientBundleBytes=${this.firstRequestClientBytes}`, | ||
| `wallMs=${wallMsSinceProcessStart()}` | ||
| ].join(" ") | ||
| ); | ||
| } | ||
| /** Resets mutable trace state for unit tests. */ | ||
| resetForTests() { | ||
| this.phaseStarts.clear(); | ||
| this.phases.clear(); | ||
| this.firstRequestPending = true; | ||
| this.firstRequestPath = void 0; | ||
| this.firstRequestBundleCount = 0; | ||
| this.firstRequestClientBytes = 0; | ||
| this.summaryEmitted = false; | ||
| } | ||
| } | ||
| const startupTrace = new StartupTrace(); | ||
| export { | ||
| startupTrace | ||
| }; |
| import type { ProcessedAsset } from './assets.types.js'; | ||
| /** Applies the public source URL to one processed asset when available. */ | ||
| export declare function finalizeProcessedAsset(processed: ProcessedAsset, resolveProcessedAssetSrcUrl: (processed: ProcessedAsset) => string | undefined): ProcessedAsset; |
| function finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl) { | ||
| const srcUrl = resolveProcessedAssetSrcUrl(processed); | ||
| return srcUrl ? { ...processed, srcUrl } : processed; | ||
| } | ||
| export { | ||
| finalizeProcessedAsset | ||
| }; |
| import type { ContentScriptAsset, ProcessedAsset } from './assets.types.js'; | ||
| /** | ||
| * Builds a processed content-script asset from one dependency declaration and a | ||
| * previously emitted output file. | ||
| * | ||
| * @remarks | ||
| * Dev disk cache entries store only output paths. Callers must materialize the | ||
| * full processed asset from the originating dependency so HTML and page-browser | ||
| * graph assembly receive grouped-bundle metadata and script attributes. | ||
| */ | ||
| export declare function materializeContentScriptAsset(dep: ContentScriptAsset, filepath: string): ProcessedAsset; |
| function materializeContentScriptAsset(dep, filepath) { | ||
| return { | ||
| filepath, | ||
| kind: "script", | ||
| inline: dep.inline ?? false, | ||
| content: dep.inline ? dep.content : void 0, | ||
| position: dep.position, | ||
| attributes: dep.attributes, | ||
| excludeFromHtml: dep.excludeFromHtml, | ||
| packageRole: dep.packageRole, | ||
| groupedBundle: dep.groupedBundle, | ||
| bundledSourceFilepaths: dep.bundledSourceFilepaths | ||
| }; | ||
| } | ||
| export { | ||
| materializeContentScriptAsset | ||
| }; |
| import type { AnyIntegrationPlugin } from '../../../plugins/integration-plugin.js'; | ||
| import type { EcoPagesAppConfig } from '../../../types/internal-types.js'; | ||
| /** Resolves the integration plugin that owns one asset-processing batch key. */ | ||
| export declare function resolveIntegrationPluginForProcessingKey(appConfig: EcoPagesAppConfig, processingKey: string): AnyIntegrationPlugin | undefined; |
| function resolveIntegrationPluginForProcessingKey(appConfig, processingKey) { | ||
| if (!appConfig.integrations?.length) { | ||
| return void 0; | ||
| } | ||
| const exactMatch = appConfig.integrations.find((integration) => integration.name === processingKey); | ||
| if (exactMatch) { | ||
| return exactMatch; | ||
| } | ||
| const baseName = processingKey.split(":")[0]; | ||
| if (!baseName || baseName === processingKey) { | ||
| return void 0; | ||
| } | ||
| return appConfig.integrations.find((integration) => integration.name === baseName); | ||
| } | ||
| export { | ||
| resolveIntegrationPluginForProcessingKey | ||
| }; |
+10
-2
| { | ||
| "name": "@ecopages/core", | ||
| "version": "0.2.0-beta.23", | ||
| "version": "0.2.0-beta.24", | ||
| "description": "Core package for Ecopages", | ||
@@ -20,3 +20,3 @@ "keywords": [ | ||
| "dependencies": { | ||
| "@ecopages/file-system": "0.2.0-beta.23", | ||
| "@ecopages/file-system": "0.2.0-beta.24", | ||
| "@ecopages/logger": "^0.2.3", | ||
@@ -211,2 +211,6 @@ "@ecopages/scripts-injector": "^0.1.5", | ||
| }, | ||
| "./build/lit-static-render-worker-context": { | ||
| "types": "./src/build/lit-static-render-worker-context.d.ts", | ||
| "default": "./src/build/lit-static-render-worker-context.js" | ||
| }, | ||
| "./cache": { | ||
@@ -421,2 +425,6 @@ "types": "./src/cache/index.d.ts", | ||
| }, | ||
| "./build/lit-static-render-worker-context.ts": { | ||
| "types": "./src/build/lit-static-render-worker-context.d.ts", | ||
| "default": "./src/build/lit-static-render-worker-context.js" | ||
| }, | ||
| "./cache.ts": { | ||
@@ -423,0 +431,0 @@ "types": "./src/cache/index.d.ts", |
@@ -13,2 +13,3 @@ import { appLogger } from "../../global/app-logger.js"; | ||
| import { parseCliArgs } from "../../utils/parse-cli-args.js"; | ||
| import { startupTrace } from "../../diagnostics/startup-trace.js"; | ||
| class AbstractApplicationAdapter { | ||
@@ -46,2 +47,3 @@ appConfig; | ||
| getAppModuleLoader(this.appConfig); | ||
| startupTrace.markConfigReady(); | ||
| if (options.clearOutput) { | ||
@@ -226,2 +228,3 @@ this.clearDistFolder().catch((error) => { | ||
| const normalizedOrigin = origin.replace(/\/$/, ""); | ||
| startupTrace.markServerListening(); | ||
| if (!this.onAppStartCallback) { | ||
@@ -228,0 +231,0 @@ this.logServerStarted(normalizedOrigin); |
| import { appLogger } from "../../global/app-logger.js"; | ||
| import { SharedApplicationAdapter } from "../shared/application-adapter.js"; | ||
| import { resolveRuntimeBinding, resolveStaticRuntimeMode } from "../shared/runtime-app-bootstrap.js"; | ||
| import { startupTrace } from "../../diagnostics/startup-trace.js"; | ||
| import { createBunServerAdapter } from "./server-adapter.js"; | ||
@@ -104,2 +105,3 @@ import { BunRuntimeHost } from "./runtime-host.js"; | ||
| const runtimeServerOptions = serverOptions; | ||
| startupTrace.beginServerListen(); | ||
| this.server = await this.runtimeHost.start({ | ||
@@ -106,0 +108,0 @@ serveOptions: runtimeServerOptions, |
@@ -8,2 +8,3 @@ import { appLogger } from "../../global/app-logger.js"; | ||
| import { hostOwnsDevClient } from "../../dev/dev-client-ownership.js"; | ||
| import { startupTrace } from "../../diagnostics/startup-trace.js"; | ||
| class NodeEcopagesApp extends SharedApplicationAdapter { | ||
@@ -91,2 +92,3 @@ serverAdapter; | ||
| const serveOptions = this.serverAdapter.getServerOptions(); | ||
| startupTrace.beginServerListen(); | ||
| this.server = await this.runtimeHost.start({ | ||
@@ -93,0 +95,0 @@ serveOptions, |
@@ -5,2 +5,4 @@ import path from "node:path"; | ||
| import { RouteRegistry } from "../../router/server/route-registry.js"; | ||
| import { startupTrace } from "../../diagnostics/startup-trace.js"; | ||
| import { requestBuildDedupe } from "../../diagnostics/request-build-dedupe.js"; | ||
| import { MemoryCacheStore } from "../../services/cache/memory-cache-store.js"; | ||
@@ -75,2 +77,3 @@ import { PageCacheService } from "../../services/cache/page-cache-service.js"; | ||
| async initSharedRouter() { | ||
| startupTrace.markPhaseStart("route-registry"); | ||
| this.router = new RouteRegistry({ | ||
@@ -85,2 +88,3 @@ pagesDir: path.join(this.appConfig.rootDir, this.appConfig.srcDir, this.appConfig.pagesDir), | ||
| await this.router.init(); | ||
| startupTrace.markPhaseEnd("route-registry"); | ||
| } | ||
@@ -434,11 +438,15 @@ createRouteRegistryPageModuleAdapter() { | ||
| async handleSharedRequest(request, context) { | ||
| const hmrResponse = this.tryHandleSharedHmrRequest(request, context); | ||
| if (hmrResponse) { | ||
| return hmrResponse; | ||
| } | ||
| const apiResponse = await this.tryHandleSharedApiRequest(request, context); | ||
| if (apiResponse) { | ||
| return apiResponse; | ||
| } | ||
| return this.routeHandler.handleResponse(request); | ||
| return requestBuildDedupe.run( | ||
| () => startupTrace.traceFirstRequest(request, async () => { | ||
| const hmrResponse = this.tryHandleSharedHmrRequest(request, context); | ||
| if (hmrResponse) { | ||
| return hmrResponse; | ||
| } | ||
| const apiResponse = await this.tryHandleSharedApiRequest(request, context); | ||
| if (apiResponse) { | ||
| return apiResponse; | ||
| } | ||
| return this.routeHandler.handleResponse(request); | ||
| }) | ||
| ); | ||
| } | ||
@@ -445,0 +453,0 @@ } |
| import { mergeBrowserRuntimeManifests } from "./browser-runtime-manifest.js"; | ||
| import { appLogger } from "../global/app-logger.js"; | ||
| import { startupTrace } from "../diagnostics/startup-trace.js"; | ||
| import { isLitStaticRenderWorkerThread } from "./lit-static-render-worker-context.js"; | ||
| function patchAppRuntime(appConfig, patch) { | ||
@@ -53,7 +55,12 @@ appConfig.runtime = { | ||
| async function setupAppRuntimePlugins(options) { | ||
| startupTrace.markPhaseStart("setupAppRuntimePlugins"); | ||
| if (options.appConfig.runtime?.runtimeAssetsPrepared) { | ||
| appLogger.debug("Skipped setupAppRuntimePlugins: runtime assets already prepared"); | ||
| registerRuntimePlugins(options.appConfig, options.onRuntimePlugin); | ||
| startupTrace.markPhaseEnd("setupAppRuntimePlugins"); | ||
| return; | ||
| } | ||
| if (isLitStaticRenderWorkerThread()) { | ||
| appLogger.debug("Lit static-render worker: skipping processor setup (main thread already prepared artifacts)"); | ||
| } | ||
| appLogger.debugTime("setupAppRuntimePlugins"); | ||
@@ -64,4 +71,7 @@ try { | ||
| } | ||
| const skipProcessorSetup = isLitStaticRenderWorkerThread(); | ||
| for (const processor of options.appConfig.processors.values()) { | ||
| await processor.setup(); | ||
| if (!skipProcessorSetup) { | ||
| await processor.setup(); | ||
| } | ||
| if (processor.plugins) { | ||
@@ -84,2 +94,3 @@ for (const plugin of processor.plugins) { | ||
| appLogger.debugTimeEnd("setupAppRuntimePlugins"); | ||
| startupTrace.markPhaseEnd("setupAppRuntimePlugins"); | ||
| } | ||
@@ -86,0 +97,0 @@ } |
+2
-0
@@ -6,2 +6,4 @@ interface EcopagesEnv { | ||
| ECOPAGES_LOGGER_DEBUG: 'true' | 'false'; | ||
| /** When `true`, emits startup phase timings to stderr. Also enabled when `ECOPAGES_LOGGER_DEBUG=true`. */ | ||
| ECOPAGES_STARTUP_TRACE?: 'true' | 'false'; | ||
| } | ||
@@ -8,0 +10,0 @@ |
@@ -201,2 +201,10 @@ import type { EcoBuildPlugin } from '../build/build-types.js'; | ||
| /** | ||
| * Shapes one dependency batch before core asset processing runs. | ||
| * | ||
| * @remarks | ||
| * Integrations use this to assign grouped-build metadata or other batch-level | ||
| * policy without teaching core about integration-specific asset graphs. | ||
| */ | ||
| prepareAssetDependencies?(dependencies: AssetDefinition[]): AssetDefinition[]; | ||
| /** | ||
| * Prepares build-facing contributions before the app build manifest is sealed. | ||
@@ -203,0 +211,0 @@ * |
@@ -106,2 +106,5 @@ import path from "node:path"; | ||
| } | ||
| if (this.isHmrEnabled()) { | ||
| continue; | ||
| } | ||
| if (!contribution?.dependencies?.length) { | ||
@@ -134,11 +137,14 @@ continue; | ||
| for (const [routeFile, groupedAssetKeys] of groupedAssetKeysByRoute) { | ||
| groupedAssetsByRoute.set( | ||
| routeFile, | ||
| processedGroupedDependencies.filter((asset) => { | ||
| if (!asset.groupedBundle) { | ||
| return false; | ||
| } | ||
| return groupedAssetKeys.has(getGroupedBundleAssetKey(asset.groupedBundle)); | ||
| }) | ||
| ); | ||
| const matchedAssets = processedGroupedDependencies.filter((asset) => { | ||
| if (!asset.groupedBundle) { | ||
| return false; | ||
| } | ||
| return groupedAssetKeys.has(getGroupedBundleAssetKey(asset.groupedBundle)); | ||
| }); | ||
| if (groupedAssetKeys.size > 0 && matchedAssets.length === 0) { | ||
| appLogger.warn( | ||
| `Grouped page-browser assets for ${routeFile} are missing groupedBundle metadata after processing. Hydration scripts may be omitted from HTML.` | ||
| ); | ||
| } | ||
| groupedAssetsByRoute.set(routeFile, matchedAssets); | ||
| } | ||
@@ -145,0 +151,0 @@ return { |
@@ -6,2 +6,8 @@ export type EcopagesVirtualImport = { | ||
| /** | ||
| * @remarks | ||
| * Content server modules static-import every MDX entry. They must never become | ||
| * browser module-script dependencies discovered from page or component sources. | ||
| */ | ||
| export declare function isBrowserEcopagesVirtualImport(specifier: string): boolean; | ||
| /** | ||
| * Extracts runtime `ecopages:` virtual-module imports from a component source file. | ||
@@ -8,0 +14,0 @@ * |
| import { readFileSync } from "node:fs"; | ||
| import { parseSync } from "oxc-parser"; | ||
| const CONTENT_SERVER_VIRTUAL_MODULE_PATTERN = /^ecopages:content\/[a-z][a-z0-9-]+\/server$/; | ||
| function isBrowserEcopagesVirtualImport(specifier) { | ||
| return specifier.startsWith("ecopages:") && !CONTENT_SERVER_VIRTUAL_MODULE_PATTERN.test(specifier); | ||
| } | ||
| function extractEcopagesVirtualImports(file) { | ||
@@ -21,3 +25,3 @@ let source; | ||
| const specifier = node.source?.value ?? ""; | ||
| if (!specifier.startsWith("ecopages:")) continue; | ||
| if (!isBrowserEcopagesVirtualImport(specifier)) continue; | ||
| if (found.get(specifier) === null) { | ||
@@ -57,3 +61,4 @@ continue; | ||
| export { | ||
| extractEcopagesVirtualImports | ||
| extractEcopagesVirtualImports, | ||
| isBrowserEcopagesVirtualImport | ||
| }; |
| import { normalizeModuleDeclarations } from "../../eco/module-dependencies.js"; | ||
| import { isBrowserEcopagesVirtualImport } from "./ecopages-virtual-imports.js"; | ||
| function getDeclaredModules(value) { | ||
@@ -25,2 +26,5 @@ if (!Array.isArray(value)) { | ||
| for (const declaration of normalizeModuleDeclarations(getDeclaredModules(declaredModules))) { | ||
| if (declaration.from.startsWith("ecopages:") && !isBrowserEcopagesVirtualImport(declaration.from)) { | ||
| continue; | ||
| } | ||
| mergeModuleDeclaration(modulesMap, declaration); | ||
@@ -27,0 +31,0 @@ } |
@@ -45,2 +45,3 @@ import type { EcoPagesAppConfig, IHmrManager } from '../../../types/internal-types.js'; | ||
| processDependencies(deps: AssetDefinition[], key: string): Promise<ProcessedAsset[]>; | ||
| private prepareDependenciesForProcessing; | ||
| /** | ||
@@ -84,2 +85,4 @@ * Processes deduplicated dependencies grouped by processor type. | ||
| private getCachedAsset; | ||
| private getCachedContentScriptAsset; | ||
| private resolveCachedContentScriptFilepath; | ||
| /** | ||
@@ -86,0 +89,0 @@ * Stores one processed asset in the dependency cache. |
@@ -7,9 +7,16 @@ import path from "node:path"; | ||
| import { | ||
| ensureGroupedContentScriptsBundle, | ||
| partitionGroupedContentScriptDependencies, | ||
| processGroupedDependencyBundles | ||
| } from "./grouped-content-bundles.js"; | ||
| import { resolveIntegrationPluginForProcessingKey } from "./resolve-integration-plugin.js"; | ||
| import { isHmrAware } from "./processor.interface.js"; | ||
| import { ProcessorRegistry } from "./processor.registry.js"; | ||
| import { processUngroupedDependency } from "./ungrouped-dependency-processing.js"; | ||
| import { materializeContentScriptAsset } from "./materialize-content-script-asset.js"; | ||
| import { | ||
| getDevBrowserScriptCacheEntry, | ||
| setDevBrowserScriptCacheEntry | ||
| } from "../../../build/dev-browser-script-cache.js"; | ||
| import { | ||
| ContentScriptProcessor, | ||
@@ -72,6 +79,12 @@ ContentStylesheetProcessor, | ||
| const dedupedDeps = deduplicateAssetDependencies(deps); | ||
| const results = await this.processDependenciesParallel(dedupedDeps, key); | ||
| const preparedDeps = this.prepareDependenciesForProcessing(dedupedDeps, key); | ||
| ensureGroupedContentScriptsBundle(preparedDeps); | ||
| const results = await this.processDependenciesParallel(preparedDeps); | ||
| await this.optimizeDependencies(results); | ||
| return results; | ||
| } | ||
| prepareDependenciesForProcessing(deps, processingKey) { | ||
| const plugin = resolveIntegrationPluginForProcessingKey(this.config, processingKey); | ||
| return plugin?.prepareAssetDependencies?.(deps) ?? deps; | ||
| } | ||
| /** | ||
@@ -85,3 +98,3 @@ * Processes deduplicated dependencies grouped by processor type. | ||
| */ | ||
| async processDependenciesParallel(deps, key) { | ||
| async processDependenciesParallel(deps) { | ||
| const grouped = this.groupDependenciesByType(deps); | ||
@@ -93,3 +106,2 @@ const groupPromises = Object.entries(grouped).map(async ([, typeDeps]) => { | ||
| dep, | ||
| key, | ||
| depKey: getAssetDependencyKey(dep), | ||
@@ -116,3 +128,2 @@ getCachedAsset: (assetDep, depKey) => this.getCachedAsset(assetDep, depKey), | ||
| bundles: Array.from(groupedBundleDeps.values()), | ||
| key, | ||
| getCachedAsset: (dep, depKey) => this.getCachedAsset(dep, depKey), | ||
@@ -237,2 +248,5 @@ getDependencyKey: getAssetDependencyKey, | ||
| } | ||
| if (dep.kind === "script" && dep.source === "content") { | ||
| return this.getCachedContentScriptAsset(dep, depKey); | ||
| } | ||
| const cached = this.cache.get(depKey); | ||
@@ -248,2 +262,22 @@ if (!cached) { | ||
| } | ||
| getCachedContentScriptAsset(dep, depKey) { | ||
| const filepath = this.resolveCachedContentScriptFilepath(depKey) ?? getDevBrowserScriptCacheEntry(this.config, depKey)?.filepath; | ||
| if (!filepath) { | ||
| return null; | ||
| } | ||
| const materialized = materializeContentScriptAsset(dep, filepath); | ||
| this.cache.set(depKey, { asset: materialized }); | ||
| return materialized; | ||
| } | ||
| resolveCachedContentScriptFilepath(depKey) { | ||
| const cached = this.cache.get(depKey); | ||
| if (!cached?.asset.filepath) { | ||
| return void 0; | ||
| } | ||
| if (!fileSystem.exists(cached.asset.filepath)) { | ||
| this.cache.delete(depKey); | ||
| return void 0; | ||
| } | ||
| return cached.asset.filepath; | ||
| } | ||
| /** | ||
@@ -254,2 +288,5 @@ * Stores one processed asset in the dependency cache. | ||
| this.cache.set(depKey, { asset }); | ||
| if (dep.kind === "script" && dep.source === "content") { | ||
| setDevBrowserScriptCacheEntry(this.config, depKey, asset); | ||
| } | ||
| } | ||
@@ -256,0 +293,0 @@ /** |
| import type { AssetDefinition, ProcessedAsset } from './assets.types.js'; | ||
| /** Forces grouped content scripts to run through the bundler in production builds. */ | ||
| export declare function ensureGroupedContentScriptsBundle(dependencies: AssetDefinition[]): void; | ||
| /** | ||
@@ -15,3 +17,2 @@ * Splits grouped content-script dependencies from ordinary dependencies so callers can | ||
| bundles: AssetDefinition[][]; | ||
| key: string; | ||
| getCachedAsset: (dep: AssetDefinition, depKey: string) => ProcessedAsset | null; | ||
@@ -18,0 +19,0 @@ getDependencyKey: (dep: AssetDefinition) => string; |
@@ -0,1 +1,16 @@ | ||
| import { isDevelopmentRuntime } from "../../../utils/runtime.js"; | ||
| import { finalizeProcessedAsset } from "./finalize-processed-asset.js"; | ||
| function ensureGroupedContentScriptsBundle(dependencies) { | ||
| if (isDevelopmentRuntime()) { | ||
| return; | ||
| } | ||
| for (const dependency of dependencies) { | ||
| if (dependency.kind !== "script" || dependency.source !== "content" || !dependency.groupedBundle?.id) { | ||
| continue; | ||
| } | ||
| if (dependency.bundle === false) { | ||
| dependency.bundle = true; | ||
| } | ||
| } | ||
| } | ||
| function partitionGroupedContentScriptDependencies(typeDeps) { | ||
@@ -21,3 +36,2 @@ const groupedBundleDeps = /* @__PURE__ */ new Map(); | ||
| bundles, | ||
| key, | ||
| getCachedAsset, | ||
@@ -33,3 +47,3 @@ getDependencyKey, | ||
| const cached = getCachedAsset(dep, getDependencyKey(dep)); | ||
| return cached ? { key, ...cached } : null; | ||
| return cached ? finalizeProcessedAsset(cached, resolveProcessedAssetSrcUrl) : null; | ||
| }); | ||
@@ -48,10 +62,5 @@ if (cachedResults.every((result) => result !== null)) { | ||
| const depKey = getDependencyKey(dep); | ||
| const srcUrl = resolveProcessedAssetSrcUrl(processed); | ||
| const processedWithKey = { | ||
| key, | ||
| ...processed, | ||
| srcUrl | ||
| }; | ||
| setCachedAsset(dep, depKey, processedWithKey); | ||
| return processedWithKey; | ||
| const finalized = finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl); | ||
| setCachedAsset(dep, depKey, finalized); | ||
| return finalized; | ||
| }); | ||
@@ -66,4 +75,5 @@ } catch (error) { | ||
| export { | ||
| ensureGroupedContentScriptsBundle, | ||
| partitionGroupedContentScriptDependencies, | ||
| processGroupedDependencyBundles | ||
| }; |
| export * from './asset.factory.js'; | ||
| export * from './grouped-content-bundles.js'; | ||
| export * from './page-package.js'; | ||
@@ -3,0 +4,0 @@ export * from './asset-processing.service.js'; |
| export * from "./asset.factory.js"; | ||
| export * from "./grouped-content-bundles.js"; | ||
| export * from "./page-package.js"; | ||
@@ -3,0 +4,0 @@ export * from "./asset-processing.service.js"; |
| import type { ContentScriptAsset, ProcessedAsset } from '../../assets.types.js'; | ||
| import { BaseScriptProcessor } from '../base/base-script-processor.js'; | ||
| export declare class ContentScriptProcessor extends BaseScriptProcessor<ContentScriptAsset> { | ||
| private getContentScriptEntryDir; | ||
| private getContentScriptEntryPath; | ||
| private createBundleConfigHash; | ||
| private createContentScriptCacheKey; | ||
| private toProcessedAsset; | ||
| private removeContentScriptEntry; | ||
| processGrouped(deps: ContentScriptAsset[]): Promise<ProcessedAsset[]>; | ||
| private getGroupedBundlerOptions; | ||
| process(dep: ContentScriptAsset): Promise<ProcessedAsset>; | ||
| } |
| import path from "node:path"; | ||
| import { fileSystem } from "@ecopages/file-system"; | ||
| import { shouldUseDevBrowserScriptCache } from "../../../../../build/dev-browser-script-cache.js"; | ||
| import { BaseScriptProcessor } from "../base/base-script-processor.js"; | ||
| class ContentScriptProcessor extends BaseScriptProcessor { | ||
| getContentScriptEntryDir() { | ||
| const dir = path.join(this.appConfig.absolutePaths.workDir, "content-script-entries"); | ||
| fileSystem.ensureDir(dir); | ||
| return dir; | ||
| } | ||
| getContentScriptEntryPath(contentHash) { | ||
| return path.join(this.getContentScriptEntryDir(), `${contentHash}.js`); | ||
| } | ||
| createBundleConfigHash(dep, shouldBundle) { | ||
| return this.generateHash( | ||
| JSON.stringify({ | ||
| bundle: shouldBundle, | ||
| minify: shouldBundle && this.isProduction, | ||
| opts: dep.bundleOptions | ||
| }) | ||
| ); | ||
| } | ||
| createContentScriptCacheKey(dep, shouldBundle) { | ||
| const contentHash = this.generateHash(dep.content); | ||
| const configHash = this.createBundleConfigHash(dep, shouldBundle); | ||
| return `${this.buildCacheKey(`content-script:${contentHash}`, contentHash, dep)}:${configHash}`; | ||
| } | ||
| toProcessedAsset(dep, filepath, inlineContent) { | ||
| return { | ||
| filepath, | ||
| content: dep.inline ? inlineContent : void 0, | ||
| kind: "script", | ||
| position: dep.position, | ||
| attributes: dep.attributes, | ||
| inline: dep.inline, | ||
| excludeFromHtml: dep.excludeFromHtml, | ||
| packageRole: dep.packageRole, | ||
| groupedBundle: dep.groupedBundle, | ||
| bundledSourceFilepaths: dep.bundledSourceFilepaths | ||
| }; | ||
| } | ||
| removeContentScriptEntry(contentHash) { | ||
| if (shouldUseDevBrowserScriptCache()) { | ||
| return; | ||
| } | ||
| fileSystem.remove(this.getContentScriptEntryPath(contentHash)); | ||
| } | ||
| async processGrouped(deps) { | ||
@@ -13,23 +56,18 @@ if (deps.length === 0) { | ||
| } | ||
| const tempDir = path.join( | ||
| this.appConfig.absolutePaths.distDir, | ||
| `grouped-script-entries-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}` | ||
| ); | ||
| fileSystem.ensureDir(tempDir); | ||
| let tempEntries = []; | ||
| try { | ||
| const tempEntries = deps.map((dep, index) => { | ||
| const entryName = dep.groupedBundle?.entryName ?? dep.name ?? `grouped-script-${index}`; | ||
| const tempFilepath = path.join(tempDir, `${entryName}.js`); | ||
| tempEntries = deps.map((dep) => { | ||
| const contentHash = this.generateHash(dep.content); | ||
| const tempFilepath = this.getContentScriptEntryPath(contentHash); | ||
| fileSystem.write(tempFilepath, dep.content); | ||
| return { | ||
| dep, | ||
| entryName, | ||
| contentHash, | ||
| tempFilepath | ||
| }; | ||
| }); | ||
| const primaryDep = deps[0]; | ||
| const outputPaths = await this.bundleScripts({ | ||
| ...this.getBundlerOptions(primaryDep), | ||
| entries: tempEntries.map(({ entryName, tempFilepath }) => ({ | ||
| entryName, | ||
| ...this.getGroupedBundlerOptions(deps), | ||
| entries: tempEntries.map(({ dep, contentHash, tempFilepath }) => ({ | ||
| entryName: dep.groupedBundle?.entryName ?? dep.name ?? contentHash, | ||
| entrypoint: tempFilepath | ||
@@ -41,3 +79,4 @@ })), | ||
| }); | ||
| return tempEntries.map(({ dep, entryName }) => { | ||
| return tempEntries.map(({ dep, contentHash }) => { | ||
| const entryName = dep.groupedBundle?.entryName ?? dep.name ?? contentHash; | ||
| const bundledFilePath = outputPaths.get(entryName); | ||
@@ -47,74 +86,61 @@ if (!bundledFilePath) { | ||
| } | ||
| return { | ||
| filepath: bundledFilePath, | ||
| content: dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0, | ||
| kind: "script", | ||
| position: dep.position, | ||
| attributes: dep.attributes, | ||
| inline: dep.inline, | ||
| excludeFromHtml: dep.excludeFromHtml, | ||
| packageRole: dep.packageRole, | ||
| groupedBundle: dep.groupedBundle, | ||
| bundledSourceFilepaths: dep.bundledSourceFilepaths | ||
| }; | ||
| return this.toProcessedAsset( | ||
| dep, | ||
| bundledFilePath, | ||
| dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0 | ||
| ); | ||
| }); | ||
| } finally { | ||
| fileSystem.remove(tempDir); | ||
| for (const { contentHash } of tempEntries) { | ||
| this.removeContentScriptEntry(contentHash); | ||
| } | ||
| } | ||
| } | ||
| async process(dep) { | ||
| const hash = this.generateHash(dep.content); | ||
| const filename = dep.name ? `${dep.name}.js` : `script-${hash}.js`; | ||
| const shouldBundle = this.shouldBundle(dep); | ||
| const filepath = path.join(this.getAssetsDir(), "scripts", filename); | ||
| if (!shouldBundle) { | ||
| if (!dep.inline) fileSystem.write(filepath, dep.content); | ||
| const unbundledProcessedAsset = { | ||
| filepath, | ||
| content: dep.inline ? dep.content : void 0, | ||
| kind: "script", | ||
| position: dep.position, | ||
| attributes: dep.attributes, | ||
| inline: dep.inline, | ||
| excludeFromHtml: dep.excludeFromHtml, | ||
| packageRole: dep.packageRole, | ||
| groupedBundle: dep.groupedBundle, | ||
| bundledSourceFilepaths: dep.bundledSourceFilepaths | ||
| getGroupedBundlerOptions(deps) { | ||
| const primaryDep = deps[0]; | ||
| const options = this.getBundlerOptions(primaryDep); | ||
| if (deps.some((dep) => dep.bundleOptions?.splitting === false)) { | ||
| return { | ||
| ...options, | ||
| splitting: false | ||
| }; | ||
| this.writeCacheFile(filename, unbundledProcessedAsset); | ||
| return unbundledProcessedAsset; | ||
| } | ||
| if (dep.content) { | ||
| const tempDir = this.appConfig.absolutePaths.distDir; | ||
| fileSystem.ensureDir(tempDir); | ||
| const tempFileName = path.join( | ||
| tempDir, | ||
| `${path.parse(filename).name}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp.js` | ||
| ); | ||
| fileSystem.write(tempFileName, dep.content); | ||
| const bundledFilePath = await this.bundleScript({ | ||
| entrypoint: tempFileName, | ||
| outdir: this.getAssetsDir(), | ||
| minify: this.isProduction, | ||
| naming: `${path.parse(filename).name}-[hash].[ext]`, | ||
| ...this.getBundlerOptions(dep) | ||
| }); | ||
| const processedAsset = { | ||
| filepath: bundledFilePath, | ||
| content: dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0, | ||
| kind: "script", | ||
| position: dep.position, | ||
| attributes: dep.attributes, | ||
| inline: dep.inline, | ||
| excludeFromHtml: dep.excludeFromHtml, | ||
| packageRole: dep.packageRole, | ||
| groupedBundle: dep.groupedBundle, | ||
| bundledSourceFilepaths: dep.bundledSourceFilepaths | ||
| }; | ||
| fileSystem.remove(tempFileName); | ||
| this.writeCacheFile(filename, processedAsset); | ||
| return processedAsset; | ||
| } | ||
| throw new Error("No content found for script asset"); | ||
| return options; | ||
| } | ||
| async process(dep) { | ||
| const shouldBundle = this.shouldBundle(dep); | ||
| const cacheKey = this.createContentScriptCacheKey(dep, shouldBundle); | ||
| return this.getOrProcess(cacheKey, async () => { | ||
| const hash = this.generateHash(dep.content); | ||
| const filename = dep.name ? `${dep.name}.js` : `script-${hash}.js`; | ||
| const filepath = path.join(this.getAssetsDir(), "scripts", filename); | ||
| if (!shouldBundle) { | ||
| if (!dep.inline) { | ||
| fileSystem.write(filepath, dep.content); | ||
| } | ||
| return this.toProcessedAsset(dep, filepath, dep.inline ? dep.content : void 0); | ||
| } | ||
| if (!dep.content) { | ||
| throw new Error("No content found for script asset"); | ||
| } | ||
| const entryPath = this.getContentScriptEntryPath(hash); | ||
| fileSystem.write(entryPath, dep.content); | ||
| try { | ||
| const bundledFilePath = await this.bundleScript({ | ||
| entrypoint: entryPath, | ||
| outdir: this.getAssetsDir(), | ||
| minify: this.isProduction, | ||
| naming: `${path.parse(filename).name}-[hash].[ext]`, | ||
| ...this.getBundlerOptions(dep) | ||
| }); | ||
| return this.toProcessedAsset( | ||
| dep, | ||
| bundledFilePath, | ||
| dep.inline ? fileSystem.readFileSync(bundledFilePath).toString() : void 0 | ||
| ); | ||
| } finally { | ||
| this.removeContentScriptEntry(hash); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
@@ -121,0 +147,0 @@ export { |
@@ -5,3 +5,2 @@ import type { AssetProcessor } from './processor.interface.js'; | ||
| dep: AssetDefinition; | ||
| key: string; | ||
| depKey: string; | ||
@@ -8,0 +7,0 @@ getCachedAsset: (dep: AssetDefinition, depKey: string) => ProcessedAsset | null; |
| import { fileSystem } from "@ecopages/file-system"; | ||
| import { finalizeProcessedAsset } from "./finalize-processed-asset.js"; | ||
| async function processUngroupedDependency(options) { | ||
| const { | ||
| dep, | ||
| key, | ||
| depKey, | ||
@@ -17,3 +17,3 @@ getCachedAsset, | ||
| if (cached) { | ||
| return { key, ...cached }; | ||
| return finalizeProcessedAsset(cached, resolveProcessedAssetSrcUrl); | ||
| } | ||
@@ -31,10 +31,5 @@ const processor = getProcessor(dep); | ||
| const processed = await processor.process(dep); | ||
| const srcUrl = resolveProcessedAssetSrcUrl(processed); | ||
| const processedWithKey = { | ||
| key, | ||
| ...processed, | ||
| srcUrl | ||
| }; | ||
| setCachedAsset(dep, depKey, processedWithKey); | ||
| return processedWithKey; | ||
| const finalized = finalizeProcessedAsset(processed, resolveProcessedAssetSrcUrl); | ||
| setCachedAsset(dep, depKey, finalized); | ||
| return finalized; | ||
| } catch (error) { | ||
@@ -41,0 +36,0 @@ logProcessingError(dep, error); |
@@ -5,2 +5,5 @@ import { getAppBrowserBuildPlugins, getAppTranspileOptions } from "../../build/build-adapter.js"; | ||
| import { getAppSourceTransforms } from "../../plugins/source-transform.js"; | ||
| import { startupTrace } from "../../diagnostics/startup-trace.js"; | ||
| import { requestBuildDedupe } from "../../diagnostics/request-build-dedupe.js"; | ||
| import { createBuildOptionsDedupeKey } from "../../build/deduping-build-executor.js"; | ||
| function resolveBrowserBundleExecutor(appConfig, profile, executor) { | ||
@@ -42,3 +45,8 @@ const buildRuntime = requireBuildRuntime(appConfig); | ||
| const buildExecutor = resolveBrowserBundleExecutor(this.appConfig, profile, executor); | ||
| return await buildExecutor.build(request); | ||
| const dedupeKey = createBuildOptionsDedupeKey(request); | ||
| return requestBuildDedupe.dedupeBuild(dedupeKey, async () => { | ||
| const result = await buildExecutor.build(request); | ||
| startupTrace.recordBrowserBundle(result.outputs); | ||
| return result; | ||
| }); | ||
| } | ||
@@ -48,3 +56,3 @@ async bundleGroupedEntries(entries, options) { | ||
| ...options, | ||
| entrypoints: entries.map((entry) => entry.entrypoint) | ||
| entrypoints: Object.fromEntries(entries.map((entry) => [entry.entryName, entry.entrypoint])) | ||
| }; | ||
@@ -51,0 +59,0 @@ return this.bundle(request); |
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 2 instances
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
1335708
1.63%495
2.91%32596
1.66%61
8.93%+ Added
- Removed