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 | */
|
---|
9 | var __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 | }));
|
---|
16 | var __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 | });
|
---|
21 | var __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 | };
|
---|
28 | Object.defineProperty(exports, "__esModule", { value: true });
|
---|
29 | exports.AngularWebpackPlugin = void 0;
|
---|
30 | const compiler_cli_1 = require("@angular/compiler-cli");
|
---|
31 | const program_1 = require("@angular/compiler-cli/src/ngtsc/program");
|
---|
32 | const crypto_1 = require("crypto");
|
---|
33 | const ts = __importStar(require("typescript"));
|
---|
34 | const ngcc_processor_1 = require("../ngcc_processor");
|
---|
35 | const paths_plugin_1 = require("../paths-plugin");
|
---|
36 | const resource_loader_1 = require("../resource_loader");
|
---|
37 | const cache_1 = require("./cache");
|
---|
38 | const diagnostics_1 = require("./diagnostics");
|
---|
39 | const host_1 = require("./host");
|
---|
40 | const paths_1 = require("./paths");
|
---|
41 | const symbol_1 = require("./symbol");
|
---|
42 | const system_1 = require("./system");
|
---|
43 | const 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 | */
|
---|
49 | const DIAGNOSTICS_AFFECTED_THRESHOLD = 1;
|
---|
50 | function 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 | }
|
---|
65 | function hashContent(content) {
|
---|
66 | return crypto_1.createHash('md5').update(content).digest();
|
---|
67 | }
|
---|
68 | const PLUGIN_NAME = 'angular-compiler';
|
---|
69 | const compilationFileEmitters = new WeakMap();
|
---|
70 | class 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 | }
|
---|
501 | exports.AngularWebpackPlugin = AngularWebpackPlugin;
|
---|