Skip to content

module-graph misses a dynamic import() inside a template ${} hole #918

Description

@vivek7405

Problem

The module-graph edge scanner misses a dynamic import() whose string-literal specifier sits inside a template-literal ${} interpolation hole, e.g. html`${import('./widget.ts')}`. Since the auth gate and preload derive from the module graph, that module is NOT admitted as a (dynamic) edge, so it can 404 if actually loaded. This is the same "admit fewer / silent 404" family as #753, but for a different, narrower construct.

Root cause: parseFile scans for edges over redactStringsAndTemplates(src, true), which blanks the ENTIRE template body INCLUDING the code inside ${} holes, so DYNAMIC_IMPORT_RE never sees the import(...) inside the hole. This is PRE-EXISTING (confirmed: main misses it identically, so it is not introduced by the #753 fix) and rare (a dynamic import written directly inside a lit-html template interpolation), which is why the #753 differential corpus, which finds zero instances in real source, stays green.

Surfaced by the #753 lexer-vs-AST differential harness: a real TypeScript AST counts ${import('./x')} as an edge, the hand-rolled scanner does not.

Design / approach

The #753 root fix deliberately scans over the fully-blanked mask so no stray keyword inside any literal can anchor a swallow. The trade-off is that ${} hole CODE is blanked too. To capture a real dynamic import inside a hole without reopening the swallow class, the scan needs a mask that keeps ${} hole code readable while still blanking template TEXT + string + regex + comment bodies. Two candidate directions:

  • Reuse redactToPlaceholders (js-scan.js, fix: false positives in component scanner and elision analyzer due to string/template literals in code samples #634), which already scans ${} holes as code and replaces string bodies with __STR_<idx>__ placeholders; recover the specifier from the returned literals array (this is how component-scanner.js reads tags). Run DYNAMIC_IMPORT_RE over that placeholder source and map the placeholder back to the literal.
  • Or a targeted second pass: after the main scan, additionally scan the code inside each ${} hole for DYNAMIC_IMPORT_RE only (static import ... from cannot appear in an expression position, so only dynamic imports are affected).

Keep it dynamic-only: a static import/export ... from is a statement and cannot appear inside a ${} expression, so only DYNAMIC_IMPORT_RE needs the extra coverage.

Implementation notes (for the implementing agent)

Acceptance criteria

  • A dynamic import('./x.ts') inside a ${} template hole is captured as a dynamic edge by buildModuleGraph (the module is servable, no 404).
  • The IMPORTANT: prove the hand-rolled lexer matches an AST (auth gate / elision / check rest on it) #753 swallow-class fix is preserved (a stray import/export word inside a comment / string / template TEXT / regex still does NOT anchor a phantom or swallow a real edge).
  • A computed import(expr) is still NOT an edge (unchanged documented limitation).
  • Fixture in packages/server/test/scanner-fuzz/ pins the hole case on both the lexer and the AST; counterfactual reds when the hole-scan is reverted.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

  • Status
    Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions