diff --git a/packages/angular/build/src/builders/dev-server/vite/index.ts b/packages/angular/build/src/builders/dev-server/vite/index.ts index 27cb6d15adbb..b3a3ca985641 100644 --- a/packages/angular/build/src/builders/dev-server/vite/index.ts +++ b/packages/angular/build/src/builders/dev-server/vite/index.ts @@ -17,7 +17,6 @@ import { ServerSsrMode } from '../../../tools/vite/plugins'; import { EsbuildLoaderOption } from '../../../tools/vite/utils'; import { normalizeSourceMaps } from '../../../utils'; import { useComponentStyleHmr, useComponentTemplateHmr } from '../../../utils/environment-options'; -import { loadEsmModule } from '../../../utils/load-esm'; import { Result, ResultKind } from '../../application/results'; import { OutputHashing } from '../../application/schema'; import { @@ -175,8 +174,7 @@ export async function* serveWithVite( // The index HTML path will be updated from the build results if provided by the builder let htmlIndexPath = 'index.html'; - // dynamically import Vite for ESM compatibility - const { createServer, normalizePath } = await loadEsmModule('vite'); + const { createServer, normalizePath } = await import('vite'); let server: ViteDevServer | undefined; let serverUrl: URL | undefined; diff --git a/packages/angular/build/src/builders/dev-server/vite/server.ts b/packages/angular/build/src/builders/dev-server/vite/server.ts index 0d48ce5325e2..f527e41d33d0 100644 --- a/packages/angular/build/src/builders/dev-server/vite/server.ts +++ b/packages/angular/build/src/builders/dev-server/vite/server.ts @@ -19,7 +19,6 @@ import { } from '../../../tools/vite/plugins'; import { EsbuildLoaderOption, getDepOptimizationConfig } from '../../../tools/vite/utils'; import { loadProxyConfiguration } from '../../../utils'; -import { loadEsmModule } from '../../../utils/load-esm'; import { type ApplicationBuilderInternalOptions, JavaScriptTransformer } from '../internal'; import type { NormalizedDevServerOptions } from '../options'; import { DevServerExternalResultMetadata, OutputAssetRecord, OutputFileRecord } from './utils'; @@ -152,8 +151,7 @@ export async function setupServer( indexHtmlTransformer?: (content: string) => Promise, thirdPartySourcemaps = false, ): Promise { - // dynamically import Vite for ESM compatibility - const { normalizePath } = await loadEsmModule('vite'); + const { normalizePath } = await import('vite'); // Path will not exist on disk and only used to provide separate path for Vite requests const virtualProjectRoot = normalizePath( diff --git a/packages/angular/build/src/builders/extract-i18n/builder.ts b/packages/angular/build/src/builders/extract-i18n/builder.ts index 15b0156f749b..f74d1d219712 100644 --- a/packages/angular/build/src/builders/extract-i18n/builder.ts +++ b/packages/angular/build/src/builders/extract-i18n/builder.ts @@ -10,7 +10,6 @@ import type { Diagnostics } from '@angular/localize/tools'; import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect'; import fs from 'node:fs'; import path from 'node:path'; -import { loadEsmModule } from '../../utils/load-esm'; import { assertCompatibleAngularVersion } from '../../utils/version'; import type { ApplicationBuilderExtensions } from '../application/options'; import { normalizeOptions } from './options'; @@ -51,8 +50,7 @@ export async function execute( // The package is a peer dependency and might not be present let localizeToolsModule; try { - localizeToolsModule = - await loadEsmModule('@angular/localize/tools'); + localizeToolsModule = await import('@angular/localize/tools'); } catch { return { success: false, diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts index b493ac7c9447..299a9731513d 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/browser-provider.ts @@ -7,15 +7,16 @@ */ import { createRequire } from 'node:module'; +import type { BrowserBuiltinProvider, BrowserConfigOptions } from 'vitest/node'; export interface BrowserConfiguration { - browser?: import('vitest/node').BrowserConfigOptions; + browser?: BrowserConfigOptions; errors?: string[]; } function findBrowserProvider( projectResolver: NodeJS.RequireResolve, -): import('vitest/node').BrowserBuiltinProvider | undefined { +): BrowserBuiltinProvider | undefined { // One of these must be installed in the project to use browser testing const vitestBuiltinProviders = ['playwright', 'webdriverio'] as const; @@ -87,7 +88,7 @@ export function setupBrowserConfiguration( const isCI = !!process.env['CI']; const headless = isCI || browsers.some((name) => name.toLowerCase().includes('headless')); - const browser = { + const browser: BrowserConfigOptions = { enabled: true, provider, headless, @@ -96,7 +97,7 @@ export function setupBrowserConfiguration( instances: browsers.map((browserName) => ({ browser: normalizeBrowserName(browserName), })), - } satisfies import('vitest/node').BrowserConfigOptions; + }; return { browser }; } diff --git a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts index f4bd4aa3ee85..077245d6d01c 100644 --- a/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts +++ b/packages/angular/build/src/builders/unit-test/runners/vitest/executor.ts @@ -11,7 +11,6 @@ import assert from 'node:assert'; import path from 'node:path'; import type { InlineConfig, Vitest } from 'vitest/node'; import { assertIsError } from '../../../../utils/error'; -import { loadEsmModule } from '../../../../utils/load-esm'; import { toPosixPath } from '../../../../utils/path'; import { type FullResult, @@ -141,7 +140,7 @@ export class VitestExecutor implements TestExecutor { } = this.options; let vitestNodeModule; try { - vitestNodeModule = await loadEsmModule('vitest/node'); + vitestNodeModule = await import('vitest/node'); } catch (error: unknown) { assertIsError(error); if (error.code !== 'ERR_MODULE_NOT_FOUND') { @@ -230,7 +229,7 @@ async function generateCoverageOption( let defaultExcludes: string[] = []; if (coverage.exclude) { try { - const vitestConfig = await loadEsmModule('vitest/config'); + const vitestConfig = await import('vitest/config'); defaultExcludes = vitestConfig.coverageConfigDefaults.exclude; } catch {} } diff --git a/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts b/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts index bea3c65e1b8a..f22e8c813ecc 100644 --- a/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/angular-compilation.ts @@ -9,7 +9,6 @@ import type ng from '@angular/compiler-cli'; import type { PartialMessage } from 'esbuild'; import type ts from 'typescript'; -import { loadEsmModule } from '../../../utils/load-esm'; import { convertTypeScriptDiagnostic } from '../../esbuild/angular/diagnostics'; import { profileAsync, profileSync } from '../../esbuild/profiling'; import type { AngularHostOptions } from '../angular-host'; @@ -33,10 +32,7 @@ export abstract class AngularCompilation { static #typescriptModule?: typeof ts; static async loadCompilerCli(): Promise { - // This uses a wrapped dynamic import to load `@angular/compiler-cli` which is ESM. - // Once TypeScript provides support for retaining dynamic imports this workaround can be dropped. - AngularCompilation.#angularCompilerCliModule ??= - await loadEsmModule('@angular/compiler-cli'); + AngularCompilation.#angularCompilerCliModule ??= await import('@angular/compiler-cli'); return AngularCompilation.#angularCompilerCliModule; } diff --git a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts index a811cb50ec0a..d2876654a15e 100644 --- a/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts +++ b/packages/angular/build/src/tools/angular/compilation/jit-compilation.ts @@ -9,7 +9,6 @@ import type ng from '@angular/compiler-cli'; import assert from 'node:assert'; import ts from 'typescript'; -import { loadEsmModule } from '../../../utils/load-esm'; import { profileSync } from '../../esbuild/profiling'; import { AngularHostOptions, createAngularCompilerHost } from '../angular-host'; import { createJitResourceTransformer } from '../transformers/jit-resource-transformer'; @@ -44,9 +43,9 @@ export class JitCompilation extends AngularCompilation { referencedFiles: readonly string[]; }> { // Dynamically load the Angular compiler CLI package - const { constructorParametersDownlevelTransform } = await loadEsmModule< - typeof import('@angular/compiler-cli/private/tooling') - >('@angular/compiler-cli/private/tooling'); + const { constructorParametersDownlevelTransform } = await import( + '@angular/compiler-cli/private/tooling' + ); // Load the compiler configuration and transform as needed const { diff --git a/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts b/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts index 9caa2ca5da45..74550e83e5de 100644 --- a/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts +++ b/packages/angular/build/src/tools/esbuild/i18n-inliner-worker.ts @@ -11,7 +11,6 @@ import { PluginObj, parseSync, transformFromAstAsync, types } from '@babel/core' import assert from 'node:assert'; import { workerData } from 'node:worker_threads'; import { assertIsError } from '../../utils/error'; -import { loadEsmModule } from '../../utils/load-esm'; /** * The options passed to the inliner for each file request @@ -131,7 +130,7 @@ async function loadLocalizeTools(): Promise { // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. // Once TypeScript provides support for keeping the dynamic import this workaround can be // changed to a direct dynamic import. - localizeToolsModule ??= await loadEsmModule('@angular/localize/tools'); + localizeToolsModule ??= await import('@angular/localize/tools'); return localizeToolsModule; } diff --git a/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts b/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts index c9e882850a64..7d86009b773f 100644 --- a/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts +++ b/packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts @@ -10,7 +10,6 @@ import { type PluginItem, transformAsync } from '@babel/core'; import fs from 'node:fs'; import path from 'node:path'; import Piscina from 'piscina'; -import { loadEsmModule } from '../../utils/load-esm'; interface JavaScriptTransformRequest { filename: string; @@ -133,11 +132,8 @@ async function requiresLinking(path: string, source: string): Promise { } async function createLinkerPlugin(options: Omit) { - linkerPluginCreator ??= ( - await loadEsmModule( - '@angular/compiler-cli/linker/babel', - ) - ).createEs2015LinkerPlugin; + linkerPluginCreator ??= (await import('@angular/compiler-cli/linker/babel')) + .createEs2015LinkerPlugin; const linkerPlugin = linkerPluginCreator({ linkerJitMode: options.jit, diff --git a/packages/angular/build/src/tools/vite/middlewares/ssr-middleware.ts b/packages/angular/build/src/tools/vite/middlewares/ssr-middleware.ts index 6484ee8391e1..0d8ac5441f1d 100644 --- a/packages/angular/build/src/tools/vite/middlewares/ssr-middleware.ts +++ b/packages/angular/build/src/tools/vite/middlewares/ssr-middleware.ts @@ -10,10 +10,8 @@ import type { AngularAppEngine as SSRAngularAppEngine, ɵgetOrCreateAngularServerApp as getOrCreateAngularServerApp, } from '@angular/ssr'; -import type * as AngularSsrNode from '@angular/ssr/node' with { 'resolution-mode': 'import' }; import type { ServerResponse } from 'node:http'; import type { Connect, ViteDevServer } from 'vite'; -import { loadEsmModule } from '../../../utils/load-esm'; import { isSsrNodeRequestHandler, isSsrRequestHandler, @@ -37,9 +35,11 @@ export function createAngularSsrInternalMiddleware( (async () => { // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, // which must be processed by the runtime linker, even if they are not used. - await loadEsmModule('@angular/compiler'); - const { writeResponseToNodeResponse, createWebRequestFromNodeRequest } = - await loadEsmModule('@angular/ssr/node'); + await import('@angular/compiler'); + const { writeResponseToNodeResponse, createWebRequestFromNodeRequest } = (await import( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + '@angular/ssr/node' as any + )) as typeof import('@angular/ssr/node', { with: { 'resolution-mode': 'import' } }); const { ɵgetOrCreateAngularServerApp } = (await server.ssrLoadModule('/main.server.mjs')) as { ɵgetOrCreateAngularServerApp: typeof getOrCreateAngularServerApp; @@ -91,10 +91,12 @@ export async function createAngularSsrExternalMiddleware( // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, // which must be processed by the runtime linker, even if they are not used. - await loadEsmModule('@angular/compiler'); + await import('@angular/compiler'); - const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = - await loadEsmModule('@angular/ssr/node'); + const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = (await import( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + '@angular/ssr/node' as any + )) as typeof import('@angular/ssr/node', { with: { 'resolution-mode': 'import' } }); return function angularSsrExternalMiddleware( req: Connect.IncomingMessage, diff --git a/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts b/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts index 5fc178b60386..be00e3437f27 100644 --- a/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts +++ b/packages/angular/build/src/tools/vite/plugins/angular-memory-plugin.ts @@ -11,7 +11,6 @@ import { readFile } from 'node:fs/promises'; import { dirname, join, relative } from 'node:path'; import { fileURLToPath } from 'node:url'; import type { Plugin } from 'vite'; -import { loadEsmModule } from '../../../utils/load-esm'; import { AngularMemoryOutputFiles } from '../utils'; interface AngularMemoryPluginOptions { @@ -30,7 +29,7 @@ export async function createAngularMemoryPlugin( options: AngularMemoryPluginOptions, ): Promise { const { virtualProjectRoot, outputFiles, external } = options; - const { normalizePath } = await loadEsmModule('vite'); + const { normalizePath } = await import('vite'); return { name: 'vite:angular-memory', diff --git a/packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts b/packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts index 21ddaa350ac1..b82cc2d3acd6 100644 --- a/packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts +++ b/packages/angular/build/src/tools/vite/plugins/setup-middlewares-plugin.ts @@ -7,7 +7,6 @@ */ import type { Connect, Plugin } from 'vite'; -import { loadEsmModule } from '../../../utils/load-esm'; import { ComponentStyleRecord, angularHtmlFallbackMiddleware, @@ -61,8 +60,7 @@ interface AngularSetupMiddlewaresPluginOptions { async function createEncapsulateStyle(): Promise< (style: Uint8Array, componentId: string) => string > { - const { encapsulateStyle } = - await loadEsmModule('@angular/compiler'); + const { encapsulateStyle } = await import('@angular/compiler'); const decoder = new TextDecoder('utf-8'); return (style, componentId) => { diff --git a/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts b/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts index 0ac7b92d442d..90d183acde02 100644 --- a/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts +++ b/packages/angular/build/src/tools/vite/plugins/ssr-transform-plugin.ts @@ -8,10 +8,9 @@ import remapping, { SourceMapInput } from '@ampproject/remapping'; import type { Plugin } from 'vite'; -import { loadEsmModule } from '../../../utils/load-esm'; export async function createAngularSsrTransformPlugin(workspaceRoot: string): Promise { - const { normalizePath } = await loadEsmModule('vite'); + const { normalizePath } = await import('vite'); return { name: 'vite:angular-ssr-transform', diff --git a/packages/angular/build/src/utils/index-file/augment-index-html.ts b/packages/angular/build/src/utils/index-file/augment-index-html.ts index 7a30770fa450..21ba39b39cc4 100644 --- a/packages/angular/build/src/utils/index-file/augment-index-html.ts +++ b/packages/angular/build/src/utils/index-file/augment-index-html.ts @@ -8,7 +8,6 @@ import { createHash } from 'node:crypto'; import { extname } from 'node:path'; -import { loadEsmModule } from '../load-esm'; import { htmlRewritingStream } from './html-rewriting-stream'; import { VALID_SELF_CLOSING_TAGS } from './valid-self-closing-tags'; @@ -352,12 +351,7 @@ async function getLanguageDirection( async function getLanguageDirectionFromLocales(locale: string): Promise { try { - const localeData = ( - await loadEsmModule( - `@angular/common/locales/${locale}`, - ) - ).default; - + const localeData = (await import(`@angular/common/locales/${locale}`)).default; const dir = localeData[localeData.length - 2]; return isString(dir) ? dir : undefined; diff --git a/packages/angular/build/src/utils/index-file/html-rewriting-stream.ts b/packages/angular/build/src/utils/index-file/html-rewriting-stream.ts index dbeeadcb2fc1..baf494be033c 100644 --- a/packages/angular/build/src/utils/index-file/html-rewriting-stream.ts +++ b/packages/angular/build/src/utils/index-file/html-rewriting-stream.ts @@ -9,7 +9,6 @@ import { Readable } from 'node:stream'; import { pipeline } from 'node:stream/promises'; import type { RewritingStream } from 'parse5-html-rewriting-stream'; -import { loadEsmModule } from '../load-esm'; // Export helper types for the rewriter export type StartTag = Parameters[0]; @@ -20,9 +19,7 @@ export async function htmlRewritingStream(content: string): Promise<{ rewriter: RewritingStream; transformedContent: () => Promise; }> { - const { RewritingStream } = await loadEsmModule( - 'parse5-html-rewriting-stream', - ); + const { RewritingStream } = await import('parse5-html-rewriting-stream'); const rewriter = new RewritingStream(); return { diff --git a/packages/angular/build/src/utils/load-proxy-config.ts b/packages/angular/build/src/utils/load-proxy-config.ts index e590ee9efc6c..cf4cb9e3c03e 100644 --- a/packages/angular/build/src/utils/load-proxy-config.ts +++ b/packages/angular/build/src/utils/load-proxy-config.ts @@ -49,33 +49,20 @@ export async function loadProxyConfiguration( break; } - case '.mjs': - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - proxyConfiguration = await loadEsmModule<{ default: unknown }>(pathToFileURL(proxyPath)); - break; - case '.cjs': - proxyConfiguration = require(proxyPath); - break; - default: - // The file could be either CommonJS or ESM. - // CommonJS is tried first then ESM if loading fails. + default: { try { - proxyConfiguration = require(proxyPath); - break; + proxyConfiguration = await import(proxyPath); } catch (e) { assertIsError(e); - if (e.code === 'ERR_REQUIRE_ESM' || e.code === 'ERR_REQUIRE_ASYNC_MODULE') { - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - proxyConfiguration = await loadEsmModule<{ default: unknown }>(pathToFileURL(proxyPath)); - break; + if (e.code !== 'ERR_REQUIRE_ASYNC_MODULE') { + throw e; } - throw e; + proxyConfiguration = await loadEsmModule<{ default: unknown }>(pathToFileURL(proxyPath)); } + + break; + } } if ('default' in proxyConfiguration) { diff --git a/packages/angular/build/src/utils/load-translations.ts b/packages/angular/build/src/utils/load-translations.ts index eeac280cbf9b..e202273dc73d 100644 --- a/packages/angular/build/src/utils/load-translations.ts +++ b/packages/angular/build/src/utils/load-translations.ts @@ -9,7 +9,6 @@ import type { Diagnostics } from '@angular/localize/tools'; import { createHash } from 'node:crypto'; import * as fs from 'node:fs'; -import { loadEsmModule } from './load-esm'; export type TranslationLoader = (path: string) => { translations: Record; @@ -62,7 +61,7 @@ async function importParsers() { Xliff1TranslationParser, Xliff2TranslationParser, XtbTranslationParser, - } = await loadEsmModule('@angular/localize/tools'); + } = await import('@angular/localize/tools'); const diagnostics = new Diagnostics(); const parsers = { diff --git a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts index 9e1a657366a0..1d0d9df32d30 100644 --- a/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts +++ b/packages/angular/build/src/utils/server-rendering/esm-in-memory-loader/loader-hooks.ts @@ -8,9 +8,9 @@ import assert from 'node:assert'; import { randomUUID } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import { JavaScriptTransformer } from '../../../tools/esbuild/javascript-transformer'; /** * @note For some unknown reason, setting `globalThis.ngServerMode = true` does not work when using ESM loader hooks. @@ -32,14 +32,6 @@ export interface ESMInMemoryFileLoaderWorkerData { let memoryVirtualRootUrl: string; let outputFiles: Record; -const javascriptTransformer = new JavaScriptTransformer( - // Always enable JIT linking to support applications built with and without AOT. - // In a development environment the additional scope information does not - // have a negative effect unlike production where final output size is relevant. - { sourcemap: true, jit: true }, - 1, -); - export function initialize(data: ESMInMemoryFileLoaderWorkerData) { // This path does not actually exist but is used to overlay the in memory files with the // actual filesystem for resolution purposes. @@ -137,7 +129,7 @@ export async function load(url: string, context: { format?: string | null }, nex // need linking are ESM only. if (format === 'module' && isFileProtocol(url)) { const filePath = fileURLToPath(url); - let source = await javascriptTransformer.transformFile(filePath); + let source = await readFile(filePath); if (filePath.includes('@angular/')) { // Prepend 'var ngServerMode=true;' to the source. @@ -158,11 +150,3 @@ export async function load(url: string, context: { format?: string | null }, nex function isFileProtocol(url: string): boolean { return url.startsWith('file://'); } - -function handleProcessExit(): void { - void javascriptTransformer.close(); -} - -process.once('exit', handleProcessExit); -process.once('SIGINT', handleProcessExit); -process.once('uncaughtException', handleProcessExit); diff --git a/packages/angular/build/src/utils/server-rendering/launch-server.ts b/packages/angular/build/src/utils/server-rendering/launch-server.ts index 9c127e3816bd..c17337178b13 100644 --- a/packages/angular/build/src/utils/server-rendering/launch-server.ts +++ b/packages/angular/build/src/utils/server-rendering/launch-server.ts @@ -6,10 +6,8 @@ * found in the LICENSE file at https://angular.dev/license */ -import type * as AngularSsrNode from '@angular/ssr/node' with { 'resolution-mode': 'import' }; import assert from 'node:assert'; import { createServer } from 'node:http'; -import { loadEsmModule } from '../load-esm'; import { loadEsmModuleFromMemory } from './load-esm-from-memory'; import { isSsrNodeRequestHandler, isSsrRequestHandler } from './utils'; @@ -22,8 +20,10 @@ export const DEFAULT_URL = new URL('http://ng-localhost/'); */ export async function launchServer(): Promise { const { reqHandler } = await loadEsmModuleFromMemory('./server.mjs'); - const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = - await loadEsmModule('@angular/ssr/node'); + const { createWebRequestFromNodeRequest, writeResponseToNodeResponse } = (await import( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + '@angular/ssr/node' as any + )) as typeof import('@angular/ssr/node', { with: { 'resolution-mode': 'import' } }); if (!isSsrNodeRequestHandler(reqHandler) && !isSsrRequestHandler(reqHandler)) { return DEFAULT_URL; diff --git a/packages/angular/build/src/utils/server-rendering/render-worker.ts b/packages/angular/build/src/utils/server-rendering/render-worker.ts index 92fad35df32a..f3fc8e93a0d0 100644 --- a/packages/angular/build/src/utils/server-rendering/render-worker.ts +++ b/packages/angular/build/src/utils/server-rendering/render-worker.ts @@ -52,6 +52,10 @@ async function renderPage({ url }: RenderOptions): Promise { } async function initialize() { + // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, + // which must be processed by the runtime linker, even if they are not used. + await import('@angular/compiler'); + if (outputMode !== undefined && hasSsrEntry) { serverURL = await launchServer(); } diff --git a/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts b/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts index 1487db07e811..423a71e83ba5 100644 --- a/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts +++ b/packages/angular/build/src/utils/server-rendering/routes-extractor-worker.ts @@ -28,6 +28,10 @@ const { outputMode, hasSsrEntry } = workerData as { /** Renders an application based on a provided options. */ async function extractRoutes(): Promise { + // Load the compiler because `@angular/ssr/node` depends on `@angular/` packages, + // which must be processed by the runtime linker, even if they are not used. + await import('@angular/compiler'); + const serverURL = outputMode !== undefined && hasSsrEntry ? await launchServer() : DEFAULT_URL; patchFetchToLoadInMemoryAssets(serverURL); diff --git a/packages/angular/build/src/utils/service-worker.ts b/packages/angular/build/src/utils/service-worker.ts index c3bd75c3ab38..1535684f635c 100644 --- a/packages/angular/build/src/utils/service-worker.ts +++ b/packages/angular/build/src/utils/service-worker.ts @@ -16,7 +16,6 @@ import * as path from 'node:path'; import { BuildOutputFile, BuildOutputFileType } from '../tools/esbuild/bundler-context'; import { BuildOutputAsset } from '../tools/esbuild/bundler-execution-result'; import { assertIsError } from './error'; -import { loadEsmModule } from './load-esm'; import { toPosixPath } from './path'; class CliFilesystem implements Filesystem { @@ -219,17 +218,14 @@ export async function augmentAppWithServiceWorkerCore( serviceWorkerFilesystem: Filesystem, baseHref: string, ): Promise<{ manifest: string; assetFiles: { source: string; destination: string }[] }> { - // Load ESM `@angular/service-worker/config` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - const GeneratorConstructor = ( - await loadEsmModule< - typeof import('@angular/service-worker/config', { with: { 'resolution-mode': 'import' } }) - >('@angular/service-worker/config') - ).Generator; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const { Generator } = (await import('@angular/service-worker/config' as any)) as typeof import( + '@angular/service-worker/config', + { with: { 'resolution-mode': 'import' } } + ); // Generate the manifest - const generator = new GeneratorConstructor(serviceWorkerFilesystem, baseHref); + const generator = new Generator(serviceWorkerFilesystem, baseHref); const output = await generator.process(config); // Write the manifest diff --git a/packages/angular/pwa/pwa/index.ts b/packages/angular/pwa/pwa/index.ts index c316ed581695..a3a2b2847c78 100644 --- a/packages/angular/pwa/pwa/index.ts +++ b/packages/angular/pwa/pwa/index.ts @@ -28,9 +28,7 @@ function updateIndexFile(path: string): Rule { return async (host: Tree) => { const originalContent = host.readText(path); - const { RewritingStream } = await loadEsmModule( - 'parse5-html-rewriting-stream', - ); + const { RewritingStream } = await import('parse5-html-rewriting-stream'); const rewriter = new RewritingStream(); let needsNoScript = true; @@ -179,19 +177,3 @@ export default function (options: PwaOptions): Rule { ]); }; } - -/** - * This uses a dynamic import to load a module which may be ESM. - * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript - * will currently, unconditionally downlevel dynamic import into a require call. - * require calls cannot load ESM code and will result in a runtime error. To workaround - * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. - * Once TypeScript provides support for keeping the dynamic import this workaround can - * be dropped. - * - * @param modulePath The path of the module to load. - * @returns A Promise that resolves to the dynamically imported module. - */ -function loadEsmModule(modulePath: string | URL): Promise { - return new Function('modulePath', `return import(modulePath);`)(modulePath) as Promise; -} diff --git a/packages/angular_devkit/architect/node/node-modules-architect-host.ts b/packages/angular_devkit/architect/node/node-modules-architect-host.ts index 554f85be14ad..bed099691865 100644 --- a/packages/angular_devkit/architect/node/node-modules-architect-host.ts +++ b/packages/angular_devkit/architect/node/node-modules-architect-host.ts @@ -282,65 +282,9 @@ export class WorkspaceNodeModulesArchitectHost implements ArchitectHost(modulePath: string | URL) => Promise) | undefined; - -/** - * This uses a dynamic import to load a module which may be ESM. - * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript - * will currently, unconditionally downlevel dynamic import into a require call. - * require calls cannot load ESM code and will result in a runtime error. To workaround - * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. - * Once TypeScript provides support for keeping the dynamic import this workaround can - * be dropped. - * - * @param modulePath The path of the module to load. - * @returns A Promise that resolves to the dynamically imported module. - */ -export function loadEsmModule(modulePath: string | URL): Promise { - load ??= new Function('modulePath', `return import(modulePath);`) as Exclude< - typeof load, - undefined - >; - - return load(modulePath); -} - // eslint-disable-next-line @typescript-eslint/no-explicit-any async function getBuilder(builderPath: string): Promise { - let builder; - switch (path.extname(builderPath)) { - case '.mjs': - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - builder = (await loadEsmModule<{ default: unknown }>(pathToFileURL(builderPath))).default; - break; - case '.cjs': - builder = localRequire(builderPath); - break; - default: - // The file could be either CommonJS or ESM. - // CommonJS is tried first then ESM if loading fails. - try { - builder = localRequire(builderPath); - } catch (e) { - if ( - (e as NodeJS.ErrnoException).code === 'ERR_REQUIRE_ESM' || - (e as NodeJS.ErrnoException).code === 'ERR_REQUIRE_ASYNC_MODULE' - ) { - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - builder = await loadEsmModule<{ default: unknown }>(pathToFileURL(builderPath)); - } - - throw e; - } - break; - } + const builder = await import(builderPath); return 'default' in builder ? builder.default : builder; } diff --git a/packages/angular_devkit/build_angular/src/builders/dev-server/webpack-server.ts b/packages/angular_devkit/build_angular/src/builders/dev-server/webpack-server.ts index d9bc6e015c3b..0bf34146a29d 100644 --- a/packages/angular_devkit/build_angular/src/builders/dev-server/webpack-server.ts +++ b/packages/angular_devkit/build_angular/src/builders/dev-server/webpack-server.ts @@ -35,7 +35,6 @@ import { ExecutionTransformer } from '../../transforms'; import { normalizeOptimization } from '../../utils'; import { colors } from '../../utils/color'; import { I18nOptions, loadTranslations } from '../../utils/i18n-webpack'; -import { loadEsmModule } from '../../utils/load-esm'; import { NormalizedCachedOptions } from '../../utils/normalize-cache'; import { generateEntryPoints } from '../../utils/package-chunk-sort'; import { @@ -248,7 +247,7 @@ export function serveWebpackBrowser( ); if (options.open) { - const open = (await loadEsmModule('open')).default; + const open = (await import('open')).default; await open(serverAddress); } } diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/builder.ts b/packages/angular_devkit/build_angular/src/builders/extract-i18n/builder.ts index 2d22fda36e96..0b08e10b40be 100644 --- a/packages/angular_devkit/build_angular/src/builders/extract-i18n/builder.ts +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/builder.ts @@ -9,11 +9,11 @@ import { assertCompatibleAngularVersion, purgeStaleBuildCache } from '@angular/build/private'; import type { Diagnostics } from '@angular/localize/tools'; import type { BuilderContext, BuilderOutput } from '@angular-devkit/architect'; +import assert from 'node:assert'; import fs from 'node:fs'; import path from 'node:path'; import type webpack from 'webpack'; import type { ExecutionTransformer } from '../../transforms'; -import { loadEsmModule } from '../../utils/load-esm'; import { normalizeOptions } from './options'; import { Schema as ExtractI18nBuilderOptions, Format } from './schema'; @@ -52,10 +52,9 @@ export async function execute( // Load the Angular localize package. // The package is a peer dependency and might not be present - let localizeToolsModule; + let localizeToolsModule: typeof import('@angular/localize/tools'); try { - localizeToolsModule = - await loadEsmModule('@angular/localize/tools'); + localizeToolsModule = await import('@angular/localize/tools'); } catch { return { success: false, @@ -138,6 +137,9 @@ export async function execute( extractionResult.useLegacyIds, diagnostics, ); + + assert(serializer); + const content = serializer.serialize(extractionResult.messages); // Ensure directory exists diff --git a/packages/angular_devkit/build_angular/src/builders/extract-i18n/ivy-extract-loader.ts b/packages/angular_devkit/build_angular/src/builders/extract-i18n/ivy-extract-loader.ts index 8d9d639d069b..e64bcc30c387 100644 --- a/packages/angular_devkit/build_angular/src/builders/extract-i18n/ivy-extract-loader.ts +++ b/packages/angular_devkit/build_angular/src/builders/extract-i18n/ivy-extract-loader.ts @@ -7,7 +7,6 @@ */ import * as nodePath from 'node:path'; -import { loadEsmModule } from '../../utils/load-esm'; // Extract loader source map parameter type since it is not exported directly type LoaderSourceMap = Parameters[1]; @@ -46,17 +45,10 @@ async function extract( map: string | LoaderSourceMap | undefined, options: LocalizeExtractLoaderOptions, ) { - // Try to load the `@angular/localize` message extractor. - // All the localize usages are setup to first try the ESM entry point then fallback to the deep imports. - // This provides interim compatibility while the framework is transitioned to bundled ESM packages. let MessageExtractor; try { - // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - const localizeToolsModule = - await loadEsmModule('@angular/localize/tools'); - MessageExtractor = localizeToolsModule.MessageExtractor; + const { MessageExtractor: MsgExtractor } = await import('@angular/localize/tools'); + MessageExtractor = MsgExtractor; } catch { throw new Error( `Unable to load message extractor. Please ensure '@angular/localize' is installed.`, diff --git a/packages/angular_devkit/build_angular/src/tools/babel/presets/application.ts b/packages/angular_devkit/build_angular/src/tools/babel/presets/application.ts index 5810269ad386..6929e7704ac2 100644 --- a/packages/angular_devkit/build_angular/src/tools/babel/presets/application.ts +++ b/packages/angular_devkit/build_angular/src/tools/babel/presets/application.ts @@ -16,7 +16,6 @@ import type { import assert from 'node:assert'; import fs from 'node:fs'; import path from 'node:path'; -import { loadEsmModule } from '../../../utils/load-esm'; /** * Cached instance of the compiler-cli linker's needsLinking function. @@ -255,12 +254,7 @@ export async function requiresLinking(path: string, source: string): Promise( - '@angular/compiler-cli/linker', - ); + const linkerModule = await import('@angular/compiler-cli/linker'); needsLinking = linkerModule.needsLinking; } diff --git a/packages/angular_devkit/build_angular/src/tools/babel/webpack-loader.ts b/packages/angular_devkit/build_angular/src/tools/babel/webpack-loader.ts index 71e0188853d2..5be925d2c6b6 100644 --- a/packages/angular_devkit/build_angular/src/tools/babel/webpack-loader.ts +++ b/packages/angular_devkit/build_angular/src/tools/babel/webpack-loader.ts @@ -7,7 +7,6 @@ */ import { custom } from 'babel-loader'; -import { loadEsmModule } from '../../utils/load-esm'; import { VERSION } from '../../utils/package-version'; import { ApplicationPresetOptions, @@ -69,11 +68,8 @@ export default custom(() => { // Load ESM `@angular/compiler-cli/linker/babel` using the TypeScript dynamic import workaround. // Once TypeScript provides support for keeping the dynamic import this workaround can be // changed to a direct dynamic import. - linkerPluginCreator ??= ( - await loadEsmModule( - '@angular/compiler-cli/linker/babel', - ) - ).createEs2015LinkerPlugin; + linkerPluginCreator ??= (await import('@angular/compiler-cli/linker/babel')) + .createEs2015LinkerPlugin; customOptions.angularLinker = { shouldLink: true, @@ -110,7 +106,7 @@ export default custom(() => { // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. // Once TypeScript provides support for keeping the dynamic import this workaround can be // changed to a direct dynamic import. - i18nPluginCreators = await loadEsmModule('@angular/localize/tools'); + i18nPluginCreators = await import('@angular/localize/tools'); } customOptions.i18n = { diff --git a/packages/angular_devkit/build_angular/src/tools/webpack/configs/common.ts b/packages/angular_devkit/build_angular/src/tools/webpack/configs/common.ts index d6796a1117bf..b18c8ebc9be9 100644 --- a/packages/angular_devkit/build_angular/src/tools/webpack/configs/common.ts +++ b/packages/angular_devkit/build_angular/src/tools/webpack/configs/common.ts @@ -19,7 +19,6 @@ import { import { SubresourceIntegrityPlugin } from 'webpack-subresource-integrity'; import { WebpackConfigOptions } from '../../../utils/build-options'; import { allowMangle } from '../../../utils/environment-options'; -import { loadEsmModule } from '../../../utils/load-esm'; import { AngularBabelLoaderOptions } from '../../babel/webpack-loader'; import { CommonJsUsageWarnPlugin, @@ -83,11 +82,10 @@ export async function getCommonConfig(wco: WebpackConfigOptions): Promise('@angular/compiler-cli'); - const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await loadEsmModule< - typeof import('@angular/compiler-cli/private/tooling') - >('@angular/compiler-cli/private/tooling'); + const { VERSION: NG_VERSION } = await import('@angular/compiler-cli'); + const { GLOBAL_DEFS_FOR_TERSER, GLOBAL_DEFS_FOR_TERSER_WITH_AOT } = await import( + '@angular/compiler-cli/private/tooling' + ); // determine hashing format const hashFormat = getOutputHashFormat(buildOptions.outputHashing); diff --git a/packages/angular_devkit/build_angular/src/tools/webpack/configs/dev-server.ts b/packages/angular_devkit/build_angular/src/tools/webpack/configs/dev-server.ts index 5606e8d13d87..5ba21e328ec3 100644 --- a/packages/angular_devkit/build_angular/src/tools/webpack/configs/dev-server.ts +++ b/packages/angular_devkit/build_angular/src/tools/webpack/configs/dev-server.ts @@ -190,35 +190,20 @@ async function addProxyConfig( break; } - case '.mjs': - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - proxyConfiguration = await loadEsmModule<{ default: Record | object[] }>( - pathToFileURL(proxyPath), - ); - break; - case '.cjs': - proxyConfiguration = require(proxyPath); - break; - default: - // The file could be either CommonJS or ESM. - // CommonJS is tried first then ESM if loading fails. + default: { try { - proxyConfiguration = require(proxyPath); + proxyConfiguration = await import(proxyPath); } catch (e) { assertIsError(e); - if (e.code !== 'ERR_REQUIRE_ESM' && e.code !== 'ERR_REQUIRE_ASYNC_MODULE') { + if (e.code !== 'ERR_REQUIRE_ASYNC_MODULE') { throw e; } - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - proxyConfiguration = await loadEsmModule<{ default: Record | object[] }>( - pathToFileURL(proxyPath), - ); + proxyConfiguration = await loadEsmModule<{ default: unknown }>(pathToFileURL(proxyPath)); } + + break; + } } if ('default' in proxyConfiguration) { diff --git a/packages/angular_devkit/build_angular/src/utils/process-bundle.ts b/packages/angular_devkit/build_angular/src/utils/process-bundle.ts index 2c0a6dcbcc58..c8ec99eef15e 100644 --- a/packages/angular_devkit/build_angular/src/utils/process-bundle.ts +++ b/packages/angular_devkit/build_angular/src/utils/process-bundle.ts @@ -24,7 +24,6 @@ import { InlineOptions } from './bundle-inline-options'; import { allowMinify, shouldBeautify } from './environment-options'; import { assertIsError } from './error'; import { I18nOptions } from './i18n-webpack'; -import { loadEsmModule } from './load-esm'; // Extract Sourcemap input type from the remapping function since it is not currently exported type SourceMapInput = Exclude[0], unknown[]>; @@ -60,10 +59,7 @@ async function loadLocalizeTools(): Promise { return localizeToolsModule; } - // Load ESM `@angular/localize/tools` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - return loadEsmModule('@angular/localize/tools'); + return import('@angular/localize/tools'); } async function createI18nPlugins( diff --git a/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts b/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts index c0dc5754f242..e5c9d5d01af2 100644 --- a/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts +++ b/packages/angular_devkit/build_angular/src/utils/read-tsconfig.ts @@ -8,7 +8,6 @@ import type { ParsedConfiguration } from '@angular/compiler-cli'; import * as path from 'node:path'; -import { loadEsmModule } from './load-esm'; /** * Reads and parses a given TsConfig file. @@ -23,11 +22,7 @@ export async function readTsconfig( ): Promise { const tsConfigFullPath = workspaceRoot ? path.resolve(workspaceRoot, tsconfigPath) : tsconfigPath; - // Load ESM `@angular/compiler-cli` using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - const { formatDiagnostics, readConfiguration } = - await loadEsmModule('@angular/compiler-cli'); + const { formatDiagnostics, readConfiguration } = await import('@angular/compiler-cli'); const configResult = readConfiguration(tsConfigFullPath); if (configResult.errors && configResult.errors.length) { diff --git a/packages/angular_devkit/build_webpack/src/utils.ts b/packages/angular_devkit/build_webpack/src/utils.ts index 47c30b548f4e..98a9aa04fe83 100644 --- a/packages/angular_devkit/build_webpack/src/utils.ts +++ b/packages/angular_devkit/build_webpack/src/utils.ts @@ -55,58 +55,12 @@ export function getEmittedFiles(compilation: Compilation): EmittedFiles[] { return files; } -/** - * This uses a dynamic import to load a module which may be ESM. - * CommonJS code can load ESM code via a dynamic import. Unfortunately, TypeScript - * will currently, unconditionally downlevel dynamic import into a require call. - * require calls cannot load ESM code and will result in a runtime error. To workaround - * this, a Function constructor is used to prevent TypeScript from changing the dynamic import. - * Once TypeScript provides support for keeping the dynamic import this workaround can - * be dropped. - * - * @param modulePath The path of the module to load. - * @returns A Promise that resolves to the dynamically imported module. - */ -function loadEsmModule(modulePath: string | URL): Promise { - return new Function('modulePath', `return import(modulePath);`)(modulePath) as Promise; -} - export async function getWebpackConfig(configPath: string): Promise { if (!existsSync(configPath)) { throw new Error(`Webpack configuration file ${configPath} does not exist.`); } - let config; - switch (path.extname(configPath)) { - case '.mjs': - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - config = await loadEsmModule<{ default: Configuration }>(pathToFileURL(configPath)); - break; - case '.cjs': - config = require(configPath); - break; - default: - // The file could be either CommonJS or ESM. - // CommonJS is tried first then ESM if loading fails. - try { - config = require(configPath); - break; - } catch (e) { - if ( - (e as NodeJS.ErrnoException).code === 'ERR_REQUIRE_ESM' || - (e as NodeJS.ErrnoException).code === 'ERR_REQUIRE_ASYNC_MODULE' - ) { - // Load the ESM configuration file using the TypeScript dynamic import workaround. - // Once TypeScript provides support for keeping the dynamic import this workaround can be - // changed to a direct dynamic import. - config = await loadEsmModule<{ default: Configuration }>(pathToFileURL(configPath)); - } - - throw e; - } - } + const config = await import(configPath); return 'default' in config ? config.default : config; }