Skip to content

Commit 9a8a985

Browse files
authored
Merge branch 'master' into hugo.silva/fix-debug-id-key-path
2 parents 99e54ac + 80ef2a7 commit 9a8a985

11 files changed

Lines changed: 168 additions & 58 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@datadog/build-plugins",
33
"private": true,
4-
"version": "3.2.8",
4+
"version": "3.2.10",
55
"license": "MIT",
66
"author": "Datadog",
77
"description": "Root of Datadog's Build Plugins monorepo",

packages/plugins/apps/src/index.test.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,24 @@ function extractViteTransform(plugins: PluginOptions[]) {
4141
}
4242

4343
function emitModuleParsed(
44-
config: { plugins?: Array<{ moduleParsed?: (moduleInfo: unknown) => void }> },
44+
config: {
45+
plugins?: Array<{
46+
moduleParsed?: (this: { parse: typeof parseAst }, moduleInfo: unknown) => void;
47+
}>;
48+
},
4549
id: string,
4650
code: string,
4751
importedIds: string[] = [],
4852
) {
4953
for (const plugin of config.plugins ?? []) {
50-
plugin.moduleParsed?.({
51-
id,
52-
ast: parseAst(code),
53-
importedIds,
54-
});
54+
plugin.moduleParsed?.call(
55+
{ parse: parseAst },
56+
{
57+
id,
58+
code,
59+
importedIds,
60+
},
61+
);
5562
}
5663
}
5764

packages/plugins/apps/src/vite/backend-module-graph-collector.test.ts

Lines changed: 83 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,40 +6,58 @@ import { parseAst } from 'rollup/parseAst';
66

77
import { createBackendModuleGraphCollector } from './backend-module-graph-collector';
88

9+
type FakeModuleInfo = { id: string; code: string | null };
10+
11+
/**
12+
* Calls the `moduleParsed` hook with a plugin context exposing `parse`, which is
13+
* where it takes its parser from. `rollup/parseAst` is what Rollup's real
14+
* context supplies. The hook reads only the few `ModuleInfo` fields these fakes
15+
* model, so building a complete one would be noise.
16+
*/
17+
const getModuleParsedHook = (collector: ReturnType<typeof createBackendModuleGraphCollector>) => {
18+
const hook = collector.plugin.moduleParsed;
19+
if (typeof hook !== 'function') {
20+
throw new Error('Expected "moduleParsed" to be a function hook.');
21+
}
22+
23+
const parse = jest.fn(parseAst);
24+
const callHook = (moduleInfo: object) => Reflect.apply(hook, { parse }, [moduleInfo]);
25+
26+
return { callHook, parse };
27+
};
28+
29+
const getEmit = (collector: ReturnType<typeof createBackendModuleGraphCollector>) => {
30+
const { callHook, parse } = getModuleParsedHook(collector);
31+
32+
const emit = (moduleInfo: FakeModuleInfo, importedIds: string[] = []) => {
33+
const importedIdResolutions = importedIds.map((id) => ({ id }));
34+
callHook({ ...moduleInfo, importedIds, importedIdResolutions });
35+
};
36+
37+
return { emit, parse };
38+
};
39+
940
describe('Backend Functions - backend module graph collector', () => {
10-
test('Should collect parsed local module records from Rollup moduleParsed hooks', () => {
41+
test('Should collect parsed local module records from moduleParsed hooks', () => {
1142
const collector = createBackendModuleGraphCollector('/project');
12-
const moduleParsed = collector.plugin.moduleParsed as (moduleInfo: unknown) => void;
13-
14-
moduleParsed({
15-
id: '/project/src/backend/actions.backend.js?import',
16-
ast: parseAst(`
17-
import { getEcho } from './helpers/http.js';
18-
export function run() {
19-
return getEcho();
20-
}
21-
`),
22-
importedIds: ['/project/src/backend/helpers/http.js?import'],
23-
importedIdResolutions: [{ id: '/project/src/backend/helpers/http.js?import' }],
24-
});
25-
moduleParsed({
26-
id: '/project/node_modules/package/index.js',
27-
ast: parseAst('export const value = true;'),
28-
importedIds: [],
29-
importedIdResolutions: [],
30-
});
31-
moduleParsed({
32-
id: '\0virtual-helper.js',
33-
ast: parseAst('export const value = true;'),
34-
importedIds: [],
35-
importedIdResolutions: [],
36-
});
37-
moduleParsed({
38-
id: 'virtual:dd-backend-dev:example.js',
39-
ast: parseAst('export const value = true;'),
40-
importedIds: [],
41-
importedIdResolutions: [],
42-
});
43+
const { emit, parse } = getEmit(collector);
44+
45+
emit(
46+
{
47+
id: '/project/src/backend/actions.backend.js?import',
48+
code: `
49+
import { getEcho } from './helpers/http.js';
50+
export function run() {
51+
return getEcho();
52+
}
53+
`,
54+
},
55+
['/project/src/backend/helpers/http.js?import'],
56+
);
57+
emit({ id: '/project/node_modules/package/index.js', code: 'export const value = true;' });
58+
emit({ id: '\0virtual-helper.js', code: 'export const value = true;' });
59+
emit({ id: 'virtual:dd-backend-dev:example.js', code: 'export const value = true;' });
60+
emit({ id: '/project/src/backend/external.js', code: null });
4361

4462
expect([...collector.getModuleRecords().keys()]).toEqual([
4563
'/project/src/backend/actions.backend.js',
@@ -54,5 +72,38 @@ describe('Backend Functions - backend module graph collector', () => {
5472
},
5573
],
5674
});
75+
// Filtering happens before the parse, so the skipped modules above never
76+
// reach the parser. Without that ordering every `node_modules` module in
77+
// the backend graph would be parsed just to be discarded.
78+
expect(parse).toHaveBeenCalledTimes(1);
79+
});
80+
81+
test('Should collect records under a bundler that does not support ModuleInfo#ast', () => {
82+
const collector = createBackendModuleGraphCollector('/project');
83+
const { callHook } = getModuleParsedHook(collector);
84+
85+
// Rolldown, Vite 8's default bundler, keeps `ast` on its Rollup-compat
86+
// object but stubs the getter to throw. Reading the property at all is
87+
// the failure, so it has to throw rather than be absent — which is also
88+
// why this is assembled in place instead of going through `getEmit`,
89+
// whose spread would trigger the getter during setup.
90+
const moduleInfo = {
91+
id: '/project/src/backend/actions.backend.ts',
92+
code: 'export const id = "conn-1";',
93+
importedIds: [],
94+
importedIdResolutions: [],
95+
};
96+
Object.defineProperty(moduleInfo, 'ast', {
97+
get() {
98+
throw new Error('UNSUPPORTED: ModuleInfo#ast');
99+
},
100+
enumerable: true,
101+
});
102+
103+
callHook(moduleInfo);
104+
105+
expect([...collector.getModuleRecords().keys()]).toEqual([
106+
'/project/src/backend/actions.backend.ts',
107+
]);
57108
});
58109
});

packages/plugins/apps/src/vite/backend-module-graph-collector.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22
// This product includes software developed at Datadog (https://www.datadoghq.com/).
33
// Copyright 2019-Present Datadog, Inc.
44

5-
import type { BaseNode } from 'estree';
65
import type { ModuleInfo } from 'rollup';
76
import type { Plugin } from 'vite';
87

98
import {
109
createParsedModuleRecord,
1110
type ParsedModuleRecord,
11+
shouldTraverseCollectedModule,
12+
unsupportedModuleGraphDependency,
1213
} from '../backend/ast-parsing/module-graph';
1314

1415
const VIRTUAL_MODULE_ID_RE = /^(?:\0|virtual:)/;
@@ -30,12 +31,49 @@ export function createBackendModuleGraphCollector(buildRoot: string): BackendMod
3031
return;
3132
}
3233

34+
// `createParsedModuleRecord` applies this same predicate, but
35+
// only after the AST exists. Checking it here keeps us from
36+
// parsing every `node_modules` module just to discard it.
37+
if (!shouldTraverseCollectedModule(moduleId, buildRoot)) {
38+
return;
39+
}
40+
41+
// External and synthetic modules have no source to parse.
42+
if (typeof moduleInfo.code !== 'string') {
43+
return;
44+
}
45+
46+
// Parse the source instead of reading `moduleInfo.ast`: Rolldown,
47+
// the bundler Vite 8 uses by default, stubs that getter to throw
48+
// `UNSUPPORTED: ModuleInfo#ast`.
49+
//
50+
// No TypeScript-capable parser is needed because `moduleParsed`
51+
// runs after `transform`, so types and JSX are already compiled
52+
// away. Note that `this.parse` is the bundler's parser but not
53+
// its parser *configuration* — Rollup binds no options to it,
54+
// while its own module parse passes `{ jsx }`. Our nested build
55+
// never enables `jsx`, so the two agree today; fail closed rather
56+
// than silently if they ever diverge, since a module we cannot
57+
// parse may hide a connection ID.
58+
let parsed;
59+
try {
60+
parsed = this.parse(moduleInfo.code);
61+
} catch (error) {
62+
const reason = error instanceof Error ? error.message : String(error);
63+
throw unsupportedModuleGraphDependency(
64+
moduleId,
65+
`unparseable module source (${reason})`,
66+
);
67+
}
68+
3369
const record = createParsedModuleRecord(
3470
moduleId,
3571
buildRoot,
36-
moduleInfo.ast as BaseNode,
72+
parsed,
3773
getStaticDependencyIds(moduleInfo).map(normalizeViteModuleId),
3874
);
75+
// Only null when the traversal predicate rejects, which the guard
76+
// above already covered; kept to narrow the nullable return type.
3977
if (!record) {
4078
return;
4179
}

packages/plugins/apps/src/vite/dev-server.test.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,24 @@ function mockBuildResult(code: string) {
113113
}
114114

115115
function emitModuleParsed(
116-
config: { plugins?: Array<{ moduleParsed?: (moduleInfo: unknown) => void }> },
116+
config: {
117+
plugins?: Array<{
118+
moduleParsed?: (this: { parse: typeof parseAst }, moduleInfo: unknown) => void;
119+
}>;
120+
},
117121
id: string,
118122
code: string,
119123
importedIds: string[] = [],
120124
) {
121125
for (const plugin of config.plugins ?? []) {
122-
plugin.moduleParsed?.({
123-
id,
124-
ast: parseAst(code),
125-
importedIds,
126-
});
126+
plugin.moduleParsed?.call(
127+
{ parse: parseAst },
128+
{
129+
id,
130+
code,
131+
importedIds,
132+
},
133+
);
127134
}
128135
}
129136

packages/plugins/apps/src/vite/index.test.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,23 @@ function mockBuildResult() {
4848
}
4949

5050
function emitModuleParsed(
51-
config: { plugins?: Array<{ moduleParsed?: (moduleInfo: unknown) => void }> },
51+
config: {
52+
plugins?: Array<{
53+
moduleParsed?: (this: { parse: typeof parseAst }, moduleInfo: unknown) => void;
54+
}>;
55+
},
5256
id: string,
5357
code: string,
5458
) {
5559
for (const plugin of config.plugins ?? []) {
56-
plugin.moduleParsed?.({
57-
id,
58-
ast: parseAst(code),
59-
importedIds: [],
60-
});
60+
plugin.moduleParsed?.call(
61+
{ parse: parseAst },
62+
{
63+
id,
64+
code,
65+
importedIds: [],
66+
},
67+
);
6168
}
6269
}
6370

packages/published/esbuild-plugin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@datadog/esbuild-plugin",
33
"packageManager": "yarn@4.0.2",
4-
"version": "3.2.8",
4+
"version": "3.2.10",
55
"license": "MIT",
66
"author": "Datadog",
77
"description": "Datadog ESBuild Plugin",

packages/published/rollup-plugin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@datadog/rollup-plugin",
33
"packageManager": "yarn@4.0.2",
4-
"version": "3.2.8",
4+
"version": "3.2.10",
55
"license": "MIT",
66
"author": "Datadog",
77
"description": "Datadog Rollup Plugin",

packages/published/rspack-plugin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@datadog/rspack-plugin",
33
"packageManager": "yarn@4.0.2",
4-
"version": "3.2.8",
4+
"version": "3.2.10",
55
"license": "MIT",
66
"author": "Datadog",
77
"description": "Datadog Rspack Plugin",

packages/published/vite-plugin/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@datadog/vite-plugin",
33
"packageManager": "yarn@4.0.2",
4-
"version": "3.2.8",
4+
"version": "3.2.10",
55
"license": "MIT",
66
"author": "Datadog",
77
"description": "Datadog Vite Plugin",

0 commit comments

Comments
 (0)