[6a3a178] | 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;
|
---|