swc-project/swc · error
Decl in ExportDecl: {:?}
Error message
Decl in ExportDecl: {:?} What it means
swc_bundler's export scanner enumerates bindings created by `export <decl>`. It handles Class, Fn, Var (via pattern ids) and the TS declaration kinds TsEnum/TsInterface/TsTypeAlias; the only remaining variant is Decl::TsModule, so `export namespace Foo {}` / `export module Foo {}` reaching the bundler un-stripped triggers this unreachable!(). The bundler expects plain ESM — TypeScript should already be removed.
Source
Thrown at crates/swc_bundler/src/bundler/export.rs:172
let v = self.info.items.entry(None).or_default();
v.push({
let i = match decl.decl {
Decl::Class(ref c) => &c.ident,
Decl::Fn(ref f) => &f.ident,
Decl::Var(ref var) => {
let ids: Vec<Id> = find_pat_ids(&var.decls);
for id in ids {
v.push(Specifier::Specific {
local: id,
alias: None,
});
}
return;
}
Decl::TsEnum(ref e) => &e.id,
Decl::TsInterface(ref i) => &i.id,
Decl::TsTypeAlias(ref a) => &a.id,
_ => unreachable!("Decl in ExportDecl: {:?}", decl.decl),
};
Specifier::Specific {
local: i.into(),
alias: None,
}
});
}
ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(_decl)) => {
self.info
.items
.entry(None)
.or_default()
.push(Specifier::Specific {
local: Id::new(atom!("default"), SyntaxContext::empty()),
alias: None,
});
}View on GitHub (pinned to 5176682b65)
Solutions
- Strip TypeScript before bundling: run swc_ecma_transforms_typescript::strip (or compile via the standard swc chain) so Decl::TsModule is gone before Bundler::bundle
- Replace `export namespace`/`export module` in bundled sources with plain ES module exports
- If TS is already stripped upstream and it still fires, minimize the repro and report it to swc
Example fix
// before
export namespace Config {
export const retries = 3;
}
// after
export const Config = { retries: 3 }; Defensive patterns
Strategy: validation
Validate before calling
// Reject TS-only `export namespace` / `export module` before bundling
fn has_exported_ts_namespace(src: &str) -> bool {
let re = regex::Regex::new(r#"(?m)^\s*export\s+(?:namespace|module)\s+\w"#).unwrap();
re.is_match(src)
}
assert!(!sources.values().any(|s| has_exported_ts_namespace(s)),
"strip TypeScript (export namespace/module) before swc_bundler"); Try / catch
if let Err(p) = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| bundler.bundle(&entries))) {
let msg = panic_message(&p);
if msg.contains("Decl in ExportDecl") {
// route: run typescript::strip on inputs, then retry the bundle
} else { std::panic::resume_unwind(p); }
} Prevention
- Always run the TypeScript strip/compile chain before the bundler
- Ban `export namespace`/`export module` via lint rules in codebases that get bundled
- Add a CI gate that feeds stripped output (no Decl::Ts* nodes) into bundler fixtures
When it happens
Trigger: Feeding swc_bundler a TypeScript source containing `export namespace X { ... }` or `export module M { ... }` without running the TypeScript strip pass first.
Common situations: Custom pipelines that run the bundler directly on raw .ts sources; test fixtures containing namespaces; plugins that re-inject TS nodes after stripping.
Related errors
- internal error: entered unreachable code
- Duplicated element (${elements[j].key})
- Method visitTsType not implemented.
- illegal conversion: Cannot convert {:?} to ModuleDeclaration
- unable to access unknown nodes
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/4dfbf492b9bfc1b0.
Report an issue: GitHub.