Skip to content

Commit 78719d6

Browse files
committed
fix(mdx): add regex fallback for MDX dependency detection on compilation failure
1 parent cd86e14 commit 78719d6

2 files changed

Lines changed: 60 additions & 10 deletions

File tree

scopes/mdx/mdx/mdx.detector.ts

Lines changed: 59 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { DependencyDetector, FileContext } from '@teambit/dependency-resolver';
22
import { compileSync } from '@mdx-js/mdx';
33
import { mdxOptions } from '@teambit/mdx.modules.mdx-v3-options';
4+
import type { Options } from '@mdx-js/mdx';
5+
import type { Logger } from '@teambit/logger';
46

57
type ImportSpecifier = {
68
/**
@@ -19,21 +21,69 @@ type ImportSpecifier = {
1921
identifier?: string;
2022
};
2123

24+
/**
25+
* MDX options for dependency detection only.
26+
* Uses the remark plugins from mdxOptions (for frontmatter and import extraction)
27+
* but excludes rehype plugins like rehypeMdxCodeProps that fail on legacy
28+
* code fence meta syntax (e.g. `live=true`).
29+
*/
30+
const detectorMdxOptions: Options = {
31+
remarkPlugins: mdxOptions.remarkPlugins,
32+
jsxImportSource: mdxOptions.jsxImportSource,
33+
};
34+
35+
/**
36+
* Regex-based fallback for extracting import sources from MDX files.
37+
* Used when compileSync fails due to MDX v3 syntax incompatibilities in user content
38+
* (e.g. HTML comments, escaped characters, bare variable declarations, unclosed tags).
39+
*/
40+
function detectImportsWithRegex(source: string): string[] {
41+
const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s*,?\s*)*\s*from\s*['"]([^'"]+)['"]/g;
42+
const modules: string[] = [];
43+
let match: RegExpExecArray | null;
44+
while ((match = importRegex.exec(source)) !== null) {
45+
modules.push(match[1]);
46+
}
47+
return modules;
48+
}
49+
2250
export class MDXDependencyDetector implements DependencyDetector {
23-
constructor(private supportedExtensions: string[]) {}
51+
private logger?: Logger;
52+
private currentFilename?: string;
53+
54+
constructor(
55+
private supportedExtensions: string[],
56+
logger?: Logger
57+
) {
58+
this.logger = logger;
59+
// Bind methods to preserve `this` context when called as detached functions.
60+
this.detect = this.detect.bind(this);
61+
this.isSupported = this.isSupported.bind(this);
62+
}
2463

2564
isSupported(context: FileContext): boolean {
65+
// Capture filename for use in detect() warning messages.
66+
// isSupported is always called immediately before detect() for the same file.
67+
this.currentFilename = context.filename;
2668
return this.supportedExtensions.includes(context.ext);
2769
}
2870

2971
detect(source: string): string[] {
30-
const output = compileSync(source, mdxOptions);
31-
const imports = (output.data?.imports as ImportSpecifier[]) || [];
32-
if (!imports.length) return [];
33-
const files: string[] = imports.map((importSpec) => {
34-
return importSpec.fromModule;
35-
});
36-
37-
return files;
72+
const filename = this.currentFilename;
73+
try {
74+
const output = compileSync(source, detectorMdxOptions);
75+
const imports = (output.data?.imports as ImportSpecifier[]) || [];
76+
if (!imports.length) return [];
77+
return imports.map((importSpec) => importSpec.fromModule);
78+
} catch (err: any) {
79+
// MDX v3 may fail to compile files with legacy syntax (HTML comments, escaped
80+
// characters in prose, bare variable declarations, unclosed tags, etc.).
81+
// Fall back to regex-based import detection which is sufficient for dependency resolution.
82+
const fileRef = filename ? ` File: ${filename}` : '';
83+
const msg = `MDX compilation failed, falling back to regex-based import detection.${fileRef} Error: ${err.message}`;
84+
this.logger?.warn(msg);
85+
this.logger?.console(msg);
86+
return detectImportsWithRegex(source);
87+
}
3888
}
3989
}

scopes/mdx/mdx/mdx.main.runtime.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export class MDXMain {
9696
);
9797

9898
envs.registerEnv(mdxEnv);
99-
depResolver.registerDetector(new MDXDependencyDetector(config.extensions));
99+
depResolver.registerDetector(new MDXDependencyDetector(config.extensions, logger));
100100
docs.registerDocReader(new MDXDocReader(config.extensions));
101101
if (generator) {
102102
const envContext = new EnvContext(ComponentID.fromString(ReactAspect.id), loggerAspect, workerMain, harmony);

0 commit comments

Comments
 (0)