diff --git a/packages/angular/build/src/builders/application/build-action.ts b/packages/angular/build/src/builders/application/build-action.ts index e71663765274..3f8d24da6ecc 100644 --- a/packages/angular/build/src/builders/application/build-action.ts +++ b/packages/angular/build/src/builders/application/build-action.ts @@ -9,7 +9,11 @@ import { BuilderContext } from '@angular-devkit/architect'; import { existsSync } from 'node:fs'; import path from 'node:path'; -import { ExecutionResult, RebuildState } from '../../tools/esbuild/bundler-execution-result'; +import { + BuildOutputAsset, + ExecutionResult, + RebuildState, +} from '../../tools/esbuild/bundler-execution-result'; import { BuildOutputFile, BuildOutputFileType } from '../../tools/esbuild/bundler-files'; import { shutdownSassWorkerPool } from '../../tools/esbuild/stylesheets/sass-language'; import { logMessages, withNoProgress, withSpinner } from '../../tools/esbuild/utils'; @@ -279,34 +283,15 @@ function* emitOutputResults( // Use a full result if there is no rebuild state (no prior build result) if (!rebuildState || !changes) { - const result: FullResult = { - kind: ResultKind.Full, - warnings: warnings as ResultMessage[], - files: {}, - detail: { - externalMetadata, - htmlIndexPath, - htmlBaseHref, - outputOptions, - }, - }; - for (const file of assetFiles) { - result.files[file.destination] = { - type: BuildOutputFileType.Browser, - inputPath: file.source, - origin: 'disk', - }; - } - for (const file of outputFiles) { - result.files[file.path] = { - type: file.type, - contents: file.contents, - origin: 'memory', - hash: file.hash, - }; - } - - yield result; + yield createFullResult( + outputFiles, + assetFiles, + warnings, + outputOptions, + externalMetadata, + htmlIndexPath, + htmlBaseHref, + ); return; } @@ -326,7 +311,7 @@ function* emitOutputResults( added: [], removed: [], modified: [], - files: {}, + files: [], detail: { externalMetadata, htmlIndexPath, @@ -340,9 +325,10 @@ function* emitOutputResults( // Initially assume all previous output files have been removed const removedOutputFiles = new Map(previousOutputInfo); for (const file of outputFiles) { - removedOutputFiles.delete(file.path); + const key = `${file.type}:${file.path}`; + removedOutputFiles.delete(key); - const previousHash = previousOutputInfo.get(file.path)?.hash; + const previousHash = previousOutputInfo.get(key)?.hash; let needFile = false; if (previousHash === undefined) { needFile = true; @@ -359,12 +345,13 @@ function* emitOutputResults( incrementalResult.background = false; } - incrementalResult.files[file.path] = { + incrementalResult.files.push({ + path: file.path, type: file.type, contents: file.contents, origin: 'memory', hash: file.hash, - }; + }); } } @@ -385,11 +372,12 @@ function* emitOutputResults( hasCssUpdates ||= destination.endsWith('.css'); - incrementalResult.files[destination] = { + incrementalResult.files.push({ + path: destination, type: BuildOutputFileType.Browser, inputPath: source, origin: 'disk', - }; + }); } // Do not remove stale files yet if there are template updates. @@ -403,12 +391,12 @@ function* emitOutputResults( // Include the removed output and asset files incrementalResult.removed.push( - ...Array.from(removedOutputFiles, ([file, { type }]) => ({ - path: file, + ...Array.from(removedOutputFiles.values(), ({ type, path }) => ({ + path, type, })), - ...Array.from(removedAssetFiles.values(), (file) => ({ - path: file, + ...Array.from(removedAssetFiles.values(), (path) => ({ + path, type: BuildOutputFileType.Browser, })), ); @@ -425,9 +413,7 @@ function* emitOutputResults( added: incrementalResult.added.filter(isCssFilePath), removed: incrementalResult.removed.filter(({ path }) => isCssFilePath(path)), modified: incrementalResult.modified.filter(isCssFilePath), - files: Object.fromEntries( - Object.entries(incrementalResult.files).filter(([path]) => isCssFilePath(path)), - ), + files: incrementalResult.files.filter((file) => isCssFilePath(file.path)), }; yield styleResult; @@ -446,6 +432,42 @@ function* emitOutputResults( } } +function createFullResult( + outputFiles: readonly BuildOutputFile[], + assetFiles: readonly BuildOutputAsset[], + warnings: readonly unknown[], + outputOptions: NormalizedApplicationBuildOptions['outputOptions'], + externalMetadata: unknown, + htmlIndexPath: unknown, + htmlBaseHref: unknown, +): FullResult { + return { + kind: ResultKind.Full, + warnings: warnings as ResultMessage[], + files: [ + ...assetFiles.map(({ source, destination }) => ({ + path: destination, + type: BuildOutputFileType.Browser, + inputPath: source, + origin: 'disk' as const, + })), + ...outputFiles.map((file) => ({ + path: file.path, + type: file.type, + contents: file.contents, + origin: 'memory' as const, + hash: file.hash, + })), + ], + detail: { + externalMetadata, + htmlIndexPath, + htmlBaseHref, + outputOptions, + }, + }; +} + function isCssFilePath(filePath: string): boolean { return /\.css(?:\.map)?$/i.test(filePath); } diff --git a/packages/angular/build/src/builders/application/index.ts b/packages/angular/build/src/builders/application/index.ts index b31fc0a8f81b..59ded1cd8182 100644 --- a/packages/angular/build/src/builders/application/index.ts +++ b/packages/angular/build/src/builders/application/index.ts @@ -199,7 +199,7 @@ export async function* buildApplication( // Writes the output files to disk and ensures the containing directories are present const directoryExists = new Set(); try { - await emitFilesToDisk(Object.entries(result.files), async ([filePath, file]) => { + await emitFilesToDisk(result.files, async (file) => { if ( outputOptions.ignoreServer && (file.type === BuildOutputFileType.ServerApplication || @@ -208,7 +208,7 @@ export async function* buildApplication( return; } - const fullFilePath = generateFullPath(filePath, file.type, outputOptions); + const fullFilePath = generateFullPath(file.path, file.type, outputOptions); // Ensure output subdirectories exist const fileBasePath = path.dirname(fullFilePath); diff --git a/packages/angular/build/src/builders/application/results.ts b/packages/angular/build/src/builders/application/results.ts index 6fa23f9f19cd..8e95176639f5 100644 --- a/packages/angular/build/src/builders/application/results.ts +++ b/packages/angular/build/src/builders/application/results.ts @@ -31,7 +31,7 @@ export interface FailureResult extends BaseResult { export interface FullResult extends BaseResult { kind: ResultKind.Full; - files: Record; + files: ResultFile[]; } export interface IncrementalResult extends BaseResult { @@ -40,13 +40,14 @@ export interface IncrementalResult extends BaseResult { added: string[]; removed: { path: string; type: BuildOutputFileType }[]; modified: string[]; - files: Record; + files: ResultFile[]; } export type ResultFile = DiskFile | MemoryFile; export interface BaseResultFile { origin: 'memory' | 'disk'; + path: string; type: BuildOutputFileType; } diff --git a/packages/angular/build/src/builders/application/tests/options/service-worker_spec.ts b/packages/angular/build/src/builders/application/tests/options/service-worker_spec.ts index 958cd5007960..444babc37a02 100644 --- a/packages/angular/build/src/builders/application/tests/options/service-worker_spec.ts +++ b/packages/angular/build/src/builders/application/tests/options/service-worker_spec.ts @@ -19,7 +19,7 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { name: 'app', installMode: 'prefetch', resources: { - files: ['/favicon.ico', '/index.html'], + files: ['/favicon.ico', '/index.html', '/*.css', '/*.js'], }, }, { @@ -88,5 +88,40 @@ describeBuilder(buildApplication, APPLICATION_BUILDER_INFO, (harness) => { const config = await harness.readFile('dist/browser/ngsw.json'); expect(JSON.parse(config)).toEqual(jasmine.objectContaining({ index: '/index.csr.html' })); }); + + it('should write JS-imported CSS chunk to browser dist when SSR is enabled', async () => { + await harness.modifyFile('src/tsconfig.app.json', (content) => { + const tsConfig = JSON.parse(content); + tsConfig.files ??= []; + tsConfig.files.push('main.server.ts', 'server.ts', 'extra.d.ts'); + + return JSON.stringify(tsConfig); + }); + + await harness.writeFile('src/extra.d.ts', `declare module '*.css';`); + await harness.writeFile('src/server.ts', `console.log('Server!');`); + await harness.writeFile('src/extra.css', `body { color: red; }`); + await harness.modifyFile('src/main.ts', (content) => `import './extra.css';\n${content}`); + + harness.useTarget('build', { + ...BASE_OPTIONS, + server: 'src/main.server.ts', + ssr: { entry: 'src/server.ts' }, + serviceWorker: true, + }); + + const { result } = await harness.executeOnce(); + expect(result?.success).toBeTrue(); + + const config = JSON.parse(harness.readFile('dist/browser/ngsw.json')); + const hashTable = config.hashTable as Record; + + const cssChunkUrls = Object.keys(hashTable).filter((url) => url.endsWith('.css')); + expect(cssChunkUrls.length).toBeGreaterThan(0); + + for (const url of Object.keys(hashTable)) { + harness.expectFile(`dist/browser${url}`).toExist(); + } + }); }); }); diff --git a/packages/angular/build/src/builders/dev-server/vite/index.ts b/packages/angular/build/src/builders/dev-server/vite/index.ts index 94192ec41b8c..8c8f23c3a339 100644 --- a/packages/angular/build/src/builders/dev-server/vite/index.ts +++ b/packages/angular/build/src/builders/dev-server/vite/index.ts @@ -262,9 +262,8 @@ export async function* serveWithVite( componentStyles.clear(); generatedFiles.clear(); - for (const [outputPath, file] of Object.entries(result.files)) { + for (const file of result.files) { updateResultRecord( - outputPath, file, normalizePath, htmlIndexPath, @@ -292,22 +291,9 @@ export async function* serveWithVite( assetFiles.delete(filePath); } - for (const modified of result.modified) { + for (const file of result.files) { updateResultRecord( - modified, - result.files[modified], - normalizePath, - htmlIndexPath, - generatedFiles, - assetFiles, - componentStyles, - ); - } - - for (const added of result.added) { - updateResultRecord( - added, - result.files[added], + file, normalizePath, htmlIndexPath, generatedFiles, diff --git a/packages/angular/build/src/builders/dev-server/vite/utils.ts b/packages/angular/build/src/builders/dev-server/vite/utils.ts index e1e6b4f96847..bc2d4a513b5a 100644 --- a/packages/angular/build/src/builders/dev-server/vite/utils.ts +++ b/packages/angular/build/src/builders/dev-server/vite/utils.ts @@ -30,7 +30,6 @@ export interface DevServerExternalResultMetadata extends Omit string, htmlIndexPath: string, @@ -40,7 +39,7 @@ export function updateResultRecord( initial = false, ): void { if (file.origin === 'disk') { - assetFiles.set('/' + normalizePath(outputPath), { + assetFiles.set('/' + normalizePath(file.path), { source: normalizePath(file.inputPath), updated: !initial, }); @@ -49,12 +48,12 @@ export function updateResultRecord( } let filePath; - if (outputPath === htmlIndexPath) { + if (file.path === htmlIndexPath) { // Convert custom index output path to standard index path for dev-server usage. // This mimics the Webpack dev-server behavior. filePath = '/index.html'; } else { - filePath = '/' + normalizePath(outputPath); + filePath = '/' + normalizePath(file.path); } const servable = @@ -74,6 +73,12 @@ export function updateResultRecord( return; } + // Avoid overwriting a servable browser file with a non-servable server file of the same path (e.g. CSS chunks) + const existing = generatedFiles.get(filePath); + if (existing?.servable && !servable) { + return; + } + // New or updated file generatedFiles.set(filePath, { contents: file.contents, diff --git a/packages/angular/build/src/builders/extract-i18n/application-extraction.ts b/packages/angular/build/src/builders/extract-i18n/application-extraction.ts index e7f8bee10c32..ff223ba0bd16 100644 --- a/packages/angular/build/src/builders/extract-i18n/application-extraction.ts +++ b/packages/angular/build/src/builders/extract-i18n/application-extraction.ts @@ -65,12 +65,12 @@ export async function extractMessages( // Extract messages from each output JavaScript file. // Output files are only present on a successful build. - for (const filePath of Object.keys(builderResult.files)) { - if (!filePath.endsWith('.js')) { + for (const file of builderResult.files) { + if (!file.path.endsWith('.js')) { continue; } - const fileMessages = extractor.extractMessages(filePath); + const fileMessages = extractor.extractMessages(file.path); messages.push(...fileMessages); } @@ -88,9 +88,10 @@ export async function extractMessages( function setupLocalizeExtractor( extractorConstructor: typeof MessageExtractor, - files: Record, + files: readonly ResultFile[], context: BuilderContext, ): MessageExtractor { + const fileMap = new Map(files.map((file) => [file.path, file])); const textDecoder = new TextDecoder(); // Setup a virtual file system instance for the extractor // * MessageExtractor itself uses readFile, relative and resolve @@ -100,7 +101,7 @@ function setupLocalizeExtractor( // Output files are stored as relative to the workspace root const requestedPath = nodePath.relative(context.workspaceRoot, path); - const file = files[requestedPath]; + const file = fileMap.get(requestedPath); let content; if (file?.origin === 'memory') { content = textDecoder.decode(file.contents); @@ -123,7 +124,7 @@ function setupLocalizeExtractor( // Output files are stored as relative to the workspace root const requestedPath = nodePath.relative(context.workspaceRoot, path); - return files[requestedPath] !== undefined; + return fileMap.has(requestedPath); }, dirname(path: string): string { return nodePath.dirname(path); diff --git a/packages/angular/build/src/builders/karma/application_builder.ts b/packages/angular/build/src/builders/karma/application_builder.ts index d0adb6c3cf0e..674a7db0e4be 100644 --- a/packages/angular/build/src/builders/karma/application_builder.ts +++ b/packages/angular/build/src/builders/karma/application_builder.ts @@ -386,7 +386,7 @@ async function configureKarma( ); } - parsedKarmaConfig.plugins.push(AngularAssetsMiddleware.createPlugin(buildOutput)); + parsedKarmaConfig.plugins.push(AngularAssetsMiddleware.createPlugin(buildOutput.files)); parsedKarmaConfig.middleware ??= []; parsedKarmaConfig.middleware.push(AngularAssetsMiddleware.NAME); diff --git a/packages/angular/build/src/builders/karma/assets-middleware.ts b/packages/angular/build/src/builders/karma/assets-middleware.ts index fd6ce489e583..45483cb4ec41 100644 --- a/packages/angular/build/src/builders/karma/assets-middleware.ts +++ b/packages/angular/build/src/builders/karma/assets-middleware.ts @@ -25,7 +25,7 @@ interface ServeFileFunction { } export interface LatestBuildFiles { - files: Record; + files: Map; } const LATEST_BUILD_FILES_TOKEN = 'angularLatestBuildFiles'; @@ -49,7 +49,7 @@ export class AngularAssetsMiddleware { pathname = pathname.replaceAll(path.posix.sep, path.win32.sep); } - const file = this.latestBuildFiles.files[pathname]; + const file = this.latestBuildFiles.files.get(pathname); if (!file) { next(); @@ -76,9 +76,12 @@ export class AngularAssetsMiddleware { } } - static createPlugin(initialFiles: LatestBuildFiles): InlinePluginDef { + static createPlugin(initialFiles: readonly ResultFile[]): InlinePluginDef { return { - [LATEST_BUILD_FILES_TOKEN]: ['value', { files: { ...initialFiles.files } }], + [LATEST_BUILD_FILES_TOKEN]: [ + 'value', + { files: new Map(initialFiles.map((file) => [file.path, file])) }, + ], [`middleware:${AngularAssetsMiddleware.NAME}`]: [ 'factory', diff --git a/packages/angular/build/src/builders/karma/progress-reporter.ts b/packages/angular/build/src/builders/karma/progress-reporter.ts index 6fc92b6c74c8..e7d40be15cfd 100644 --- a/packages/angular/build/src/builders/karma/progress-reporter.ts +++ b/packages/angular/build/src/builders/karma/progress-reporter.ts @@ -76,12 +76,14 @@ export function injectKarmaReporter( buildOutput.kind === ResultKind.Full ) { if (buildOutput.kind === ResultKind.Full) { - this.latestBuildFiles.files = buildOutput.files; + this.latestBuildFiles.files.clear(); } else { - this.latestBuildFiles.files = { - ...this.latestBuildFiles.files, - ...buildOutput.files, - }; + for (const { path } of buildOutput.removed) { + this.latestBuildFiles.files.delete(path); + } + } + for (const file of buildOutput.files) { + this.latestBuildFiles.files.set(file.path, file); } await writeTestFiles(buildOutput.files, buildOptions.outputPath); this.emitter.refreshFiles(); diff --git a/packages/angular/build/src/builders/karma/utils.ts b/packages/angular/build/src/builders/karma/utils.ts index 9a8aa749c269..ecdda5516cbd 100644 --- a/packages/angular/build/src/builders/karma/utils.ts +++ b/packages/angular/build/src/builders/karma/utils.ts @@ -8,8 +8,8 @@ import type { BuilderContext } from '@angular-devkit/architect'; import { createRequire } from 'node:module'; -import { BuildOutputFileType } from '../../tools/esbuild/bundler-files'; import { getProjectRootPaths } from '../../utils/project-metadata'; +import type { ResultFile } from '../application/results'; import { findTests, getTestEntrypoints } from './find-tests'; import type { NormalizedKarmaBuilderOptions } from './options'; @@ -62,9 +62,9 @@ export async function collectEntrypoints( return getTestEntrypoints(testFiles, { projectSourceRoot, workspaceRoot: context.workspaceRoot }); } -export function hasChunkOrWorkerFiles(files: Record): boolean { - return Object.keys(files).some((filename) => { - return /(?:^|\/)(?:worker|chunk)[^/]+\.js$/.test(filename); +export function hasChunkOrWorkerFiles(files: readonly ResultFile[]): boolean { + return files.some((file) => { + return /(?:^|\/)(?:worker|chunk)[^/]+\.js$/.test(file.path); }); } diff --git a/packages/angular/build/src/builders/unit-test/builder.ts b/packages/angular/build/src/builders/unit-test/builder.ts index 146ce6654bd0..755b91c40544 100644 --- a/packages/angular/build/src/builders/unit-test/builder.ts +++ b/packages/angular/build/src/builders/unit-test/builder.ts @@ -227,7 +227,7 @@ export async function* execute( await using executor = await runner.createExecutor(context, normalizedOptions, undefined); yield* executor.execute({ kind: ResultKind.Full, - files: {}, + files: [], }); } catch (e) { assertIsError(e); diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts index ac2bfe20fa38..fc64e432e19f 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts @@ -104,8 +104,8 @@ export class VitestExecutor implements TestExecutor { if (buildResult.kind === ResultKind.Full) { this.buildResultFiles.clear(); - for (const [path, file] of Object.entries(buildResult.files)) { - this.buildResultFiles.set(this.normalizePath(path), file); + for (const file of buildResult.files) { + this.buildResultFiles.set(this.normalizePath(file.path), file); } this.debugLog( DebugLogLevel.Info, @@ -124,8 +124,8 @@ export class VitestExecutor implements TestExecutor { for (const file of buildResult.removed) { this.buildResultFiles.delete(this.normalizePath(file.path)); } - for (const [path, file] of Object.entries(buildResult.files)) { - this.buildResultFiles.set(this.normalizePath(path), file); + for (const file of buildResult.files) { + this.buildResultFiles.set(this.normalizePath(file.path), file); } } diff --git a/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts b/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts index 53e17e7e427c..eeb7045dfb61 100644 --- a/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts +++ b/packages/angular/build/src/tools/esbuild/bundler-execution-result.ts @@ -27,7 +27,10 @@ export interface RebuildState { componentStyleBundler: ComponentStylesheetBundler; codeBundleCache?: SourceFileCache; fileChanges: ChangedFiles; - previousOutputInfo: ReadonlyMap; + previousOutputInfo: ReadonlyMap< + string, + { hash: string; type: BuildOutputFileType; path: string } + >; previousAssetsInfo: ReadonlyMap; templateUpdates?: Map; } @@ -171,7 +174,7 @@ export class ExecutionResult { componentStyleBundler: this.componentStyleBundler, fileChanges, previousOutputInfo: new Map( - this.outputFiles.map(({ path, hash, type }) => [path, { hash, type }]), + this.outputFiles.map(({ path, hash, type }) => [`${type}:${path}`, { hash, type, path }]), ), previousAssetsInfo: new Map( this.assetFiles.map(({ source, destination }) => [source, destination]), @@ -185,7 +188,7 @@ export class ExecutionResult { ): Set { const changed = new Set(); for (const file of this.outputFiles) { - const previousHash = previousOutputHashes.get(file.path)?.hash; + const previousHash = previousOutputHashes.get(`${file.type}:${file.path}`)?.hash; if (previousHash === undefined || previousHash !== file.hash) { changed.add(file.path); } diff --git a/packages/angular/build/src/tools/esbuild/utils.ts b/packages/angular/build/src/tools/esbuild/utils.ts index 7d9dce4a5522..3bbaabdd2400 100644 --- a/packages/angular/build/src/tools/esbuild/utils.ts +++ b/packages/angular/build/src/tools/esbuild/utils.ts @@ -214,7 +214,7 @@ export function getFeatureSupport(nativeAsyncAwait: boolean): BuildOptions['supp const MAX_CONCURRENT_WRITES = 64; export async function emitFilesToDisk( - files: T[], + files: readonly T[], writeFileCallback: (file: T) => Promise, ): Promise { // Write files in groups of MAX_CONCURRENT_WRITES to avoid too many open files diff --git a/packages/angular/build/src/utils/test-files.ts b/packages/angular/build/src/utils/test-files.ts index d5f0edb2815d..6790d007e220 100644 --- a/packages/angular/build/src/utils/test-files.ts +++ b/packages/angular/build/src/utils/test-files.ts @@ -17,21 +17,18 @@ import { emitFilesToDisk } from '../tools/esbuild/utils'; * This function handles both in-memory and on-disk files, creating subdirectories * as needed. * - * @param files A map of file paths to `ResultFile` objects, representing the build output. + * @param files A collection of `ResultFile` objects, representing the build output. * @param testDir The absolute path to the directory where the files should be written. */ -export async function writeTestFiles( - files: Record, - testDir: string, -): Promise { +export async function writeTestFiles(files: readonly ResultFile[], testDir: string): Promise { const directoryExists = new Set(); // Writes the test related output files to disk and ensures the containing directories are present - await emitFilesToDisk(Object.entries(files), async ([filePath, file]) => { + await emitFilesToDisk(files, async (file) => { if (file.type !== BuildOutputFileType.Browser && file.type !== BuildOutputFileType.Media) { return; } - const fullFilePath = path.join(testDir, filePath); + const fullFilePath = path.join(testDir, file.path); // Ensure output subdirectories exist const fileBasePath = path.dirname(fullFilePath); diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/application-extraction.ts b/packages/angular_devkit/build_angular/src/builders/extract-i18n/application-extraction.ts index fb6e9138982e..8de8b6863aaf 100644 --- a/packages/angular_devkit/build_angular/src/builders/extract-i18n/application-extraction.ts +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/application-extraction.ts @@ -70,12 +70,12 @@ export async function extractMessages( // Extract messages from each output JavaScript file. // Output files are only present on a successful build. - for (const filePath of Object.keys(builderResult.files)) { - if (!filePath.endsWith('.js')) { + for (const file of builderResult.files) { + if (!file.path.endsWith('.js')) { continue; } - const fileMessages = extractor.extractMessages(filePath); + const fileMessages = extractor.extractMessages(file.path); messages.push(...fileMessages); } @@ -93,9 +93,10 @@ export async function extractMessages( function setupLocalizeExtractor( extractorConstructor: typeof MessageExtractor, - files: Record, + files: readonly ResultFile[], context: BuilderContext, ): MessageExtractor { + const fileMap = new Map(files.map((file) => [file.path, file])); const textDecoder = new TextDecoder(); // Setup a virtual file system instance for the extractor // * MessageExtractor itself uses readFile, relative and resolve @@ -105,7 +106,7 @@ function setupLocalizeExtractor( // Output files are stored as relative to the workspace root const requestedPath = nodePath.relative(context.workspaceRoot, path); - const file = files[requestedPath]; + const file = fileMap.get(requestedPath); let content; if (file?.origin === 'memory') { content = textDecoder.decode(file.contents); @@ -128,7 +129,7 @@ function setupLocalizeExtractor( // Output files are stored as relative to the workspace root const requestedPath = nodePath.relative(context.workspaceRoot, path); - return files[requestedPath] !== undefined; + return fileMap.has(requestedPath); }, dirname(path: string): string { return nodePath.dirname(path);