UNPKG

30.2 kBJavaScriptView Raw
1"use strict";
2/**
3 * @license
4 * Copyright Google LLC All Rights Reserved.
5 *
6 * Use of this source code is governed by an MIT-style license that can be
7 * found in the LICENSE file at https://angular.io/license
8 */
9var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 var desc = Object.getOwnPropertyDescriptor(m, k);
12 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
13 desc = { enumerable: true, get: function() { return m[k]; } };
14 }
15 Object.defineProperty(o, k2, desc);
16}) : (function(o, m, k, k2) {
17 if (k2 === undefined) k2 = k;
18 o[k2] = m[k];
19}));
20var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
21 Object.defineProperty(o, "default", { enumerable: true, value: v });
22}) : function(o, v) {
23 o["default"] = v;
24});
25var __importStar = (this && this.__importStar) || function (mod) {
26 if (mod && mod.__esModule) return mod;
27 var result = {};
28 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
29 __setModuleDefault(result, mod);
30 return result;
31};
32Object.defineProperty(exports, "__esModule", { value: true });
33exports.AngularWebpackPlugin = void 0;
34const assert_1 = require("assert");
35const ts = __importStar(require("typescript"));
36const ngcc_processor_1 = require("../ngcc_processor");
37const paths_plugin_1 = require("../paths-plugin");
38const resource_loader_1 = require("../resource_loader");
39const cache_1 = require("./cache");
40const diagnostics_1 = require("./diagnostics");
41const host_1 = require("./host");
42const paths_1 = require("./paths");
43const symbol_1 = require("./symbol");
44const system_1 = require("./system");
45const transformation_1 = require("./transformation");
46/**
47 * The threshold used to determine whether Angular file diagnostics should optimize for full programs
48 * or single files. If the number of affected files for a build is more than the threshold, full
49 * program optimization will be used.
50 */
51const DIAGNOSTICS_AFFECTED_THRESHOLD = 1;
52function initializeNgccProcessor(compiler, tsconfig, compilerNgccModule) {
53 var _a, _b, _c;
54 const { inputFileSystem, options: webpackOptions } = compiler;
55 const mainFields = (_c = (_b = (_a = webpackOptions.resolve) === null || _a === void 0 ? void 0 : _a.mainFields) === null || _b === void 0 ? void 0 : _b.flat()) !== null && _c !== void 0 ? _c : [];
56 const errors = [];
57 const warnings = [];
58 const resolver = compiler.resolverFactory.get('normal', {
59 // Caching must be disabled because it causes the resolver to become async after a rebuild
60 cache: false,
61 extensions: ['.json'],
62 useSyncFileSystemCalls: true,
63 });
64 // The compilerNgccModule field is guaranteed to be defined during a compilation
65 // due to the `beforeCompile` hook. Usage of this property accessor prior to the
66 // hook execution is an implementation error.
67 assert_1.strict.ok(compilerNgccModule, `'@angular/compiler-cli/ngcc' used prior to Webpack compilation.`);
68 const processor = new ngcc_processor_1.NgccProcessor(compilerNgccModule, mainFields, warnings, errors, compiler.context, tsconfig, inputFileSystem, resolver);
69 return { processor, errors, warnings };
70}
71const PLUGIN_NAME = 'angular-compiler';
72const compilationFileEmitters = new WeakMap();
73class AngularWebpackPlugin {
74 constructor(options = {}) {
75 this.fileDependencies = new Map();
76 this.requiredFilesToEmit = new Set();
77 this.requiredFilesToEmitCache = new Map();
78 this.fileEmitHistory = new Map();
79 this.pluginOptions = {
80 emitClassMetadata: false,
81 emitNgModuleScope: false,
82 jitMode: false,
83 fileReplacements: {},
84 substitutions: {},
85 directTemplateLoading: true,
86 tsconfig: 'tsconfig.json',
87 ...options,
88 };
89 }
90 get compilerCli() {
91 // The compilerCliModule field is guaranteed to be defined during a compilation
92 // due to the `beforeCompile` hook. Usage of this property accessor prior to the
93 // hook execution is an implementation error.
94 assert_1.strict.ok(this.compilerCliModule, `'@angular/compiler-cli' used prior to Webpack compilation.`);
95 return this.compilerCliModule;
96 }
97 get options() {
98 return this.pluginOptions;
99 }
100 apply(compiler) {
101 const { NormalModuleReplacementPlugin, WebpackError, util } = compiler.webpack;
102 this.webpackCreateHash = util.createHash;
103 // Setup file replacements with webpack
104 for (const [key, value] of Object.entries(this.pluginOptions.fileReplacements)) {
105 new NormalModuleReplacementPlugin(new RegExp('^' + key.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&') + '$'), value).apply(compiler);
106 }
107 // Set resolver options
108 const pathsPlugin = new paths_plugin_1.TypeScriptPathsPlugin();
109 compiler.hooks.afterResolvers.tap(PLUGIN_NAME, (compiler) => {
110 // When Ivy is enabled we need to add the fields added by NGCC
111 // to take precedence over the provided mainFields.
112 // NGCC adds fields in package.json suffixed with '_ivy_ngcc'
113 // Example: module -> module__ivy_ngcc
114 compiler.resolverFactory.hooks.resolveOptions
115 .for('normal')
116 .tap(PLUGIN_NAME, (resolveOptions) => {
117 var _a, _b;
118 const originalMainFields = resolveOptions.mainFields;
119 const ivyMainFields = (_a = originalMainFields === null || originalMainFields === void 0 ? void 0 : originalMainFields.flat().map((f) => `${f}_ivy_ngcc`)) !== null && _a !== void 0 ? _a : [];
120 (_b = resolveOptions.plugins) !== null && _b !== void 0 ? _b : (resolveOptions.plugins = []);
121 resolveOptions.plugins.push(pathsPlugin);
122 // https://github.com/webpack/webpack/issues/11635#issuecomment-707016779
123 return util.cleverMerge(resolveOptions, { mainFields: [...ivyMainFields, '...'] });
124 });
125 });
126 // Load the compiler-cli if not already available
127 compiler.hooks.beforeCompile.tapPromise(PLUGIN_NAME, () => this.initializeCompilerCli());
128 const compilationState = { pathsPlugin };
129 compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => {
130 try {
131 this.setupCompilation(compilation, compilationState);
132 }
133 catch (error) {
134 compilation.errors.push(new WebpackError(`Failed to initialize Angular compilation - ${error instanceof Error ? error.message : error}`));
135 }
136 });
137 }
138 setupCompilation(compilation, state) {
139 const compiler = compilation.compiler;
140 // Register plugin to ensure deterministic emit order in multi-plugin usage
141 const emitRegistration = this.registerWithCompilation(compilation);
142 this.watchMode = compiler.watchMode;
143 // Initialize webpack cache
144 if (!this.webpackCache && compilation.options.cache) {
145 this.webpackCache = compilation.getCache(PLUGIN_NAME);
146 }
147 // Initialize the resource loader if not already setup
148 if (!state.resourceLoader) {
149 state.resourceLoader = new resource_loader_1.WebpackResourceLoader(this.watchMode);
150 }
151 // Initialize and process eager ngcc if not already setup
152 if (!state.ngccProcessor) {
153 const { processor, errors, warnings } = initializeNgccProcessor(compiler, this.pluginOptions.tsconfig, this.compilerNgccModule);
154 processor.process();
155 warnings.forEach((warning) => (0, diagnostics_1.addWarning)(compilation, warning));
156 errors.forEach((error) => (0, diagnostics_1.addError)(compilation, error));
157 state.ngccProcessor = processor;
158 }
159 // Setup and read TypeScript and Angular compiler configuration
160 const { compilerOptions, rootNames, errors } = this.loadConfiguration();
161 // Create diagnostics reporter and report configuration file errors
162 const diagnosticsReporter = (0, diagnostics_1.createDiagnosticsReporter)(compilation, (diagnostic) => this.compilerCli.formatDiagnostics([diagnostic]));
163 diagnosticsReporter(errors);
164 // Update TypeScript path mapping plugin with new configuration
165 state.pathsPlugin.update(compilerOptions);
166 // Create a Webpack-based TypeScript compiler host
167 const system = (0, system_1.createWebpackSystem)(
168 // Webpack lacks an InputFileSytem type definition with sync functions
169 compiler.inputFileSystem, (0, paths_1.normalizePath)(compiler.context));
170 const host = ts.createIncrementalCompilerHost(compilerOptions, system);
171 // Setup source file caching and reuse cache from previous compilation if present
172 let cache = this.sourceFileCache;
173 let changedFiles;
174 if (cache) {
175 changedFiles = new Set();
176 for (const changedFile of [...compiler.modifiedFiles, ...compiler.removedFiles]) {
177 const normalizedChangedFile = (0, paths_1.normalizePath)(changedFile);
178 // Invalidate file dependencies
179 this.fileDependencies.delete(normalizedChangedFile);
180 // Invalidate existing cache
181 cache.invalidate(normalizedChangedFile);
182 changedFiles.add(normalizedChangedFile);
183 }
184 }
185 else {
186 // Initialize a new cache
187 cache = new cache_1.SourceFileCache();
188 // Only store cache if in watch mode
189 if (this.watchMode) {
190 this.sourceFileCache = cache;
191 }
192 }
193 (0, host_1.augmentHostWithCaching)(host, cache);
194 const moduleResolutionCache = ts.createModuleResolutionCache(host.getCurrentDirectory(), host.getCanonicalFileName.bind(host), compilerOptions);
195 // Setup source file dependency collection
196 (0, host_1.augmentHostWithDependencyCollection)(host, this.fileDependencies, moduleResolutionCache);
197 // Setup on demand ngcc
198 (0, host_1.augmentHostWithNgcc)(host, state.ngccProcessor, moduleResolutionCache);
199 // Setup resource loading
200 state.resourceLoader.update(compilation, changedFiles);
201 (0, host_1.augmentHostWithResources)(host, state.resourceLoader, {
202 directTemplateLoading: this.pluginOptions.directTemplateLoading,
203 inlineStyleFileExtension: this.pluginOptions.inlineStyleFileExtension,
204 });
205 // Setup source file adjustment options
206 (0, host_1.augmentHostWithReplacements)(host, this.pluginOptions.fileReplacements, moduleResolutionCache);
207 (0, host_1.augmentHostWithSubstitutions)(host, this.pluginOptions.substitutions);
208 // Create the file emitter used by the webpack loader
209 const { fileEmitter, builder, internalFiles } = this.pluginOptions.jitMode
210 ? this.updateJitProgram(compilerOptions, rootNames, host, diagnosticsReporter)
211 : this.updateAotProgram(compilerOptions, rootNames, host, diagnosticsReporter, state.resourceLoader);
212 // Set of files used during the unused TypeScript file analysis
213 const currentUnused = new Set();
214 for (const sourceFile of builder.getSourceFiles()) {
215 if (internalFiles === null || internalFiles === void 0 ? void 0 : internalFiles.has(sourceFile)) {
216 continue;
217 }
218 // Ensure all program files are considered part of the compilation and will be watched.
219 // Webpack does not normalize paths. Therefore, we need to normalize the path with FS seperators.
220 compilation.fileDependencies.add((0, paths_1.externalizePath)(sourceFile.fileName));
221 // Add all non-declaration files to the initial set of unused files. The set will be
222 // analyzed and pruned after all Webpack modules are finished building.
223 if (!sourceFile.isDeclarationFile) {
224 currentUnused.add((0, paths_1.normalizePath)(sourceFile.fileName));
225 }
226 }
227 compilation.hooks.finishModules.tapPromise(PLUGIN_NAME, async (modules) => {
228 var _a, _b;
229 // Rebuild any remaining AOT required modules
230 await this.rebuildRequiredFiles(modules, compilation, fileEmitter);
231 // Clear out the Webpack compilation to avoid an extra retaining reference
232 (_a = state.resourceLoader) === null || _a === void 0 ? void 0 : _a.clearParentCompilation();
233 // Analyze program for unused files
234 if (compilation.errors.length > 0) {
235 return;
236 }
237 for (const webpackModule of modules) {
238 const resource = webpackModule.resource;
239 if (resource) {
240 this.markResourceUsed((0, paths_1.normalizePath)(resource), currentUnused);
241 }
242 }
243 for (const unused of currentUnused) {
244 if ((_b = state.previousUnused) === null || _b === void 0 ? void 0 : _b.has(unused)) {
245 continue;
246 }
247 (0, diagnostics_1.addWarning)(compilation, `${unused} is part of the TypeScript compilation but it's unused.\n` +
248 `Add only entry points to the 'files' or 'include' properties in your tsconfig.`);
249 }
250 state.previousUnused = currentUnused;
251 });
252 // Store file emitter for loader usage
253 emitRegistration.update(fileEmitter);
254 }
255 registerWithCompilation(compilation) {
256 let fileEmitters = compilationFileEmitters.get(compilation);
257 if (!fileEmitters) {
258 fileEmitters = new symbol_1.FileEmitterCollection();
259 compilationFileEmitters.set(compilation, fileEmitters);
260 compilation.compiler.webpack.NormalModule.getCompilationHooks(compilation).loader.tap(PLUGIN_NAME, (loaderContext) => {
261 loaderContext[symbol_1.AngularPluginSymbol] = fileEmitters;
262 });
263 }
264 const emitRegistration = fileEmitters.register();
265 return emitRegistration;
266 }
267 markResourceUsed(normalizedResourcePath, currentUnused) {
268 if (!currentUnused.has(normalizedResourcePath)) {
269 return;
270 }
271 currentUnused.delete(normalizedResourcePath);
272 const dependencies = this.fileDependencies.get(normalizedResourcePath);
273 if (!dependencies) {
274 return;
275 }
276 for (const dependency of dependencies) {
277 this.markResourceUsed((0, paths_1.normalizePath)(dependency), currentUnused);
278 }
279 }
280 async rebuildRequiredFiles(modules, compilation, fileEmitter) {
281 if (this.requiredFilesToEmit.size === 0) {
282 return;
283 }
284 const filesToRebuild = new Set();
285 for (const requiredFile of this.requiredFilesToEmit) {
286 const history = await this.getFileEmitHistory(requiredFile);
287 if (history) {
288 const emitResult = await fileEmitter(requiredFile);
289 if ((emitResult === null || emitResult === void 0 ? void 0 : emitResult.content) === undefined ||
290 history.length !== emitResult.content.length ||
291 emitResult.hash === undefined ||
292 Buffer.compare(history.hash, emitResult.hash) !== 0) {
293 // New emit result is different so rebuild using new emit result
294 this.requiredFilesToEmitCache.set(requiredFile, emitResult);
295 filesToRebuild.add(requiredFile);
296 }
297 }
298 else {
299 // No emit history so rebuild
300 filesToRebuild.add(requiredFile);
301 }
302 }
303 if (filesToRebuild.size > 0) {
304 const rebuild = (webpackModule) => new Promise((resolve) => compilation.rebuildModule(webpackModule, () => resolve()));
305 const modulesToRebuild = [];
306 for (const webpackModule of modules) {
307 const resource = webpackModule.resource;
308 if (resource && filesToRebuild.has((0, paths_1.normalizePath)(resource))) {
309 modulesToRebuild.push(webpackModule);
310 }
311 }
312 await Promise.all(modulesToRebuild.map((webpackModule) => rebuild(webpackModule)));
313 }
314 this.requiredFilesToEmit.clear();
315 this.requiredFilesToEmitCache.clear();
316 }
317 loadConfiguration() {
318 const { options: compilerOptions, rootNames, errors, } = this.compilerCli.readConfiguration(this.pluginOptions.tsconfig, this.pluginOptions.compilerOptions);
319 compilerOptions.enableIvy = true;
320 compilerOptions.noEmitOnError = false;
321 compilerOptions.suppressOutputPathCheck = true;
322 compilerOptions.outDir = undefined;
323 compilerOptions.inlineSources = compilerOptions.sourceMap;
324 compilerOptions.inlineSourceMap = false;
325 compilerOptions.mapRoot = undefined;
326 compilerOptions.sourceRoot = undefined;
327 compilerOptions.allowEmptyCodegenFiles = false;
328 compilerOptions.annotationsAs = 'decorators';
329 compilerOptions.enableResourceInlining = false;
330 return { compilerOptions, rootNames, errors };
331 }
332 updateAotProgram(compilerOptions, rootNames, host, diagnosticsReporter, resourceLoader) {
333 // Create the Angular specific program that contains the Angular compiler
334 const angularProgram = new this.compilerCli.NgtscProgram(rootNames, compilerOptions, host, this.ngtscNextProgram);
335 const angularCompiler = angularProgram.compiler;
336 // The `ignoreForEmit` return value can be safely ignored when emitting. Only files
337 // that will be bundled (requested by Webpack) will be emitted. Combined with TypeScript's
338 // eliding of type only imports, this will cause type only files to be automatically ignored.
339 // Internal Angular type check files are also not resolvable by the bundler. Even if they
340 // were somehow errantly imported, the bundler would error before an emit was attempted.
341 // Diagnostics are still collected for all files which requires using `ignoreForDiagnostics`.
342 const { ignoreForDiagnostics, ignoreForEmit } = angularCompiler;
343 // SourceFile versions are required for builder programs.
344 // The wrapped host inside NgtscProgram adds additional files that will not have versions.
345 const typeScriptProgram = angularProgram.getTsProgram();
346 (0, host_1.augmentProgramWithVersioning)(typeScriptProgram);
347 let builder;
348 if (this.watchMode) {
349 builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, this.builder);
350 this.ngtscNextProgram = angularProgram;
351 }
352 else {
353 // When not in watch mode, the startup cost of the incremental analysis can be avoided by
354 // using an abstract builder that only wraps a TypeScript program.
355 builder = ts.createAbstractBuilder(typeScriptProgram, host);
356 }
357 // Update semantic diagnostics cache
358 const affectedFiles = new Set();
359 // Analyze affected files when in watch mode for incremental type checking
360 if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) {
361 // eslint-disable-next-line no-constant-condition
362 while (true) {
363 const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => {
364 // If the affected file is a TTC shim, add the shim's original source file.
365 // This ensures that changes that affect TTC are typechecked even when the changes
366 // are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes.
367 // For example, changing @Input property types of a directive used in another component's
368 // template.
369 if (ignoreForDiagnostics.has(sourceFile) &&
370 sourceFile.fileName.endsWith('.ngtypecheck.ts')) {
371 // This file name conversion relies on internal compiler logic and should be converted
372 // to an official method when available. 15 is length of `.ngtypecheck.ts`
373 const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts';
374 const originalSourceFile = builder.getSourceFile(originalFilename);
375 if (originalSourceFile) {
376 affectedFiles.add(originalSourceFile);
377 }
378 return true;
379 }
380 return false;
381 });
382 if (!result) {
383 break;
384 }
385 affectedFiles.add(result.affected);
386 }
387 }
388 // Collect program level diagnostics
389 const diagnostics = [
390 ...angularCompiler.getOptionDiagnostics(),
391 ...builder.getOptionsDiagnostics(),
392 ...builder.getGlobalDiagnostics(),
393 ];
394 diagnosticsReporter(diagnostics);
395 // Collect source file specific diagnostics
396 for (const sourceFile of builder.getSourceFiles()) {
397 if (!ignoreForDiagnostics.has(sourceFile)) {
398 diagnosticsReporter(builder.getSyntacticDiagnostics(sourceFile));
399 diagnosticsReporter(builder.getSemanticDiagnostics(sourceFile));
400 }
401 }
402 const transformers = (0, transformation_1.createAotTransformers)(builder, this.pluginOptions);
403 const getDependencies = (sourceFile) => {
404 const dependencies = [];
405 for (const resourcePath of angularCompiler.getResourceDependencies(sourceFile)) {
406 dependencies.push(resourcePath,
407 // Retrieve all dependencies of the resource (stylesheet imports, etc.)
408 ...resourceLoader.getResourceDependencies(resourcePath));
409 }
410 return dependencies;
411 };
412 // Required to support asynchronous resource loading
413 // Must be done before creating transformers or getting template diagnostics
414 const pendingAnalysis = angularCompiler
415 .analyzeAsync()
416 .then(() => {
417 var _a;
418 this.requiredFilesToEmit.clear();
419 for (const sourceFile of builder.getSourceFiles()) {
420 if (sourceFile.isDeclarationFile) {
421 continue;
422 }
423 // Collect sources that are required to be emitted
424 if (!ignoreForEmit.has(sourceFile) &&
425 !angularCompiler.incrementalDriver.safeToSkipEmit(sourceFile)) {
426 this.requiredFilesToEmit.add((0, paths_1.normalizePath)(sourceFile.fileName));
427 // If required to emit, diagnostics may have also changed
428 if (!ignoreForDiagnostics.has(sourceFile)) {
429 affectedFiles.add(sourceFile);
430 }
431 }
432 else if (this.sourceFileCache &&
433 !affectedFiles.has(sourceFile) &&
434 !ignoreForDiagnostics.has(sourceFile)) {
435 // Use cached Angular diagnostics for unchanged and unaffected files
436 const angularDiagnostics = this.sourceFileCache.getAngularDiagnostics(sourceFile);
437 if (angularDiagnostics) {
438 diagnosticsReporter(angularDiagnostics);
439 }
440 }
441 }
442 // Collect new Angular diagnostics for files affected by changes
443 const OptimizeFor = this.compilerCli.OptimizeFor;
444 const optimizeDiagnosticsFor = affectedFiles.size <= DIAGNOSTICS_AFFECTED_THRESHOLD
445 ? OptimizeFor.SingleFile
446 : OptimizeFor.WholeProgram;
447 for (const affectedFile of affectedFiles) {
448 const angularDiagnostics = angularCompiler.getDiagnosticsForFile(affectedFile, optimizeDiagnosticsFor);
449 diagnosticsReporter(angularDiagnostics);
450 (_a = this.sourceFileCache) === null || _a === void 0 ? void 0 : _a.updateAngularDiagnostics(affectedFile, angularDiagnostics);
451 }
452 return {
453 emitter: this.createFileEmitter(builder, (0, transformation_1.mergeTransformers)(angularCompiler.prepareEmit().transformers, transformers), getDependencies, (sourceFile) => {
454 this.requiredFilesToEmit.delete((0, paths_1.normalizePath)(sourceFile.fileName));
455 angularCompiler.incrementalDriver.recordSuccessfulEmit(sourceFile);
456 }),
457 };
458 })
459 .catch((err) => ({ errorMessage: err instanceof Error ? err.message : `${err}` }));
460 const analyzingFileEmitter = async (file) => {
461 const analysis = await pendingAnalysis;
462 if ('errorMessage' in analysis) {
463 throw new Error(analysis.errorMessage);
464 }
465 return analysis.emitter(file);
466 };
467 return {
468 fileEmitter: analyzingFileEmitter,
469 builder,
470 internalFiles: ignoreForEmit,
471 };
472 }
473 updateJitProgram(compilerOptions, rootNames, host, diagnosticsReporter) {
474 let builder;
475 if (this.watchMode) {
476 builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, compilerOptions, host, this.builder);
477 }
478 else {
479 // When not in watch mode, the startup cost of the incremental analysis can be avoided by
480 // using an abstract builder that only wraps a TypeScript program.
481 builder = ts.createAbstractBuilder(rootNames, compilerOptions, host);
482 }
483 const diagnostics = [
484 ...builder.getOptionsDiagnostics(),
485 ...builder.getGlobalDiagnostics(),
486 ...builder.getSyntacticDiagnostics(),
487 // Gather incremental semantic diagnostics
488 ...builder.getSemanticDiagnostics(),
489 ];
490 diagnosticsReporter(diagnostics);
491 const transformers = (0, transformation_1.createJitTransformers)(builder, this.compilerCli, this.pluginOptions);
492 return {
493 fileEmitter: this.createFileEmitter(builder, transformers, () => []),
494 builder,
495 internalFiles: undefined,
496 };
497 }
498 createFileEmitter(program, transformers = {}, getExtraDependencies, onAfterEmit) {
499 return async (file) => {
500 const filePath = (0, paths_1.normalizePath)(file);
501 if (this.requiredFilesToEmitCache.has(filePath)) {
502 return this.requiredFilesToEmitCache.get(filePath);
503 }
504 const sourceFile = program.getSourceFile(filePath);
505 if (!sourceFile) {
506 return undefined;
507 }
508 let content;
509 let map;
510 program.emit(sourceFile, (filename, data) => {
511 if (filename.endsWith('.map')) {
512 map = data;
513 }
514 else if (filename.endsWith('.js')) {
515 content = data;
516 }
517 }, undefined, undefined, transformers);
518 onAfterEmit === null || onAfterEmit === void 0 ? void 0 : onAfterEmit(sourceFile);
519 // Capture emit history info for Angular rebuild analysis
520 const hash = content ? (await this.addFileEmitHistory(filePath, content)).hash : undefined;
521 const dependencies = [
522 ...(this.fileDependencies.get(filePath) || []),
523 ...getExtraDependencies(sourceFile),
524 ].map(paths_1.externalizePath);
525 return { content, map, dependencies, hash };
526 };
527 }
528 async initializeCompilerCli() {
529 if (this.compilerCliModule) {
530 return;
531 }
532 // This uses a dynamic import to load `@angular/compiler-cli` which may be ESM.
533 // CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript
534 // will currently, unconditionally downlevel dynamic import into a require call.
535 // require calls cannot load ESM code and will result in a runtime error. To workaround
536 // this, a Function constructor is used to prevent TypeScript from changing the dynamic import.
537 // Once TypeScript provides support for keeping the dynamic import this workaround can
538 // be dropped.
539 this.compilerCliModule = await new Function(`return import('@angular/compiler-cli');`)();
540 this.compilerNgccModule = await new Function(`return import('@angular/compiler-cli/ngcc');`)();
541 }
542 async addFileEmitHistory(filePath, content) {
543 assert_1.strict.ok(this.webpackCreateHash, 'File emitter is used prior to Webpack compilation');
544 const historyData = {
545 length: content.length,
546 hash: this.webpackCreateHash('xxhash64').update(content).digest(),
547 };
548 if (this.webpackCache) {
549 const history = await this.getFileEmitHistory(filePath);
550 if (!history || Buffer.compare(history.hash, historyData.hash) !== 0) {
551 // Hash doesn't match or item doesn't exist.
552 await this.webpackCache.storePromise(filePath, null, historyData);
553 }
554 }
555 else if (this.watchMode) {
556 // The in memory file emit history is only required during watch mode.
557 this.fileEmitHistory.set(filePath, historyData);
558 }
559 return historyData;
560 }
561 async getFileEmitHistory(filePath) {
562 return this.webpackCache
563 ? this.webpackCache.getPromise(filePath, null)
564 : this.fileEmitHistory.get(filePath);
565 }
566}
567exports.AngularWebpackPlugin = AngularWebpackPlugin;