swc-project/swc · error
failed to downgrade Flow type-only module to script because
Error message
failed to downgrade Flow type-only module to script because module declarations remain after stripping
What it means
When a file is parsed as Flow in 'script-like module' mode, swc strips Flow types and then downgrades the Program::Module to a Program::Script (crates/swc/src/lib.rs). This bail fires in the pre-conversion scan when module body still contains ModuleItem::ModuleDecl - real runtime module syntax (import/export) survived stripping, so the module cannot be presented as a script.
Source
Thrown at crates/swc/src/lib.rs:294
.body
.first()
.map(Spanned::span)
.unwrap_or(module.span);
FlowScriptLikeModuleKind::RuntimeModule(span)
}
}
fn downgrade_flow_script_like_module(program: Program) -> Result<Program, Error> {
let Program::Module(module) = program else {
return Ok(program);
};
if module
.body
.iter()
.any(|module_item| matches!(module_item, ModuleItem::ModuleDecl(..)))
{
bail!(
"failed to downgrade Flow type-only module to script because module declarations \
remain after stripping"
);
}
let Module {
span,
body,
shebang,
} = module;
let body = body
.into_iter()
.map(|module_item| match module_item {
ModuleItem::Stmt(stmt) => Ok(stmt),
ModuleItem::ModuleDecl(..) => bail!(
"failed to downgrade Flow type-only module to script because module declarations \
remain after stripping"View on GitHub (pinned to 5176682b65)
Solutions
- Use the matching parser for the file: typescript (with tsx as needed) or ecmascript, instead of flow
- Scope the `flow` syntax config with `test` patterns so it only applies to genuine Flow files
- Upgrade swc/swc_core - Flow module-decl stripping behavior has changed across versions
- If embedding swc, avoid enabling the flow script-like downgrade path for files with runtime module syntax
Example fix
// before (.swcrc)
{ "jsc": { "parser": { "syntax": "flow" } } }
// after (.swcrc)
{
"test": "\\.(js|jsx|ts|tsx)$",
"jsc": { "parser": { "syntax": "typescript", "tsx": true } }
} Defensive patterns
Strategy: try-catch
Try / catch
try {
result = await transformFile(file, flowOptions);
} catch (e) {
if (/downgrade Flow type-only module/.test(String(e?.message ?? e))) {
// File has runtime import/export: recompile as TypeScript/ECMAScript
result = await transformFile(file, esOptions);
} else {
throw e;
}
} Prevention
- Scope `flow` parser config to genuine Flow files via `test` patterns
- Never apply the flow parser to plain ESM/TypeScript sources
- Pin swc_core versions you have validated against your Flow corpus
When it happens
Trigger: Configuring `jsc.parser.syntax: flow` for a file that contains actual runtime `import`/`export` statements (not just Flow type-only syntax), so after type stripping module declarations remain and the script downgrade is impossible.
Common situations: Pointing the Flow parser at TypeScript or plain ESM files in mixed codebases; sharing one .swcrc between Flow sources and ESM .js files; swc_core versions where Flow stripping of certain module-level type constructs is incomplete.
Related errors
- The requested module '{specifier}' does not provide an expor
- Cannot use import.meta outside a module
- Assignment to constant variable.
- attempted to ${action} private field on non-instance
- Class "${name}" cannot be referenced in computed property ke
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/e465297f7e4f05ed.
Report an issue: GitHub.