diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap
index e648587449a..a82d6424f51 100644
--- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap
@@ -49,6 +49,21 @@ return { fooBar, get FooBaz() { return FooBaz }, get FooQux() { return FooQux },
})"
`;
+exports[`custom template lang 1`] = `
+"import { defineComponent as _defineComponent } from 'vue'
+import { Thing1, Thing2, Thing3, Thing4, Thing5, Thing6 } from "./types.ts"
+
+export default /*@__PURE__*/_defineComponent({
+ setup(__props, { expose: __expose }) {
+ __expose();
+
+
+return { get Thing2() { return Thing2 }, get Thing3() { return Thing3 }, get Thing4() { return Thing4 }, get Thing5() { return Thing5 }, get Thing6() { return Thing6 } }
+}
+
+})"
+`;
+
exports[`directive 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { vMyDir } from './x'
diff --git a/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts b/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts
index 210fa09688a..66c38697b1a 100644
--- a/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts
@@ -265,3 +265,31 @@ test('shorthand binding w/ kebab-case', () => {
)
expect(content).toMatch('return { get fooBar() { return fooBar }')
})
+
+test('custom template lang', () => {
+ const { content } = compile(
+ `
+
+
+ h1 Thing1
+ div {{ Thing2 }}
+
+ Thing3 World
+
+ div(v-bind:abc='Thing4')
+
+ div(v-text='Thing5')
+
+ div(ref='Thing6')
+
+ `,
+ { templateOptions: { preprocessLang: 'pug' } },
+ )
+ // Thing1 is just a string in the template so should not be included
+ expect(content).toMatch(
+ 'return { get Thing2() { return Thing2 }, get Thing3() { return Thing3 }, get Thing4() { return Thing4 }, get Thing5() { return Thing5 }, get Thing6() { return Thing6 } }',
+ )
+ assertCode(content)
+})
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index cbd4abdf71a..430df7555c1 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -9,6 +9,7 @@ import {
DEFAULT_FILENAME,
type SFCDescriptor,
type SFCScriptBlock,
+ type SFCTemplateBlock,
} from './parse'
import type { ParserPlugin } from '@babel/parser'
import { generateCodeFrame } from '@vue/shared'
@@ -36,6 +37,7 @@ import {
import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
import {
type SFCTemplateCompileOptions,
+ type SFCTemplateCompileResults,
compileTemplate,
} from './compileTemplate'
import { warnOnce } from './warn'
@@ -245,6 +247,8 @@ export function compileScript(
ctx.s.move(start, end, 0)
}
+ let customTemplateLangCompiledSFC: SFCTemplateCompileResults | undefined
+
function registerUserImport(
source: string,
local: string,
@@ -260,10 +264,22 @@ export function compileScript(
needTemplateUsageCheck &&
ctx.isTS &&
sfc.template &&
- !sfc.template.src &&
- !sfc.template.lang
+ !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!,
+ )
+ }
}
ctx.userImports[local] = {
@@ -293,6 +309,28 @@ export function compileScript(
})
}
+ function compileSFCTemplate(
+ sfcTemplate: SFCTemplateBlock,
+ ): SFCTemplateCompileResults {
+ return compileTemplate({
+ filename,
+ ast: sfcTemplate.ast,
+ source: sfcTemplate.content,
+ inMap: sfcTemplate.map,
+ ...options.templateOptions,
+ id: scopeId,
+ scoped: sfc.styles.some(s => s.scoped),
+ isProd: options.isProd,
+ ssrCssVars: sfc.cssVars,
+ compilerOptions: {
+ ...(options.templateOptions && options.templateOptions.compilerOptions),
+ inline: true,
+ isTS: ctx.isTS,
+ bindingMetadata: ctx.bindingMetadata,
+ },
+ })
+ }
+
const scriptAst = ctx.scriptAst
const scriptSetupAst = ctx.scriptSetupAst!
@@ -880,24 +918,9 @@ export function compileScript(
}
// inline render function mode - we are going to compile the template and
// inline it right here
- const { code, ast, preamble, tips, errors, map } = compileTemplate({
- filename,
- ast: sfc.template.ast,
- source: sfc.template.content,
- inMap: sfc.template.map,
- ...options.templateOptions,
- id: scopeId,
- scoped: sfc.styles.some(s => s.scoped),
- isProd: options.isProd,
- ssrCssVars: sfc.cssVars,
- compilerOptions: {
- ...(options.templateOptions &&
- options.templateOptions.compilerOptions),
- inline: true,
- isTS: ctx.isTS,
- bindingMetadata: ctx.bindingMetadata,
- },
- })
+ const { code, ast, preamble, tips, errors, map } = compileSFCTemplate(
+ sfc.template,
+ )
templateMap = map
if (tips.length) {
tips.forEach(warnOnce)
diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts
index c8be865508f..ce8b93f0316 100644
--- a/packages/compiler-sfc/src/parse.ts
+++ b/packages/compiler-sfc/src/parse.ts
@@ -448,7 +448,10 @@ export function hmrShouldReload(
for (const key in prevImports) {
// if an import was previous unused, but now is used, we need to force
// reload so that the script now includes that import.
- if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next)) {
+ if (
+ !prevImports[key].isUsedInTemplate &&
+ isImportUsed(key, next.template!.content!, next.template!.ast!)
+ ) {
return true
}
}
diff --git a/packages/compiler-sfc/src/script/importUsageCheck.ts b/packages/compiler-sfc/src/script/importUsageCheck.ts
index 22ef37cf37f..2251388aa5e 100644
--- a/packages/compiler-sfc/src/script/importUsageCheck.ts
+++ b/packages/compiler-sfc/src/script/importUsageCheck.ts
@@ -1,7 +1,7 @@
-import type { SFCDescriptor } from '../parse'
import {
type ExpressionNode,
NodeTypes,
+ type RootNode,
type SimpleExpressionNode,
type TemplateChildNode,
parserOptions,
@@ -15,14 +15,20 @@ import { camelize, capitalize, isBuiltInDirective } from '@vue/shared'
* the properties that should be included in the object returned from setup()
* when not using inline mode.
*/
-export function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
- return resolveTemplateUsedIdentifiers(sfc).has(local)
+export function isImportUsed(
+ local: string,
+ content: string,
+ ast: RootNode,
+): boolean {
+ return resolveTemplateUsedIdentifiers(content, ast).has(local)
}
const templateUsageCheckCache = createCache>()
-function resolveTemplateUsedIdentifiers(sfc: SFCDescriptor): Set {
- const { content, ast } = sfc.template!
+function resolveTemplateUsedIdentifiers(
+ content: string,
+ ast: RootNode,
+): Set {
const cached = templateUsageCheckCache.get(content)
if (cached) {
return cached
@@ -30,7 +36,7 @@ function resolveTemplateUsedIdentifiers(sfc: SFCDescriptor): Set {
const ids = new Set()
- ast!.children.forEach(walk)
+ ast.children.forEach(walk)
function walk(node: TemplateChildNode) {
switch (node.type) {
@@ -89,6 +95,10 @@ function extractIdentifiers(ids: Set, node: ExpressionNode) {
if (node.ast) {
walkIdentifiers(node.ast, n => ids.add(n.name))
} else if (node.ast === null) {
- ids.add((node as SimpleExpressionNode).content)
+ const content = (node as SimpleExpressionNode).content.replace(
+ /^_ctx\./,
+ '',
+ )
+ ids.add(content)
}
}