Skip to content

Conversation

alex-snezhko
Copy link
Contributor

@alex-snezhko alex-snezhko commented Sep 8, 2025

close #12542

Improve detection of imported binding usage in template for non-inline SFC compilation mode.

Sample code

App.vue

<script setup lang="ts">
import { MyType, myVal } from './decls'

defineProps<MyType>()
</script>

<template lang="pug">
  div Hello
    div {{ myVal }}
</template>

decls.ts

export interface MyType {
  abc: number;
}

export const myVal = 123

In dev/non-inline mode before this change:
Screenshot From 2025-09-07 22-56-37

After change; builds/runs as expected:
Screenshot From 2025-09-07 22-57-49

no change in behavior in prod/inline mode, sample code works before and after change

Summary by CodeRabbit

  • New Features
    • Proper support for custom template languages (e.g., Pug) in <script setup lang="ts"> import usage detection and template inlining.
  • Bug Fixes
    • More accurate detection of template-used imports, including handling of identifiers without the _ctx. prefix.
    • Improved HMR reload decisions by analyzing the actual template content/AST.
  • Performance
    • Caching of compiled custom-language templates to reduce redundant compilations.
  • Tests
    • Added coverage for custom template language usage with TypeScript to ensure correct exposure of bindings.

Copy link

coderabbitai bot commented Sep 8, 2025

Walkthrough

Implements template-language–aware import usage detection and template inlining in compileScript for inline SFC templates, including Pug. Refactors isImportUsed to accept template content/AST. Adjusts parse HMR reload check accordingly. Adds a test covering TS + script setup with Pug preprocessing to verify correct exposure of imports.

Changes

Cohort / File(s) Summary
Tests: import usage with custom template lang
packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts
Adds a test for script setup with lang="ts" and template lang="pug", compiling with templateOptions.preprocessLang='pug', asserting correct getter exposure and code generation.
Template compilation integration in compileScript
packages/compiler-sfc/src/compileScript.ts
Adds compileSFCTemplate helper and caching for custom template lang; uses compiled AST for usage detection and inlining; handles inline templates regardless of template.lang; imports/exported types for template block/results.
HMR usage check context
packages/compiler-sfc/src/parse.ts
Updates hmrShouldReload to call isImportUsed with template content and AST instead of full SFC descriptor.
Import usage analysis refactor
packages/compiler-sfc/src/script/importUsageCheck.ts
Changes isImportUsed and resolveTemplateUsedIdentifiers to accept content and RootNode AST; adjusts traversal and identifier extraction; removes SFCDescriptor dependency.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as SFC (script setup ts + template lang="pug")
  participant CS as compileScript
  participant CT as compileSFCTemplate
  participant IU as isImportUsed

  Dev->>CS: compile()
  alt Inline template present
    alt template.lang not set
      CS->>IU: isImportUsed(local, template.content, template.ast)
    else template.lang set (e.g., pug)
      CS->>CT: compileSFCTemplate(template block, { inline: true, isTS, bindings })
      CT-->>CS: { code, ast, map, ... }
      CS->>IU: isImportUsed(local, template.content, compiled.ast)
    end
    IU-->>CS: usage boolean
    CS->>CS: Register user imports accordingly
    CS->>CT: compileSFCTemplate(...) for inlining
    CT-->>CS: compiled template code/results
    CS-->>Dev: output code
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Handle TS script setup with Pug templates so type-only imports don’t cause runtime ESM import errors; ensure usage detection works with custom template languages (#12542)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Exporting/using types SFCTemplateBlock and SFCTemplateCompileResults within compileScript (packages/compiler-sfc/src/compileScript.ts) Likely internal typing alignment for new flow; seems related. Not clearly out of scope.

Suggested labels

ready to merge, scope: sfc, :hammer: p3-minor-bug

Suggested reviewers

  • edison1105

Poem

I nibbled through templates, a Pug-flavored maze,
Sniffed out imports, set ASTs ablaze.
Types hop away from runtime fright,
While scripts and templates now align just right.
Thump-thump! says my reviewy heart—
Another bug fixed with a carrot-smart start. 🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

github-actions bot commented Sep 8, 2025

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 101 kB 38.5 kB 34.6 kB
vue.global.prod.js 159 kB 58.6 kB 52.1 kB

Usages

Name Size Gzip Brotli
createApp (CAPI only) 46.6 kB 18.2 kB 16.7 kB
createApp 54.6 kB 21.3 kB 19.4 kB
createSSRApp 58.9 kB 23 kB 21 kB
defineCustomElement 59.6 kB 22.8 kB 20.9 kB
overall 68.8 kB 26.4 kB 24.2 kB

Copy link

pkg-pr-new bot commented Sep 8, 2025

Open in StackBlitz

@vue/compiler-core

npm i https://pkg.pr.new/@vue/compiler-core@13858

@vue/compiler-dom

npm i https://pkg.pr.new/@vue/compiler-dom@13858

@vue/compiler-sfc

npm i https://pkg.pr.new/@vue/compiler-sfc@13858

@vue/compiler-ssr

npm i https://pkg.pr.new/@vue/compiler-ssr@13858

@vue/reactivity

npm i https://pkg.pr.new/@vue/reactivity@13858

@vue/runtime-core

npm i https://pkg.pr.new/@vue/runtime-core@13858

@vue/runtime-dom

npm i https://pkg.pr.new/@vue/runtime-dom@13858

@vue/server-renderer

npm i https://pkg.pr.new/@vue/server-renderer@13858

@vue/shared

npm i https://pkg.pr.new/@vue/shared@13858

vue

npm i https://pkg.pr.new/vue@13858

@vue/compat

npm i https://pkg.pr.new/@vue/compat@13858

commit: 531b91e

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (3)
packages/compiler-sfc/src/script/importUsageCheck.ts (1)

28-32: Cache key may be too coarse (content-only)

Results can theoretically vary with different parse options even when content is identical. Consider salting the cache key with a lightweight hash of relevant parse options (or a caller-provided cacheKey) to avoid cross-context collisions. Keep current behavior as default if adding an optional param.

packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts (1)

268-295: Nice coverage for custom template languages

The pug case validates components, interpolations, bindings, v-text, and ref usage. Consider adding an explicit negative check to ensure Thing1 is not exposed to make the intent self-verifying.

   expect(content).toMatch(
     'return { get Thing2() { return Thing2 }, get Thing3() { return Thing3 }, get Thing4() { return Thing4 }, get Thing5() { return Thing5 }, get Thing6() { return Thing6 } }',
   )
+  expect(content).not.toMatch('get Thing1\\(\\)') // literal text shouldn't be exposed
packages/compiler-sfc/src/compileScript.ts (1)

250-250: Cache avoids repeated template compilation — minor naming nit

Optional: rename to compiledTemplateCache for brevity.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 75220c7 and 531b91e.

⛔ Files ignored due to path filters (1)
  • packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts (1 hunks)
  • packages/compiler-sfc/src/compileScript.ts (6 hunks)
  • packages/compiler-sfc/src/parse.ts (1 hunks)
  • packages/compiler-sfc/src/script/importUsageCheck.ts (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts (1)
packages/compiler-sfc/__tests__/utils.ts (1)
  • assertCode (27-42)
packages/compiler-sfc/src/parse.ts (1)
packages/compiler-sfc/src/script/importUsageCheck.ts (1)
  • isImportUsed (18-24)
packages/compiler-sfc/src/script/importUsageCheck.ts (2)
packages/compiler-core/src/ast.ts (2)
  • RootNode (104-121)
  • SimpleExpressionNode (225-247)
packages/compiler-sfc/src/cache.ts (1)
  • createCache (3-11)
packages/compiler-sfc/src/compileScript.ts (3)
packages/compiler-sfc/src/compileTemplate.ts (2)
  • SFCTemplateCompileResults (37-45)
  • compileTemplate (107-160)
packages/compiler-sfc/src/script/importUsageCheck.ts (1)
  • isImportUsed (18-24)
packages/compiler-sfc/src/parse.ts (1)
  • SFCTemplateBlock (45-48)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Redirect rules
  • GitHub Check: Header rules
  • GitHub Check: Pages changed
  • GitHub Check: test / e2e-test
🔇 Additional comments (6)
packages/compiler-sfc/src/script/importUsageCheck.ts (3)

18-24: API change aligns with new call sites

Switching isImportUsed to accept (content, ast) is sound and makes the dependency explicit. No functional concerns here.


39-39: Traversal entrypoint looks good

Walking ast.children directly is correct now that a concrete RootNode is required.


98-103: Good fix: strip leading _ctx. for simple identifiers

This improves identifier matching for static simple expressions without over-matching.

packages/compiler-sfc/src/compileScript.ts (3)

12-12: Imports for SFCTemplate types — LGTM*

Types are correctly imported for downstream usage.

Also applies to: 40-41


921-923: Unified inlining via compileSFCTemplate — LGTM

Consistent path for inline and detection flows; source maps and helpers remain intact.


312-333: The scripts above will locate and inspect the relevant definitions and usages to verify whether preprocessLang is handled correctly in both inline and non-inline flows. Let me know when you have the results.

Comment on lines +267 to 283
!sfc.template.src
) {
isUsedInTemplate = isImportUsed(local, sfc)
if (!sfc.template.lang) {
isUsedInTemplate = isImportUsed(
local,
sfc.template.content,
sfc.template.ast!,
)
} else {
customTemplateLangCompiledSFC ||= compileSFCTemplate(sfc.template)
isUsedInTemplate = isImportUsed(
local,
sfc.template.content,
customTemplateLangCompiledSFC.ast!,
)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Bug: using preprocessed AST with raw (un-preprocessed) content can mis-detect template imports

When lang is set (e.g., pug), compileSFCTemplate returns an AST for the preprocessed template, but content passed to isImportUsed is the raw SFC template (pug). This mismatch can lead to false negatives/positives. Also, ast may be undefined on preprocessing failure; defaulting to “used” is safer in dev/non-inline to avoid runtime breakage.

Apply the following:

-      if (!sfc.template.lang) {
-        isUsedInTemplate = isImportUsed(
-          local,
-          sfc.template.content,
-          sfc.template.ast!,
-        )
-      } else {
-        customTemplateLangCompiledSFC ||= compileSFCTemplate(sfc.template)
-        isUsedInTemplate = isImportUsed(
-          local,
-          sfc.template.content,
-          customTemplateLangCompiledSFC.ast!,
-        )
-      }
+      if (!sfc.template.lang) {
+        isUsedInTemplate = sfc.template.ast
+          ? isImportUsed(local, sfc.template.content, sfc.template.ast)
+          : true
+      } else {
+        const compiled =
+          (customTemplateLangCompiledSFC ||= compileSFCTemplate(sfc.template))
+        isUsedInTemplate = compiled.ast
+          ? isImportUsed(local, compiled.source, compiled.ast)
+          : true
+      }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
!sfc.template.src
) {
isUsedInTemplate = isImportUsed(local, sfc)
if (!sfc.template.lang) {
isUsedInTemplate = isImportUsed(
local,
sfc.template.content,
sfc.template.ast!,
)
} else {
customTemplateLangCompiledSFC ||= compileSFCTemplate(sfc.template)
isUsedInTemplate = isImportUsed(
local,
sfc.template.content,
customTemplateLangCompiledSFC.ast!,
)
}
}
!sfc.template.src
) {
if (!sfc.template.lang) {
isUsedInTemplate = sfc.template.ast
? isImportUsed(local, sfc.template.content, sfc.template.ast)
: true
} else {
const compiled =
(customTemplateLangCompiledSFC ||= compileSFCTemplate(sfc.template))
isUsedInTemplate = compiled.ast
? isImportUsed(local, compiled.source, compiled.ast)
: true
}
}
🤖 Prompt for AI Agents
In packages/compiler-sfc/src/compileScript.ts around lines 267 to 283, the code
calls isImportUsed with the raw SFC template content when a custom template lang
is used, but passes the preprocessed AST — this mismatch can mis-detect imports
and also assumes ast always exists; change the call to use the preprocessed
template content together with its AST when lang is set (i.e., use
customTemplateLangCompiledSFC.content and .ast), and if compileSFCTemplate fails
to produce an ast, avoid a false negative by defaulting isUsedInTemplate to true
(at least for dev/non-inline cases) so runtime behavior isn’t broken.

Comment on lines +451 to +454
if (
!prevImports[key].isUsedInTemplate &&
isImportUsed(key, next.template!.content!, next.template!.ast!)
) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential runtime crash when template/AST is absent

next.template or next.template.ast can be null (e.g., external src templates). The non-null assertions will throw at runtime during HMR checks. Guard before calling isImportUsed.

Apply this diff:

-    if (
-      !prevImports[key].isUsedInTemplate &&
-      isImportUsed(key, next.template!.content!, next.template!.ast!)
-    ) {
+    // skip when there is no in-file template or no parsed AST (e.g. src=)
+    if (!next.template || !next.template.ast) {
+      continue
+    }
+    if (
+      !prevImports[key].isUsedInTemplate &&
+      isImportUsed(key, next.template.content, next.template.ast)
+    ) {
       return true
     }

Optionally short-circuit earlier:

   ) {
     return false
   }
+  if (!next.template || !next.template.ast) {
+    return false
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (
!prevImports[key].isUsedInTemplate &&
isImportUsed(key, next.template!.content!, next.template!.ast!)
) {
// skip when there is no in-file template or no parsed AST (e.g. src=)
if (!next.template || !next.template.ast) {
continue
}
if (
!prevImports[key].isUsedInTemplate &&
isImportUsed(key, next.template.content, next.template.ast)
) {
return true
}
🤖 Prompt for AI Agents
In packages/compiler-sfc/src/parse.ts around lines 451 to 454, the code uses
non-null assertions on next.template and next.template.ast which can be null and
cause runtime crashes; guard these properties before calling isImportUsed by
first checking that next.template and next.template.ast exist (e.g., if
(next.template && next.template.ast) ) and only then call isImportUsed, or
short-circuit earlier so imports marked as unused when the template/AST is
absent; update the conditional to avoid using ! and .! on possibly null values.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Uncaught SyntaxError when using Composition API with Pug: Missing type in import statement
1 participant