Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/plugins/apps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@
"eslint-scope": "7.2.2",
"glob": "11.1.0",
"jszip": "3.10.1",
"pretty-bytes": "5.6.0"
"pretty-bytes": "5.6.0",
"rollup": "4.45.1"
},
"devDependencies": {
"@types/eslint-scope": "3.7.7",
"@types/estree": "1.0.8",
"rollup": "4.45.1",
"typescript": "5.4.3",
"vite": "6.3.5"
}
Expand Down
3 changes: 3 additions & 0 deletions packages/plugins/apps/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export const LOCAL_EXECUTION_LOAD_SUFFIX = '?dd-local-exec';
export const LOCAL_EXECUTION_LOAD_RE = new RegExp(
`${BACKEND_FILE_RE.source.slice(0, -1)}\\${LOCAL_EXECUTION_LOAD_SUFFIX}$`,
);

/** Vite's own `--mode` value for `npm run dev:verify`, read server-side from `server.config.mode` rather than `import.meta.env.MODE`, which has no CommonJS equivalent and breaks Jest's ts-jest transform. */
export const DEV_VERIFY_MODE = 'dev-verify';
export const BACKEND_CODE_EXTENSIONS = [
'.ts',
'.tsx',
Expand Down
104 changes: 104 additions & 0 deletions packages/plugins/apps/src/vite/dev-server-module-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Unless explicitly stated otherwise all files in this repository are licensed under the MIT License.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2019-Present Datadog, Inc.

import { parseAst } from 'rollup/parseAst';
import type { ModuleNode, ViteDevServer } from 'vite';

import {
createParsedModuleRecord,
type ParsedModuleRecord,
shouldTraverseCollectedModule,
unsupportedModuleGraphDependency,
} from '../backend/ast-parsing/module-graph';

/**
* Builds the same `ReadonlyMap<string, ParsedModuleRecord>` shape
* `createBackendModuleGraphCollector`'s `moduleParsed` hook produces during a
* real Rollup build — but for the dev server, where `moduleParsed` never
* fires at all (it's a Rollup-build-only hook; Vite's dev-server plugin
* container doesn't implement it). Instead, this walks Vite's own
* `server.moduleGraph`, which the dev server already populates as a side
* effect of `ssrLoadModule`: by the time an `await server.ssrLoadModule(id)`
* call resolves, the entry's `ModuleNode.importedModules` — and every
* imported module's own `importedModules` — already reflect the full
* transitive static-import graph, recursively, with no extra ticks needed.
*
* Call this only after `loadModule` has resolved for `entryId` in the same
* request — the graph it reads is a live side effect of that call, not
* independently maintained state.
*/
export function collectModuleGraphFromServer(
server: ViteDevServer,
entryId: string,
buildRoot: string,
): ReadonlyMap<string, ParsedModuleRecord> {
const records = new Map<string, ParsedModuleRecord>();
const visited = new Set<string>();
const pending: ModuleNode[] = [];

const entryNode = server.moduleGraph.getModuleById(entryId);
if (entryNode) {
pending.push(entryNode);
}

while (pending.length > 0) {
const node = pending.shift()!;
const moduleId = node.id ? normalizeViteModuleId(node.id) : undefined;
if (!moduleId || visited.has(moduleId)) {
continue;
}
visited.add(moduleId);

if (!shouldTraverseCollectedModule(moduleId, buildRoot)) {
continue;
}

// `transformResult` is the client/browser transform; SSR loads (what
// local execution always is, via server.ssrLoadModule) populate
// `ssrTransformResult` instead. Neither exists yet if Vite hasn't
// transformed this module — a local dependency the caller's own
// ssrLoadModule call never actually reached.
const transformResult = node.ssrTransformResult ?? node.transformResult;
if (typeof transformResult?.code !== 'string') {
continue;
}

let ast;
try {
ast = parseAst(transformResult.code);
} catch (error) {
const reason = error instanceof Error ? error.message : String(error);
throw unsupportedModuleGraphDependency(
moduleId,
`unparseable module source (${reason})`,
);
}

// `deps` are this module's own static imports, already resolved to
// real module ids by Vite's import-analysis plugin — the SSR
// equivalent of `moduleParsed`'s `importedIds`/
// `importedIdResolutions`. `dynamicDeps` (deliberately unused here)
// folds in `import()` calls; `module-graph.ts`'s own AST walk is what
// flags those as unsupported, so only static deps belong here.
const staticDependencyIds = (transformResult.deps ?? []).map(normalizeViteModuleId);

const record = createParsedModuleRecord(moduleId, buildRoot, ast, staticDependencyIds);
if (record) {
records.set(record.id, record);
}

for (const dependencyId of staticDependencyIds) {
const dependencyNode = server.moduleGraph.getModuleById(dependencyId);
if (dependencyNode) {
pending.push(dependencyNode);
}
}
}

return records;
}

function normalizeViteModuleId(id: string): string {
return id.split('?')[0];
}
Loading
Loading