Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): resolve and load sourcemaps when …
Browse files Browse the repository at this point in the history
…using vitedev server with prerendering and ssr

This commit improves the printed error messages when using Vite with SSR and/or SSG by doing a couple of things.

- Enabling resolving and loading of sourcemap in Node.js by using `process.setSourceMapsEnabled`. See https://nodejs.org/api/process.html#processsetsourcemapsenabledval
- Amends Vite's `ssrTransform` method to remap the sourcemaps and inlines them.
- Enables `__zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION` Zone.js flag to output cleaner stacktraces.

To enable, the above mentioned zone.js flag we had to create a server polyfill bundle as otherwise in some cases, zone.js would have been split and loaded before the flag.

Before
```

ERROR ReferenceError: window is not defined
    at console (/src/app/app.component.ts:19:11)
    at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
    at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
    at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
    at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
    at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
    at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
    at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
ERROR Error: Uncaught (in promise): ReferenceError: window is not defined
ReferenceError: window is not defined
    at console (/src/app/app.component.ts:19:11)
    at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
    at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
    at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
    at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
    at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
    at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
    at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
    at resolvePromise (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:1124:31)
    at /usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:1195:17
    at _ZoneDelegate.invokeTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:402:31)
    at AsyncStackTaggingZoneSpec.onInvokeTask (/usr/local/xxx/packages/core/src/zone/async-stack-tagging.ts:46:22)
    at _ZoneDelegate.invokeTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:401:60)
    at Object.onInvokeTask (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:428:29)
    at _ZoneDelegate.invokeTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:401:60)
    at Zone.runTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:173:47)
    at drainMicroTaskQueue (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:581:35) {
  rejection: ReferenceError: window is not defined
      at console (/src/app/app.component.ts:19:11)
      at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
      at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
      at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
      at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
      at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
      at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
      at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52),
  promise: ZoneAwarePromise [Promise] {
    __zone_symbol__state: 0,
    __zone_symbol__value: ReferenceError: window is not defined
        at console (/src/app/app.component.ts:19:11)
        at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
        at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
        at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
        at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
        at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
        at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
        at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
        at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
        at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
  },
  zone: <ref *1> Zone {
    _parent: Zone {
      _parent: [Zone],
      _name: 'asyncStackTagging for Angular',
      _properties: {},
      _zoneDelegate: [_ZoneDelegate]
    },
    _name: 'angular',
    _properties: { isAngularZone: true },
    _zoneDelegate: <ref *2> _ZoneDelegate {
      _taskCounts: [Object],
      zone: [Circular *1],
      _parentDelegate: [_ZoneDelegate],
      _forkZS: null,
      _forkDlgt: null,
      _forkCurrZone: null,
      _interceptZS: null,
      _interceptDlgt: null,
      _interceptCurrZone: null,
      _invokeZS: [Object],
      _invokeDlgt: [_ZoneDelegate],
      _invokeCurrZone: [Circular *1],
      _handleErrorZS: [Object],
      _handleErrorDlgt: [_ZoneDelegate],
      _handleErrorCurrZone: [Circular *1],
      _scheduleTaskZS: [Object],
      _scheduleTaskDlgt: [_ZoneDelegate],
      _scheduleTaskCurrZone: [Circular *1],
      _invokeTaskZS: [Object],
      _invokeTaskDlgt: [_ZoneDelegate],
      _invokeTaskCurrZone: [Circular *1],
      _cancelTaskZS: [Object],
      _cancelTaskDlgt: [_ZoneDelegate],
      _cancelTaskCurrZone: [Circular *1],
      _hasTaskZS: [Object],
      _hasTaskDlgt: [_ZoneDelegate],
      _hasTaskDlgtOwner: [Circular *2],
      _hasTaskCurrZone: [Circular *1]
6:22:33 AM [vite] Internal server error: window is not defined
      at console (/src/app/app.component.ts:19:11)
      at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
      at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
      at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
      at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
      at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
      at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
      at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
ERROR ReferenceError: window is not defined
    at console (/src/app/app.component.ts:19:11)
    at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
    at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
    at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
    at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
    at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
    at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
    at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
ERROR Error: Uncaught (in promise): ReferenceError: window is not defined
ReferenceError: window is not defined
    at console (/src/app/app.component.ts:19:11)
    at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
    at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
    at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
    at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
    at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
    at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
    at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
    at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
    at resolvePromise (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:1124:31)
    at /usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:1195:17
    at _ZoneDelegate.invokeTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:402:31)
    at AsyncStackTaggingZoneSpec.onInvokeTask (/usr/local/xxx/packages/core/src/zone/async-stack-tagging.ts:46:22)
    at _ZoneDelegate.invokeTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:401:60)
    at Object.onInvokeTask (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:428:29)
    at _ZoneDelegate.invokeTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:401:60)
    at Zone.runTask (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:173:47)
    at drainMicroTaskQueue (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:581:35)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
  rejection: ReferenceError: window is not defined
      at console (/src/app/app.component.ts:19:11)
      at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
      at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
      at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
      at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
      at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
      at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
      at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52),
  promise: ZoneAwarePromise [Promise] {
    __zone_symbol__state: 0,
    __zone_symbol__value: ReferenceError: window is not defined
        at console (/src/app/app.component.ts:19:11)
        at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
        at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
        at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
        at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
        at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
        at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
        at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
        at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
        at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
  },
  zone: <ref *1> Zone {
    _parent: Zone {
      _parent: [Zone],
      _name: 'asyncStackTagging for Angular',
      _properties: {},
      _zoneDelegate: [_ZoneDelegate]
    },
    _name: 'angular',
    _properties: { isAngularZone: true },
    _zoneDelegate: <ref *2> _ZoneDelegate {
      _taskCounts: [Object],
      zone: [Circular *1],
      _parentDelegate: [_ZoneDelegate],
      _forkZS: null,
      _forkDlgt: null,
      _forkCurrZone: null,
      _interceptZS: null,
      _interceptDlgt: null,
      _interceptCurrZone: null,
      _invokeZS: [Object],
      _invokeDlgt: [_ZoneDelegate],
      _invokeCurrZone: [Circular *1],
      _handleErrorZS: [Object],
      _handleErrorDlgt: [_ZoneDelegate],
      _handleErrorCurrZone: [Circular *1],
      _scheduleTaskZS: [Object],
      _scheduleTaskDlgt: [_ZoneDelegate],
      _scheduleTaskCurrZone: [Circular *1],
      _invokeTaskZS: [Object],
      _invokeTaskDlgt: [_ZoneDelegate],
      _invokeTaskCurrZone: [Circular *1],
      _cancelTaskZS: [Object],
      _cancelTaskDlgt: [_ZoneDelegate],
      _cancelTaskCurrZone: [Circular *1],
      _hasTaskZS: [Object],
      _hasTaskDlgt: [_ZoneDelegate],
      _hasTaskDlgtOwner: [Circular *2],
      _hasTaskCurrZone: [Circular *1]
    }
  },
6:23:04 AM [vite] Internal server error: window is not defined
      at console (/src/app/app.component.ts:19:11)
      at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
      at getNodeInjectable (/usr/local/xxx/packages/core/src/render3/di.ts:659:38)
      at createRootComponent (/usr/local/xxx/packages/core/src/render3/component_ref.ts:464:31)
      at ComponentFactory.create (/usr/local/xxx/packages/core/src/render3/component_ref.ts:288:19)
      at ApplicationRef.bootstrap (/usr/local/xxx/packages/core/src/application_ref.ts:1017:38)
      at <anonymous> (/usr/local/xxx/packages/core/src/application_ref.ts:287:20)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:368:26)
      at Object.onInvoke (/usr/local/xxx/packages/core/src/zone/ng_zone.ts:443:29)
      at _ZoneDelegate.invoke (/usr/local/xxx/cli-reproduction/test-ssr-/node_modules/zone.js/fesm2015/zone-node.js:367:52)
```

After
```
ERROR ReferenceError: window is not defined
    at console (/src/app/app.component.ts:17:3)
    at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
    at getNodeInjectable (/usr/local/xxx/git/packages/core/src/render3/di.ts:659:38)
    at createRootComponent (/usr/local/xxx/git/packages/core/src/render3/component_ref.ts:464:31)
    at ComponentFactory.create (/usr/local/xxx/git/packages/core/src/render3/component_ref.ts:288:19)
    at ApplicationRef.bootstrap (/usr/local/xxx/git/packages/core/src/application_ref.ts:1017:38)
    at <anonymous> (/usr/local/xxx/git/packages/core/src/application_ref.ts:287:20)
    at _ZoneDelegate.invoke (/node_modules/zone.js/fesm2015/zone-node.js:370:40)
    at Object.onInvoke (/usr/local/xxx/git/packages/core/src/zone/ng_zone.ts:443:29)
    at _ZoneDelegate.invoke (/node_modules/zone.js/fesm2015/zone-node.js:370:40)
ERROR ReferenceError: window is not defined
    at console (/src/app/app.component.ts:17:3)
    at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
    at getNodeInjectable (/usr/local/xxx/git/packages/core/src/render3/di.ts:659:38)
    at createRootComponent (/usr/local/xxx/git/packages/core/src/render3/component_ref.ts:464:31)
    at ComponentFactory.create (/usr/local/xxx/git/packages/core/src/render3/component_ref.ts:288:19)
    at ApplicationRef.bootstrap (/usr/local/xxx/git/packages/core/src/application_ref.ts:1017:38)
    at <anonymous> (/usr/local/xxx/git/packages/core/src/application_ref.ts:287:20)
    at _ZoneDelegate.invoke (/node_modules/zone.js/fesm2015/zone-node.js:370:40)
    at Object.onInvoke (/usr/local/xxx/git/packages/core/src/zone/ng_zone.ts:443:29)
    at _ZoneDelegate.invoke (/node_modules/zone.js/fesm2015/zone-node.js:370:40)
8:13:37 AM [vite] Internal server error: window is not defined
      at console (/src/app/app.component.ts:17:3)
      at NodeInjectorFactory.AppComponent_Factory (/src/app/app.component.ts:12:26)
      at getNodeInjectable (/usr/local/xxx/git/packages/core/src/render3/di.ts:659:38)
      at createRootComponent (/usr/local/xxx/git/packages/core/src/render3/component_ref.ts:464:31)
      at ComponentFactory.create (/usr/local/xxx/git/packages/core/src/render3/component_ref.ts:288:19)
      at ApplicationRef.bootstrap (/usr/local/xxx/git/packages/core/src/application_ref.ts:1017:38)
      at <anonymous> (/usr/local/xxx/git/packages/core/src/application_ref.ts:287:20)
      at _ZoneDelegate.invoke (/node_modules/zone.js/fesm2015/zone-node.js:370:40)
      at Object.onInvoke (/usr/local/xxx/git/packages/core/src/zone/ng_zone.ts:443:29)
      at _ZoneDelegate.invoke (/node_modules/zone.js/fesm2015/zone-node.js:370:40)
```

Note: in the above case the error is printed 3x, this will be addressed in the future.
  • Loading branch information
alan-agius4 committed Oct 16, 2023
1 parent 0f45e21 commit cf2de5c
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
createBrowserCodeBundleOptions,
createBrowserPolyfillBundleOptions,
createServerCodeBundleOptions,
createServerPolyfillBundleOptions,
} from '../../tools/esbuild/application-code-bundle';
import { generateBudgetStats } from '../../tools/esbuild/budget-stats';
import { BuildOutputFileType, BundlerContext } from '../../tools/esbuild/bundler-context';
Expand Down Expand Up @@ -122,15 +123,23 @@ export async function executeBuild(
}
}

// Server application code
// Skip server build when none of the features are enabled.
if (serverEntryPoint && (prerenderOptions || appShellOptions || ssrOptions)) {
const nodeTargets = getSupportedNodeTargets();
const nodeTargets = [...target, ...getSupportedNodeTargets()];
bundlerContexts.push(
// Server application code
new BundlerContext(
workspaceRoot,
!!options.watch,
createServerCodeBundleOptions(options, [...target, ...nodeTargets], codeBundleCache),
createServerCodeBundleOptions(options, nodeTargets, codeBundleCache),
() => false,
),

// Server polyfills code
new BundlerContext(
workspaceRoot,
!!options.watch,
createServerPolyfillBundleOptions(options, nodeTargets),
() => false,
),
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import remapping, { SourceMapInput } from '@ampproject/remapping';
import type { BuilderContext } from '@angular-devkit/architect';
import type { json, logging } from '@angular-devkit/core';
import type { Plugin } from 'esbuild';
Expand Down Expand Up @@ -72,6 +73,10 @@ export async function* serveWithVite(
// This is so instead of prerendering all the routes for every change, the page is "prerendered" when it is requested.
browserOptions.ssr = true;
browserOptions.prerender = false;

// https://nodejs.org/api/process.html#processsetsourcemapsenabledval
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(process as any).setSourceMapsEnabled(true);
}

// Set all packages as external to support Vite's prebundle caching
Expand Down Expand Up @@ -403,6 +408,31 @@ export async function setupServer(
};
},
configureServer(server) {
const originalssrTransform = server.ssrTransform;
server.ssrTransform = async (code, map, url, originalCode) => {
const result = await originalssrTransform(code, null, url, originalCode);
if (!result) {
return null;
}

let transformedCode = result.code;
if (result.map && map) {
transformedCode +=
`\n//# sourceMappingURL=` +
`data:application/json;base64,${Buffer.from(
JSON.stringify(
remapping([result.map as SourceMapInput, map as SourceMapInput], () => null),
),
).toString('base64')}`;
}

return {
...result,
map: null,
code: transformedCode,
};
};

// Assets and resources get handled first
server.middlewares.use(function angularAssetsMiddleware(req, res, next) {
if (req.url === undefined || res.writableEnded) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import type { BuildOptions } from 'esbuild';
import type { BuildOptions, Plugin } from 'esbuild';
import assert from 'node:assert';
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
Expand Down Expand Up @@ -75,8 +75,7 @@ export function createBrowserPolyfillBundleOptions(
target: string[],
sourceFileCache?: SourceFileCache,
): BuildOptions | undefined {
const { workspaceRoot, outputNames, jit } = options;

const { outputNames, jit, workspaceRoot, i18nOptions } = options;
const { pluginOptions, styleOptions } = createCompilerPluginOptions(
options,
target,
Expand Down Expand Up @@ -114,38 +113,7 @@ export function createBrowserPolyfillBundleOptions(
polyfills.push('@angular/compiler');
}

// Add Angular's global locale data if i18n options are present.
// Locale data should go first so that project provided polyfill code can augment if needed.
let needLocaleDataPlugin = false;
if (options.i18nOptions.shouldInline) {
// When inlining, a placeholder is used to allow the post-processing step to inject the $localize locale identifier
polyfills.unshift('angular:locale/placeholder');
buildOptions.plugins?.unshift(
createVirtualModulePlugin({
namespace: 'angular:locale/placeholder',
entryPointOnly: false,
loadContent: () => ({
contents: `(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n`,
loader: 'js',
resolveDir: workspaceRoot,
}),
}),
);

// Add locale data for all active locales
// TODO: Inject each individually within the inlining process itself
for (const locale of options.i18nOptions.inlineLocales) {
polyfills.unshift(`angular:locale/data:${locale}`);
}
needLocaleDataPlugin = true;
} else if (options.i18nOptions.hasDefinedSourceLocale) {
// When not inlining and a source local is present, use the source locale data directly
polyfills.unshift(`angular:locale/data:${options.i18nOptions.sourceLocale}`);
needLocaleDataPlugin = true;
}
if (needLocaleDataPlugin) {
buildOptions.plugins?.push(createAngularLocaleDataPlugin());
}
addLocalizePolyfillsAndPlugins(options, polyfills, buildOptions);

if (polyfills.length === 0) {
return;
Expand Down Expand Up @@ -180,7 +148,7 @@ export function createBrowserPolyfillBundleOptions(
}),
);

if (!options.i18nOptions.shouldInline && !hasLocalizePolyfill) {
if (!i18nOptions.shouldInline && !hasLocalizePolyfill) {
// Cannot use `build.resolve` here since it does not allow overriding the external options
// and the actual presence of the `@angular/localize` package needs to be checked here.
const workspaceRequire = createRequire(workspaceRoot + '/');
Expand All @@ -197,8 +165,8 @@ export function createBrowserPolyfillBundleOptions(
.join('\n');

// If not inlining translations and source locale is defined, inject the locale specifier
if (!options.i18nOptions.shouldInline && options.i18nOptions.hasDefinedSourceLocale) {
contents += `(globalThis.$localize ??= {}).locale = "${options.i18nOptions.sourceLocale}";\n`;
if (!i18nOptions.shouldInline && i18nOptions.hasDefinedSourceLocale) {
contents += `(globalThis.$localize ??= {}).locale = "${i18nOptions.sourceLocale}";\n`;
}

return {
Expand Down Expand Up @@ -246,7 +214,6 @@ export function createServerCodeBundleOptions(

const mainServerNamespace = 'angular:main-server';
const ssrEntryNamespace = 'angular:ssr-entry';

const entryPoints: Record<string, string> = {
'main.server': mainServerNamespace,
};
Expand All @@ -268,6 +235,7 @@ export function createServerCodeBundleOptions(
// More details: https://github.com/angular/angular-cli/issues/25405.
mainFields: ['es2020', 'es2015', 'module', 'main'],
entryNames: '[name]',
external: ['./polyfills.server.mjs'],
target,
banner: {
// Note: Needed as esbuild does not provide require shims / proxy from ESModules.
Expand Down Expand Up @@ -297,42 +265,14 @@ export function createServerCodeBundleOptions(
buildOptions.plugins.push(createRxjsEsmResolutionPlugin());
}

const polyfills: string[] = [];
if (options.polyfills?.includes('zone.js')) {
polyfills.push(`import 'zone.js/node';`);
}

if (jit) {
polyfills.push(`import '@angular/compiler';`);
}

polyfills.push(`import '@angular/platform-server/init';`);

// Add Angular's global locale data if i18n options are present.
let needLocaleDataPlugin = false;
if (options.i18nOptions.shouldInline) {
// Add locale data for all active locales
for (const locale of options.i18nOptions.inlineLocales) {
polyfills.unshift(`import 'angular:locale/data:${locale}';`);
}
needLocaleDataPlugin = true;
} else if (options.i18nOptions.hasDefinedSourceLocale) {
// When not inlining and a source local is present, use the source locale data directly
polyfills.unshift(`import 'angular:locale/data:${options.i18nOptions.sourceLocale}';`);
needLocaleDataPlugin = true;
}
if (needLocaleDataPlugin) {
buildOptions.plugins.push(createAngularLocaleDataPlugin());
}

buildOptions.plugins.push(
createVirtualModulePlugin({
namespace: mainServerNamespace,
loadContent: async () => {
const mainServerEntryPoint = relative(workspaceRoot, serverEntryPoint).replace(/\\/g, '/');

const contents = [
...polyfills,
`import './polyfills.server.mjs';`,
`import moduleOrBootstrapFn from './${mainServerEntryPoint}';`,
`export default moduleOrBootstrapFn;`,
`export * from './${mainServerEntryPoint}';`,
Expand All @@ -344,27 +284,6 @@ export function createServerCodeBundleOptions(
contents.push(`export { ɵresetCompiledComponents } from '@angular/core';`);
}

if (!options.i18nOptions.shouldInline) {
// Cannot use `build.resolve` here since it does not allow overriding the external options
// and the actual presence of the `@angular/localize` package needs to be checked here.
const workspaceRequire = createRequire(workspaceRoot + '/');
try {
workspaceRequire.resolve('@angular/localize');
// The resolve call above will throw if not found
contents.push(`import '@angular/localize/init';`);
} catch {}
}

if (options.i18nOptions.shouldInline) {
// When inlining, a placeholder is used to allow the post-processing step to inject the $localize locale identifier
contents.push('(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";');
} else if (options.i18nOptions.hasDefinedSourceLocale) {
// If not inlining translations and source locale is defined, inject the locale specifier
contents.push(
`(globalThis.$localize ??= {}).locale = "${options.i18nOptions.sourceLocale}";`,
);
}

if (prerenderOptions?.discoverRoutes) {
// We do not import it directly so that node.js modules are resolved using the correct context.
const routesExtractorCode = await readFile(
Expand Down Expand Up @@ -394,7 +313,7 @@ export function createServerCodeBundleOptions(

return {
contents: [
...polyfills,
`import './polyfills.server.mjs';`,
`import './${serverEntryPoint}';`,
`export * from './${serverEntryPoint}';`,
].join('\n'),
Expand All @@ -413,6 +332,76 @@ export function createServerCodeBundleOptions(
return buildOptions;
}

export function createServerPolyfillBundleOptions(
options: NormalizedApplicationBuildOptions,
target: string[],
): BuildOptions {
const { outputNames, workspaceRoot, jit } = options;

const buildOptions: BuildOptions = {
...getEsBuildCommonOptions(options),
platform: 'node',
outExtension: { '.js': '.mjs' },
// Note: `es2015` is needed for RxJS v6. If not specified, `module` would
// match and the ES5 distribution would be bundled and ends up breaking at
// runtime with the RxJS testing library.
// More details: https://github.com/angular/angular-cli/issues/25405.
mainFields: ['es2020', 'es2015', 'module', 'main'],
entryNames: outputNames.bundles,
target,
splitting: false,
supported: getFeatureSupport(target),
plugins: [createSourcemapIgnorelistPlugin()],
};

buildOptions.plugins ??= [];
const polyfills: string[] = [];
if (options.polyfills?.includes('zone.js')) {
const zoneFlagsNamespace = 'angular:zone-flags/placeholder';
polyfills.push(`import '${zoneFlagsNamespace}';`, `import 'zone.js/node';`);

// Disable Zone.js uncaught promise rejections to provide cleaner stacktraces.
buildOptions.plugins.unshift(
createVirtualModulePlugin({
namespace: zoneFlagsNamespace,
entryPointOnly: false,
loadContent: () => ({
contents: `globalThis.__zone_symbol__DISABLE_WRAPPING_UNCAUGHT_PROMISE_REJECTION = true;`,
loader: 'js',
resolveDir: workspaceRoot,
}),
}),
);
}

if (jit) {
polyfills.push(`import '@angular/compiler';`);
}

polyfills.push(`import '@angular/platform-server/init';`);

addLocalizePolyfillsAndPlugins(options, polyfills, buildOptions);

// Add polyfill entry point
const namespace = 'angular:server-polyfills';
buildOptions.entryPoints = {
'polyfills.server': namespace,
};

buildOptions.plugins.push(
createVirtualModulePlugin({
namespace,
loadContent: () => ({
contents: polyfills.join('\n'),
loader: 'js',
resolveDir: workspaceRoot,
}),
}),
);

return buildOptions;
}

function getEsBuildCommonOptions(options: NormalizedApplicationBuildOptions): BuildOptions {
const {
workspaceRoot,
Expand Down Expand Up @@ -475,3 +464,42 @@ function getEsBuildCommonOptions(options: NormalizedApplicationBuildOptions): Bu
publicPath: options.publicPath,
};
}

function addLocalizePolyfillsAndPlugins(
options: NormalizedApplicationBuildOptions,
polyfills: string[],
buildOptions: BuildOptions,
): void {
// Add Angular's global locale data if i18n options are present.
// Locale data should go first so that project provided polyfill code can augment if needed.
let needLocaleDataPlugin = false;
if (options.i18nOptions.shouldInline) {
// When inlining, a placeholder is used to allow the post-processing step to inject the $localize locale identifier
polyfills.unshift('angular:locale/placeholder');
buildOptions.plugins?.unshift(
createVirtualModulePlugin({
namespace: 'angular:locale/placeholder',
entryPointOnly: false,
loadContent: () => ({
contents: `(globalThis.$localize ??= {}).locale = "___NG_LOCALE_INSERT___";\n`,
loader: 'js',
resolveDir: options.workspaceRoot,
}),
}),
);

// Add locale data for all active locales
// TODO: Inject each individually within the inlining process itself
for (const locale of options.i18nOptions.inlineLocales) {
polyfills.unshift(`angular:locale/data:${locale}`);
}
needLocaleDataPlugin = true;
} else if (options.i18nOptions.hasDefinedSourceLocale) {
// When not inlining and a source local is present, use the source locale data directly
polyfills.unshift(`angular:locale/data:${options.i18nOptions.sourceLocale}`);
needLocaleDataPlugin = true;
}
if (needLocaleDataPlugin) {
buildOptions.plugins?.push(createAngularLocaleDataPlugin());
}
}

0 comments on commit cf2de5c

Please sign in to comment.