refactor(compiler-sfc): improve script setup import expose heuristics
This commit is contained in:
parent
5a3ccfd914
commit
f0ca233d8b
@ -206,7 +206,7 @@ return { x }
|
||||
|
||||
exports[`SFC compile <script setup> imports imports not used in <template> should not be exposed 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { FooBar, FooBaz, FooQux, vMyDir, x, y } from './x'
|
||||
import { FooBar, FooBaz, FooQux, vMyDir, x, y, z } from './x'
|
||||
|
||||
export default _defineComponent({
|
||||
setup(__props, { expose }) {
|
||||
@ -214,7 +214,7 @@ export default _defineComponent({
|
||||
|
||||
const fooBar: FooBar = 1
|
||||
|
||||
return { fooBar, FooBaz, FooQux, vMyDir, x }
|
||||
return { fooBar, FooBaz, FooQux, vMyDir, x, z }
|
||||
}
|
||||
|
||||
})"
|
||||
|
@ -213,20 +213,23 @@ defineExpose({ foo: 123 })
|
||||
test('imports not used in <template> should not be exposed', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
import { FooBar, FooBaz, FooQux, vMyDir, x, y } from './x'
|
||||
import { FooBar, FooBaz, FooQux, vMyDir, x, y, z } from './x'
|
||||
const fooBar: FooBar = 1
|
||||
</script>
|
||||
<template>
|
||||
<FooBaz v-my-dir>{{ x }} {{ yy }}</FooBaz>
|
||||
<foo-qux/>
|
||||
<div :id="z + 'y'">FooBar</div>
|
||||
</template>
|
||||
`)
|
||||
assertCode(content)
|
||||
// FooBar: should not be matched by plain text
|
||||
// FooBaz: used as PascalCase component
|
||||
// FooQux: used as kebab-case component
|
||||
// vMyDir: used as directive v-my-dir
|
||||
// x: used in interpolation
|
||||
expect(content).toMatch(`return { fooBar, FooBaz, FooQux, vMyDir, x }`)
|
||||
// y: should not be matched by {{ yy }} or 'y' in binding exps
|
||||
expect(content).toMatch(`return { fooBar, FooBaz, FooQux, vMyDir, x, z }`)
|
||||
})
|
||||
})
|
||||
|
||||
|
5
packages/compiler-sfc/src/cache.ts
Normal file
5
packages/compiler-sfc/src/cache.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export function createCache<T>(size = 500) {
|
||||
return __GLOBAL__ || __ESM_BROWSER__
|
||||
? new Map<string, T>()
|
||||
: (new (require('lru-cache'))(size) as Map<string, T>)
|
||||
}
|
@ -2,9 +2,14 @@ import MagicString from 'magic-string'
|
||||
import {
|
||||
BindingMetadata,
|
||||
BindingTypes,
|
||||
createRoot,
|
||||
locStub,
|
||||
UNREF
|
||||
} from '@vue/compiler-core'
|
||||
NodeTypes,
|
||||
transform,
|
||||
parserOptions,
|
||||
UNREF,
|
||||
SimpleExpressionNode
|
||||
} from '@vue/compiler-dom'
|
||||
import {
|
||||
ScriptSetupTextRanges,
|
||||
SFCDescriptor,
|
||||
@ -14,8 +19,10 @@ import {
|
||||
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
|
||||
import {
|
||||
babelParserDefaultPlugins,
|
||||
camelize,
|
||||
capitalize,
|
||||
generateCodeFrame,
|
||||
hyphenate
|
||||
makeMap
|
||||
} from '@vue/shared'
|
||||
import {
|
||||
Node,
|
||||
@ -49,6 +56,7 @@ import {
|
||||
import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
|
||||
import { warnExperimental, warnOnce } from './warn'
|
||||
import { rewriteDefault } from './rewriteDefault'
|
||||
import { createCache } from './cache'
|
||||
|
||||
// Special compiler macros
|
||||
const DEFINE_PROPS = 'defineProps'
|
||||
@ -61,6 +69,10 @@ const $COMPUTED = `$computed`
|
||||
const $FROM_REFS = `$fromRefs`
|
||||
const $RAW = `$raw`
|
||||
|
||||
const isBuiltInDir = makeMap(
|
||||
`once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
|
||||
)
|
||||
|
||||
export interface SFCScriptCompileOptions {
|
||||
/**
|
||||
* Scope ID for prefixing injected CSS varialbes.
|
||||
@ -319,10 +331,10 @@ export function compileScript(
|
||||
}
|
||||
|
||||
let isUsedInTemplate = true
|
||||
if (sfc.template && !sfc.template.src) {
|
||||
isUsedInTemplate = new RegExp(
|
||||
`\\b(?:${local}|${hyphenate(local)})\\b`
|
||||
).test(sfc.template.content)
|
||||
if (isTS && sfc.template && !sfc.template.src) {
|
||||
isUsedInTemplate = new RegExp(`\\b${local}\\b`).test(
|
||||
resolveTemplateUsageCheckString(sfc)
|
||||
)
|
||||
}
|
||||
|
||||
userImports[local] = {
|
||||
@ -2154,3 +2166,53 @@ function toTextRange(node: Node): TextRange {
|
||||
end: node.end!
|
||||
}
|
||||
}
|
||||
|
||||
const templateUsageCheckCache = createCache<string>()
|
||||
|
||||
function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
|
||||
const { content, ast } = sfc.template!
|
||||
const cached = templateUsageCheckCache.get(content)
|
||||
if (cached) {
|
||||
return cached
|
||||
}
|
||||
|
||||
let code = ''
|
||||
transform(createRoot([ast]), {
|
||||
nodeTransforms: [
|
||||
node => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
if (
|
||||
!parserOptions.isNativeTag!(node.tag) &&
|
||||
!parserOptions.isBuiltInComponent!(node.tag)
|
||||
) {
|
||||
code += `,${capitalize(camelize(node.tag))}`
|
||||
}
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const prop = node.props[i]
|
||||
if (prop.type === NodeTypes.DIRECTIVE) {
|
||||
if (!isBuiltInDir(prop.name)) {
|
||||
code += `,v${capitalize(camelize(prop.name))}`
|
||||
}
|
||||
if (prop.exp) {
|
||||
code += `,${stripStrings(
|
||||
(prop.exp as SimpleExpressionNode).content
|
||||
)}`
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (node.type === NodeTypes.INTERPOLATION) {
|
||||
code += `,${stripStrings(
|
||||
(node.content as SimpleExpressionNode).content
|
||||
)}`
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
templateUsageCheckCache.set(content, code)
|
||||
return code
|
||||
}
|
||||
|
||||
function stripStrings(exp: string) {
|
||||
return exp.replace(/'[^']+'|"[^"]+"|`[^`]+`/g, '')
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import { TemplateCompiler } from './compileTemplate'
|
||||
import { Statement } from '@babel/types'
|
||||
import { parseCssVars } from './cssVars'
|
||||
import { warnExperimental } from './warn'
|
||||
import { createCache } from './cache'
|
||||
|
||||
export interface SFCParseOptions {
|
||||
filename?: string
|
||||
@ -89,14 +90,7 @@ export interface SFCParseResult {
|
||||
errors: (CompilerError | SyntaxError)[]
|
||||
}
|
||||
|
||||
const SFC_CACHE_MAX_SIZE = 500
|
||||
const sourceToSFC =
|
||||
__GLOBAL__ || __ESM_BROWSER__
|
||||
? new Map<string, SFCParseResult>()
|
||||
: (new (require('lru-cache'))(SFC_CACHE_MAX_SIZE) as Map<
|
||||
string,
|
||||
SFCParseResult
|
||||
>)
|
||||
const sourceToSFC = createCache<SFCParseResult>()
|
||||
|
||||
export function parse(
|
||||
source: string,
|
||||
|
Loading…
Reference in New Issue
Block a user