source: trip-planner-front/node_modules/@ngtools/webpack/src/ivy/plugin.js@ fa375fe

Last change on this file since fa375fe was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

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