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`] = `
|
exports[`SFC compile <script setup> imports imports not used in <template> should not be exposed 1`] = `
|
||||||
"import { defineComponent as _defineComponent } from 'vue'
|
"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({
|
export default _defineComponent({
|
||||||
setup(__props, { expose }) {
|
setup(__props, { expose }) {
|
||||||
@ -214,7 +214,7 @@ export default _defineComponent({
|
|||||||
|
|
||||||
const fooBar: FooBar = 1
|
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', () => {
|
test('imports not used in <template> should not be exposed', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup lang="ts">
|
<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
|
const fooBar: FooBar = 1
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<FooBaz v-my-dir>{{ x }} {{ yy }}</FooBaz>
|
<FooBaz v-my-dir>{{ x }} {{ yy }}</FooBaz>
|
||||||
<foo-qux/>
|
<foo-qux/>
|
||||||
|
<div :id="z + 'y'">FooBar</div>
|
||||||
</template>
|
</template>
|
||||||
`)
|
`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
|
// FooBar: should not be matched by plain text
|
||||||
// FooBaz: used as PascalCase component
|
// FooBaz: used as PascalCase component
|
||||||
// FooQux: used as kebab-case component
|
// FooQux: used as kebab-case component
|
||||||
// vMyDir: used as directive v-my-dir
|
// vMyDir: used as directive v-my-dir
|
||||||
// x: used in interpolation
|
// 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 {
|
import {
|
||||||
BindingMetadata,
|
BindingMetadata,
|
||||||
BindingTypes,
|
BindingTypes,
|
||||||
|
createRoot,
|
||||||
locStub,
|
locStub,
|
||||||
UNREF
|
NodeTypes,
|
||||||
} from '@vue/compiler-core'
|
transform,
|
||||||
|
parserOptions,
|
||||||
|
UNREF,
|
||||||
|
SimpleExpressionNode
|
||||||
|
} from '@vue/compiler-dom'
|
||||||
import {
|
import {
|
||||||
ScriptSetupTextRanges,
|
ScriptSetupTextRanges,
|
||||||
SFCDescriptor,
|
SFCDescriptor,
|
||||||
@ -14,8 +19,10 @@ import {
|
|||||||
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
|
import { parse as _parse, ParserOptions, ParserPlugin } from '@babel/parser'
|
||||||
import {
|
import {
|
||||||
babelParserDefaultPlugins,
|
babelParserDefaultPlugins,
|
||||||
|
camelize,
|
||||||
|
capitalize,
|
||||||
generateCodeFrame,
|
generateCodeFrame,
|
||||||
hyphenate
|
makeMap
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
Node,
|
Node,
|
||||||
@ -49,6 +56,7 @@ import {
|
|||||||
import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
|
import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate'
|
||||||
import { warnExperimental, warnOnce } from './warn'
|
import { warnExperimental, warnOnce } from './warn'
|
||||||
import { rewriteDefault } from './rewriteDefault'
|
import { rewriteDefault } from './rewriteDefault'
|
||||||
|
import { createCache } from './cache'
|
||||||
|
|
||||||
// Special compiler macros
|
// Special compiler macros
|
||||||
const DEFINE_PROPS = 'defineProps'
|
const DEFINE_PROPS = 'defineProps'
|
||||||
@ -61,6 +69,10 @@ const $COMPUTED = `$computed`
|
|||||||
const $FROM_REFS = `$fromRefs`
|
const $FROM_REFS = `$fromRefs`
|
||||||
const $RAW = `$raw`
|
const $RAW = `$raw`
|
||||||
|
|
||||||
|
const isBuiltInDir = makeMap(
|
||||||
|
`once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
|
||||||
|
)
|
||||||
|
|
||||||
export interface SFCScriptCompileOptions {
|
export interface SFCScriptCompileOptions {
|
||||||
/**
|
/**
|
||||||
* Scope ID for prefixing injected CSS varialbes.
|
* Scope ID for prefixing injected CSS varialbes.
|
||||||
@ -319,10 +331,10 @@ export function compileScript(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let isUsedInTemplate = true
|
let isUsedInTemplate = true
|
||||||
if (sfc.template && !sfc.template.src) {
|
if (isTS && sfc.template && !sfc.template.src) {
|
||||||
isUsedInTemplate = new RegExp(
|
isUsedInTemplate = new RegExp(`\\b${local}\\b`).test(
|
||||||
`\\b(?:${local}|${hyphenate(local)})\\b`
|
resolveTemplateUsageCheckString(sfc)
|
||||||
).test(sfc.template.content)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
userImports[local] = {
|
userImports[local] = {
|
||||||
@ -2154,3 +2166,53 @@ function toTextRange(node: Node): TextRange {
|
|||||||
end: node.end!
|
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 { Statement } from '@babel/types'
|
||||||
import { parseCssVars } from './cssVars'
|
import { parseCssVars } from './cssVars'
|
||||||
import { warnExperimental } from './warn'
|
import { warnExperimental } from './warn'
|
||||||
|
import { createCache } from './cache'
|
||||||
|
|
||||||
export interface SFCParseOptions {
|
export interface SFCParseOptions {
|
||||||
filename?: string
|
filename?: string
|
||||||
@ -89,14 +90,7 @@ export interface SFCParseResult {
|
|||||||
errors: (CompilerError | SyntaxError)[]
|
errors: (CompilerError | SyntaxError)[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const SFC_CACHE_MAX_SIZE = 500
|
const sourceToSFC = createCache<SFCParseResult>()
|
||||||
const sourceToSFC =
|
|
||||||
__GLOBAL__ || __ESM_BROWSER__
|
|
||||||
? new Map<string, SFCParseResult>()
|
|
||||||
: (new (require('lru-cache'))(SFC_CACHE_MAX_SIZE) as Map<
|
|
||||||
string,
|
|
||||||
SFCParseResult
|
|
||||||
>)
|
|
||||||
|
|
||||||
export function parse(
|
export function parse(
|
||||||
source: string,
|
source: string,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user