11import type { DependencyDetector , FileContext } from '@teambit/dependency-resolver' ;
22import { compileSync } from '@mdx-js/mdx' ;
33import { mdxOptions } from '@teambit/mdx.modules.mdx-v3-options' ;
4+ import type { Options } from '@mdx-js/mdx' ;
5+ import type { Logger } from '@teambit/logger' ;
46
57type 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 = / i m p o r t \s + (?: (?: \{ [ ^ } ] * \} | \* \s + a s \s + \w + | \w + ) \s * , ? \s * ) * \s * f r o m \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+
2250export 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}
0 commit comments