@@ -108,56 +108,62 @@ fn parse_js_ts_config(source: &str, extension: &str) -> StaticConfig {
108108 return None ;
109109 }
110110
111- extract_default_export_fields ( & result. program )
111+ extract_config_fields ( & result. program )
112112}
113113
114- /// Find the default export in a parsed program and extract its object fields.
114+ /// Find the config object in a parsed program and extract its fields.
115115///
116- /// Returns `None` if no `export default` is found or the exported value is not
117- /// an object literal (or `defineConfig({...})` call).
118- ///
119- /// Supports two patterns:
116+ /// Searches for the config value in the following patterns (in order):
120117/// 1. `export default defineConfig({ ... })`
121118/// 2. `export default { ... }`
122- fn extract_default_export_fields ( program : & Program < ' _ > ) -> StaticConfig {
119+ /// 3. `module.exports = defineConfig({ ... })`
120+ /// 4. `module.exports = { ... }`
121+ fn extract_config_fields ( program : & Program < ' _ > ) -> StaticConfig {
123122 for stmt in & program. body {
124- let Statement :: ExportDefaultDeclaration ( decl) = stmt else {
125- continue ;
126- } ;
123+ // ESM: export default ...
124+ if let Statement :: ExportDefaultDeclaration ( decl) = stmt {
125+ if let Some ( expr) = decl. declaration . as_expression ( ) {
126+ return extract_config_from_expr ( expr) ;
127+ }
128+ // export default class/function — not analyzable
129+ return None ;
130+ }
127131
128- let Some ( expr) = decl. declaration . as_expression ( ) else {
129- continue ;
130- } ;
132+ // CJS: module.exports = ...
133+ if let Statement :: ExpressionStatement ( expr_stmt) = stmt
134+ && let Expression :: AssignmentExpression ( assign) = & expr_stmt. expression
135+ && assign. left . as_member_expression ( ) . is_some_and ( |m| {
136+ m. object ( ) . is_specific_id ( "module" ) && m. static_property_name ( ) == Some ( "exports" )
137+ } )
138+ {
139+ return extract_config_from_expr ( & assign. right ) ;
140+ }
141+ }
131142
132- // Unwrap parenthesized expressions
133- let expr = expr . without_parentheses ( ) ;
143+ None
144+ }
134145
135- match expr {
136- // Pattern: export default defineConfig({ ... })
137- Expression :: CallExpression ( call) => {
138- if !call. callee . is_specific_id ( "defineConfig" ) {
139- // Unknown function call — not analyzable
140- return None ;
141- }
142- if let Some ( first_arg) = call. arguments . first ( )
143- && let Some ( Expression :: ObjectExpression ( obj) ) = first_arg. as_expression ( )
144- {
145- return Some ( extract_object_fields ( obj) ) ;
146- }
147- // defineConfig() with non-object arg — not analyzable
146+ /// Extract the config object from an expression that is either:
147+ /// - `defineConfig({ ... })` → extract the object argument
148+ /// - `{ ... }` → extract directly
149+ /// - anything else → not analyzable
150+ fn extract_config_from_expr ( expr : & Expression < ' _ > ) -> StaticConfig {
151+ let expr = expr. without_parentheses ( ) ;
152+ match expr {
153+ Expression :: CallExpression ( call) => {
154+ if !call. callee . is_specific_id ( "defineConfig" ) {
148155 return None ;
149156 }
150- // Pattern: export default { ... }
151- Expression :: ObjectExpression ( obj) => {
157+ if let Some ( first_arg) = call. arguments . first ( )
158+ && let Some ( Expression :: ObjectExpression ( obj) ) = first_arg. as_expression ( )
159+ {
152160 return Some ( extract_object_fields ( obj) ) ;
153161 }
154- // e.g. export default 42, export default someVar — not analyzable
155- _ => return None ,
162+ None
156163 }
164+ Expression :: ObjectExpression ( obj) => Some ( extract_object_fields ( obj) ) ,
165+ _ => None ,
157166 }
158-
159- // No export default found
160- None
161167}
162168
163169/// Extract fields from an object expression, converting each value to JSON.
@@ -403,6 +409,40 @@ mod tests {
403409 assert_json ( & result, "lint" , serde_json:: json!( { "plugins" : [ "a" ] } ) ) ;
404410 }
405411
412+ // ── module.exports = { ... } ───────────────────────────────────────
413+
414+ #[ test]
415+ fn module_exports_object ( ) {
416+ let result = parse_js_ts_config ( "module.exports = { run: { cache: true } }" , "cjs" )
417+ . expect ( "expected analyzable config" ) ;
418+ assert_json ( & result, "run" , serde_json:: json!( { "cache" : true } ) ) ;
419+ }
420+
421+ #[ test]
422+ fn module_exports_define_config ( ) {
423+ let result = parse_js_ts_config (
424+ r"
425+ const { defineConfig } = require('vite-plus');
426+ module.exports = defineConfig({
427+ run: { cacheScripts: true },
428+ });
429+ " ,
430+ "cjs" ,
431+ )
432+ . expect ( "expected analyzable config" ) ;
433+ assert_json ( & result, "run" , serde_json:: json!( { "cacheScripts" : true } ) ) ;
434+ }
435+
436+ #[ test]
437+ fn module_exports_non_object ( ) {
438+ assert ! ( parse_js_ts_config( "module.exports = 42;" , "cjs" ) . is_none( ) ) ;
439+ }
440+
441+ #[ test]
442+ fn module_exports_unknown_call ( ) {
443+ assert ! ( parse_js_ts_config( "module.exports = otherFn({ a: 1 });" , "cjs" ) . is_none( ) ) ;
444+ }
445+
406446 // ── Primitive values ────────────────────────────────────────────────
407447
408448 #[ test]
0 commit comments