Skip to content

Commit

Permalink
fix(@angular/build): remove Vite "/@id/" prefix for explicit external…
Browse files Browse the repository at this point in the history
… dependencies

Adds a Vite plugin which will remove the /@id/ prefix (which gets inserted by Vite during import-analysis) for explicit externalDependencies.
  • Loading branch information
kfrederix authored and dgp1130 committed Jul 15, 2024
1 parent 24aaf1e commit 394f9ce
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* @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.dev/license
*/

import { executeDevServer } from '../../index';
import { executeOnceAndFetch } from '../execute-fetch';
import { describeServeBuilder } from '../jasmine-helpers';
import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup';

describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => {
describe('Behavior: "browser builder external dependencies"', () => {
beforeEach(async () => {
setupTarget(harness, {
externalDependencies: ['rxjs', 'rxjs/operators'],
});

await harness.writeFile(
'src/main.ts',
`
import { BehaviorSubject } from 'rxjs';
import { map } from 'rxjs/operators';
const subject = new BehaviorSubject<string>('hello');
console.log(subject.value);
subject.pipe(map((val) => val + ' there')).subscribe(console.log);
`,
);
});

it('respects import specifiers for externalized dependencies', async () => {
harness.useTarget('serve', {
...BASE_OPTIONS,
});

const { result, response } = await executeOnceAndFetch(harness, 'main.js');

expect(result?.success).toBeTrue();

const text = await response?.text();
expect(text).toContain(`import { BehaviorSubject } from "rxjs";`);
expect(text).toContain(`import { map } from "rxjs/operators";`);
});
});
});
2 changes: 2 additions & 0 deletions packages/angular/build/src/builders/dev-server/vite-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { basename, join } from 'node:path';
import type { Connect, DepOptimizationConfig, InlineConfig, ViteDevServer } from 'vite';
import { createAngularMemoryPlugin } from '../../tools/vite/angular-memory-plugin';
import { createAngularLocaleDataPlugin } from '../../tools/vite/i18n-locale-plugin';
import { createRemoveIdPrefixPlugin } from '../../tools/vite/id-prefix-plugin';
import { loadProxyConfiguration, normalizeSourceMaps } from '../../utils';
import { loadEsmModule } from '../../utils/load-esm';
import { ApplicationBuilderOutput } from '../application';
Expand Down Expand Up @@ -577,6 +578,7 @@ export async function setupServer(
extensionMiddleware,
normalizePath,
}),
createRemoveIdPrefixPlugin(externalMetadata.explicit),
],
// Browser only optimizeDeps. (This does not run for SSR dependencies).
optimizeDeps: getDepOptimizationConfig({
Expand Down
52 changes: 52 additions & 0 deletions packages/angular/build/src/tools/vite/id-prefix-plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* @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.dev/license
*/

import type { Plugin } from 'vite';

// NOTE: the implementation for this Vite plugin is roughly based on:
// https://github.com/MilanKovacic/vite-plugin-externalize-dependencies

const VITE_ID_PREFIX = '/@id/';

const escapeRegexSpecialChars = (inputString: string): string => {
return inputString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
};

export const createRemoveIdPrefixPlugin = (externals: string[]): Plugin => ({
name: 'vite-plugin-remove-id-prefix',
apply: 'serve',
configResolved: (resolvedConfig) => {
// don't do anything when the list of externals is empty
if (externals.length === 0) {
return;
}

const escapedExternals = externals.map(escapeRegexSpecialChars);
const prefixedExternalRegex = new RegExp(
`${VITE_ID_PREFIX}(${escapedExternals.join('|')})`,
'g',
);

// @ts-expect-error: Property 'push' does not exist on type 'readonly Plugin<any>[]'
// Reasoning:
// since the /@id/ prefix is added by Vite's import-analysis plugin,
// we must add our actual plugin dynamically, to ensure that it will run
// AFTER the import-analysis.
resolvedConfig.plugins.push({
name: 'vite-plugin-remove-id-prefix-transform',
transform: (code: string) => {
// don't do anything when code does not contain the Vite prefix
if (!code.includes(VITE_ID_PREFIX)) {
return code;
}

return code.replace(prefixedExternalRegex, (_, externalName) => externalName);
},
});
},
});

0 comments on commit 394f9ce

Please sign in to comment.