Skip to content

Commit 83a7896

Browse files
authored
Merge pull request #190 from DataDog/yoann/unplugin-v2
[chore] Unplugin v2
2 parents b3fcc6a + 93e9577 commit 83a7896

22 files changed

Lines changed: 323 additions & 200 deletions
Binary file not shown.
136 KB
Binary file not shown.
-51.2 KB
Binary file not shown.
56.5 KB
Binary file not shown.

.yarn/patches/unplugin-npm-1.10.1-b23391b255.patch

Lines changed: 0 additions & 52 deletions
This file was deleted.

LICENSES-3rdparty.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ unicode-canonical-property-names-ecmascript,npm,MIT,Mathias Bynens (https://gith
761761
unicode-match-property-ecmascript,npm,MIT,Mathias Bynens (https://github.com/mathiasbynens/unicode-match-property-ecmascript)
762762
unicode-match-property-value-ecmascript,npm,MIT,Mathias Bynens (https://github.com/mathiasbynens/unicode-match-property-value-ecmascript)
763763
unicode-property-aliases-ecmascript,npm,MIT,Mathias Bynens (https://github.com/mathiasbynens/unicode-property-aliases-ecmascript)
764-
unplugin,npm,MIT,(https://www.npmjs.com/package/unplugin)
764+
unplugin,npm,MIT,(https://unplugin.unjs.io)
765765
unrs-resolver,npm,MIT,JounQin (https://github.com/unrs/unrs-resolver#readme)
766766
update-browserslist-db,virtual,MIT,Andrey Sitnik (https://www.npmjs.com/package/update-browserslist-db)
767767
uri-js,npm,BSD-2-Clause,Gary Court (https://github.com/garycourt/uri-js)

packages/core/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,6 @@
3333
"@types/node": "^20",
3434
"esbuild": "0.24.0",
3535
"typescript": "5.4.3",
36-
"unplugin": "1.16.0"
36+
"unplugin": "2.3.5"
3737
}
3838
}

packages/core/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Copyright 2019-Present Datadog, Inc.
44

55
export const INJECTED_FILE = '__datadog-helper-file';
6+
export const INJECTED_FILE_RX = new RegExp(INJECTED_FILE);
67

78
export const ALL_ENVS = ['development', 'production', 'test'] as const;
89
export const ALL_BUNDLERS = ['webpack', 'vite', 'esbuild', 'rollup', 'rspack', 'rolldown', 'farm'];

packages/factory/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"@dd/rum-plugin": "workspace:*",
3333
"@dd/telemetry-plugin": "workspace:*",
3434
"chalk": "2.3.1",
35-
"unplugin": "1.16.0"
35+
"unplugin": "2.3.5"
3636
},
3737
"devDependencies": {
3838
"typescript": "5.4.3"

packages/factory/src/helpers/wrapPlugins.test.ts

Lines changed: 181 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -85,18 +85,6 @@ describe('profilePlugins', () => {
8585
jest.spyOn(timer, 'end');
8686
jest.spyOn(logger, 'time').mockReturnValue(timer);
8787

88-
const mockPlugin: PluginOptions = {
89-
name: 'datadog-test-1-plugin',
90-
transform: jest.fn(async () => {
91-
await wait(500);
92-
return 'transform';
93-
}),
94-
resolveId: jest.fn(async () => {
95-
await wait(500);
96-
throw new Error('resolveId');
97-
}),
98-
};
99-
10088
beforeAll(() => {
10189
jest.useFakeTimers();
10290
});
@@ -105,37 +93,193 @@ describe('profilePlugins', () => {
10593
jest.useRealTimers();
10694
});
10795

108-
test('Should wrap the hook and measure time.', async () => {
109-
const wrappedHook = wrapHook(
110-
mockPlugin.name,
111-
'buildStart',
112-
mockPlugin.transform!,
113-
logger,
114-
);
96+
describe('Function hooks', () => {
97+
test('Should wrap a function hook and measure time', async () => {
98+
const mockTransform = jest.fn(async (code: string) => {
99+
await wait(500);
100+
return { code: `${code}-transformed`, map: null };
101+
});
115102

116-
const prom = wrappedHook();
117-
jest.advanceTimersByTime(500);
118-
const result = await prom;
103+
const wrappedHook = wrapHook('test-plugin', 'transform', mockTransform, logger);
119104

120-
expect(timer.end).toHaveBeenCalledTimes(1);
121-
expect(timer.timer.total).toBeGreaterThan(500);
122-
expect(result).toBe('transform');
105+
const prom = wrappedHook('const a = 1');
106+
jest.advanceTimersByTime(500);
107+
const result = await prom;
108+
109+
expect(mockTransform).toHaveBeenCalledWith('const a = 1');
110+
expect(timer.end).toHaveBeenCalledTimes(1);
111+
expect(timer.timer.total).toBeGreaterThan(500);
112+
expect(result).toEqual({ code: 'const a = 1-transformed', map: null });
113+
});
114+
115+
test('Should handle synchronous function hooks', () => {
116+
const mockResolveId = jest.fn((id: string) => {
117+
return id.startsWith('./') ? `/resolved${id}` : null;
118+
});
119+
120+
const wrappedHook = wrapHook('test-plugin', 'resolveId', mockResolveId, logger);
121+
122+
const result = wrappedHook('./test.ts');
123+
124+
expect(mockResolveId).toHaveBeenCalledWith('./test.ts');
125+
expect(timer.end).toHaveBeenCalledTimes(1);
126+
expect(result).toBe('/resolved./test.ts');
127+
});
128+
129+
test('Should measure time for throwing hooks', async () => {
130+
const mockBuildStart = jest.fn(async () => {
131+
await wait(500);
132+
throw new Error('Build failed');
133+
});
134+
135+
const wrappedHook = wrapHook('test-plugin', 'buildStart', mockBuildStart, logger);
136+
137+
const prom = wrappedHook();
138+
jest.advanceTimersByTime(500);
139+
140+
await expect(prom).rejects.toThrow('Build failed');
141+
expect(timer.end).toHaveBeenCalledTimes(1);
142+
expect(timer.timer.total).toBeGreaterThan(500);
143+
});
123144
});
124145

125-
test('Should still measure a throwing hook.', async () => {
126-
const wrappedHook = wrapHook(
127-
mockPlugin.name,
128-
'buildStart',
129-
mockPlugin.resolveId!,
130-
logger,
131-
);
146+
describe('Object hooks', () => {
147+
test('Should wrap transform hook with filter object', async () => {
148+
const mockHandler = jest.fn(async (code: string) => {
149+
await wait(300);
150+
return { code: `${code}-filtered`, map: null };
151+
});
132152

133-
const prom = wrappedHook();
134-
jest.advanceTimersByTime(500);
153+
const transformHook = {
154+
filter: {
155+
id: {
156+
include: ['**/*.ts', '**/*.tsx'],
157+
exclude: ['node_modules/**'],
158+
},
159+
},
160+
handler: mockHandler,
161+
};
135162

136-
await expect(prom).rejects.toThrow('resolveId');
137-
expect(timer.end).toHaveBeenCalledTimes(1);
138-
expect(timer.timer.total).toBeGreaterThan(500);
163+
const wrappedHook = wrapHook('test-plugin', 'transform', transformHook, logger);
164+
165+
// Verify the filter is preserved
166+
expect(wrappedHook.filter).toEqual(transformHook.filter);
167+
168+
// Test the wrapped handler
169+
const prom = wrappedHook.handler('const a = 1');
170+
jest.advanceTimersByTime(300);
171+
const result = await prom;
172+
173+
expect(mockHandler).toHaveBeenCalledWith('const a = 1');
174+
expect(timer.end).toHaveBeenCalledTimes(1);
175+
expect(timer.timer.total).toBeGreaterThan(300);
176+
expect(result).toEqual({ code: 'const a = 1-filtered', map: null });
177+
});
178+
179+
test('Should wrap load hook with RegExp filter', async () => {
180+
const mockHandler = jest.fn(async (id: string) => {
181+
await wait(200);
182+
return { code: `export default "${id}"` };
183+
});
184+
185+
const loadHook = {
186+
filter: {
187+
id: /\.virtual$/,
188+
},
189+
handler: mockHandler,
190+
};
191+
192+
const wrappedHook = wrapHook('test-plugin', 'load', loadHook, logger);
193+
194+
// Verify the filter is preserved
195+
expect(wrappedHook.filter).toEqual(loadHook.filter);
196+
197+
// Test the wrapped handler
198+
const prom = wrappedHook.handler('/src/test.virtual');
199+
jest.advanceTimersByTime(200);
200+
const result = await prom;
201+
202+
expect(mockHandler).toHaveBeenCalledWith('/src/test.virtual');
203+
expect(timer.end).toHaveBeenCalledTimes(1);
204+
expect(timer.timer.total).toBeGreaterThan(200);
205+
expect(result).toEqual({ code: 'export default "/src/test.virtual"' });
206+
});
207+
208+
test('Should handle synchronous object hook handlers', () => {
209+
const mockHandler = jest.fn((code: string) => {
210+
return code.toUpperCase();
211+
});
212+
213+
const transformHook = {
214+
filter: {
215+
id: '*.css',
216+
},
217+
handler: mockHandler,
218+
};
219+
220+
const wrappedHook = wrapHook('test-plugin', 'transform', transformHook, logger);
221+
222+
const result = wrappedHook.handler('body { color: red; }');
223+
224+
expect(mockHandler).toHaveBeenCalledWith('body { color: red; }');
225+
expect(timer.end).toHaveBeenCalledTimes(1);
226+
expect(result).toBe('BODY { COLOR: RED; }');
227+
});
228+
229+
test('Should preserve all properties of object hooks', () => {
230+
const transformHook = {
231+
filter: {
232+
id: {
233+
include: ['**/*.vue'],
234+
exclude: ['**/*.test.vue'],
235+
},
236+
},
237+
handler: jest.fn((code: string) => code),
238+
enforce: 'pre',
239+
// Custom property that might be used by the bundler
240+
customOption: true,
241+
};
242+
243+
const wrappedHook = wrapHook('test-plugin', 'transform', transformHook, logger);
244+
245+
// All properties should be preserved
246+
expect(wrappedHook.filter).toEqual(transformHook.filter);
247+
expect(wrappedHook.enforce).toBe('pre');
248+
expect((wrappedHook as any).customOption).toBe(true);
249+
expect(wrappedHook.handler).not.toBe(transformHook.handler); // Handler should be wrapped
250+
});
251+
});
252+
253+
describe('Edge cases', () => {
254+
test('Should handle hooks that return void', () => {
255+
const mockBuildEnd = jest.fn(() => {
256+
// Side effect only, no return value
257+
});
258+
259+
const wrappedHook = wrapHook('test-plugin', 'buildEnd', mockBuildEnd, logger);
260+
261+
const result = wrappedHook();
262+
263+
expect(mockBuildEnd).toHaveBeenCalled();
264+
expect(timer.end).toHaveBeenCalledTimes(1);
265+
expect(result).toBeUndefined();
266+
});
267+
268+
test('Should preserve "this" context for hooks', async () => {
269+
let capturedThis: any;
270+
const mockTransform = jest.fn(function (this: any, code: string) {
271+
capturedThis = this;
272+
return { code: `${code}-modified` };
273+
});
274+
275+
const wrappedHook = wrapHook('test-plugin', 'transform', mockTransform, logger);
276+
277+
const mockContext = { addWatchFile: jest.fn() };
278+
const result = wrappedHook.call(mockContext, 'const a = 1');
279+
280+
expect(capturedThis).toBe(mockContext);
281+
expect(result).toEqual({ code: 'const a = 1-modified' });
282+
});
139283
});
140284
});
141285
});

0 commit comments

Comments
 (0)