Skip to content

Commit

Permalink
fix(@angular-devkit/build-angular): allow package file loader option …
Browse files Browse the repository at this point in the history
…with Vite prebundling

Previously, the `application` builder would consider all imports originating from a package
to be considered external when caching was enabled. This allows Vite's prebundling to function
and optimize the build/rebuild experience for the development server. However, when using the
newly introduced `loader` option, this also inadvertently caused files that should be affected
by the option that originate from a package to also be considered external. This behavior would
then prevent the loader customization from being performed. To rectify this situation, all files
that would be affected by a loader customization will not be marked as external for the purposes
of prebundling unless explicitly configured by the `externalDependencies` option.

(cherry picked from commit f83a485)
  • Loading branch information
clydin authored and alan-agius4 committed Jan 23, 2024
1 parent 412fe6e commit 4e2586a
Show file tree
Hide file tree
Showing 2 changed files with 89 additions and 4 deletions.
Expand Up @@ -17,6 +17,7 @@ import { createCompilerPlugin } from './angular/compiler-plugin';
import { SourceFileCache } from './angular/source-file-cache';
import { BundlerOptionsFactory } from './bundler-context';
import { createCompilerPluginOptions } from './compiler-plugin-options';
import { createExternalPackagesPlugin } from './external-packages-plugin';
import { createAngularLocaleDataPlugin } from './i18n-locale-plugin';
import { createRxjsEsmResolutionPlugin } from './rxjs-esm-resolution-plugin';
import { createSourcemapIgnorelistPlugin } from './sourcemap-ignorelist-plugin';
Expand Down Expand Up @@ -59,14 +60,21 @@ export function createBrowserCodeBundleOptions(
],
};

if (options.externalPackages) {
buildOptions.packages = 'external';
}

if (options.plugins) {
buildOptions.plugins?.push(...options.plugins);
}

if (options.externalPackages) {
// Package files affected by a customized loader should not be implicitly marked as external
if (options.loaderExtensions || options.plugins) {
// Plugin must be added after custom plugins to ensure any added loader options are considered
buildOptions.plugins?.push(createExternalPackagesPlugin());
} else {
// Safe to use the packages external option directly
buildOptions.packages = 'external';
}
}

return buildOptions;
}

Expand Down
@@ -0,0 +1,77 @@
/**
* @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 type { Plugin } from 'esbuild';
import { extname } from 'node:path';

const EXTERNAL_PACKAGE_RESOLUTION = Symbol('EXTERNAL_PACKAGE_RESOLUTION');

/**
* Creates a plugin that marks any resolved path as external if it is within a node modules directory.
* This is used instead of the esbuild `packages` option to avoid marking files that should be loaded
* via customized loaders. This is necessary to prevent Vite development server pre-bundling errors.
*
* @returns An esbuild plugin.
*/
export function createExternalPackagesPlugin(): Plugin {
return {
name: 'angular-external-packages',
setup(build) {
// Safe to use native packages external option if no loader options present
if (
build.initialOptions.loader === undefined ||
Object.keys(build.initialOptions.loader).length === 0
) {
build.initialOptions.packages = 'external';

return;
}

const loaderFileExtensions = new Set(Object.keys(build.initialOptions.loader));

// Only attempt resolve of non-relative and non-absolute paths
build.onResolve({ filter: /^[^./]/ }, async (args) => {
if (args.pluginData?.[EXTERNAL_PACKAGE_RESOLUTION]) {
return null;
}

const { importer, kind, resolveDir, namespace, pluginData = {} } = args;
pluginData[EXTERNAL_PACKAGE_RESOLUTION] = true;

const result = await build.resolve(args.path, {
importer,
kind,
namespace,
pluginData,
resolveDir,
});

// Return result if unable to resolve or explicitly marked external (externalDependencies option)
if (!result.path || result.external) {
return result;
}

// Allow customized loaders to run against configured paths regardless of location
if (loaderFileExtensions.has(extname(result.path))) {
return result;
}

// Mark paths from a node modules directory as external
if (/[\\/]node_modules[\\/]/.test(result.path)) {
return {
path: args.path,
external: true,
};
}

// Otherwise return original result
return result;
});
},
};
}

0 comments on commit 4e2586a

Please sign in to comment.