facebook/relay · error
Unexpected JSModuleDependency
Error message
Unexpected JSModuleDependency
What it means
Primitive::JSModuleDependency represents a required JS module import (e.g. from @match or client extensions) and is emitted via module import statements, never as a constant value. write_constant_value panics if such a dependency appears in an argument or embedded literal position, since there is no valid JS literal for a module reference in that context.
Source
Thrown at compiler/crates/relay-codegen/src/printer.rs:933
write_constant_value(f, builder, value)?;
f.push(',');
}
if !obj.is_empty() {
f.pop();
}
f.push('}');
Ok(())
}
}
}
Primitive::Null | Primitive::SkippableNull => {
f.push_str("null");
Ok(())
}
Primitive::StorageKey(_, _) => panic!("Unexpected StorageKey"),
Primitive::RawString(_) => panic!("Unexpected RawString"),
Primitive::GraphQLModuleDependency(_) => panic!("Unexpected GraphQLModuleDependency"),
Primitive::JSModuleDependency { .. } => panic!("Unexpected JSModuleDependency"),
Primitive::ResolverModuleReference { .. } => panic!("Unexpected ResolverModuleReference"),
Primitive::PropertyAccessor(_) => panic!("Unexpected PropertyAccessor"),
Primitive::DynamicImport { .. } => panic!("Unexpected DynamicImport"),
Primitive::RelayResolverModel { .. } => panic!("Unexpected RelayResolver"),
}
}
View on GitHub (pinned to 668b1b85e0)
Solutions
- Ensure module dependencies are only produced in module/import positions (write_module_import path), not in arguments
- Restructure the document so @match/@module usage keeps dependencies in selections rather than arguments
- Fix custom transforms to use the proper import mechanism instead of embedding dependencies in values
- Update the Relay compiler to a version whose IR/printer contract matches your pipeline
Example fix
// before
argument: <JSModuleDependency("./Avatar_user.react")> // inside an argument value
// after
argument: "Avatar_user" // plain string literal; import emitted via module path Defensive patterns
Strategy: validation
Validate before calling
function assertNoJsDepsInArgs(doc) {
visit(doc, {
Argument(node) {
if (node.value.kind !== 'Variable' && !isLiteralValue(node.value)) {
throw new Error(`Argument ${node.name.value} must be a literal value`);
}
}
});
} Type guard
const isJsDep = (p) => p != null && p.type === 'jsModuleDependency';
Try / catch
try {
generateArtifacts();
} catch (e) {
if (String(e).includes('Unexpected JSModuleDependency')) {
// route module imports through the import path, not arguments
}
throw e;
} Prevention
- Emit JS module dependencies only via import statements in generated artifacts
- Keep arguments strictly to literal values
- Review custom codegen passes for misplaced dependency primitives
- Keep transforms and printer in the same compiler version
When it happens
Trigger: A JSModuleDependency primitive produced by the builder for @match/client-extension handling reaching an argument value or constant position during printing; custom transforms inserting JS module dependencies into argument values.
Common situations: Using @match/@module features where the dependency ends up in an argument position instead of a selection; custom codegen passes constructing JSModuleDependency Primitives in the wrong slot; compiler upgrade changing where dependencies are emitted.
Related errors
- Unexpected StorageKey
- Unexpected RawString
- Unexpected GraphQLModuleDependency
- Unexpected ResolverModuleReference
- Unexpected PropertyAccessor
AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02).
Data as JSON: /api/errors/d1980058b7dd0da4.
Report an issue: GitHub.