TLDR
before I had it over to the bots who are smarter than me:
-The bundler treats commented-out import/export text as real module syntax.
- This caused quite a bit of confusion in my codebase, leaving me with a file that was fully commented out, and yet I could not delete without causing a cascade of TS errors. I gave up and left this "load bearing" comment file for many months.
- After finally finding this bug, I finally deleted the file and realized those TS errors actually were real. But with the file, TS appeared to be fully satisfied. I
- I wonder if this could have other unexpected impacts in complex TS projects, some of which are open issues
Summary
entryPoints() in src/bundler/index.ts:482 filters TypeScript entry points using a raw-text regex:
/^\s{0,100}(import|export)/m.test(contents)
Because the check is not syntax-aware, files containing only commented-out import/export text are classified as TypeScript modules and included in _generated/api.d.ts as phantom entries.
Reproducer
A convex/sentinel.ts containing only:
/*
import { query } from "./_generated/server";
export const oldDebugQuery = query({ args: {}, handler: async () => "x" });
*/
…has no top-level import or export. But after npx convex codegen, sentinel appears in _generated/api.d.ts. Remove the commented import/export lines and rerun codegen — sentinel disappears. Codegen output is being driven by comment text.
Concrete failure: invalid generated .d.ts under path-alias collision
Given:
convex/foo_bar.ts — a real module
convex/foo/bar.ts — comment-only
Both paths normalize to the import alias foo_bar. Generated output:
import type * as foo_bar from "../foo/bar.js";
import type * as foo_bar from "../foo_bar.js";
Strict-mode check of the generated file fails:
TS2300: Duplicate identifier 'foo_bar'.
TS2306: File '.../convex/foo/bar.ts' is not a module.
Real-world impact: phantom entries silently widen unrelated references to any
In a production project (~250 modules), a controlled experiment showed that the presence of a single 3-line comment-only .ts file changed how unrelated internal[...] references resolve:
| State |
internal["internal/X"] resolves to |
| Comment-only file present |
any (silent failure) |
| Comment-only file deleted |
TS2339: Property does not exist |
Control reference (internal.admin) resolves to a real type in both states.
The phantom entry adds enough complexity to push specific key resolutions in ApiFromModules<...> past TypeScript's instantiation depth limit, which silently widens those subexpressions to any instead of erroring.
Suggested fix
@babel/parser is already imported in src/bundler/index.ts (line 4) and used elsewhere in the same file (lines 358, 509). The check can be replaced with a syntax-aware top-level declaration scan with no new dependency. A conservative fallback can strip comments and apply the original regex if parsing fails, preserving behavior on syntax errors.
Versions
- Reconfirmed against
main at a487de2 — regex unchanged at src/bundler/index.ts:482
- Originally encountered in
convex@1.28.2; reproduces in every version since
TLDR
before I had it over to the bots who are smarter than me:
-The bundler treats commented-out
import/exporttext as real module syntax.Summary
entryPoints()insrc/bundler/index.ts:482filters TypeScript entry points using a raw-text regex:Because the check is not syntax-aware, files containing only commented-out
import/exporttext are classified as TypeScript modules and included in_generated/api.d.tsas phantom entries.Reproducer
A
convex/sentinel.tscontaining only:…has no top-level import or export. But after
npx convex codegen,sentinelappears in_generated/api.d.ts. Remove the commentedimport/exportlines and rerun codegen —sentineldisappears. Codegen output is being driven by comment text.Concrete failure: invalid generated
.d.tsunder path-alias collisionGiven:
convex/foo_bar.ts— a real moduleconvex/foo/bar.ts— comment-onlyBoth paths normalize to the import alias
foo_bar. Generated output:Strict-mode check of the generated file fails:
Real-world impact: phantom entries silently widen unrelated references to
anyIn a production project (~250 modules), a controlled experiment showed that the presence of a single 3-line comment-only
.tsfile changed how unrelatedinternal[...]references resolve:internal["internal/X"]resolves toany(silent failure)TS2339: Property does not existControl reference (
internal.admin) resolves to a real type in both states.The phantom entry adds enough complexity to push specific key resolutions in
ApiFromModules<...>past TypeScript's instantiation depth limit, which silently widens those subexpressions toanyinstead of erroring.Suggested fix
@babel/parseris already imported insrc/bundler/index.ts(line 4) and used elsewhere in the same file (lines 358, 509). The check can be replaced with a syntax-aware top-level declaration scan with no new dependency. A conservative fallback can strip comments and apply the original regex if parsing fails, preserving behavior on syntax errors.Versions
mainata487de2— regex unchanged atsrc/bundler/index.ts:482convex@1.28.2; reproduces in every version since