Skip to content

Commit 96dc7e6

Browse files
kfrederixdgp1130
authored andcommittedJul 15, 2024·
fix(@angular/build): remove Vite "/@id/" prefix for explicit external dependencies
Adds a Vite plugin which will remove the /@id/ prefix (which gets inserted by Vite during import-analysis) for explicit externalDependencies. (cherry picked from commit 394f9ce)
1 parent 4f6cee2 commit 96dc7e6

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { executeDevServer } from '../../index';
10+
import { executeOnceAndFetch } from '../execute-fetch';
11+
import { describeServeBuilder } from '../jasmine-helpers';
12+
import { BASE_OPTIONS, DEV_SERVER_BUILDER_INFO } from '../setup';
13+
14+
describeServeBuilder(executeDevServer, DEV_SERVER_BUILDER_INFO, (harness, setupTarget) => {
15+
describe('Behavior: "browser builder external dependencies"', () => {
16+
beforeEach(async () => {
17+
setupTarget(harness, {
18+
externalDependencies: ['rxjs', 'rxjs/operators'],
19+
});
20+
21+
await harness.writeFile(
22+
'src/main.ts',
23+
`
24+
import { BehaviorSubject } from 'rxjs';
25+
import { map } from 'rxjs/operators';
26+
27+
const subject = new BehaviorSubject<string>('hello');
28+
console.log(subject.value);
29+
30+
subject.pipe(map((val) => val + ' there')).subscribe(console.log);
31+
`,
32+
);
33+
});
34+
35+
it('respects import specifiers for externalized dependencies', async () => {
36+
harness.useTarget('serve', {
37+
...BASE_OPTIONS,
38+
});
39+
40+
const { result, response } = await executeOnceAndFetch(harness, 'main.js');
41+
42+
expect(result?.success).toBeTrue();
43+
44+
const text = await response?.text();
45+
expect(text).toContain(`import { BehaviorSubject } from "rxjs";`);
46+
expect(text).toContain(`import { map } from "rxjs/operators";`);
47+
});
48+
});
49+
});

‎packages/angular/build/src/builders/dev-server/vite-server.ts

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { basename, join } from 'node:path';
1616
import type { Connect, DepOptimizationConfig, InlineConfig, ViteDevServer } from 'vite';
1717
import { createAngularMemoryPlugin } from '../../tools/vite/angular-memory-plugin';
1818
import { createAngularLocaleDataPlugin } from '../../tools/vite/i18n-locale-plugin';
19+
import { createRemoveIdPrefixPlugin } from '../../tools/vite/id-prefix-plugin';
1920
import { loadProxyConfiguration, normalizeSourceMaps } from '../../utils';
2021
import { loadEsmModule } from '../../utils/load-esm';
2122
import { ApplicationBuilderOutput } from '../application';
@@ -577,6 +578,7 @@ export async function setupServer(
577578
extensionMiddleware,
578579
normalizePath,
579580
}),
581+
createRemoveIdPrefixPlugin(externalMetadata.explicit),
580582
],
581583
// Browser only optimizeDeps. (This does not run for SSR dependencies).
582584
optimizeDeps: getDepOptimizationConfig({
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import type { Plugin } from 'vite';
10+
11+
// NOTE: the implementation for this Vite plugin is roughly based on:
12+
// https://github.com/MilanKovacic/vite-plugin-externalize-dependencies
13+
14+
const VITE_ID_PREFIX = '/@id/';
15+
16+
const escapeRegexSpecialChars = (inputString: string): string => {
17+
return inputString.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
18+
};
19+
20+
export const createRemoveIdPrefixPlugin = (externals: string[]): Plugin => ({
21+
name: 'vite-plugin-remove-id-prefix',
22+
apply: 'serve',
23+
configResolved: (resolvedConfig) => {
24+
// don't do anything when the list of externals is empty
25+
if (externals.length === 0) {
26+
return;
27+
}
28+
29+
const escapedExternals = externals.map(escapeRegexSpecialChars);
30+
const prefixedExternalRegex = new RegExp(
31+
`${VITE_ID_PREFIX}(${escapedExternals.join('|')})`,
32+
'g',
33+
);
34+
35+
// @ts-expect-error: Property 'push' does not exist on type 'readonly Plugin<any>[]'
36+
// Reasoning:
37+
// since the /@id/ prefix is added by Vite's import-analysis plugin,
38+
// we must add our actual plugin dynamically, to ensure that it will run
39+
// AFTER the import-analysis.
40+
resolvedConfig.plugins.push({
41+
name: 'vite-plugin-remove-id-prefix-transform',
42+
transform: (code: string) => {
43+
// don't do anything when code does not contain the Vite prefix
44+
if (!code.includes(VITE_ID_PREFIX)) {
45+
return code;
46+
}
47+
48+
return code.replace(prefixedExternalRegex, (_, externalName) => externalName);
49+
},
50+
});
51+
},
52+
});

0 commit comments

Comments
 (0)
Please sign in to comment.