Skip to content

Commit 606bae2

Browse files
fix(vite): handle Vite query strings in linker transform filter (#177)
1 parent e6596ec commit 606bae2

File tree

2 files changed

+201
-1
lines changed

2 files changed

+201
-1
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { describe, it, expect } from 'vitest'
2+
3+
import { linkAngularPackageSync } from '../index.js'
4+
5+
/**
6+
* Minimal Angular partial declaration fixtures that simulate the structure
7+
* of FESM bundle files (including Angular 21+ chunk files).
8+
* Uses actual Unicode ɵ (U+0275) characters as they appear in real Angular packages.
9+
*/
10+
const INJECTABLE_CHUNK = `
11+
import * as i0 from '@angular/core';
12+
13+
class PlatformLocation {
14+
historyGo(relativePosition) {
15+
throw new Error('Not implemented');
16+
}
17+
static \u0275fac = i0.\u0275\u0275ngDeclareFactory({
18+
minVersion: "12.0.0",
19+
version: "21.0.0",
20+
ngImport: i0,
21+
type: PlatformLocation,
22+
deps: [],
23+
target: i0.\u0275\u0275FactoryTarget.Injectable
24+
});
25+
static \u0275prov = i0.\u0275\u0275ngDeclareInjectable({
26+
minVersion: "12.0.0",
27+
version: "21.0.0",
28+
ngImport: i0,
29+
type: PlatformLocation,
30+
providedIn: "platform",
31+
useClass: undefined
32+
});
33+
}
34+
35+
export { PlatformLocation };
36+
`
37+
38+
const NG_MODULE_CHUNK = `
39+
import * as i0 from '@angular/core';
40+
41+
class CommonModule {
42+
static \u0275fac = i0.\u0275\u0275ngDeclareFactory({
43+
minVersion: "12.0.0",
44+
version: "21.0.0",
45+
ngImport: i0,
46+
type: CommonModule,
47+
deps: [],
48+
target: i0.\u0275\u0275FactoryTarget.NgModule
49+
});
50+
static \u0275mod = i0.\u0275\u0275ngDeclareNgModule({
51+
minVersion: "14.0.0",
52+
version: "21.0.0",
53+
ngImport: i0,
54+
type: CommonModule,
55+
imports: [],
56+
exports: []
57+
});
58+
static \u0275inj = i0.\u0275\u0275ngDeclareInjector({
59+
minVersion: "12.0.0",
60+
version: "21.0.0",
61+
ngImport: i0,
62+
type: CommonModule
63+
});
64+
}
65+
66+
export { CommonModule };
67+
`
68+
69+
const PIPE_CHUNK = `
70+
import * as i0 from '@angular/core';
71+
72+
class AsyncPipe {
73+
constructor(ref) {
74+
this._ref = ref;
75+
}
76+
static \u0275fac = i0.\u0275\u0275ngDeclareFactory({
77+
minVersion: "12.0.0",
78+
version: "21.0.0",
79+
ngImport: i0,
80+
type: AsyncPipe,
81+
deps: [{ token: i0.ChangeDetectorRef }],
82+
target: i0.\u0275\u0275FactoryTarget.Pipe
83+
});
84+
static \u0275pipe = i0.\u0275\u0275ngDeclarePipe({
85+
minVersion: "14.0.0",
86+
version: "21.0.0",
87+
ngImport: i0,
88+
type: AsyncPipe,
89+
isStandalone: false,
90+
name: "async",
91+
pure: false
92+
});
93+
}
94+
95+
export { AsyncPipe };
96+
`
97+
98+
describe('Angular linker - chunk file linking', () => {
99+
it('should link \u0275\u0275ngDeclareFactory and \u0275\u0275ngDeclareInjectable', () => {
100+
const result = linkAngularPackageSync(
101+
INJECTABLE_CHUNK,
102+
'node_modules/@angular/common/fesm2022/_platform_location-chunk.mjs',
103+
)
104+
105+
expect(result.linked).toBe(true)
106+
expect(result.code).not.toContain('\u0275\u0275ngDeclare')
107+
})
108+
109+
it('should link \u0275\u0275ngDeclareNgModule and \u0275\u0275ngDeclareInjector', () => {
110+
const result = linkAngularPackageSync(
111+
NG_MODULE_CHUNK,
112+
'node_modules/@angular/common/fesm2022/_common_module-chunk.mjs',
113+
)
114+
115+
expect(result.linked).toBe(true)
116+
expect(result.code).not.toContain('\u0275\u0275ngDeclare')
117+
})
118+
119+
it('should link \u0275\u0275ngDeclarePipe', () => {
120+
const result = linkAngularPackageSync(
121+
PIPE_CHUNK,
122+
'node_modules/@angular/common/fesm2022/_pipes-chunk.mjs',
123+
)
124+
125+
expect(result.linked).toBe(true)
126+
expect(result.code).not.toContain('\u0275\u0275ngDeclare')
127+
})
128+
129+
it('should return linked: false for files without declarations', () => {
130+
const code = `
131+
export function helper() { return 42; }
132+
`
133+
const result = linkAngularPackageSync(
134+
code,
135+
'node_modules/@angular/common/fesm2022/_utils-chunk.mjs',
136+
)
137+
138+
expect(result.linked).toBe(false)
139+
})
140+
})
141+
142+
describe('NODE_MODULES_JS_REGEX filter matching', () => {
143+
// This is the fixed regex from angular-linker-plugin.ts
144+
const NODE_MODULES_JS_REGEX = /node_modules[\\/].*\.[cm]?js(?:\?.*)?$/
145+
146+
it('should match standard Angular FESM files', () => {
147+
expect(NODE_MODULES_JS_REGEX.test('node_modules/@angular/common/fesm2022/common.mjs')).toBe(
148+
true,
149+
)
150+
})
151+
152+
it('should match chunk files', () => {
153+
expect(
154+
NODE_MODULES_JS_REGEX.test(
155+
'node_modules/@angular/common/fesm2022/_platform_location-chunk.mjs',
156+
),
157+
).toBe(true)
158+
})
159+
160+
it('should match absolute paths', () => {
161+
expect(
162+
NODE_MODULES_JS_REGEX.test(
163+
'/Users/dev/project/node_modules/@angular/common/fesm2022/_platform_location-chunk.mjs',
164+
),
165+
).toBe(true)
166+
})
167+
168+
it('should match paths with Vite query strings', () => {
169+
expect(
170+
NODE_MODULES_JS_REGEX.test('node_modules/@angular/common/fesm2022/common.mjs?v=abc123'),
171+
).toBe(true)
172+
})
173+
174+
it('should match chunk files with Vite query strings', () => {
175+
expect(
176+
NODE_MODULES_JS_REGEX.test(
177+
'node_modules/@angular/common/fesm2022/_platform_location-chunk.mjs?v=df7b0864',
178+
),
179+
).toBe(true)
180+
})
181+
182+
it('should match Windows-style backslash paths', () => {
183+
expect(NODE_MODULES_JS_REGEX.test('node_modules\\@angular\\common\\fesm2022\\common.mjs')).toBe(
184+
true,
185+
)
186+
})
187+
188+
it('should match .js and .cjs files', () => {
189+
expect(NODE_MODULES_JS_REGEX.test('node_modules/@ngrx/store/fesm2022/ngrx-store.js')).toBe(true)
190+
expect(NODE_MODULES_JS_REGEX.test('node_modules/some-lib/index.cjs')).toBe(true)
191+
})
192+
193+
it('should not match non-JS files', () => {
194+
expect(NODE_MODULES_JS_REGEX.test('node_modules/@angular/common/fesm2022/common.d.ts')).toBe(
195+
false,
196+
)
197+
expect(NODE_MODULES_JS_REGEX.test('src/app/app.component.ts')).toBe(false)
198+
})
199+
})

napi/angular-compiler/vite-plugin/angular-linker-plugin.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const LINKER_DECLARATION_PREFIX = '\u0275\u0275ngDeclare'
2525
const SKIP_REGEX = /[\\/]@angular[\\/](?:compiler|core)[\\/]/
2626

2727
// Match JS files in node_modules (Angular FESM bundles)
28-
const NODE_MODULES_JS_REGEX = /node_modules\/.*\.[cm]?js$/
28+
// Allows optional query strings (?v=...) that Vite appends to module IDs
29+
const NODE_MODULES_JS_REGEX = /node_modules[\\/].*\.[cm]?js(?:\?.*)?$/
2930

3031
/**
3132
* Run the OXC Rust linker on the given code.

0 commit comments

Comments
 (0)