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( + ` + + + `, + { 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) } }