├── LICENSE ├── README.md ├── index.d.ts ├── index.js ├── package.json ├── src ├── benchmark.d.ts ├── benchmark.js ├── index.d.ts ├── index.js ├── ivy │ ├── cache.d.ts │ ├── cache.js │ ├── diagnostics.d.ts │ ├── diagnostics.js │ ├── host.d.ts │ ├── host.js │ ├── index.d.ts │ ├── index.js │ ├── loader.d.ts │ ├── loader.js │ ├── paths.d.ts │ ├── paths.js │ ├── plugin.d.ts │ ├── plugin.js │ ├── symbol.d.ts │ ├── symbol.js │ ├── system.d.ts │ ├── system.js │ ├── transformation.d.ts │ └── transformation.js ├── loaders │ ├── inline-resource.d.ts │ └── inline-resource.js ├── paths-plugin.d.ts ├── paths-plugin.js ├── resource_loader.d.ts ├── resource_loader.js └── transformers │ ├── elide_imports.d.ts │ ├── elide_imports.js │ ├── find_image_domains.d.ts │ ├── find_image_domains.js │ ├── index.d.ts │ ├── index.js │ ├── remove-ivy-jit-support-calls.d.ts │ ├── remove-ivy-jit-support-calls.js │ ├── replace_resources.d.ts │ ├── replace_resources.js │ ├── spec_helpers.d.ts │ └── spec_helpers.js └── uniqueId /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2010-2025 Google LLC. https://angular.dev/license 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Snapshot build of @ngtools/webpack 3 | 4 | This repository is a snapshot of a commit on the original repository. The original code used to 5 | generate this is located at http://github.com/angular/angular-cli. 6 | 7 | We do not accept PRs or Issues opened on this repository. You should not use this over a tested and 8 | released version of this package. 9 | 10 | To test this snapshot in your own project, use 11 | 12 | ```bash 13 | npm install git+https://github.com/angular/ngtools-webpack-builds.git 14 | ``` 15 | 16 | ---- 17 | # Angular Compiler Webpack Plugin 18 | 19 | Webpack 5.x plugin for the Angular Ahead-of-Time compiler. The plugin also supports Angular JIT mode. 20 | When this plugin is used outside of the Angular CLI, the Ivy linker will also be needed to support 21 | the usage of Angular libraries. An example configuration of the Babel-based Ivy linker is provided 22 | in the linker section. For additional information regarding the linker, please see: https://angular.dev/tools/libraries/creating-libraries#consuming-partial-ivy-code-outside-the-angular-cli 23 | 24 | ## Usage 25 | 26 | In your webpack config, add the following plugin and loader. 27 | 28 | ```typescript 29 | import { AngularWebpackPlugin } from '@ngtools/webpack'; 30 | 31 | exports = { 32 | /* ... */ 33 | module: { 34 | rules: [ 35 | /* ... */ 36 | { 37 | test: /\.[jt]sx?$/, 38 | loader: '@ngtools/webpack', 39 | }, 40 | ], 41 | }, 42 | 43 | plugins: [ 44 | new AngularWebpackPlugin({ 45 | tsconfig: 'path/to/tsconfig.json', 46 | // ... other options as needed 47 | }), 48 | ], 49 | }; 50 | ``` 51 | 52 | The loader works with webpack plugin to compile the application's TypeScript. It is important to include both, and to not include any other TypeScript loader. 53 | 54 | ## Options 55 | 56 | - `tsconfig` [default: `tsconfig.json`] - The path to the application's TypeScript Configuration file. In the `tsconfig.json`, you can pass options to the Angular Compiler with `angularCompilerOptions`. Relative paths will be resolved from the Webpack compilation's context. 57 | - `compilerOptions` [default: none] - Overrides options in the application's TypeScript Configuration file (`tsconfig.json`). 58 | - `jitMode` [default: `false`] - Enables JIT compilation and do not refactor the code to bootstrap. This replaces `templateUrl: "string"` with `template: require("string")` (and similar for styles) to allow for webpack to properly link the resources. 59 | - `directTemplateLoading` [default: `true`] - Causes the plugin to load component templates (HTML) directly from the filesystem. This is more efficient if only using the `raw-loader` to load component templates. Do not enable this option if additional loaders are configured for component templates. 60 | - `fileReplacements` [default: none] - Allows replacing TypeScript files with other TypeScript files in the build. This option acts on fully resolved file paths. 61 | - `inlineStyleFileExtension` [default: none] - When set inline component styles will be processed by Webpack as files with the provided extension. 62 | 63 | ## Ivy Linker 64 | 65 | The Ivy linker can be setup by using the Webpack `babel-loader` package. 66 | If not already installed, add the `babel-loader` package using your project's package manager. 67 | Then in your webpack config, add the `babel-loader` with the following configuration. 68 | If the `babel-loader` is already present in your configuration, the linker plugin can be added to 69 | the existing loader configuration as well. 70 | Enabling caching for the `babel-loader` is recommended to avoid reprocessing libraries on 71 | every build. 72 | For additional information regarding the `babel-loader` package, please see: https://github.com/babel/babel-loader/tree/main#readme 73 | 74 | ```typescript 75 | import linkerPlugin from '@angular/compiler-cli/linker/babel'; 76 | 77 | exports = { 78 | /* ... */ 79 | module: { 80 | rules: [ 81 | /* ... */ 82 | { 83 | test: /\.[cm]?js$/, 84 | use: { 85 | loader: 'babel-loader', 86 | options: { 87 | cacheDirectory: true, 88 | compact: false, 89 | plugins: [linkerPlugin], 90 | }, 91 | }, 92 | }, 93 | ], 94 | }, 95 | }; 96 | ``` 97 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | export * from './src/index'; 9 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 21 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 22 | }; 23 | Object.defineProperty(exports, "__esModule", { value: true }); 24 | __exportStar(require("./src/index"), exports); 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ngtools/webpack", 3 | "version": "20.1.0-next.0+sha-1c19e0d", 4 | "description": "Webpack plugin that AoT compiles your Angular components and modules.", 5 | "main": "./src/index.js", 6 | "typings": "src/index.d.ts", 7 | "license": "MIT", 8 | "keywords": [ 9 | "Angular CLI", 10 | "Angular DevKit", 11 | "angular", 12 | "aot", 13 | "devkit", 14 | "plugin", 15 | "sdk", 16 | "webpack" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/angular/angular-cli.git" 21 | }, 22 | "author": "Angular Authors", 23 | "bugs": { 24 | "url": "https://github.com/angular/angular-cli/issues" 25 | }, 26 | "homepage": "https://github.com/angular/angular-cli", 27 | "peerDependencies": { 28 | "@angular/compiler-cli": "^20.0.0 || ^20.1.0-next.0", 29 | "typescript": ">=5.8 <5.9", 30 | "webpack": "^5.54.0" 31 | }, 32 | "packageManager": "pnpm@9.15.9", 33 | "engines": { 34 | "node": "^20.19.0 || ^22.12.0 || >=24.0.0", 35 | "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", 36 | "yarn": ">= 1.13.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/benchmark.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | export declare function time(label: string): void; 9 | export declare function timeEnd(label: string): void; 10 | -------------------------------------------------------------------------------- /src/benchmark.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.time = time; 11 | exports.timeEnd = timeEnd; 12 | // Internal benchmark reporting flag. 13 | // Use with CLI --no-progress flag for best results. 14 | // This should be false for commited code. 15 | const _benchmark = false; 16 | /* eslint-disable no-console */ 17 | function time(label) { 18 | if (_benchmark) { 19 | console.time(label); 20 | } 21 | } 22 | function timeEnd(label) { 23 | if (_benchmark) { 24 | console.timeEnd(label); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | export { AngularWebpackLoaderPath, AngularWebpackPlugin, type AngularWebpackPluginOptions, imageDomains, default, } from './ivy'; 9 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __importDefault = (this && this.__importDefault) || function (mod) { 10 | return (mod && mod.__esModule) ? mod : { "default": mod }; 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | exports.default = exports.imageDomains = exports.AngularWebpackPlugin = exports.AngularWebpackLoaderPath = void 0; 14 | var ivy_1 = require("./ivy"); 15 | Object.defineProperty(exports, "AngularWebpackLoaderPath", { enumerable: true, get: function () { return ivy_1.AngularWebpackLoaderPath; } }); 16 | Object.defineProperty(exports, "AngularWebpackPlugin", { enumerable: true, get: function () { return ivy_1.AngularWebpackPlugin; } }); 17 | Object.defineProperty(exports, "imageDomains", { enumerable: true, get: function () { return ivy_1.imageDomains; } }); 18 | Object.defineProperty(exports, "default", { enumerable: true, get: function () { return __importDefault(ivy_1).default; } }); 19 | -------------------------------------------------------------------------------- /src/ivy/cache.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import * as ts from 'typescript'; 9 | export declare class SourceFileCache extends Map { 10 | private readonly angularDiagnostics; 11 | invalidate(file: string): void; 12 | updateAngularDiagnostics(sourceFile: ts.SourceFile, diagnostics: ts.Diagnostic[]): void; 13 | getAngularDiagnostics(sourceFile: ts.SourceFile): ts.Diagnostic[] | undefined; 14 | } 15 | -------------------------------------------------------------------------------- /src/ivy/cache.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.SourceFileCache = void 0; 11 | class SourceFileCache extends Map { 12 | angularDiagnostics = new Map(); 13 | invalidate(file) { 14 | const sourceFile = this.get(file); 15 | if (sourceFile) { 16 | this.delete(file); 17 | this.angularDiagnostics.delete(sourceFile); 18 | } 19 | } 20 | updateAngularDiagnostics(sourceFile, diagnostics) { 21 | if (diagnostics.length > 0) { 22 | this.angularDiagnostics.set(sourceFile, diagnostics); 23 | } 24 | else { 25 | this.angularDiagnostics.delete(sourceFile); 26 | } 27 | } 28 | getAngularDiagnostics(sourceFile) { 29 | return this.angularDiagnostics.get(sourceFile); 30 | } 31 | } 32 | exports.SourceFileCache = SourceFileCache; 33 | -------------------------------------------------------------------------------- /src/ivy/diagnostics.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import { Diagnostic } from 'typescript'; 9 | import type { Compilation } from 'webpack'; 10 | export type DiagnosticsReporter = (diagnostics: readonly Diagnostic[]) => void; 11 | export declare function createDiagnosticsReporter(compilation: Compilation, formatter: (diagnostic: Diagnostic) => string): DiagnosticsReporter; 12 | export declare function addWarning(compilation: Compilation, message: string): void; 13 | export declare function addError(compilation: Compilation, message: string): void; 14 | -------------------------------------------------------------------------------- /src/ivy/diagnostics.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.createDiagnosticsReporter = createDiagnosticsReporter; 11 | exports.addWarning = addWarning; 12 | exports.addError = addError; 13 | const typescript_1 = require("typescript"); 14 | function createDiagnosticsReporter(compilation, formatter) { 15 | return (diagnostics) => { 16 | for (const diagnostic of diagnostics) { 17 | const text = formatter(diagnostic); 18 | if (diagnostic.category === typescript_1.DiagnosticCategory.Error) { 19 | addError(compilation, text); 20 | } 21 | else { 22 | addWarning(compilation, text); 23 | } 24 | } 25 | }; 26 | } 27 | function addWarning(compilation, message) { 28 | compilation.warnings.push(new compilation.compiler.webpack.WebpackError(message)); 29 | } 30 | function addError(compilation, message) { 31 | compilation.errors.push(new compilation.compiler.webpack.WebpackError(message)); 32 | } 33 | -------------------------------------------------------------------------------- /src/ivy/host.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import * as ts from 'typescript'; 9 | import { WebpackResourceLoader } from '../resource_loader'; 10 | export declare function augmentHostWithResources(host: ts.CompilerHost, resourceLoader: WebpackResourceLoader, options?: { 11 | directTemplateLoading?: boolean; 12 | inlineStyleFileExtension?: string; 13 | }): void; 14 | /** 15 | * Augments a TypeScript Compiler Host's resolveModuleNames function to collect dependencies 16 | * of the containing file passed to the resolveModuleNames function. This process assumes 17 | * that consumers of the Compiler Host will only call resolveModuleNames with modules that are 18 | * actually present in a containing file. 19 | * This process is a workaround for gathering a TypeScript SourceFile's dependencies as there 20 | * is no currently exposed public method to do so. A BuilderProgram does have a `getAllDependencies` 21 | * function. However, that function returns all transitive dependencies as well which can cause 22 | * excessive Webpack rebuilds. 23 | * 24 | * @param host The CompilerHost to augment. 25 | * @param dependencies A Map which will be used to store file dependencies. 26 | * @param moduleResolutionCache An optional resolution cache to use when the host resolves a module. 27 | */ 28 | export declare function augmentHostWithDependencyCollection(host: ts.CompilerHost, dependencies: Map>, moduleResolutionCache?: ts.ModuleResolutionCache): void; 29 | export declare function augmentHostWithReplacements(host: ts.CompilerHost, replacements: Record, moduleResolutionCache?: ts.ModuleResolutionCache): void; 30 | export declare function augmentHostWithSubstitutions(host: ts.CompilerHost, substitutions: Record): void; 31 | export declare function augmentProgramWithVersioning(program: ts.Program): void; 32 | export declare function augmentHostWithCaching(host: ts.CompilerHost, cache: Map): void; 33 | -------------------------------------------------------------------------------- /src/ivy/host.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.augmentHostWithResources = augmentHostWithResources; 44 | exports.augmentHostWithDependencyCollection = augmentHostWithDependencyCollection; 45 | exports.augmentHostWithReplacements = augmentHostWithReplacements; 46 | exports.augmentHostWithSubstitutions = augmentHostWithSubstitutions; 47 | exports.augmentProgramWithVersioning = augmentProgramWithVersioning; 48 | exports.augmentHostWithCaching = augmentHostWithCaching; 49 | const node_crypto_1 = require("node:crypto"); 50 | const path = __importStar(require("node:path")); 51 | const ts = __importStar(require("typescript")); 52 | const paths_1 = require("./paths"); 53 | function augmentHostWithResources(host, resourceLoader, options = {}) { 54 | const resourceHost = host; 55 | resourceHost.readResource = function (fileName) { 56 | const filePath = (0, paths_1.normalizePath)(fileName); 57 | if (options.directTemplateLoading && 58 | (filePath.endsWith('.html') || filePath.endsWith('.svg'))) { 59 | const content = this.readFile(filePath); 60 | if (content === undefined) { 61 | throw new Error('Unable to locate component resource: ' + fileName); 62 | } 63 | resourceLoader.setAffectedResources(filePath, [filePath]); 64 | return Promise.resolve(content); 65 | } 66 | else { 67 | return resourceLoader.get(filePath); 68 | } 69 | }; 70 | resourceHost.resourceNameToFileName = function (resourceName, containingFile) { 71 | return path.join(path.dirname(containingFile), resourceName); 72 | }; 73 | resourceHost.getModifiedResourceFiles = function () { 74 | return resourceLoader.getModifiedResourceFiles(); 75 | }; 76 | resourceHost.transformResource = async function (data, context) { 77 | // Only inline style resources are supported currently 78 | if (context.resourceFile || context.type !== 'style') { 79 | return null; 80 | } 81 | if (options.inlineStyleFileExtension) { 82 | const content = await resourceLoader.process(data, options.inlineStyleFileExtension, context.type, context.containingFile); 83 | return { content }; 84 | } 85 | return null; 86 | }; 87 | } 88 | function augmentResolveModuleNames(host, resolvedModuleModifier, moduleResolutionCache) { 89 | if (host.resolveModuleNames) { 90 | const baseResolveModuleNames = host.resolveModuleNames; 91 | host.resolveModuleNames = function (moduleNames, ...parameters) { 92 | return moduleNames.map((name) => { 93 | const result = baseResolveModuleNames.call(host, [name], ...parameters); 94 | return resolvedModuleModifier(result[0], name); 95 | }); 96 | }; 97 | } 98 | else { 99 | host.resolveModuleNames = function (moduleNames, containingFile, _reusedNames, redirectedReference, options) { 100 | return moduleNames.map((name) => { 101 | const result = ts.resolveModuleName(name, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule; 102 | return resolvedModuleModifier(result, name); 103 | }); 104 | }; 105 | } 106 | } 107 | /** 108 | * Augments a TypeScript Compiler Host's resolveModuleNames function to collect dependencies 109 | * of the containing file passed to the resolveModuleNames function. This process assumes 110 | * that consumers of the Compiler Host will only call resolveModuleNames with modules that are 111 | * actually present in a containing file. 112 | * This process is a workaround for gathering a TypeScript SourceFile's dependencies as there 113 | * is no currently exposed public method to do so. A BuilderProgram does have a `getAllDependencies` 114 | * function. However, that function returns all transitive dependencies as well which can cause 115 | * excessive Webpack rebuilds. 116 | * 117 | * @param host The CompilerHost to augment. 118 | * @param dependencies A Map which will be used to store file dependencies. 119 | * @param moduleResolutionCache An optional resolution cache to use when the host resolves a module. 120 | */ 121 | function augmentHostWithDependencyCollection(host, dependencies, moduleResolutionCache) { 122 | if (host.resolveModuleNames) { 123 | const baseResolveModuleNames = host.resolveModuleNames; 124 | host.resolveModuleNames = function (moduleNames, containingFile, ...parameters) { 125 | const results = baseResolveModuleNames.call(host, moduleNames, containingFile, ...parameters); 126 | const containingFilePath = (0, paths_1.normalizePath)(containingFile); 127 | for (const result of results) { 128 | if (result) { 129 | const containingFileDependencies = dependencies.get(containingFilePath); 130 | if (containingFileDependencies) { 131 | containingFileDependencies.add(result.resolvedFileName); 132 | } 133 | else { 134 | dependencies.set(containingFilePath, new Set([result.resolvedFileName])); 135 | } 136 | } 137 | } 138 | return results; 139 | }; 140 | } 141 | else { 142 | host.resolveModuleNames = function (moduleNames, containingFile, _reusedNames, redirectedReference, options) { 143 | return moduleNames.map((name) => { 144 | const result = ts.resolveModuleName(name, containingFile, options, host, moduleResolutionCache, redirectedReference).resolvedModule; 145 | if (result) { 146 | const containingFilePath = (0, paths_1.normalizePath)(containingFile); 147 | const containingFileDependencies = dependencies.get(containingFilePath); 148 | if (containingFileDependencies) { 149 | containingFileDependencies.add(result.resolvedFileName); 150 | } 151 | else { 152 | dependencies.set(containingFilePath, new Set([result.resolvedFileName])); 153 | } 154 | } 155 | return result; 156 | }); 157 | }; 158 | } 159 | } 160 | function augmentHostWithReplacements(host, replacements, moduleResolutionCache) { 161 | if (Object.keys(replacements).length === 0) { 162 | return; 163 | } 164 | const normalizedReplacements = {}; 165 | for (const [key, value] of Object.entries(replacements)) { 166 | normalizedReplacements[(0, paths_1.normalizePath)(key)] = (0, paths_1.normalizePath)(value); 167 | } 168 | const tryReplace = (resolvedModule) => { 169 | const replacement = resolvedModule && normalizedReplacements[resolvedModule.resolvedFileName]; 170 | if (replacement) { 171 | return { 172 | resolvedFileName: replacement, 173 | isExternalLibraryImport: /[/\\]node_modules[/\\]/.test(replacement), 174 | }; 175 | } 176 | else { 177 | return resolvedModule; 178 | } 179 | }; 180 | augmentResolveModuleNames(host, tryReplace, moduleResolutionCache); 181 | } 182 | function augmentHostWithSubstitutions(host, substitutions) { 183 | const regexSubstitutions = []; 184 | for (const [key, value] of Object.entries(substitutions)) { 185 | regexSubstitutions.push([new RegExp(`\\b${key}\\b`, 'g'), value]); 186 | } 187 | if (regexSubstitutions.length === 0) { 188 | return; 189 | } 190 | const baseReadFile = host.readFile; 191 | host.readFile = function (...parameters) { 192 | let file = baseReadFile.call(host, ...parameters); 193 | if (file) { 194 | for (const entry of regexSubstitutions) { 195 | file = file.replace(entry[0], entry[1]); 196 | } 197 | } 198 | return file; 199 | }; 200 | } 201 | function augmentProgramWithVersioning(program) { 202 | const baseGetSourceFiles = program.getSourceFiles; 203 | program.getSourceFiles = function (...parameters) { 204 | const files = baseGetSourceFiles(...parameters); 205 | for (const file of files) { 206 | if (file.version === undefined) { 207 | file.version = (0, node_crypto_1.createHash)('sha256').update(file.text).digest('hex'); 208 | } 209 | } 210 | return files; 211 | }; 212 | } 213 | function augmentHostWithCaching(host, cache) { 214 | const baseGetSourceFile = host.getSourceFile; 215 | host.getSourceFile = function (fileName, languageVersion, onError, shouldCreateNewSourceFile, ...parameters) { 216 | if (!shouldCreateNewSourceFile && cache.has(fileName)) { 217 | return cache.get(fileName); 218 | } 219 | const file = baseGetSourceFile.call(host, fileName, languageVersion, onError, true, ...parameters); 220 | if (file) { 221 | cache.set(fileName, file); 222 | } 223 | return file; 224 | }; 225 | } 226 | -------------------------------------------------------------------------------- /src/ivy/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | export { angularWebpackLoader as default } from './loader'; 9 | export { type AngularWebpackPluginOptions, AngularWebpackPlugin, imageDomains } from './plugin'; 10 | export declare const AngularWebpackLoaderPath: string; 11 | -------------------------------------------------------------------------------- /src/ivy/index.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.AngularWebpackLoaderPath = exports.imageDomains = exports.AngularWebpackPlugin = exports.default = void 0; 11 | var loader_1 = require("./loader"); 12 | Object.defineProperty(exports, "default", { enumerable: true, get: function () { return loader_1.angularWebpackLoader; } }); 13 | var plugin_1 = require("./plugin"); 14 | Object.defineProperty(exports, "AngularWebpackPlugin", { enumerable: true, get: function () { return plugin_1.AngularWebpackPlugin; } }); 15 | Object.defineProperty(exports, "imageDomains", { enumerable: true, get: function () { return plugin_1.imageDomains; } }); 16 | exports.AngularWebpackLoaderPath = __filename; 17 | -------------------------------------------------------------------------------- /src/ivy/loader.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import type { LoaderContext } from 'webpack'; 9 | export declare function angularWebpackLoader(this: LoaderContext, content: string, map: string): void; 10 | export { angularWebpackLoader as default }; 11 | -------------------------------------------------------------------------------- /src/ivy/loader.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.angularWebpackLoader = angularWebpackLoader; 44 | exports.default = angularWebpackLoader; 45 | const path = __importStar(require("node:path")); 46 | const symbol_1 = require("./symbol"); 47 | const JS_FILE_REGEXP = /\.[cm]?js$/; 48 | function angularWebpackLoader(content, map) { 49 | const callback = this.async(); 50 | if (!callback) { 51 | throw new Error('Invalid webpack version'); 52 | } 53 | const fileEmitter = this[symbol_1.AngularPluginSymbol]; 54 | if (!fileEmitter || typeof fileEmitter !== 'object') { 55 | if (JS_FILE_REGEXP.test(this.resourcePath)) { 56 | // Passthrough for JS files when no plugin is used 57 | this.callback(undefined, content, map); 58 | return; 59 | } 60 | callback(new Error('The Angular Webpack loader requires the AngularWebpackPlugin.')); 61 | return; 62 | } 63 | fileEmitter 64 | .emit(this.resourcePath) 65 | .then((result) => { 66 | if (!result) { 67 | if (JS_FILE_REGEXP.test(this.resourcePath)) { 68 | // Return original content for JS files if not compiled by TypeScript ("allowJs") 69 | this.callback(undefined, content, map); 70 | } 71 | else { 72 | // File is not part of the compilation 73 | const message = `${this.resourcePath} is missing from the TypeScript compilation. ` + 74 | `Please make sure it is in your tsconfig via the 'files' or 'include' property.`; 75 | callback(new Error(message)); 76 | } 77 | return; 78 | } 79 | result.dependencies.forEach((dependency) => this.addDependency(dependency)); 80 | let resultContent = result.content || ''; 81 | let resultMap; 82 | if (result.map) { 83 | resultContent = resultContent.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''); 84 | resultMap = JSON.parse(result.map); 85 | resultMap.sources = resultMap.sources.map((source) => path.join(path.dirname(this.resourcePath), source)); 86 | } 87 | callback(undefined, resultContent, resultMap); 88 | }) 89 | .catch((err) => { 90 | // The below is needed to hide stacktraces from users. 91 | const message = err instanceof Error ? err.message : err; 92 | callback(new Error(message)); 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /src/ivy/paths.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | export declare function normalizePath(path: string): string; 9 | declare function externalizeForWindows(path: string): string; 10 | export declare const externalizePath: typeof externalizeForWindows; 11 | export {}; 12 | -------------------------------------------------------------------------------- /src/ivy/paths.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.externalizePath = void 0; 44 | exports.normalizePath = normalizePath; 45 | const nodePath = __importStar(require("node:path")); 46 | const normalizationCache = new Map(); 47 | function normalizePath(path) { 48 | let result = normalizationCache.get(path); 49 | if (result === undefined) { 50 | result = nodePath.win32.normalize(path).replace(/\\/g, nodePath.posix.sep); 51 | normalizationCache.set(path, result); 52 | } 53 | return result; 54 | } 55 | const externalizationCache = new Map(); 56 | function externalizeForWindows(path) { 57 | let result = externalizationCache.get(path); 58 | if (result === undefined) { 59 | result = nodePath.win32.normalize(path); 60 | externalizationCache.set(path, result); 61 | } 62 | return result; 63 | } 64 | exports.externalizePath = (() => { 65 | if (process.platform !== 'win32') { 66 | return (path) => path; 67 | } 68 | return externalizeForWindows; 69 | })(); 70 | -------------------------------------------------------------------------------- /src/ivy/plugin.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import type { CompilerOptions } from '@angular/compiler-cli'; 9 | import type { Compiler } from 'webpack'; 10 | export declare const imageDomains: Set; 11 | export interface AngularWebpackPluginOptions { 12 | tsconfig: string; 13 | compilerOptions?: CompilerOptions; 14 | fileReplacements: Record; 15 | substitutions: Record; 16 | directTemplateLoading: boolean; 17 | emitClassMetadata: boolean; 18 | emitNgModuleScope: boolean; 19 | emitSetClassDebugInfo?: boolean; 20 | jitMode: boolean; 21 | inlineStyleFileExtension?: string; 22 | } 23 | export declare class AngularWebpackPlugin { 24 | private readonly pluginOptions; 25 | private compilerCliModule?; 26 | private compilerCliToolingModule?; 27 | private watchMode?; 28 | private ngtscNextProgram?; 29 | private builder?; 30 | private sourceFileCache?; 31 | private webpackCache?; 32 | private webpackCreateHash?; 33 | private readonly fileDependencies; 34 | private readonly requiredFilesToEmit; 35 | private readonly requiredFilesToEmitCache; 36 | private readonly fileEmitHistory; 37 | constructor(options?: Partial); 38 | private get compilerCli(); 39 | private get compilerCliTooling(); 40 | get options(): AngularWebpackPluginOptions; 41 | apply(compiler: Compiler): void; 42 | private setupCompilation; 43 | private registerWithCompilation; 44 | private markResourceUsed; 45 | private rebuildRequiredFiles; 46 | private loadConfiguration; 47 | private updateAotProgram; 48 | private updateJitProgram; 49 | private createFileEmitter; 50 | private initializeCompilerCli; 51 | private addFileEmitHistory; 52 | private getFileEmitHistory; 53 | } 54 | -------------------------------------------------------------------------------- /src/ivy/plugin.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.AngularWebpackPlugin = exports.imageDomains = void 0; 44 | const node_assert_1 = require("node:assert"); 45 | const ts = __importStar(require("typescript")); 46 | const paths_plugin_1 = require("../paths-plugin"); 47 | const resource_loader_1 = require("../resource_loader"); 48 | const cache_1 = require("./cache"); 49 | const diagnostics_1 = require("./diagnostics"); 50 | const host_1 = require("./host"); 51 | const paths_1 = require("./paths"); 52 | const symbol_1 = require("./symbol"); 53 | const system_1 = require("./system"); 54 | const transformation_1 = require("./transformation"); 55 | /** 56 | * The threshold used to determine whether Angular file diagnostics should optimize for full programs 57 | * or single files. If the number of affected files for a build is more than the threshold, full 58 | * program optimization will be used. 59 | */ 60 | const DIAGNOSTICS_AFFECTED_THRESHOLD = 1; 61 | exports.imageDomains = new Set(); 62 | const PLUGIN_NAME = 'angular-compiler'; 63 | const compilationFileEmitters = new WeakMap(); 64 | class AngularWebpackPlugin { 65 | pluginOptions; 66 | compilerCliModule; 67 | compilerCliToolingModule; 68 | watchMode; 69 | ngtscNextProgram; 70 | builder; 71 | sourceFileCache; 72 | webpackCache; 73 | webpackCreateHash; 74 | fileDependencies = new Map(); 75 | requiredFilesToEmit = new Set(); 76 | requiredFilesToEmitCache = new Map(); 77 | fileEmitHistory = new Map(); 78 | constructor(options = {}) { 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 | node_assert_1.strict.ok(this.compilerCliModule, `'@angular/compiler-cli' used prior to Webpack compilation.`); 95 | return this.compilerCliModule; 96 | } 97 | get compilerCliTooling() { 98 | // The compilerCliToolingModule field is guaranteed to be defined during a compilation 99 | // due to the `beforeCompile` hook. Usage of this property accessor prior to the 100 | // hook execution is an implementation error. 101 | node_assert_1.strict.ok(this.compilerCliToolingModule, `'@angular/compiler-cli' used prior to Webpack compilation.`); 102 | return this.compilerCliToolingModule; 103 | } 104 | get options() { 105 | return this.pluginOptions; 106 | } 107 | apply(compiler) { 108 | const { NormalModuleReplacementPlugin, util } = compiler.webpack; 109 | this.webpackCreateHash = util.createHash; 110 | // Setup file replacements with webpack 111 | for (const [key, value] of Object.entries(this.pluginOptions.fileReplacements)) { 112 | new NormalModuleReplacementPlugin(new RegExp('^' + key.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&') + '$'), value).apply(compiler); 113 | } 114 | // Set resolver options 115 | const pathsPlugin = new paths_plugin_1.TypeScriptPathsPlugin(); 116 | compiler.hooks.afterResolvers.tap(PLUGIN_NAME, (compiler) => { 117 | compiler.resolverFactory.hooks.resolveOptions 118 | .for('normal') 119 | .tap(PLUGIN_NAME, (resolveOptions) => { 120 | resolveOptions.plugins ??= []; 121 | resolveOptions.plugins.push(pathsPlugin); 122 | return resolveOptions; 123 | }); 124 | }); 125 | // Load the compiler-cli if not already available 126 | compiler.hooks.beforeCompile.tapPromise(PLUGIN_NAME, () => this.initializeCompilerCli()); 127 | const compilationState = { pathsPlugin }; 128 | compiler.hooks.thisCompilation.tap(PLUGIN_NAME, (compilation) => { 129 | try { 130 | this.setupCompilation(compilation, compilationState); 131 | } 132 | catch (error) { 133 | (0, diagnostics_1.addError)(compilation, `Failed to initialize Angular compilation - ${error instanceof Error ? error.message : error}`); 134 | } 135 | }); 136 | } 137 | setupCompilation(compilation, state) { 138 | const compiler = compilation.compiler; 139 | // Register plugin to ensure deterministic emit order in multi-plugin usage 140 | const emitRegistration = this.registerWithCompilation(compilation); 141 | this.watchMode = compiler.watchMode; 142 | // Initialize webpack cache 143 | if (!this.webpackCache && compilation.options.cache) { 144 | this.webpackCache = compilation.getCache(PLUGIN_NAME); 145 | } 146 | // Initialize the resource loader if not already setup 147 | if (!state.resourceLoader) { 148 | state.resourceLoader = new resource_loader_1.WebpackResourceLoader(this.watchMode); 149 | } 150 | // Setup and read TypeScript and Angular compiler configuration 151 | const { compilerOptions, rootNames, errors } = this.loadConfiguration(); 152 | // Create diagnostics reporter and report configuration file errors 153 | const diagnosticsReporter = (0, diagnostics_1.createDiagnosticsReporter)(compilation, (diagnostic) => this.compilerCli.formatDiagnostics([diagnostic])); 154 | diagnosticsReporter(errors); 155 | // Update TypeScript path mapping plugin with new configuration 156 | state.pathsPlugin.update(compilerOptions); 157 | // Create a Webpack-based TypeScript compiler host 158 | const system = (0, system_1.createWebpackSystem)( 159 | // Webpack lacks an InputFileSytem type definition with sync functions 160 | compiler.inputFileSystem, (0, paths_1.normalizePath)(compiler.context)); 161 | const host = ts.createIncrementalCompilerHost(compilerOptions, system); 162 | // Setup source file caching and reuse cache from previous compilation if present 163 | let cache = this.sourceFileCache; 164 | let changedFiles; 165 | if (cache) { 166 | changedFiles = new Set(); 167 | for (const changedFile of [ 168 | ...(compiler.modifiedFiles ?? []), 169 | ...(compiler.removedFiles ?? []), 170 | ]) { 171 | const normalizedChangedFile = (0, paths_1.normalizePath)(changedFile); 172 | // Invalidate file dependencies 173 | this.fileDependencies.delete(normalizedChangedFile); 174 | // Invalidate existing cache 175 | cache.invalidate(normalizedChangedFile); 176 | changedFiles.add(normalizedChangedFile); 177 | } 178 | } 179 | else { 180 | // Initialize a new cache 181 | cache = new cache_1.SourceFileCache(); 182 | // Only store cache if in watch mode 183 | if (this.watchMode) { 184 | this.sourceFileCache = cache; 185 | } 186 | } 187 | (0, host_1.augmentHostWithCaching)(host, cache); 188 | const moduleResolutionCache = ts.createModuleResolutionCache(host.getCurrentDirectory(), host.getCanonicalFileName.bind(host), compilerOptions); 189 | // Setup source file dependency collection 190 | (0, host_1.augmentHostWithDependencyCollection)(host, this.fileDependencies, moduleResolutionCache); 191 | // Setup resource loading 192 | state.resourceLoader.update(compilation, changedFiles); 193 | (0, host_1.augmentHostWithResources)(host, state.resourceLoader, { 194 | directTemplateLoading: this.pluginOptions.directTemplateLoading, 195 | inlineStyleFileExtension: this.pluginOptions.inlineStyleFileExtension, 196 | }); 197 | // Setup source file adjustment options 198 | (0, host_1.augmentHostWithReplacements)(host, this.pluginOptions.fileReplacements, moduleResolutionCache); 199 | (0, host_1.augmentHostWithSubstitutions)(host, this.pluginOptions.substitutions); 200 | // Create the file emitter used by the webpack loader 201 | const { fileEmitter, builder, internalFiles } = this.pluginOptions.jitMode 202 | ? this.updateJitProgram(compilerOptions, rootNames, host, diagnosticsReporter) 203 | : this.updateAotProgram(compilerOptions, rootNames, host, diagnosticsReporter, state.resourceLoader); 204 | // Set of files used during the unused TypeScript file analysis 205 | const currentUnused = new Set(); 206 | for (const sourceFile of builder.getSourceFiles()) { 207 | if (internalFiles?.has(sourceFile)) { 208 | continue; 209 | } 210 | // Ensure all program files are considered part of the compilation and will be watched. 211 | // Webpack does not normalize paths. Therefore, we need to normalize the path with FS seperators. 212 | compilation.fileDependencies.add((0, paths_1.externalizePath)(sourceFile.fileName)); 213 | // Add all non-declaration files to the initial set of unused files. The set will be 214 | // analyzed and pruned after all Webpack modules are finished building. 215 | if (!sourceFile.isDeclarationFile) { 216 | currentUnused.add((0, paths_1.normalizePath)(sourceFile.fileName)); 217 | } 218 | } 219 | compilation.hooks.finishModules.tapPromise(PLUGIN_NAME, async (modules) => { 220 | // Rebuild any remaining AOT required modules 221 | await this.rebuildRequiredFiles(modules, compilation, fileEmitter); 222 | // Clear out the Webpack compilation to avoid an extra retaining reference 223 | state.resourceLoader?.clearParentCompilation(); 224 | // Analyze program for unused files 225 | if (compilation.errors.length > 0) { 226 | return; 227 | } 228 | for (const webpackModule of modules) { 229 | const resource = webpackModule.resource; 230 | if (resource) { 231 | this.markResourceUsed((0, paths_1.normalizePath)(resource), currentUnused); 232 | } 233 | } 234 | for (const unused of currentUnused) { 235 | if (state.previousUnused?.has(unused)) { 236 | continue; 237 | } 238 | (0, diagnostics_1.addWarning)(compilation, `${unused} is part of the TypeScript compilation but it's unused.\n` + 239 | `Add only entry points to the 'files' or 'include' properties in your tsconfig.`); 240 | } 241 | state.previousUnused = currentUnused; 242 | }); 243 | // Store file emitter for loader usage 244 | emitRegistration.update(fileEmitter); 245 | } 246 | registerWithCompilation(compilation) { 247 | let fileEmitters = compilationFileEmitters.get(compilation); 248 | if (!fileEmitters) { 249 | fileEmitters = new symbol_1.FileEmitterCollection(); 250 | compilationFileEmitters.set(compilation, fileEmitters); 251 | compilation.compiler.webpack.NormalModule.getCompilationHooks(compilation).loader.tap(PLUGIN_NAME, (context) => { 252 | const loaderContext = context; 253 | loaderContext[symbol_1.AngularPluginSymbol] = fileEmitters; 254 | }); 255 | } 256 | const emitRegistration = fileEmitters.register(); 257 | return emitRegistration; 258 | } 259 | markResourceUsed(normalizedResourcePath, currentUnused) { 260 | if (!currentUnused.has(normalizedResourcePath)) { 261 | return; 262 | } 263 | currentUnused.delete(normalizedResourcePath); 264 | const dependencies = this.fileDependencies.get(normalizedResourcePath); 265 | if (!dependencies) { 266 | return; 267 | } 268 | for (const dependency of dependencies) { 269 | this.markResourceUsed((0, paths_1.normalizePath)(dependency), currentUnused); 270 | } 271 | } 272 | async rebuildRequiredFiles(modules, compilation, fileEmitter) { 273 | if (this.requiredFilesToEmit.size === 0) { 274 | return; 275 | } 276 | const filesToRebuild = new Set(); 277 | for (const requiredFile of this.requiredFilesToEmit) { 278 | const history = await this.getFileEmitHistory(requiredFile); 279 | if (history) { 280 | const emitResult = await fileEmitter(requiredFile); 281 | if (emitResult?.content === undefined || 282 | history.length !== emitResult.content.length || 283 | emitResult.hash === undefined || 284 | Buffer.compare(history.hash, emitResult.hash) !== 0) { 285 | // New emit result is different so rebuild using new emit result 286 | this.requiredFilesToEmitCache.set(requiredFile, emitResult); 287 | filesToRebuild.add(requiredFile); 288 | } 289 | } 290 | else { 291 | // No emit history so rebuild 292 | filesToRebuild.add(requiredFile); 293 | } 294 | } 295 | if (filesToRebuild.size > 0) { 296 | const rebuild = (webpackModule) => new Promise((resolve) => compilation.rebuildModule(webpackModule, () => resolve())); 297 | const modulesToRebuild = []; 298 | for (const webpackModule of modules) { 299 | const resource = webpackModule.resource; 300 | if (resource && filesToRebuild.has((0, paths_1.normalizePath)(resource))) { 301 | modulesToRebuild.push(webpackModule); 302 | } 303 | } 304 | await Promise.all(modulesToRebuild.map((webpackModule) => rebuild(webpackModule))); 305 | } 306 | this.requiredFilesToEmit.clear(); 307 | this.requiredFilesToEmitCache.clear(); 308 | } 309 | loadConfiguration() { 310 | const { options: compilerOptions, rootNames, errors, } = this.compilerCli.readConfiguration(this.pluginOptions.tsconfig, this.pluginOptions.compilerOptions); 311 | compilerOptions.composite = false; 312 | compilerOptions.noEmitOnError = false; 313 | compilerOptions.suppressOutputPathCheck = true; 314 | compilerOptions.outDir = undefined; 315 | compilerOptions.inlineSources = compilerOptions.sourceMap; 316 | compilerOptions.inlineSourceMap = false; 317 | compilerOptions.mapRoot = undefined; 318 | compilerOptions.sourceRoot = undefined; 319 | compilerOptions.allowEmptyCodegenFiles = false; 320 | compilerOptions.annotationsAs = 'decorators'; 321 | compilerOptions.enableResourceInlining = false; 322 | return { compilerOptions, rootNames, errors }; 323 | } 324 | updateAotProgram(compilerOptions, rootNames, host, diagnosticsReporter, resourceLoader) { 325 | // Create the Angular specific program that contains the Angular compiler 326 | const angularProgram = new this.compilerCli.NgtscProgram(rootNames, compilerOptions, host, this.ngtscNextProgram); 327 | const angularCompiler = angularProgram.compiler; 328 | // The `ignoreForEmit` return value can be safely ignored when emitting. Only files 329 | // that will be bundled (requested by Webpack) will be emitted. Combined with TypeScript's 330 | // eliding of type only imports, this will cause type only files to be automatically ignored. 331 | // Internal Angular type check files are also not resolvable by the bundler. Even if they 332 | // were somehow errantly imported, the bundler would error before an emit was attempted. 333 | // Diagnostics are still collected for all files which requires using `ignoreForDiagnostics`. 334 | const { ignoreForDiagnostics, ignoreForEmit } = angularCompiler; 335 | // SourceFile versions are required for builder programs. 336 | // The wrapped host inside NgtscProgram adds additional files that will not have versions. 337 | const typeScriptProgram = angularProgram.getTsProgram(); 338 | (0, host_1.augmentProgramWithVersioning)(typeScriptProgram); 339 | let builder; 340 | if (this.watchMode) { 341 | builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(typeScriptProgram, host, this.builder); 342 | this.ngtscNextProgram = angularProgram; 343 | } 344 | else { 345 | // When not in watch mode, the startup cost of the incremental analysis can be avoided by 346 | // using an abstract builder that only wraps a TypeScript program. 347 | builder = ts.createAbstractBuilder(typeScriptProgram, host); 348 | } 349 | // Update semantic diagnostics cache 350 | const affectedFiles = new Set(); 351 | // Analyze affected files when in watch mode for incremental type checking 352 | if ('getSemanticDiagnosticsOfNextAffectedFile' in builder) { 353 | // eslint-disable-next-line no-constant-condition 354 | while (true) { 355 | const result = builder.getSemanticDiagnosticsOfNextAffectedFile(undefined, (sourceFile) => { 356 | // If the affected file is a TTC shim, add the shim's original source file. 357 | // This ensures that changes that affect TTC are typechecked even when the changes 358 | // are otherwise unrelated from a TS perspective and do not result in Ivy codegen changes. 359 | // For example, changing @Input property types of a directive used in another component's 360 | // template. 361 | if (ignoreForDiagnostics.has(sourceFile) && 362 | sourceFile.fileName.endsWith('.ngtypecheck.ts')) { 363 | // This file name conversion relies on internal compiler logic and should be converted 364 | // to an official method when available. 15 is length of `.ngtypecheck.ts` 365 | const originalFilename = sourceFile.fileName.slice(0, -15) + '.ts'; 366 | const originalSourceFile = builder.getSourceFile(originalFilename); 367 | if (originalSourceFile) { 368 | affectedFiles.add(originalSourceFile); 369 | } 370 | return true; 371 | } 372 | return false; 373 | }); 374 | if (!result) { 375 | break; 376 | } 377 | affectedFiles.add(result.affected); 378 | } 379 | } 380 | // Collect program level diagnostics 381 | const diagnostics = [ 382 | ...angularCompiler.getOptionDiagnostics(), 383 | ...builder.getOptionsDiagnostics(), 384 | ...builder.getGlobalDiagnostics(), 385 | ]; 386 | diagnosticsReporter(diagnostics); 387 | // Collect source file specific diagnostics 388 | for (const sourceFile of builder.getSourceFiles()) { 389 | if (!ignoreForDiagnostics.has(sourceFile)) { 390 | diagnosticsReporter(builder.getSyntacticDiagnostics(sourceFile)); 391 | diagnosticsReporter(builder.getSemanticDiagnostics(sourceFile)); 392 | } 393 | } 394 | const transformers = (0, transformation_1.createAotTransformers)(builder, this.pluginOptions, exports.imageDomains); 395 | const getDependencies = (sourceFile) => { 396 | const dependencies = []; 397 | for (const resourcePath of angularCompiler.getResourceDependencies(sourceFile)) { 398 | dependencies.push(resourcePath, 399 | // Retrieve all dependencies of the resource (stylesheet imports, etc.) 400 | ...resourceLoader.getResourceDependencies(resourcePath)); 401 | } 402 | return dependencies; 403 | }; 404 | // Required to support asynchronous resource loading 405 | // Must be done before creating transformers or getting template diagnostics 406 | const pendingAnalysis = angularCompiler 407 | .analyzeAsync() 408 | .then(() => { 409 | this.requiredFilesToEmit.clear(); 410 | for (const sourceFile of builder.getSourceFiles()) { 411 | if (sourceFile.isDeclarationFile) { 412 | continue; 413 | } 414 | // Collect sources that are required to be emitted 415 | if (!ignoreForEmit.has(sourceFile) && 416 | !angularCompiler.incrementalCompilation.safeToSkipEmit(sourceFile)) { 417 | this.requiredFilesToEmit.add((0, paths_1.normalizePath)(sourceFile.fileName)); 418 | // If required to emit, diagnostics may have also changed 419 | if (!ignoreForDiagnostics.has(sourceFile)) { 420 | affectedFiles.add(sourceFile); 421 | } 422 | } 423 | else if (this.sourceFileCache && 424 | !affectedFiles.has(sourceFile) && 425 | !ignoreForDiagnostics.has(sourceFile)) { 426 | // Use cached Angular diagnostics for unchanged and unaffected files 427 | const angularDiagnostics = this.sourceFileCache.getAngularDiagnostics(sourceFile); 428 | if (angularDiagnostics) { 429 | diagnosticsReporter(angularDiagnostics); 430 | } 431 | } 432 | } 433 | // Collect new Angular diagnostics for files affected by changes 434 | const OptimizeFor = this.compilerCli.OptimizeFor; 435 | const optimizeDiagnosticsFor = affectedFiles.size <= DIAGNOSTICS_AFFECTED_THRESHOLD 436 | ? OptimizeFor.SingleFile 437 | : OptimizeFor.WholeProgram; 438 | for (const affectedFile of affectedFiles) { 439 | const angularDiagnostics = angularCompiler.getDiagnosticsForFile(affectedFile, optimizeDiagnosticsFor); 440 | diagnosticsReporter(angularDiagnostics); 441 | this.sourceFileCache?.updateAngularDiagnostics(affectedFile, angularDiagnostics); 442 | } 443 | return { 444 | emitter: this.createFileEmitter(builder, (0, transformation_1.mergeTransformers)(angularCompiler.prepareEmit().transformers, transformers), getDependencies, (sourceFile) => { 445 | this.requiredFilesToEmit.delete((0, paths_1.normalizePath)(sourceFile.fileName)); 446 | angularCompiler.incrementalCompilation.recordSuccessfulEmit(sourceFile); 447 | }), 448 | }; 449 | }) 450 | .catch((err) => ({ errorMessage: err instanceof Error ? err.message : `${err}` })); 451 | const analyzingFileEmitter = async (file) => { 452 | const analysis = await pendingAnalysis; 453 | if ('errorMessage' in analysis) { 454 | throw new Error(analysis.errorMessage); 455 | } 456 | return analysis.emitter(file); 457 | }; 458 | return { 459 | fileEmitter: analyzingFileEmitter, 460 | builder, 461 | internalFiles: ignoreForEmit, 462 | }; 463 | } 464 | updateJitProgram(compilerOptions, rootNames, host, diagnosticsReporter) { 465 | let builder; 466 | if (this.watchMode) { 467 | builder = this.builder = ts.createEmitAndSemanticDiagnosticsBuilderProgram(rootNames, compilerOptions, host, this.builder); 468 | } 469 | else { 470 | // When not in watch mode, the startup cost of the incremental analysis can be avoided by 471 | // using an abstract builder that only wraps a TypeScript program. 472 | builder = ts.createAbstractBuilder(rootNames, compilerOptions, host); 473 | } 474 | const diagnostics = [ 475 | ...builder.getOptionsDiagnostics(), 476 | ...builder.getGlobalDiagnostics(), 477 | ...builder.getSyntacticDiagnostics(), 478 | // Gather incremental semantic diagnostics 479 | ...builder.getSemanticDiagnostics(), 480 | ]; 481 | diagnosticsReporter(diagnostics); 482 | const transformers = (0, transformation_1.createJitTransformers)(builder, this.compilerCliTooling, this.pluginOptions); 483 | return { 484 | fileEmitter: this.createFileEmitter(builder, transformers, () => []), 485 | builder, 486 | internalFiles: undefined, 487 | }; 488 | } 489 | createFileEmitter(program, transformers = {}, getExtraDependencies, onAfterEmit) { 490 | return async (file) => { 491 | const filePath = (0, paths_1.normalizePath)(file); 492 | if (this.requiredFilesToEmitCache.has(filePath)) { 493 | return this.requiredFilesToEmitCache.get(filePath); 494 | } 495 | const sourceFile = program.getSourceFile(filePath); 496 | if (!sourceFile) { 497 | return undefined; 498 | } 499 | let content; 500 | let map; 501 | program.emit(sourceFile, (filename, data) => { 502 | if (filename.endsWith('.map')) { 503 | map = data; 504 | } 505 | else if (filename.endsWith('.js')) { 506 | content = data; 507 | } 508 | }, undefined, undefined, transformers); 509 | onAfterEmit?.(sourceFile); 510 | // Capture emit history info for Angular rebuild analysis 511 | const hash = content ? (await this.addFileEmitHistory(filePath, content)).hash : undefined; 512 | const dependencies = [ 513 | ...(this.fileDependencies.get(filePath) || []), 514 | ...getExtraDependencies(sourceFile), 515 | ].map(paths_1.externalizePath); 516 | return { content, map, dependencies, hash }; 517 | }; 518 | } 519 | async initializeCompilerCli() { 520 | // This uses a dynamic import to load `@angular/compiler-cli` which may be ESM. 521 | // CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript 522 | // will currently, unconditionally downlevel dynamic import into a require call. 523 | // require calls cannot load ESM code and will result in a runtime error. To workaround 524 | // this, a Function constructor is used to prevent TypeScript from changing the dynamic import. 525 | // Once TypeScript provides support for keeping the dynamic import this workaround can 526 | // be dropped. 527 | this.compilerCliModule ??= await new Function(`return import('@angular/compiler-cli');`)(); 528 | this.compilerCliToolingModule ??= await new Function(`return import('@angular/compiler-cli/private/tooling');`)(); 529 | } 530 | async addFileEmitHistory(filePath, content) { 531 | node_assert_1.strict.ok(this.webpackCreateHash, 'File emitter is used prior to Webpack compilation'); 532 | const historyData = { 533 | length: content.length, 534 | hash: this.webpackCreateHash('xxhash64').update(content).digest(), 535 | }; 536 | if (this.webpackCache) { 537 | const history = await this.getFileEmitHistory(filePath); 538 | if (!history || Buffer.compare(history.hash, historyData.hash) !== 0) { 539 | // Hash doesn't match or item doesn't exist. 540 | await this.webpackCache.storePromise(filePath, null, historyData); 541 | } 542 | } 543 | else if (this.watchMode) { 544 | // The in memory file emit history is only required during watch mode. 545 | this.fileEmitHistory.set(filePath, historyData); 546 | } 547 | return historyData; 548 | } 549 | async getFileEmitHistory(filePath) { 550 | return this.webpackCache 551 | ? this.webpackCache.getPromise(filePath, null) 552 | : this.fileEmitHistory.get(filePath); 553 | } 554 | } 555 | exports.AngularWebpackPlugin = AngularWebpackPlugin; 556 | -------------------------------------------------------------------------------- /src/ivy/symbol.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | export declare const AngularPluginSymbol: unique symbol; 9 | export interface EmitFileResult { 10 | content?: string; 11 | map?: string; 12 | dependencies: readonly string[]; 13 | hash?: Uint8Array; 14 | } 15 | export type FileEmitter = (file: string) => Promise; 16 | export declare class FileEmitterRegistration { 17 | #private; 18 | update(emitter: FileEmitter): void; 19 | emit(file: string): Promise; 20 | } 21 | export declare class FileEmitterCollection { 22 | #private; 23 | register(): FileEmitterRegistration; 24 | emit(file: string): Promise; 25 | } 26 | -------------------------------------------------------------------------------- /src/ivy/symbol.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.FileEmitterCollection = exports.FileEmitterRegistration = exports.AngularPluginSymbol = void 0; 11 | exports.AngularPluginSymbol = Symbol.for('@ngtools/webpack[angular-compiler]'); 12 | class FileEmitterRegistration { 13 | #fileEmitter; 14 | update(emitter) { 15 | this.#fileEmitter = emitter; 16 | } 17 | emit(file) { 18 | if (!this.#fileEmitter) { 19 | throw new Error('Emit attempted before Angular Webpack plugin initialization.'); 20 | } 21 | return this.#fileEmitter(file); 22 | } 23 | } 24 | exports.FileEmitterRegistration = FileEmitterRegistration; 25 | class FileEmitterCollection { 26 | #registrations = []; 27 | register() { 28 | const registration = new FileEmitterRegistration(); 29 | this.#registrations.push(registration); 30 | return registration; 31 | } 32 | async emit(file) { 33 | if (this.#registrations.length === 1) { 34 | return this.#registrations[0].emit(file); 35 | } 36 | for (const registration of this.#registrations) { 37 | const result = await registration.emit(file); 38 | if (result) { 39 | return result; 40 | } 41 | } 42 | } 43 | } 44 | exports.FileEmitterCollection = FileEmitterCollection; 45 | -------------------------------------------------------------------------------- /src/ivy/system.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import * as ts from 'typescript'; 9 | import { Compiler } from 'webpack'; 10 | export type InputFileSystem = NonNullable; 11 | export interface InputFileSystemSync extends InputFileSystem { 12 | readFileSync: NonNullable; 13 | statSync: NonNullable; 14 | } 15 | export declare function createWebpackSystem(input: InputFileSystemSync, currentDirectory: string): ts.System; 16 | -------------------------------------------------------------------------------- /src/ivy/system.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.createWebpackSystem = createWebpackSystem; 44 | const ts = __importStar(require("typescript")); 45 | const paths_1 = require("./paths"); 46 | function shouldNotWrite() { 47 | throw new Error('Webpack TypeScript System should not write.'); 48 | } 49 | function createWebpackSystem(input, currentDirectory) { 50 | // Webpack's CachedInputFileSystem uses the default directory separator in the paths it uses 51 | // for keys to its cache. If the keys do not match then the file watcher will not purge outdated 52 | // files and cause stale data to be used in the next rebuild. TypeScript always uses a `/` (POSIX) 53 | // directory separator internally which is also supported with Windows system APIs. However, 54 | // if file operations are performed with the non-default directory separator, the Webpack cache 55 | // will contain a key that will not be purged. `externalizePath` ensures the paths are as expected. 56 | const system = { 57 | ...ts.sys, 58 | readFile(path) { 59 | let data; 60 | try { 61 | data = input.readFileSync((0, paths_1.externalizePath)(path)); 62 | } 63 | catch { 64 | return undefined; 65 | } 66 | // Strip BOM if present 67 | let start = 0; 68 | if (data.length > 3 && data[0] === 0xef && data[1] === 0xbb && data[2] === 0xbf) { 69 | start = 3; 70 | } 71 | return data.toString('utf8', start); 72 | }, 73 | getFileSize(path) { 74 | try { 75 | return input.statSync((0, paths_1.externalizePath)(path)).size; 76 | } 77 | catch { 78 | return 0; 79 | } 80 | }, 81 | fileExists(path) { 82 | try { 83 | return input.statSync((0, paths_1.externalizePath)(path)).isFile(); 84 | } 85 | catch { 86 | return false; 87 | } 88 | }, 89 | directoryExists(path) { 90 | try { 91 | return input.statSync((0, paths_1.externalizePath)(path)).isDirectory(); 92 | } 93 | catch { 94 | return false; 95 | } 96 | }, 97 | getModifiedTime(path) { 98 | try { 99 | return input.statSync((0, paths_1.externalizePath)(path)).mtime; 100 | } 101 | catch { 102 | return undefined; 103 | } 104 | }, 105 | getCurrentDirectory() { 106 | return currentDirectory; 107 | }, 108 | writeFile: shouldNotWrite, 109 | createDirectory: shouldNotWrite, 110 | deleteFile: shouldNotWrite, 111 | setModifiedTime: shouldNotWrite, 112 | }; 113 | return system; 114 | } 115 | -------------------------------------------------------------------------------- /src/ivy/transformation.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import * as ts from 'typescript'; 9 | export declare function createAotTransformers(builder: ts.BuilderProgram, options: { 10 | emitClassMetadata?: boolean; 11 | emitNgModuleScope?: boolean; 12 | emitSetClassDebugInfo?: boolean; 13 | }, imageDomains: Set): ts.CustomTransformers; 14 | export declare function createJitTransformers(builder: ts.BuilderProgram, compilerCli: typeof import('@angular/compiler-cli/private/tooling'), options: { 15 | inlineStyleFileExtension?: string; 16 | }): ts.CustomTransformers; 17 | export declare function mergeTransformers(first: ts.CustomTransformers, second: ts.CustomTransformers): ts.CustomTransformers; 18 | export declare function replaceBootstrap(getTypeChecker: () => ts.TypeChecker): ts.TransformerFactory; 19 | -------------------------------------------------------------------------------- /src/ivy/transformation.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.createAotTransformers = createAotTransformers; 44 | exports.createJitTransformers = createJitTransformers; 45 | exports.mergeTransformers = mergeTransformers; 46 | exports.replaceBootstrap = replaceBootstrap; 47 | const ts = __importStar(require("typescript")); 48 | const elide_imports_1 = require("../transformers/elide_imports"); 49 | const find_image_domains_1 = require("../transformers/find_image_domains"); 50 | const remove_ivy_jit_support_calls_1 = require("../transformers/remove-ivy-jit-support-calls"); 51 | const replace_resources_1 = require("../transformers/replace_resources"); 52 | function createAotTransformers(builder, options, imageDomains) { 53 | const getTypeChecker = () => builder.getProgram().getTypeChecker(); 54 | const transformers = { 55 | before: [(0, find_image_domains_1.findImageDomains)(imageDomains), replaceBootstrap(getTypeChecker)], 56 | after: [], 57 | }; 58 | const removeClassMetadata = !options.emitClassMetadata; 59 | const removeNgModuleScope = !options.emitNgModuleScope; 60 | const removeSetClassDebugInfo = !options.emitSetClassDebugInfo; 61 | if (removeClassMetadata || removeNgModuleScope || removeSetClassDebugInfo) { 62 | // eslint-disable-next-line @typescript-eslint/no-non-null-assertion 63 | transformers.before.push((0, remove_ivy_jit_support_calls_1.removeIvyJitSupportCalls)(removeClassMetadata, removeNgModuleScope, removeSetClassDebugInfo, getTypeChecker)); 64 | } 65 | return transformers; 66 | } 67 | function createJitTransformers(builder, compilerCli, options) { 68 | const getTypeChecker = () => builder.getProgram().getTypeChecker(); 69 | return { 70 | before: [ 71 | (0, replace_resources_1.replaceResources)(() => true, getTypeChecker, options.inlineStyleFileExtension), 72 | compilerCli.constructorParametersDownlevelTransform(builder.getProgram()), 73 | ], 74 | }; 75 | } 76 | function mergeTransformers(first, second) { 77 | const result = {}; 78 | if (first.before || second.before) { 79 | result.before = [...(first.before || []), ...(second.before || [])]; 80 | } 81 | if (first.after || second.after) { 82 | result.after = [...(first.after || []), ...(second.after || [])]; 83 | } 84 | if (first.afterDeclarations || second.afterDeclarations) { 85 | result.afterDeclarations = [ 86 | ...(first.afterDeclarations || []), 87 | ...(second.afterDeclarations || []), 88 | ]; 89 | } 90 | return result; 91 | } 92 | /** 93 | * The name of the Angular platform that should be replaced within 94 | * bootstrap call expressions to support AOT. 95 | */ 96 | const PLATFORM_BROWSER_DYNAMIC_NAME = 'platformBrowserDynamic'; 97 | function replaceBootstrap(getTypeChecker) { 98 | return (context) => { 99 | let bootstrapImport; 100 | let bootstrapNamespace; 101 | const replacedNodes = []; 102 | const nodeFactory = context.factory; 103 | const visitNode = (node) => { 104 | if (ts.isCallExpression(node) && ts.isIdentifier(node.expression)) { 105 | const target = node.expression; 106 | if (target.text === PLATFORM_BROWSER_DYNAMIC_NAME) { 107 | if (!bootstrapNamespace) { 108 | bootstrapNamespace = nodeFactory.createUniqueName('__NgCli_bootstrap_'); 109 | bootstrapImport = nodeFactory.createImportDeclaration(undefined, nodeFactory.createImportClause(false, undefined, nodeFactory.createNamespaceImport(bootstrapNamespace)), nodeFactory.createStringLiteral('@angular/platform-browser')); 110 | } 111 | replacedNodes.push(target); 112 | return nodeFactory.updateCallExpression(node, nodeFactory.createPropertyAccessExpression(bootstrapNamespace, 'platformBrowser'), node.typeArguments, node.arguments); 113 | } 114 | } 115 | return ts.visitEachChild(node, visitNode, context); 116 | }; 117 | return (sourceFile) => { 118 | if (!sourceFile.text.includes(PLATFORM_BROWSER_DYNAMIC_NAME)) { 119 | return sourceFile; 120 | } 121 | let updatedSourceFile = ts.visitEachChild(sourceFile, visitNode, context); 122 | if (bootstrapImport) { 123 | // Remove any unused platform browser dynamic imports 124 | const removals = (0, elide_imports_1.elideImports)(updatedSourceFile, replacedNodes, getTypeChecker, context.getCompilerOptions()); 125 | if (removals.size > 0) { 126 | updatedSourceFile = ts.visitEachChild(updatedSourceFile, (node) => (removals.has(node) ? undefined : node), context); 127 | } 128 | // Add new platform browser import 129 | return nodeFactory.updateSourceFile(updatedSourceFile, ts.setTextRange(nodeFactory.createNodeArray([bootstrapImport, ...updatedSourceFile.statements]), sourceFile.statements)); 130 | } 131 | else { 132 | return updatedSourceFile; 133 | } 134 | }; 135 | }; 136 | } 137 | -------------------------------------------------------------------------------- /src/loaders/inline-resource.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import type { Compilation, LoaderContext } from 'webpack'; 9 | export declare const InlineAngularResourceLoaderPath: string; 10 | export declare const InlineAngularResourceSymbol: unique symbol; 11 | export interface CompilationWithInlineAngularResource extends Compilation { 12 | [InlineAngularResourceSymbol]: string; 13 | } 14 | export default function (this: LoaderContext<{ 15 | data?: string; 16 | }>): void; 17 | -------------------------------------------------------------------------------- /src/loaders/inline-resource.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | exports.InlineAngularResourceSymbol = exports.InlineAngularResourceLoaderPath = void 0; 11 | exports.default = default_1; 12 | exports.InlineAngularResourceLoaderPath = __filename; 13 | exports.InlineAngularResourceSymbol = Symbol('@ngtools/webpack[angular-resource]'); 14 | function default_1() { 15 | const callback = this.async(); 16 | const { data } = this.getOptions(); 17 | if (data) { 18 | callback(undefined, Buffer.from(data, 'base64').toString()); 19 | } 20 | else { 21 | const content = this._compilation[exports.InlineAngularResourceSymbol]; 22 | callback(undefined, content); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/paths-plugin.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import { CompilerOptions } from 'typescript'; 9 | import type { Resolver } from 'webpack'; 10 | export interface TypeScriptPathsPluginOptions extends Pick { 11 | } 12 | type ResolverRequest = NonNullable[4]>[2]>; 13 | interface PathPluginResolverRequest extends ResolverRequest { 14 | context?: { 15 | issuer?: string; 16 | }; 17 | typescriptPathMapped?: boolean; 18 | } 19 | export declare class TypeScriptPathsPlugin { 20 | private baseUrl?; 21 | private patterns?; 22 | constructor(options?: TypeScriptPathsPluginOptions); 23 | /** 24 | * Update the plugin with new path mapping option values. 25 | * The options will also be preprocessed to reduce the overhead of individual resolve actions 26 | * during a build. 27 | * 28 | * @param options The `paths` and `baseUrl` options from TypeScript's `CompilerOptions`. 29 | */ 30 | update(options: TypeScriptPathsPluginOptions): void; 31 | apply(resolver: Resolver): void; 32 | findReplacements(originalRequest: string): IterableIterator; 33 | createReplacementRequests(request: PathPluginResolverRequest, originalRequest: string): IterableIterator; 34 | } 35 | export {}; 36 | -------------------------------------------------------------------------------- /src/paths-plugin.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.TypeScriptPathsPlugin = void 0; 44 | const path = __importStar(require("node:path")); 45 | class TypeScriptPathsPlugin { 46 | baseUrl; 47 | patterns; 48 | constructor(options) { 49 | if (options) { 50 | this.update(options); 51 | } 52 | } 53 | /** 54 | * Update the plugin with new path mapping option values. 55 | * The options will also be preprocessed to reduce the overhead of individual resolve actions 56 | * during a build. 57 | * 58 | * @param options The `paths` and `baseUrl` options from TypeScript's `CompilerOptions`. 59 | */ 60 | update(options) { 61 | this.baseUrl = options.baseUrl; 62 | this.patterns = undefined; 63 | if (options.paths) { 64 | for (const [pattern, potentials] of Object.entries(options.paths)) { 65 | // Ignore any entries that would not result in a new mapping 66 | if (potentials.length === 0 || potentials.every((potential) => potential === '*')) { 67 | continue; 68 | } 69 | const starIndex = pattern.indexOf('*'); 70 | let prefix = pattern; 71 | let suffix; 72 | if (starIndex > -1) { 73 | prefix = pattern.slice(0, starIndex); 74 | if (starIndex < pattern.length - 1) { 75 | suffix = pattern.slice(starIndex + 1); 76 | } 77 | } 78 | this.patterns ??= []; 79 | this.patterns.push({ 80 | starIndex, 81 | prefix, 82 | suffix, 83 | potentials: potentials.map((potential) => { 84 | const potentialStarIndex = potential.indexOf('*'); 85 | if (potentialStarIndex === -1) { 86 | return { hasStar: false, prefix: potential }; 87 | } 88 | return { 89 | hasStar: true, 90 | prefix: potential.slice(0, potentialStarIndex), 91 | suffix: potentialStarIndex < potential.length - 1 92 | ? potential.slice(potentialStarIndex + 1) 93 | : undefined, 94 | }; 95 | }), 96 | }); 97 | } 98 | // Sort patterns so that exact matches take priority then largest prefix match 99 | this.patterns?.sort((a, b) => { 100 | if (a.starIndex === -1) { 101 | return -1; 102 | } 103 | else if (b.starIndex === -1) { 104 | return 1; 105 | } 106 | else { 107 | return b.starIndex - a.starIndex; 108 | } 109 | }); 110 | } 111 | } 112 | apply(resolver) { 113 | const target = resolver.ensureHook('resolve'); 114 | // To support synchronous resolvers this hook cannot be promise based. 115 | // Webpack supports synchronous resolution with `tap` and `tapAsync` hooks. 116 | resolver 117 | .getHook('described-resolve') 118 | .tapAsync('TypeScriptPathsPlugin', (request, resolveContext, callback) => { 119 | // Preprocessing of the options will ensure that `patterns` is either undefined or has elements to check 120 | if (!this.patterns) { 121 | callback(); 122 | return; 123 | } 124 | if (!request || request.typescriptPathMapped) { 125 | callback(); 126 | return; 127 | } 128 | const originalRequest = request.request || request.path; 129 | if (!originalRequest) { 130 | callback(); 131 | return; 132 | } 133 | // Only work on Javascript/TypeScript issuers. 134 | if (!request?.context?.issuer?.match(/\.[cm]?[jt]sx?$/)) { 135 | callback(); 136 | return; 137 | } 138 | // Absolute requests are not mapped 139 | if (path.isAbsolute(originalRequest)) { 140 | callback(); 141 | return; 142 | } 143 | switch (originalRequest[0]) { 144 | case '.': 145 | // Relative requests are not mapped 146 | callback(); 147 | return; 148 | case '!': 149 | // Ignore all webpack special requests 150 | if (originalRequest.length > 1 && originalRequest[1] === '!') { 151 | callback(); 152 | return; 153 | } 154 | break; 155 | } 156 | // A generator is used to limit the amount of replacements requests that need to be created. 157 | // For example, if the first one resolves, any others are not needed and do not need 158 | // to be created. 159 | const requests = this.createReplacementRequests(request, originalRequest); 160 | const tryResolve = () => { 161 | const next = requests.next(); 162 | if (next.done) { 163 | callback(); 164 | return; 165 | } 166 | resolver.doResolve(target, next.value, '', resolveContext, (error, result) => { 167 | if (error) { 168 | callback(error); 169 | } 170 | else if (result) { 171 | callback(undefined, result); 172 | } 173 | else { 174 | tryResolve(); 175 | } 176 | }); 177 | }; 178 | tryResolve(); 179 | }); 180 | } 181 | *findReplacements(originalRequest) { 182 | if (!this.patterns) { 183 | return; 184 | } 185 | // check if any path mapping rules are relevant 186 | for (const { starIndex, prefix, suffix, potentials } of this.patterns) { 187 | let partial; 188 | if (starIndex === -1) { 189 | // No star means an exact match is required 190 | if (prefix === originalRequest) { 191 | partial = ''; 192 | } 193 | } 194 | else if (starIndex === 0 && !suffix) { 195 | // Everything matches a single wildcard pattern ("*") 196 | partial = originalRequest; 197 | } 198 | else if (!suffix) { 199 | // No suffix means the star is at the end of the pattern 200 | if (originalRequest.startsWith(prefix)) { 201 | partial = originalRequest.slice(prefix.length); 202 | } 203 | } 204 | else { 205 | // Star was in the middle of the pattern 206 | if (originalRequest.startsWith(prefix) && originalRequest.endsWith(suffix)) { 207 | partial = originalRequest.substring(prefix.length, originalRequest.length - suffix.length); 208 | } 209 | } 210 | // If request was not matched, move on to the next pattern 211 | if (partial === undefined) { 212 | continue; 213 | } 214 | // Create the full replacement values based on the original request and the potentials 215 | // for the successfully matched pattern. 216 | for (const { hasStar, prefix, suffix } of potentials) { 217 | let replacement = prefix; 218 | if (hasStar) { 219 | replacement += partial; 220 | if (suffix) { 221 | replacement += suffix; 222 | } 223 | } 224 | yield replacement; 225 | } 226 | } 227 | } 228 | *createReplacementRequests(request, originalRequest) { 229 | for (const replacement of this.findReplacements(originalRequest)) { 230 | const targetPath = path.resolve(this.baseUrl ?? '', replacement); 231 | // Resolution in the original callee location, but with the updated request 232 | // to point to the mapped target location. 233 | yield { 234 | ...request, 235 | request: targetPath, 236 | typescriptPathMapped: true, 237 | }; 238 | // If there is no extension. i.e. the target does not refer to an explicit 239 | // file, then this is a candidate for module/package resolution. 240 | const canBeModule = path.extname(targetPath) === ''; 241 | if (canBeModule) { 242 | // Resolution in the target location, preserving the original request. 243 | // This will work with the `resolve-in-package` resolution hook, supporting 244 | // package exports for e.g. locally-built APF libraries. 245 | yield { 246 | ...request, 247 | path: targetPath, 248 | typescriptPathMapped: true, 249 | }; 250 | } 251 | } 252 | } 253 | } 254 | exports.TypeScriptPathsPlugin = TypeScriptPathsPlugin; 255 | -------------------------------------------------------------------------------- /src/resource_loader.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import type { Compilation } from 'webpack'; 9 | export declare class WebpackResourceLoader { 10 | private _parentCompilation?; 11 | private _fileDependencies; 12 | private _reverseDependencies; 13 | private fileCache?; 14 | private assetCache?; 15 | private modifiedResources; 16 | private outputPathCounter; 17 | private readonly inlineDataLoaderPath; 18 | constructor(shouldCache: boolean); 19 | update(parentCompilation: Compilation, changedFiles?: Iterable): void; 20 | clearParentCompilation(): void; 21 | getModifiedResourceFiles(): Set; 22 | getResourceDependencies(filePath: string): Iterable; 23 | getAffectedResources(file: string): Iterable; 24 | setAffectedResources(file: string, resources: Iterable): void; 25 | private _compile; 26 | private _evaluate; 27 | get(filePath: string): Promise; 28 | process(data: string, fileExtension: string | undefined, resourceType: 'template' | 'style', containingFile?: string): Promise; 29 | } 30 | -------------------------------------------------------------------------------- /src/resource_loader.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | var __importDefault = (this && this.__importDefault) || function (mod) { 43 | return (mod && mod.__esModule) ? mod : { "default": mod }; 44 | }; 45 | Object.defineProperty(exports, "__esModule", { value: true }); 46 | exports.WebpackResourceLoader = void 0; 47 | const node_assert_1 = __importDefault(require("node:assert")); 48 | const node_buffer_1 = require("node:buffer"); 49 | const path = __importStar(require("node:path")); 50 | const vm = __importStar(require("node:vm")); 51 | const diagnostics_1 = require("./ivy/diagnostics"); 52 | const paths_1 = require("./ivy/paths"); 53 | const inline_resource_1 = require("./loaders/inline-resource"); 54 | const replace_resources_1 = require("./transformers/replace_resources"); 55 | class WebpackResourceLoader { 56 | _parentCompilation; 57 | _fileDependencies = new Map(); 58 | _reverseDependencies = new Map(); 59 | fileCache; 60 | assetCache; 61 | modifiedResources = new Set(); 62 | outputPathCounter = 1; 63 | inlineDataLoaderPath = inline_resource_1.InlineAngularResourceLoaderPath; 64 | constructor(shouldCache) { 65 | if (shouldCache) { 66 | this.fileCache = new Map(); 67 | this.assetCache = new Map(); 68 | } 69 | } 70 | update(parentCompilation, changedFiles) { 71 | this._parentCompilation = parentCompilation; 72 | // Update resource cache and modified resources 73 | this.modifiedResources.clear(); 74 | if (changedFiles) { 75 | for (const changedFile of changedFiles) { 76 | const changedFileNormalized = (0, paths_1.normalizePath)(changedFile); 77 | this.assetCache?.delete(changedFileNormalized); 78 | for (const affectedResource of this.getAffectedResources(changedFile)) { 79 | const affectedResourceNormalized = (0, paths_1.normalizePath)(affectedResource); 80 | this.fileCache?.delete(affectedResourceNormalized); 81 | this.modifiedResources.add(affectedResource); 82 | for (const effectedDependencies of this.getResourceDependencies(affectedResourceNormalized)) { 83 | this.assetCache?.delete((0, paths_1.normalizePath)(effectedDependencies)); 84 | } 85 | } 86 | } 87 | } 88 | else { 89 | this.fileCache?.clear(); 90 | this.assetCache?.clear(); 91 | } 92 | // Re-emit all assets for un-effected files 93 | if (this.assetCache) { 94 | for (const [, { name, source, info }] of this.assetCache) { 95 | this._parentCompilation.emitAsset(name, source, info); 96 | } 97 | } 98 | } 99 | clearParentCompilation() { 100 | this._parentCompilation = undefined; 101 | } 102 | getModifiedResourceFiles() { 103 | return this.modifiedResources; 104 | } 105 | getResourceDependencies(filePath) { 106 | return this._fileDependencies.get(filePath) || []; 107 | } 108 | getAffectedResources(file) { 109 | return this._reverseDependencies.get(file) || []; 110 | } 111 | setAffectedResources(file, resources) { 112 | this._reverseDependencies.set(file, new Set(resources)); 113 | } 114 | // eslint-disable-next-line max-lines-per-function 115 | async _compile(filePath, data, fileExtension, resourceType, containingFile) { 116 | if (!this._parentCompilation) { 117 | throw new Error('WebpackResourceLoader cannot be used without parentCompilation'); 118 | } 119 | const { context, webpack } = this._parentCompilation.compiler; 120 | const { EntryPlugin, NormalModule, library, node, sources, util: { createHash }, } = webpack; 121 | const getEntry = () => { 122 | if (filePath) { 123 | return `${filePath}?${replace_resources_1.NG_COMPONENT_RESOURCE_QUERY}`; 124 | } 125 | else if (resourceType) { 126 | return ( 127 | // app.component.ts-2.css?ngResource!=!@ngtools/webpack/src/loaders/inline-resource.js!app.component.ts 128 | `${containingFile}-${this.outputPathCounter}.${fileExtension}` + 129 | `?${replace_resources_1.NG_COMPONENT_RESOURCE_QUERY}!=!${this.inlineDataLoaderPath}!${containingFile}`); 130 | } 131 | else if (data) { 132 | // Create a special URL for reading the resource from memory 133 | return `angular-resource:${resourceType},${createHash('xxhash64') 134 | .update(data) 135 | .digest('hex')}`; 136 | } 137 | throw new Error(`"filePath", "resourceType" or "data" must be specified.`); 138 | }; 139 | const entry = getEntry(); 140 | // Simple sanity check. 141 | if (filePath?.match(/\.[jt]s$/)) { 142 | throw new Error(`Cannot use a JavaScript or TypeScript file (${filePath}) in a component's styleUrls or templateUrl.`); 143 | } 144 | const outputFilePath = filePath || 145 | `${containingFile}-angular-inline--${this.outputPathCounter++}.${resourceType === 'template' ? 'html' : 'css'}`; 146 | const outputOptions = { 147 | filename: outputFilePath, 148 | library: { 149 | type: 'var', 150 | name: 'resource', 151 | }, 152 | }; 153 | const childCompiler = this._parentCompilation.createChildCompiler('angular-compiler:resource', outputOptions, [ 154 | new node.NodeTemplatePlugin(), 155 | new node.NodeTargetPlugin(), 156 | new EntryPlugin(context, entry, { name: 'resource' }), 157 | new library.EnableLibraryPlugin('var'), 158 | ]); 159 | childCompiler.hooks.thisCompilation.tap('angular-compiler', (compilation, { normalModuleFactory }) => { 160 | // If no data is provided, the resource will be read from the filesystem 161 | if (data !== undefined) { 162 | normalModuleFactory.hooks.resolveForScheme 163 | .for('angular-resource') 164 | .tap('angular-compiler', (resourceData) => { 165 | if (filePath) { 166 | resourceData.path = filePath; 167 | resourceData.resource = filePath; 168 | } 169 | return true; 170 | }); 171 | NormalModule.getCompilationHooks(compilation) 172 | .readResourceForScheme.for('angular-resource') 173 | .tap('angular-compiler', () => data); 174 | compilation[inline_resource_1.InlineAngularResourceSymbol] = data; 175 | } 176 | compilation.hooks.additionalAssets.tap('angular-compiler', () => { 177 | const asset = compilation.assets[outputFilePath]; 178 | if (!asset) { 179 | return; 180 | } 181 | try { 182 | const output = this._evaluate(outputFilePath, asset.source().toString()); 183 | if (typeof output === 'string') { 184 | compilation.assets[outputFilePath] = new sources.RawSource(output); 185 | } 186 | } 187 | catch (error) { 188 | (0, node_assert_1.default)(error instanceof Error, 'catch clause variable is not an Error instance'); 189 | // Use compilation errors, as otherwise webpack will choke 190 | (0, diagnostics_1.addError)(compilation, error.message); 191 | } 192 | }); 193 | }); 194 | let finalContent; 195 | childCompiler.hooks.compilation.tap('angular-compiler', (childCompilation) => { 196 | childCompilation.hooks.processAssets.tap({ name: 'angular-compiler', stage: webpack.Compilation.PROCESS_ASSETS_STAGE_REPORT }, () => { 197 | finalContent = childCompilation.assets[outputFilePath]?.source().toString(); 198 | for (const { files } of childCompilation.chunks) { 199 | for (const file of files) { 200 | childCompilation.deleteAsset(file); 201 | } 202 | } 203 | }); 204 | }); 205 | return new Promise((resolve, reject) => { 206 | childCompiler.runAsChild((error, _, childCompilation) => { 207 | if (error) { 208 | reject(error); 209 | return; 210 | } 211 | else if (!childCompilation) { 212 | reject(new Error('Unknown child compilation error')); 213 | return; 214 | } 215 | // Workaround to attempt to reduce memory usage of child compilations. 216 | // This removes the child compilation from the main compilation and manually propagates 217 | // all dependencies, warnings, and errors. 218 | const parent = childCompiler.parentCompilation; 219 | if (parent) { 220 | parent.children = parent.children.filter((child) => child !== childCompilation); 221 | let fileDependencies; 222 | for (const dependency of childCompilation.fileDependencies) { 223 | // Skip paths that do not appear to be files (have no extension). 224 | // `fileDependencies` can contain directories and not just files which can 225 | // cause incorrect cache invalidation on rebuilds. 226 | if (!path.extname(dependency)) { 227 | continue; 228 | } 229 | if (data && containingFile && dependency.endsWith(entry)) { 230 | // use containing file if the resource was inline 231 | parent.fileDependencies.add(containingFile); 232 | } 233 | else { 234 | parent.fileDependencies.add(dependency); 235 | } 236 | // Save the dependencies for this resource. 237 | if (filePath) { 238 | const resolvedFile = (0, paths_1.normalizePath)(dependency); 239 | const entry = this._reverseDependencies.get(resolvedFile); 240 | if (entry) { 241 | entry.add(filePath); 242 | } 243 | else { 244 | this._reverseDependencies.set(resolvedFile, new Set([filePath])); 245 | } 246 | if (fileDependencies) { 247 | fileDependencies.add(dependency); 248 | } 249 | else { 250 | fileDependencies = new Set([dependency]); 251 | this._fileDependencies.set(filePath, fileDependencies); 252 | } 253 | } 254 | } 255 | parent.contextDependencies.addAll(childCompilation.contextDependencies); 256 | parent.missingDependencies.addAll(childCompilation.missingDependencies); 257 | parent.buildDependencies.addAll(childCompilation.buildDependencies); 258 | parent.warnings.push(...childCompilation.warnings); 259 | parent.errors.push(...childCompilation.errors); 260 | if (this.assetCache) { 261 | for (const { info, name, source } of childCompilation.getAssets()) { 262 | // Use the originating file as the cache key if present 263 | // Otherwise, generate a cache key based on the generated name 264 | const cacheKey = info.sourceFilename ?? `!![GENERATED]:${name}`; 265 | this.assetCache.set(cacheKey, { info, name, source }); 266 | } 267 | } 268 | } 269 | resolve({ 270 | content: finalContent ?? '', 271 | success: childCompilation.errors?.length === 0, 272 | }); 273 | }); 274 | }); 275 | } 276 | _evaluate(filename, source) { 277 | // Evaluate code 278 | // css-loader requires the btoa function to exist to correctly generate inline sourcemaps 279 | const context = { 280 | btoa(input) { 281 | return node_buffer_1.Buffer.from(input).toString('base64'); 282 | }, 283 | }; 284 | try { 285 | vm.runInNewContext(source, context, { filename }); 286 | } 287 | catch { 288 | // Error are propagated through the child compilation. 289 | return null; 290 | } 291 | if (typeof context.resource === 'string') { 292 | return context.resource; 293 | } 294 | else if (typeof context.resource?.default === 'string') { 295 | return context.resource.default; 296 | } 297 | throw new Error(`The loader "${filename}" didn't return a string.`); 298 | } 299 | async get(filePath) { 300 | const normalizedFile = (0, paths_1.normalizePath)(filePath); 301 | let compilationResult = this.fileCache?.get(normalizedFile); 302 | if (compilationResult === undefined) { 303 | // cache miss so compile resource 304 | compilationResult = await this._compile(filePath); 305 | // Only cache if compilation was successful 306 | if (this.fileCache && compilationResult.success) { 307 | this.fileCache.set(normalizedFile, compilationResult); 308 | } 309 | } 310 | return compilationResult.content; 311 | } 312 | async process(data, fileExtension, resourceType, containingFile) { 313 | if (data.trim().length === 0) { 314 | return ''; 315 | } 316 | const compilationResult = await this._compile(undefined, data, fileExtension, resourceType, containingFile); 317 | return compilationResult.content; 318 | } 319 | } 320 | exports.WebpackResourceLoader = WebpackResourceLoader; 321 | -------------------------------------------------------------------------------- /src/transformers/elide_imports.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import * as ts from 'typescript'; 9 | export declare function elideImports(sourceFile: ts.SourceFile, removedNodes: ts.Node[], getTypeChecker: () => ts.TypeChecker, compilerOptions: ts.CompilerOptions): Set; 10 | -------------------------------------------------------------------------------- /src/transformers/elide_imports.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.elideImports = elideImports; 44 | const ts = __importStar(require("typescript")); 45 | // Remove imports for which all identifiers have been removed. 46 | // Needs type checker, and works even if it's not the first transformer. 47 | // Works by removing imports for symbols whose identifiers have all been removed. 48 | // Doesn't use the `symbol.declarations` because that previous transforms might have removed nodes 49 | // but the type checker doesn't know. 50 | // See https://github.com/Microsoft/TypeScript/issues/17552 for more information. 51 | function elideImports(sourceFile, removedNodes, getTypeChecker, compilerOptions) { 52 | const importNodeRemovals = new Set(); 53 | if (removedNodes.length === 0) { 54 | return importNodeRemovals; 55 | } 56 | const typeChecker = getTypeChecker(); 57 | // Collect all imports and used identifiers 58 | const usedSymbols = new Set(); 59 | const imports = []; 60 | ts.forEachChild(sourceFile, function visit(node) { 61 | // Skip removed nodes. 62 | if (removedNodes.includes(node)) { 63 | return; 64 | } 65 | // Consider types for 'implements' as unused. 66 | // A HeritageClause token can also be an 'AbstractKeyword' 67 | // which in that case we should not elide the import. 68 | if (ts.isHeritageClause(node) && node.token === ts.SyntaxKind.ImplementsKeyword) { 69 | return; 70 | } 71 | // Record import and skip 72 | if (ts.isImportDeclaration(node)) { 73 | if (!node.importClause?.isTypeOnly) { 74 | imports.push(node); 75 | } 76 | return; 77 | } 78 | // Type reference imports do not need to be emitted when emitDecoratorMetadata is disabled. 79 | if (ts.isTypeReferenceNode(node) && !compilerOptions.emitDecoratorMetadata) { 80 | return; 81 | } 82 | let symbol; 83 | switch (node.kind) { 84 | case ts.SyntaxKind.Identifier: 85 | if (node.parent && ts.isShorthandPropertyAssignment(node.parent)) { 86 | const shorthandSymbol = typeChecker.getShorthandAssignmentValueSymbol(node.parent); 87 | if (shorthandSymbol) { 88 | symbol = shorthandSymbol; 89 | } 90 | } 91 | else { 92 | symbol = typeChecker.getSymbolAtLocation(node); 93 | } 94 | break; 95 | case ts.SyntaxKind.ExportSpecifier: 96 | symbol = typeChecker.getExportSpecifierLocalTargetSymbol(node); 97 | break; 98 | case ts.SyntaxKind.ShorthandPropertyAssignment: 99 | symbol = typeChecker.getShorthandAssignmentValueSymbol(node); 100 | break; 101 | } 102 | if (symbol) { 103 | usedSymbols.add(symbol); 104 | } 105 | ts.forEachChild(node, visit); 106 | }); 107 | if (imports.length === 0) { 108 | return importNodeRemovals; 109 | } 110 | const isUnused = (node) => { 111 | // Do not remove JSX factory imports 112 | if (node.text === compilerOptions.jsxFactory) { 113 | return false; 114 | } 115 | const symbol = typeChecker.getSymbolAtLocation(node); 116 | return symbol && !usedSymbols.has(symbol); 117 | }; 118 | for (const node of imports) { 119 | if (!node.importClause) { 120 | // "import 'abc';" 121 | continue; 122 | } 123 | const namedBindings = node.importClause.namedBindings; 124 | if (namedBindings && ts.isNamespaceImport(namedBindings)) { 125 | // "import * as XYZ from 'abc';" 126 | if (isUnused(namedBindings.name)) { 127 | importNodeRemovals.add(node); 128 | } 129 | } 130 | else { 131 | const specifierNodeRemovals = []; 132 | let clausesCount = 0; 133 | // "import { XYZ, ... } from 'abc';" 134 | if (namedBindings && ts.isNamedImports(namedBindings)) { 135 | let removedClausesCount = 0; 136 | clausesCount += namedBindings.elements.length; 137 | for (const specifier of namedBindings.elements) { 138 | if (specifier.isTypeOnly || isUnused(specifier.name)) { 139 | removedClausesCount++; 140 | // in case we don't have any more namedImports we should remove the parent ie the {} 141 | const nodeToRemove = clausesCount === removedClausesCount ? specifier.parent : specifier; 142 | specifierNodeRemovals.push(nodeToRemove); 143 | } 144 | } 145 | } 146 | // "import XYZ from 'abc';" 147 | if (node.importClause.name) { 148 | clausesCount++; 149 | if (node.importClause.isTypeOnly || isUnused(node.importClause.name)) { 150 | specifierNodeRemovals.push(node.importClause.name); 151 | } 152 | } 153 | if (specifierNodeRemovals.length === clausesCount) { 154 | importNodeRemovals.add(node); 155 | } 156 | else { 157 | for (const specifierNodeRemoval of specifierNodeRemovals) { 158 | importNodeRemovals.add(specifierNodeRemoval); 159 | } 160 | } 161 | } 162 | } 163 | return importNodeRemovals; 164 | } 165 | -------------------------------------------------------------------------------- /src/transformers/find_image_domains.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import ts from 'typescript'; 9 | export declare function findImageDomains(imageDomains: Set): ts.TransformerFactory; 10 | -------------------------------------------------------------------------------- /src/transformers/find_image_domains.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __importDefault = (this && this.__importDefault) || function (mod) { 10 | return (mod && mod.__esModule) ? mod : { "default": mod }; 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | exports.findImageDomains = findImageDomains; 14 | const typescript_1 = __importDefault(require("typescript")); 15 | const BUILTIN_LOADERS = new Set([ 16 | 'provideCloudflareLoader', 17 | 'provideCloudinaryLoader', 18 | 'provideImageKitLoader', 19 | 'provideImgixLoader', 20 | ]); 21 | const URL_REGEX = /(https?:\/\/[^/]*)\//g; 22 | function findImageDomains(imageDomains) { 23 | return (context) => { 24 | return (sourceFile) => { 25 | const isBuiltinImageLoader = (node) => { 26 | return BUILTIN_LOADERS.has(node.expression.getText()); 27 | }; 28 | const findDomainString = (node) => { 29 | if (typescript_1.default.isStringLiteral(node) || 30 | typescript_1.default.isTemplateHead(node) || 31 | typescript_1.default.isTemplateMiddle(node) || 32 | typescript_1.default.isTemplateTail(node)) { 33 | const domain = node.text.match(URL_REGEX); 34 | if (domain && domain[0]) { 35 | imageDomains.add(domain[0]); 36 | return node; 37 | } 38 | } 39 | typescript_1.default.visitEachChild(node, findDomainString, context); 40 | return node; 41 | }; 42 | function isImageProviderKey(property) { 43 | return (typescript_1.default.isPropertyAssignment(property) && 44 | property.name.getText() === 'provide' && 45 | property.initializer.getText() === 'IMAGE_LOADER'); 46 | } 47 | function isImageProviderValue(property) { 48 | return typescript_1.default.isPropertyAssignment(property) && property.name.getText() === 'useValue'; 49 | } 50 | function checkForDomain(node) { 51 | if (node.properties.find(isImageProviderKey)) { 52 | const value = node.properties.find(isImageProviderValue); 53 | if (value && typescript_1.default.isPropertyAssignment(value)) { 54 | if (typescript_1.default.isArrowFunction(value.initializer) || 55 | typescript_1.default.isFunctionExpression(value.initializer)) { 56 | typescript_1.default.visitEachChild(node, findDomainString, context); 57 | } 58 | } 59 | } 60 | } 61 | function findImageLoaders(node) { 62 | if (typescript_1.default.isCallExpression(node)) { 63 | if (isBuiltinImageLoader(node)) { 64 | const firstArg = node.arguments[0]; 65 | if (typescript_1.default.isStringLiteralLike(firstArg)) { 66 | imageDomains.add(firstArg.text); 67 | } 68 | } 69 | } 70 | else if (typescript_1.default.isObjectLiteralExpression(node)) { 71 | checkForDomain(node); 72 | } 73 | return node; 74 | } 75 | function findProvidersAssignment(node) { 76 | if (typescript_1.default.isPropertyAssignment(node)) { 77 | if (typescript_1.default.isIdentifier(node.name) && node.name.escapedText === 'providers') { 78 | typescript_1.default.visitEachChild(node.initializer, findImageLoaders, context); 79 | } 80 | } 81 | return node; 82 | } 83 | function findFeaturesAssignment(node) { 84 | if (typescript_1.default.isPropertyAssignment(node)) { 85 | if (typescript_1.default.isIdentifier(node.name) && 86 | node.name.escapedText === 'features' && 87 | typescript_1.default.isArrayLiteralExpression(node.initializer)) { 88 | const providerElement = node.initializer.elements.find(isProvidersFeatureElement); 89 | if (providerElement && 90 | typescript_1.default.isCallExpression(providerElement) && 91 | providerElement.arguments[0]) { 92 | typescript_1.default.visitEachChild(providerElement.arguments[0], findImageLoaders, context); 93 | } 94 | } 95 | } 96 | return node; 97 | } 98 | function isProvidersFeatureElement(node) { 99 | return (typescript_1.default.isCallExpression(node) && 100 | typescript_1.default.isPropertyAccessExpression(node.expression) && 101 | typescript_1.default.isIdentifier(node.expression.expression) && 102 | node.expression.expression.escapedText === 'i0' && 103 | typescript_1.default.isIdentifier(node.expression.name) && 104 | node.expression.name.escapedText === 'ɵɵProvidersFeature'); 105 | } 106 | function findPropertyDeclaration(node) { 107 | if (typescript_1.default.isPropertyDeclaration(node) && 108 | typescript_1.default.isIdentifier(node.name) && 109 | node.initializer && 110 | typescript_1.default.isCallExpression(node.initializer) && 111 | node.initializer.arguments[0]) { 112 | if (node.name.escapedText === 'ɵinj') { 113 | typescript_1.default.visitEachChild(node.initializer.arguments[0], findProvidersAssignment, context); 114 | } 115 | else if (node.name.escapedText === 'ɵcmp') { 116 | typescript_1.default.visitEachChild(node.initializer.arguments[0], findFeaturesAssignment, context); 117 | } 118 | } 119 | return node; 120 | } 121 | function findClassDeclaration(node) { 122 | if (typescript_1.default.isClassDeclaration(node)) { 123 | typescript_1.default.visitEachChild(node, findPropertyDeclaration, context); 124 | } 125 | return node; 126 | } 127 | typescript_1.default.visitEachChild(sourceFile, findClassDeclaration, context); 128 | return sourceFile; 129 | }; 130 | }; 131 | } 132 | -------------------------------------------------------------------------------- /src/transformers/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | export * from './elide_imports'; 9 | export * from './replace_resources'; 10 | -------------------------------------------------------------------------------- /src/transformers/index.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 21 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 22 | }; 23 | Object.defineProperty(exports, "__esModule", { value: true }); 24 | __exportStar(require("./elide_imports"), exports); 25 | __exportStar(require("./replace_resources"), exports); 26 | -------------------------------------------------------------------------------- /src/transformers/remove-ivy-jit-support-calls.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import * as ts from 'typescript'; 9 | export declare function removeIvyJitSupportCalls(classMetadata: boolean, ngModuleScope: boolean, debugInfo: boolean, getTypeChecker: () => ts.TypeChecker): ts.TransformerFactory; 10 | -------------------------------------------------------------------------------- /src/transformers/remove-ivy-jit-support-calls.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.removeIvyJitSupportCalls = removeIvyJitSupportCalls; 44 | const ts = __importStar(require("typescript")); 45 | const elide_imports_1 = require("./elide_imports"); 46 | function removeIvyJitSupportCalls(classMetadata, ngModuleScope, debugInfo, getTypeChecker) { 47 | return (context) => { 48 | const removedNodes = []; 49 | const visitNode = (node) => { 50 | const innerExpression = ts.isExpressionStatement(node) ? getIifeExpression(node) : null; 51 | if (innerExpression) { 52 | if (ngModuleScope && 53 | ts.isBinaryExpression(innerExpression) && 54 | isIvyPrivateCallExpression(innerExpression.right, 'ɵɵsetNgModuleScope')) { 55 | removedNodes.push(innerExpression); 56 | return undefined; 57 | } 58 | if (classMetadata) { 59 | const expression = ts.isBinaryExpression(innerExpression) 60 | ? innerExpression.right 61 | : innerExpression; 62 | if (isIvyPrivateCallExpression(expression, 'ɵsetClassMetadata') || 63 | isIvyPrivateCallExpression(expression, 'ɵsetClassMetadataAsync')) { 64 | removedNodes.push(innerExpression); 65 | return undefined; 66 | } 67 | } 68 | if (debugInfo && 69 | ts.isBinaryExpression(innerExpression) && 70 | isIvyPrivateCallExpression(innerExpression.right, 'ɵsetClassDebugInfo')) { 71 | removedNodes.push(innerExpression); 72 | return undefined; 73 | } 74 | } 75 | return ts.visitEachChild(node, visitNode, context); 76 | }; 77 | return (sourceFile) => { 78 | let updatedSourceFile = ts.visitEachChild(sourceFile, visitNode, context); 79 | if (removedNodes.length > 0) { 80 | // Remove any unused imports 81 | const importRemovals = (0, elide_imports_1.elideImports)(updatedSourceFile, removedNodes, getTypeChecker, context.getCompilerOptions()); 82 | if (importRemovals.size > 0) { 83 | updatedSourceFile = ts.visitEachChild(updatedSourceFile, function visitForRemoval(node) { 84 | return importRemovals.has(node) 85 | ? undefined 86 | : ts.visitEachChild(node, visitForRemoval, context); 87 | }, context); 88 | } 89 | } 90 | return updatedSourceFile; 91 | }; 92 | }; 93 | } 94 | // Each Ivy private call expression is inside an IIFE 95 | function getIifeExpression(exprStmt) { 96 | const expression = exprStmt.expression; 97 | if (!expression || !ts.isCallExpression(expression) || expression.arguments.length !== 0) { 98 | return null; 99 | } 100 | const parenExpr = expression; 101 | if (!ts.isParenthesizedExpression(parenExpr.expression)) { 102 | return null; 103 | } 104 | const funExpr = parenExpr.expression.expression; 105 | if (!ts.isFunctionExpression(funExpr) && !ts.isArrowFunction(funExpr)) { 106 | return null; 107 | } 108 | if (!ts.isBlock(funExpr.body)) { 109 | return funExpr.body; 110 | } 111 | const innerStmts = funExpr.body.statements; 112 | if (innerStmts.length !== 1) { 113 | return null; 114 | } 115 | const innerExprStmt = innerStmts[0]; 116 | if (!ts.isExpressionStatement(innerExprStmt)) { 117 | return null; 118 | } 119 | return innerExprStmt.expression; 120 | } 121 | function isIvyPrivateCallExpression(expression, name) { 122 | // Now we're in the IIFE and have the inner expression statement. We can check if it matches 123 | // a private Ivy call. 124 | if (!ts.isCallExpression(expression)) { 125 | return false; 126 | } 127 | const propAccExpr = expression.expression; 128 | if (!ts.isPropertyAccessExpression(propAccExpr)) { 129 | return false; 130 | } 131 | if (propAccExpr.name.text !== name) { 132 | return false; 133 | } 134 | return true; 135 | } 136 | -------------------------------------------------------------------------------- /src/transformers/replace_resources.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import * as ts from 'typescript'; 9 | export declare const NG_COMPONENT_RESOURCE_QUERY = "ngResource"; 10 | export declare function replaceResources(shouldTransform: (fileName: string) => boolean, getTypeChecker: () => ts.TypeChecker, inlineStyleFileExtension?: string): ts.TransformerFactory; 11 | export declare function getResourceUrl(node: ts.Node): string | null; 12 | -------------------------------------------------------------------------------- /src/transformers/replace_resources.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __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 | })); 20 | var __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 | }); 25 | var __importStar = (this && this.__importStar) || (function () { 26 | var ownKeys = function(o) { 27 | ownKeys = Object.getOwnPropertyNames || function (o) { 28 | var ar = []; 29 | for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; 30 | return ar; 31 | }; 32 | return ownKeys(o); 33 | }; 34 | return function (mod) { 35 | if (mod && mod.__esModule) return mod; 36 | var result = {}; 37 | if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); 38 | __setModuleDefault(result, mod); 39 | return result; 40 | }; 41 | })(); 42 | Object.defineProperty(exports, "__esModule", { value: true }); 43 | exports.NG_COMPONENT_RESOURCE_QUERY = void 0; 44 | exports.replaceResources = replaceResources; 45 | exports.getResourceUrl = getResourceUrl; 46 | const ts = __importStar(require("typescript")); 47 | const inline_resource_1 = require("../loaders/inline-resource"); 48 | exports.NG_COMPONENT_RESOURCE_QUERY = 'ngResource'; 49 | function replaceResources(shouldTransform, getTypeChecker, inlineStyleFileExtension) { 50 | return (context) => { 51 | const typeChecker = getTypeChecker(); 52 | const resourceImportDeclarations = []; 53 | const moduleKind = context.getCompilerOptions().module; 54 | const nodeFactory = context.factory; 55 | const visitNode = (node) => { 56 | if (ts.isClassDeclaration(node)) { 57 | const decorators = ts.getDecorators(node); 58 | if (!decorators || decorators.length === 0) { 59 | return node; 60 | } 61 | return nodeFactory.updateClassDeclaration(node, [ 62 | ...decorators.map((current) => visitDecorator(nodeFactory, current, typeChecker, resourceImportDeclarations, moduleKind, inlineStyleFileExtension)), 63 | ...(ts.getModifiers(node) ?? []), 64 | ], node.name, node.typeParameters, node.heritageClauses, node.members); 65 | } 66 | return ts.visitEachChild(node, visitNode, context); 67 | }; 68 | return (sourceFile) => { 69 | if (!shouldTransform(sourceFile.fileName)) { 70 | return sourceFile; 71 | } 72 | const updatedSourceFile = ts.visitNode(sourceFile, visitNode); 73 | if (resourceImportDeclarations.length) { 74 | // Add resource imports 75 | return context.factory.updateSourceFile(updatedSourceFile, ts.setTextRange(context.factory.createNodeArray([ 76 | ...resourceImportDeclarations, 77 | ...updatedSourceFile.statements, 78 | ]), updatedSourceFile.statements)); 79 | } 80 | return updatedSourceFile; 81 | }; 82 | }; 83 | } 84 | function visitDecorator(nodeFactory, node, typeChecker, resourceImportDeclarations, moduleKind, inlineStyleFileExtension) { 85 | if (!isComponentDecorator(node, typeChecker)) { 86 | return node; 87 | } 88 | if (!ts.isCallExpression(node.expression)) { 89 | return node; 90 | } 91 | const decoratorFactory = node.expression; 92 | const args = decoratorFactory.arguments; 93 | if (args.length !== 1 || !ts.isObjectLiteralExpression(args[0])) { 94 | // Unsupported component metadata 95 | return node; 96 | } 97 | const objectExpression = args[0]; 98 | const styleReplacements = []; 99 | // visit all properties 100 | let properties = ts.visitNodes(objectExpression.properties, (node) => ts.isObjectLiteralElementLike(node) 101 | ? visitComponentMetadata(nodeFactory, node, styleReplacements, resourceImportDeclarations, moduleKind, inlineStyleFileExtension) 102 | : node); 103 | // replace properties with updated properties 104 | if (styleReplacements.length > 0) { 105 | const styleProperty = nodeFactory.createPropertyAssignment(nodeFactory.createIdentifier('styles'), nodeFactory.createArrayLiteralExpression(styleReplacements)); 106 | properties = nodeFactory.createNodeArray([...properties, styleProperty]); 107 | } 108 | return nodeFactory.updateDecorator(node, nodeFactory.updateCallExpression(decoratorFactory, decoratorFactory.expression, decoratorFactory.typeArguments, [nodeFactory.updateObjectLiteralExpression(objectExpression, properties)])); 109 | } 110 | function visitComponentMetadata(nodeFactory, node, styleReplacements, resourceImportDeclarations, moduleKind = ts.ModuleKind.ES2015, inlineStyleFileExtension) { 111 | if (!ts.isPropertyAssignment(node) || ts.isComputedPropertyName(node.name)) { 112 | return node; 113 | } 114 | const name = node.name.text; 115 | switch (name) { 116 | case 'moduleId': 117 | return undefined; 118 | case 'templateUrl': { 119 | const url = getResourceUrl(node.initializer); 120 | if (!url) { 121 | return node; 122 | } 123 | const importName = createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind); 124 | if (!importName) { 125 | return node; 126 | } 127 | return nodeFactory.updatePropertyAssignment(node, nodeFactory.createIdentifier('template'), importName); 128 | } 129 | case 'styles': 130 | case 'styleUrl': 131 | case 'styleUrls': { 132 | const isInlineStyle = name === 'styles'; 133 | let styles; 134 | if (ts.isStringLiteralLike(node.initializer)) { 135 | styles = [ 136 | transformInlineStyleLiteral(node.initializer, nodeFactory, isInlineStyle, inlineStyleFileExtension, resourceImportDeclarations, moduleKind), 137 | ]; 138 | } 139 | else if (ts.isArrayLiteralExpression(node.initializer)) { 140 | styles = ts.visitNodes(node.initializer.elements, (node) => transformInlineStyleLiteral(node, nodeFactory, isInlineStyle, inlineStyleFileExtension, resourceImportDeclarations, moduleKind)); 141 | } 142 | else { 143 | return node; 144 | } 145 | // Styles should be placed first 146 | if (isInlineStyle) { 147 | styleReplacements.unshift(...styles); 148 | } 149 | else { 150 | styleReplacements.push(...styles); 151 | } 152 | return undefined; 153 | } 154 | default: 155 | return node; 156 | } 157 | } 158 | function transformInlineStyleLiteral(node, nodeFactory, isInlineStyle, inlineStyleFileExtension, resourceImportDeclarations, moduleKind) { 159 | if (!ts.isStringLiteralLike(node)) { 160 | return node; 161 | } 162 | // Don't transform empty strings as PostCSS will choke on them. No work to do anyways. 163 | if (node.text === '') { 164 | return node; 165 | } 166 | if (!isInlineStyle) { 167 | const url = getResourceUrl(node); 168 | return url 169 | ? createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind) 170 | : node; 171 | } 172 | if (!inlineStyleFileExtension) { 173 | return nodeFactory.createStringLiteral(node.text); 174 | } 175 | const data = Buffer.from(node.text).toString('base64'); 176 | const containingFile = node.getSourceFile().fileName; 177 | // app.component.ts.css?ngResource!=!@ngtools/webpack/src/loaders/inline-resource.js?data=...!app.component.ts 178 | const url = `${containingFile}.${inlineStyleFileExtension}?${exports.NG_COMPONENT_RESOURCE_QUERY}` + 179 | `!=!${inline_resource_1.InlineAngularResourceLoaderPath}?data=${encodeURIComponent(data)}!${containingFile}`; 180 | return createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind); 181 | } 182 | function getResourceUrl(node) { 183 | // only analyze strings 184 | if (!ts.isStringLiteralLike(node)) { 185 | return null; 186 | } 187 | return `${/^\.?\.\//.test(node.text) ? '' : './'}${node.text}?${exports.NG_COMPONENT_RESOURCE_QUERY}`; 188 | } 189 | function isComponentDecorator(node, typeChecker) { 190 | if (!ts.isDecorator(node)) { 191 | return false; 192 | } 193 | const origin = getDecoratorOrigin(node, typeChecker); 194 | if (origin && origin.module === '@angular/core' && origin.name === 'Component') { 195 | return true; 196 | } 197 | return false; 198 | } 199 | function createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind) { 200 | const urlLiteral = nodeFactory.createStringLiteral(url); 201 | if (moduleKind < ts.ModuleKind.ES2015) { 202 | return nodeFactory.createCallExpression(nodeFactory.createIdentifier('require'), [], [urlLiteral]); 203 | } 204 | else { 205 | const importName = nodeFactory.createIdentifier(`__NG_CLI_RESOURCE__${resourceImportDeclarations.length}`); 206 | resourceImportDeclarations.push(nodeFactory.createImportDeclaration(undefined, nodeFactory.createImportClause(false, importName, undefined), urlLiteral)); 207 | return importName; 208 | } 209 | } 210 | function getDecoratorOrigin(decorator, typeChecker) { 211 | if (!ts.isCallExpression(decorator.expression)) { 212 | return null; 213 | } 214 | let identifier; 215 | let name = ''; 216 | if (ts.isPropertyAccessExpression(decorator.expression.expression)) { 217 | identifier = decorator.expression.expression.expression; 218 | name = decorator.expression.expression.name.text; 219 | } 220 | else if (ts.isIdentifier(decorator.expression.expression)) { 221 | identifier = decorator.expression.expression; 222 | } 223 | else { 224 | return null; 225 | } 226 | // NOTE: resolver.getReferencedImportDeclaration would work as well but is internal 227 | const symbol = typeChecker.getSymbolAtLocation(identifier); 228 | if (symbol && symbol.declarations && symbol.declarations.length > 0) { 229 | const declaration = symbol.declarations[0]; 230 | let module; 231 | if (ts.isImportSpecifier(declaration)) { 232 | name = (declaration.propertyName || declaration.name).text; 233 | module = declaration.parent.parent.parent.moduleSpecifier.text; 234 | } 235 | else if (ts.isNamespaceImport(declaration)) { 236 | // Use the name from the decorator namespace property access 237 | module = declaration.parent.parent.moduleSpecifier.text; 238 | } 239 | else if (ts.isImportClause(declaration)) { 240 | name = declaration.name.text; 241 | module = declaration.parent.moduleSpecifier.text; 242 | } 243 | else { 244 | return null; 245 | } 246 | return { name, module }; 247 | } 248 | return null; 249 | } 250 | -------------------------------------------------------------------------------- /src/transformers/spec_helpers.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright Google LLC All Rights Reserved. 4 | * 5 | * Use of this source code is governed by an MIT-style license that can be 6 | * found in the LICENSE file at https://angular.dev/license 7 | */ 8 | import ts from 'typescript'; 9 | export declare function createTypescriptContext(content: string, additionalFiles?: Record, useLibs?: boolean, extraCompilerOptions?: ts.CompilerOptions, jsxFile?: boolean): { 10 | compilerHost: ts.CompilerHost; 11 | program: ts.Program; 12 | }; 13 | export declare function transformTypescript(content: string | undefined, transformers: ts.TransformerFactory[], program?: ts.Program, compilerHost?: ts.CompilerHost): string | undefined; 14 | -------------------------------------------------------------------------------- /src/transformers/spec_helpers.js: -------------------------------------------------------------------------------- 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.dev/license 8 | */ 9 | var __importDefault = (this && this.__importDefault) || function (mod) { 10 | return (mod && mod.__esModule) ? mod : { "default": mod }; 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | exports.createTypescriptContext = createTypescriptContext; 14 | exports.transformTypescript = transformTypescript; 15 | const node_path_1 = require("node:path"); 16 | const typescript_1 = __importDefault(require("typescript")); 17 | // Test transform helpers. 18 | const basefileName = 'test-file.ts'; 19 | function createTypescriptContext(content, additionalFiles, useLibs = false, extraCompilerOptions = {}, jsxFile = false) { 20 | const fileName = basefileName + (jsxFile ? 'x' : ''); 21 | // Set compiler options. 22 | const compilerOptions = { 23 | noEmitOnError: useLibs, 24 | allowJs: true, 25 | newLine: typescript_1.default.NewLineKind.LineFeed, 26 | moduleResolution: typescript_1.default.ModuleResolutionKind.Node10, 27 | module: typescript_1.default.ModuleKind.ES2020, 28 | target: typescript_1.default.ScriptTarget.ES2020, 29 | skipLibCheck: true, 30 | sourceMap: false, 31 | importHelpers: true, 32 | experimentalDecorators: true, 33 | types: [], 34 | ...extraCompilerOptions, 35 | }; 36 | // Create compiler host. 37 | const compilerHost = typescript_1.default.createCompilerHost(compilerOptions, true); 38 | const baseFileExists = compilerHost.fileExists; 39 | compilerHost.fileExists = function (compilerFileName) { 40 | return (compilerFileName === fileName || 41 | !!additionalFiles?.[(0, node_path_1.basename)(compilerFileName)] || 42 | baseFileExists(compilerFileName)); 43 | }; 44 | const baseReadFile = compilerHost.readFile; 45 | compilerHost.readFile = function (compilerFileName) { 46 | if (compilerFileName === fileName) { 47 | return content; 48 | } 49 | else if (additionalFiles?.[(0, node_path_1.basename)(compilerFileName)]) { 50 | return additionalFiles[(0, node_path_1.basename)(compilerFileName)]; 51 | } 52 | else { 53 | return baseReadFile(compilerFileName); 54 | } 55 | }; 56 | // Create the TypeScript program. 57 | const program = typescript_1.default.createProgram([fileName], compilerOptions, compilerHost); 58 | return { compilerHost, program }; 59 | } 60 | function transformTypescript(content, transformers, program, compilerHost) { 61 | // Use given context or create a new one. 62 | if (content !== undefined) { 63 | const typescriptContext = createTypescriptContext(content); 64 | if (!program) { 65 | program = typescriptContext.program; 66 | } 67 | if (!compilerHost) { 68 | compilerHost = typescriptContext.compilerHost; 69 | } 70 | } 71 | else if (!program || !compilerHost) { 72 | throw new Error('transformTypescript needs either `content` or a `program` and `compilerHost'); 73 | } 74 | const outputFileName = basefileName.replace(/\.tsx?$/, '.js'); 75 | let outputContent; 76 | // Emit. 77 | const { emitSkipped, diagnostics } = program.emit(undefined, (filename, data) => { 78 | if (filename === outputFileName) { 79 | outputContent = data; 80 | } 81 | }, undefined, undefined, { before: transformers }); 82 | // Throw error with diagnostics if emit wasn't successfull. 83 | if (emitSkipped) { 84 | throw new Error(typescript_1.default.formatDiagnostics(diagnostics, compilerHost)); 85 | } 86 | // Return the transpiled js. 87 | return outputContent; 88 | } 89 | -------------------------------------------------------------------------------- /uniqueId: -------------------------------------------------------------------------------- 1 | Fri Jun 06 2025 16:47:17 GMT+0000 (Coordinated Universal Time) --------------------------------------------------------------------------------