From 570757887f1cbd3e5bd41a7c21ffc7a063ffc7d0 Mon Sep 17 00:00:00 2001 From: Alan Agius Date: Tue, 12 Mar 2024 08:38:31 +0000 Subject: [PATCH] refactor(@angular-devkit/build-angular): properly display errors originating in ESM loader hooks Currently, errors occurring in ESM loader hooks while using `--import` are not correctly displayed, as they cannot be transferred from the worker to the main thread. Although the error is an instance of Error, it contains non-transferable properties and cannot be transmitted from a worker when --import is used. Consequently, when read outside of the worker, the error object displays as `[Object object]`. To address this issue, we reconstruct the error message. See: https://github.com/angular/angular-cli/issues/27251 (cherry picked from commit 51debcdb7828ac2a22ab88856aad7acfa9f99239) --- .../server-rendering/load-esm-from-memory.ts | 33 +++++++++++++++++++ .../src/utils/server-rendering/render-page.ts | 4 +-- .../utils/server-rendering/render-worker.ts | 2 -- .../routes-extractor-worker.ts | 11 ++----- 4 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 packages/angular_devkit/build_angular/src/utils/server-rendering/load-esm-from-memory.ts diff --git a/packages/angular_devkit/build_angular/src/utils/server-rendering/load-esm-from-memory.ts b/packages/angular_devkit/build_angular/src/utils/server-rendering/load-esm-from-memory.ts new file mode 100644 index 000000000000..ae84d899d600 --- /dev/null +++ b/packages/angular_devkit/build_angular/src/utils/server-rendering/load-esm-from-memory.ts @@ -0,0 +1,33 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import { assertIsError } from '../error'; +import { loadEsmModule } from '../load-esm'; +import { MainServerBundleExports, RenderUtilsServerBundleExports } from './main-bundle-exports'; + +export function loadEsmModuleFromMemory( + path: './main.server.mjs', +): Promise; +export function loadEsmModuleFromMemory( + path: './render-utils.server.mjs', +): Promise; +export function loadEsmModuleFromMemory(path: string): Promise { + return loadEsmModule(new URL(path, 'memory://')).catch((e) => { + assertIsError(e); + + // While the error is an 'instanceof Error', it is extended with non transferable properties + // and cannot be transferred from a worker when using `--import`. This results in the error object + // displaying as '[Object object]' when read outside of the worker. Therefore, we reconstruct the error message here. + const error: Error & { code?: string } = new Error(e.message); + error.stack = e.stack; + error.name = e.name; + error.code = e.code; + + throw error; + }); +} diff --git a/packages/angular_devkit/build_angular/src/utils/server-rendering/render-page.ts b/packages/angular_devkit/build_angular/src/utils/server-rendering/render-page.ts index a7cf0af1e577..2d3718e54891 100644 --- a/packages/angular_devkit/build_angular/src/utils/server-rendering/render-page.ts +++ b/packages/angular_devkit/build_angular/src/utils/server-rendering/render-page.ts @@ -9,7 +9,7 @@ import type { ApplicationRef, StaticProvider } from '@angular/core'; import assert from 'node:assert'; import { basename } from 'node:path'; -import { loadEsmModule } from '../load-esm'; +import { loadEsmModuleFromMemory } from './load-esm-from-memory'; import { MainServerBundleExports, RenderUtilsServerBundleExports } from './main-bundle-exports'; export interface RenderOptions { @@ -39,7 +39,7 @@ export async function renderPage({ document, inlineCriticalCss, outputFiles, - loadBundle = loadEsmModule, + loadBundle = loadEsmModuleFromMemory, }: RenderOptions): Promise { const { default: bootstrapAppFnOrModule } = await loadBundle('./main.server.mjs'); const { ɵSERVER_CONTEXT, renderModule, renderApplication, ɵresetCompiledComponents, ɵConsole } = diff --git a/packages/angular_devkit/build_angular/src/utils/server-rendering/render-worker.ts b/packages/angular_devkit/build_angular/src/utils/server-rendering/render-worker.ts index e5c71d31d441..8007986454f9 100644 --- a/packages/angular_devkit/build_angular/src/utils/server-rendering/render-worker.ts +++ b/packages/angular_devkit/build_angular/src/utils/server-rendering/render-worker.ts @@ -7,7 +7,6 @@ */ import { workerData } from 'node:worker_threads'; -import { loadEsmModule } from '../load-esm'; import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks'; import { patchFetchToLoadInMemoryAssets } from './fetch-patch'; import { RenderResult, ServerContext, renderPage } from './render-page'; @@ -35,7 +34,6 @@ function render(options: RenderOptions): Promise { outputFiles, document, inlineCriticalCss, - loadBundle: async (path) => await loadEsmModule(new URL(path, 'memory://')), }); } diff --git a/packages/angular_devkit/build_angular/src/utils/server-rendering/routes-extractor-worker.ts b/packages/angular_devkit/build_angular/src/utils/server-rendering/routes-extractor-worker.ts index 36b46e3fcaed..966032c4d96d 100644 --- a/packages/angular_devkit/build_angular/src/utils/server-rendering/routes-extractor-worker.ts +++ b/packages/angular_devkit/build_angular/src/utils/server-rendering/routes-extractor-worker.ts @@ -7,10 +7,9 @@ */ import { workerData } from 'node:worker_threads'; -import { loadEsmModule } from '../load-esm'; import type { ESMInMemoryFileLoaderWorkerData } from './esm-in-memory-loader/loader-hooks'; import { patchFetchToLoadInMemoryAssets } from './fetch-patch'; -import { MainServerBundleExports, RenderUtilsServerBundleExports } from './main-bundle-exports'; +import { loadEsmModuleFromMemory } from './load-esm-from-memory'; export interface RoutesExtractorWorkerData extends ESMInMemoryFileLoaderWorkerData { document: string; @@ -30,12 +29,8 @@ const { document, verbose } = workerData as RoutesExtractorWorkerData; /** Renders an application based on a provided options. */ async function extractRoutes(): Promise { - const { extractRoutes } = await loadEsmModule( - new URL('./render-utils.server.mjs', 'memory://'), - ); - const { default: bootstrapAppFnOrModule } = await loadEsmModule( - new URL('./main.server.mjs', 'memory://'), - ); + const { extractRoutes } = await loadEsmModuleFromMemory('./render-utils.server.mjs'); + const { default: bootstrapAppFnOrModule } = await loadEsmModuleFromMemory('./main.server.mjs'); const skippedRedirects: string[] = []; const skippedOthers: string[] = [];