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

  1. 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
  2. Replace `export namespace`/`export module` in bundled sources with plain ES module exports
  3. 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

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


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/4dfbf492b9bfc1b0. Report an issue: GitHub.