swc-project/swc · error
illegal conversion: Cannot convert {:?} to ModuleDeclaration
Error message
illegal conversion: Cannot convert {:?} to ModuleDeclaration What it means
After babelifying a ModuleDecl, the result is a ModuleDeclOutput enum with seven variants, but From<ModuleDeclOutput> for ModuleDeclaration only converts the four plain-ESM ones (Import, ExportDefault, ExportNamed, ExportAll). The three TypeScript-specific variants — TsImportEquals (`import x = require(...)`), TsExportAssignment (`export = ...`), and TsNamespaceExport (`export namespace ...`) — have no plain Babel ModuleDeclaration form, so converting them panics with 'illegal conversion'. This fires in Module::babelify where each module-level item is turned into a Babel node via `.into()`.
Source
Thrown at crates/swc_estree_compat/src/babelify/module_decl.rs:65
ModuleDeclOutput::TsExportAssignment(a.babelify(ctx))
}
ModuleDecl::TsNamespaceExport(e) => {
ModuleDeclOutput::TsNamespaceExport(e.babelify(ctx))
}
#[cfg(swc_ast_unknown)]
_ => panic!("unable to access unknown nodes"),
}
}
}
impl From<ModuleDeclOutput> for ModuleDeclaration {
fn from(module: ModuleDeclOutput) -> Self {
match module {
ModuleDeclOutput::Import(i) => ModuleDeclaration::Import(i),
ModuleDeclOutput::ExportDefault(e) => ModuleDeclaration::ExportDefault(e),
ModuleDeclOutput::ExportNamed(n) => ModuleDeclaration::ExportNamed(n),
ModuleDeclOutput::ExportAll(a) => ModuleDeclaration::ExportAll(a),
_ => panic!(
"illegal conversion: Cannot convert {:?} to ModuleDeclaration",
&module
),
}
}
}
impl Babelify for ExportDefaultExpr {
type Output = ExportDefaultDeclaration;
fn babelify(self, ctx: &Context) -> Self::Output {
ExportDefaultDeclaration {
base: ctx.base(self.span),
declaration: ExportDefaultDeclType::Expr(
Box::alloc().init(self.expr.babelify(ctx).into()),
),
}
}View on GitHub (pinned to 5176682b65)
Solutions
- Run swc's TypeScript strip pass (swc_ecma_transforms_typescript strip / @swc/core with typescript syntax stripping) before babelify so TS module decls become plain ESM/CJS
- Reject or skip TS inputs that use import-equals/export-assignment before conversion
- If you need TS output, babelify per-item and handle ModuleDeclOutput::Ts* variants yourself instead of using the ModuleDeclaration From impl
- As a stopgap, wrap the conversion in catch_unwind and report the offending file
Example fix
// before: TS module babelified directly let program = parse_ts_as_module(fm)?; let babel_ast = program.babelify(&ctx); // panics on `import x = require(...)` // after: strip TypeScript first let program = strip_types(parse_ts_as_module(fm)?)?; let babel_ast = program.babelify(&ctx);
Defensive patterns
Strategy: validation
Validate before calling
fn module_babelifiable(module: &Module) -> Result<(), String> {
for item in &module.body {
if let ModuleItem::ModuleDecl(d) = item {
let ts = matches!(
d,
ModuleDecl::TsImportEquals(_) | ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_)
);
if ts {
return Err("module uses TS import-equals/export-assignment; strip types first".into());
}
}
}
Ok(())
} Type guard
fn module_decl_is_plain_esm(d: &ModuleDecl) -> bool {
!matches!(d, ModuleDecl::TsImportEquals(_) | ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_))
} Try / catch
let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| program.babelify(&ctx)))
.map_err(|_| anyhow::anyhow!("module contains TypeScript module declarations (import = / export = / export namespace)"))?; Prevention
- Strip TypeScript before requesting the Babel AST flavor
- Detect `import x = require(...)` and `export =` in inputs and route them through the TS pipeline
- Add fixtures with TS module syntax to your conversion tests
- Document that babelify targets plain ESM/CJS modules
When it happens
Trigger: Babelifying a Program::Module parsed from TypeScript that still contains `import lib = require('lib')`, `export = value`, or `export namespace NS { ... }` at module top level — e.g. parsing a .ts file as a module and calling babelify without stripping types first.
Common situations: Tooling that parses TS with swc and requests the Babel AST flavor (acorn/babel output, AST viewers, codemod pipelines) without running swc's TypeScript stripper; CommonJS-style TS (export =) files fed straight into the estree conversion.
Related errors
- unable to access unknown nodes
- Duplicated element (${elements[j].key})
- illegal conversion: Cannot convert {:?} to ClassBodyEl
- illegal conversion: Cannot convert {:?} to ExprOutput
- illegal conversion: Cannot convert {:?} to ExprOutput - babe
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/3294938ce26ef51c.
Report an issue: GitHub.