templateOptions?: Partial<SFCTemplateCompileOptions>
}
-interface ImportBinding {
+export interface ImportBinding {
isType: boolean
imported: string
source: string
let isUsedInTemplate = true
if (isTS && sfc.template && !sfc.template.src && !sfc.template.lang) {
- isUsedInTemplate = new RegExp(
- // #4274 escape $ since it's a special char in regex
- // (and is the only regex special char that is valid in identifiers)
- `[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
- ).test(resolveTemplateUsageCheckString(sfc))
+ isUsedInTemplate = isImportUsed(local, sfc)
}
userImports[local] = {
return {
...scriptSetup,
bindings: bindingMetadata,
+ imports: userImports,
content: s.toString(),
map: genSourceMap
? (s.generateMap({
const templateUsageCheckCache = createCache<string>()
-export function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
+function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
const { content, ast } = sfc.template!
const cached = templateUsageCheckCache.get(content)
if (cached) {
}
return ''
}
+
+function isImportUsed(local: string, sfc: SFCDescriptor): boolean {
+ return new RegExp(
+ // #4274 escape $ since it's a special char in regex
+ // (and is the only regex special char that is valid in identifiers)
+ `[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`
+ ).test(resolveTemplateUsageCheckString(sfc))
+}
+
+/**
+ * Note: this comparison assumes the prev/next script are already identical,
+ * and only checks the special case where <script setup lang="ts"> unused import
+ * pruning result changes due to template changes.
+ */
+export function hmrShouldReload(
+ prevImports: Record<string, ImportBinding>,
+ next: SFCDescriptor
+): boolean {
+ if (
+ !next.scriptSetup ||
+ (next.scriptSetup.lang !== 'ts' && next.scriptSetup.lang !== 'tsx')
+ ) {
+ return false
+ }
+
+ // for each previous import, check if its used status remain the same based on
+ // the next descriptor's template
+ 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)) {
+ return true
+ }
+ }
+
+ return false
+}
export { parse } from './parse'
export { compileTemplate } from './compileTemplate'
export { compileStyle, compileStyleAsync } from './compileStyle'
-export { compileScript, resolveTemplateUsageCheckString } from './compileScript'
+export { compileScript } from './compileScript'
export { rewriteDefault } from './rewriteDefault'
export {
shouldTransform as shouldTransformRef,
import { TemplateCompiler } from './compileTemplate'
import { parseCssVars } from './cssVars'
import { createCache } from './cache'
+import { hmrShouldReload, ImportBinding } from './compileScript'
export interface SFCParseOptions {
filename?: string
type: 'script'
setup?: string | boolean
bindings?: BindingMetadata
+ imports?: Record<string, ImportBinding>
/**
* import('\@babel/types').Statement
*/
*/
scriptSetupAst?: any[]
}
+
export interface SFCStyleBlock extends SFCBlock {
type: 'style'
scoped?: boolean
styles: SFCStyleBlock[]
customBlocks: SFCBlock[]
cssVars: string[]
- // whether the SFC uses :slotted() modifier.
- // this is used as a compiler optimization hint.
+ /**
+ * whether the SFC uses :slotted() modifier.
+ * this is used as a compiler optimization hint.
+ */
slotted: boolean
+
+ /**
+ * compare with an existing descriptor to determine whether HMR should perform
+ * a reload vs. re-render.
+ *
+ * Note: this comparison assumes the prev/next script are already identical,
+ * and only checks the special case where <script setup lang="ts"> unused import
+ * pruning result changes due to template changes.
+ */
+ shouldForceReload: (prevImports: Record<string, ImportBinding>) => boolean
}
export interface SFCParseResult {
styles: [],
customBlocks: [],
cssVars: [],
- slotted: false
+ slotted: false,
+ shouldForceReload: prevImports => hmrShouldReload(prevImports, descriptor)
}
const errors: (CompilerError | SyntaxError)[] = []