refactor: finer grained binding types for setup

This commit is contained in:
Evan You 2020-11-18 15:17:50 -05:00
parent 760443dca6
commit 4449fc3b9e
5 changed files with 161 additions and 102 deletions

View File

@ -62,10 +62,31 @@ export type HoistTransform = (
) => void ) => void
export const enum BindingTypes { export const enum BindingTypes {
/**
* returned from data()
*/
DATA = 'data', DATA = 'data',
/**
* decalred as a prop
*/
PROPS = 'props', PROPS = 'props',
SETUP = 'setup', /**
CONST = 'const', * a let binding (may or may not be a ref)
*/
SETUP_LET = 'setup-let',
/**
* a const binding that can never be a ref.
* these bindings don't need `unref()` calls when processed in inlined
* template expressions.
*/
SETUP_CONST = 'setup-const',
/**
* a const binding that may be a ref.
*/
SETUP_CONST_REF = 'setup-const-ref',
/**
* declared by other options, e.g. computed, inject
*/
OPTIONS = 'options' OPTIONS = 'options'
} }

View File

@ -263,20 +263,22 @@ export function resolveComponentType(
return resolvedTag return resolvedTag
} }
} }
const tagFromSetup = checkType(BindingTypes.SETUP) const tagFromConst = checkType(BindingTypes.SETUP_CONST)
if (tagFromSetup) {
return context.inline
? // setup scope bindings may be refs so they need to be unrefed
`${context.helperString(UNREF)}(${tagFromSetup})`
: `$setup[${JSON.stringify(tagFromSetup)}]`
}
const tagFromConst = checkType(BindingTypes.CONST)
if (tagFromConst) { if (tagFromConst) {
return context.inline return context.inline
? // in inline mode, const setup bindings (e.g. imports) can be used as-is ? // in inline mode, const setup bindings (e.g. imports) can be used as-is
tagFromConst tagFromConst
: `$setup[${JSON.stringify(tagFromConst)}]` : `$setup[${JSON.stringify(tagFromConst)}]`
} }
const tagFromSetup =
checkType(BindingTypes.SETUP_LET) ||
checkType(BindingTypes.SETUP_CONST_REF)
if (tagFromSetup) {
return context.inline
? // setup scope bindings that may be refs need to be unrefed
`${context.helperString(UNREF)}(${tagFromSetup})`
: `$setup[${JSON.stringify(tagFromSetup)}]`
}
} }
// 4. user component (resolve) // 4. user component (resolve)

View File

@ -104,9 +104,12 @@ export function processExpression(
const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw] const type = hasOwn(bindingMetadata, raw) && bindingMetadata[raw]
if (inline) { if (inline) {
// setup inline mode // setup inline mode
if (type === BindingTypes.CONST) { if (type === BindingTypes.SETUP_CONST) {
return raw return raw
} else if (type === BindingTypes.SETUP) { } else if (
type === BindingTypes.SETUP_CONST_REF ||
type === BindingTypes.SETUP_LET
) {
return `${context.helperString(UNREF)}(${raw})` return `${context.helperString(UNREF)}(${raw})`
} else if (type === BindingTypes.PROPS) { } else if (type === BindingTypes.PROPS) {
// use __props which is generated by compileScript so in ts mode // use __props which is generated by compileScript so in ts mode
@ -114,8 +117,12 @@ export function processExpression(
return `__props.${raw}` return `__props.${raw}`
} }
} else { } else {
if (type === BindingTypes.CONST) { if (
// setup const binding in non-inline mode type === BindingTypes.SETUP_LET ||
type === BindingTypes.SETUP_CONST ||
type === BindingTypes.SETUP_CONST_REF
) {
// setup bindings in non-inline mode
return `$setup.${raw}` return `$setup.${raw}`
} else if (type) { } else if (type) {
return `$${type}.${raw}` return `$${type}.${raw}`
@ -131,7 +138,9 @@ export function processExpression(
const bailConstant = rawExp.indexOf(`(`) > -1 const bailConstant = rawExp.indexOf(`(`) > -1
if (isSimpleIdentifier(rawExp)) { if (isSimpleIdentifier(rawExp)) {
// const bindings exposed from setup - we know they never change // const bindings exposed from setup - we know they never change
if (bindingMetadata[node.content] === BindingTypes.CONST) { // marking it as runtime constant will prevent it from being listed as
// a dynamic prop.
if (bindingMetadata[node.content] === BindingTypes.SETUP_CONST) {
node.isRuntimeConstant = true node.isRuntimeConstant = true
} }
if ( if (

View File

@ -1,3 +1,4 @@
import { BindingTypes } from '@vue/compiler-dom/src'
import { compileSFCScript as compile, assertCode } from './utils' import { compileSFCScript as compile, assertCode } from './utils'
describe('SFC compile <script setup>', () => { describe('SFC compile <script setup>', () => {
@ -33,10 +34,10 @@ const bar = 1
assertCode(content) assertCode(content)
// should anayze bindings // should anayze bindings
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: 'props', foo: BindingTypes.PROPS,
bar: 'const', bar: BindingTypes.SETUP_CONST,
props: 'const', props: BindingTypes.SETUP_CONST,
emit: 'const' emit: BindingTypes.SETUP_CONST
}) })
// should remove defineOptions import and call // should remove defineOptions import and call
@ -259,27 +260,27 @@ const { props, emit } = defineOptions({
) )
expect(content).toMatch(`intersection: { type: Object, required: true }`) expect(content).toMatch(`intersection: { type: Object, required: true }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
string: 'props', string: BindingTypes.PROPS,
number: 'props', number: BindingTypes.PROPS,
boolean: 'props', boolean: BindingTypes.PROPS,
object: 'props', object: BindingTypes.PROPS,
objectLiteral: 'props', objectLiteral: BindingTypes.PROPS,
fn: 'props', fn: BindingTypes.PROPS,
functionRef: 'props', functionRef: BindingTypes.PROPS,
objectRef: 'props', objectRef: BindingTypes.PROPS,
array: 'props', array: BindingTypes.PROPS,
arrayRef: 'props', arrayRef: BindingTypes.PROPS,
tuple: 'props', tuple: BindingTypes.PROPS,
set: 'props', set: BindingTypes.PROPS,
literal: 'props', literal: BindingTypes.PROPS,
optional: 'props', optional: BindingTypes.PROPS,
recordRef: 'props', recordRef: BindingTypes.PROPS,
interface: 'props', interface: BindingTypes.PROPS,
alias: 'props', alias: BindingTypes.PROPS,
union: 'props', union: BindingTypes.PROPS,
literalUnion: 'props', literalUnion: BindingTypes.PROPS,
literalUnionMixed: 'props', literalUnionMixed: BindingTypes.PROPS,
intersection: 'props' intersection: BindingTypes.PROPS
}) })
}) })
@ -380,11 +381,11 @@ const { props, emit } = defineOptions({
expect(content).toMatch(`let d`) expect(content).toMatch(`let d`)
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: 'setup', foo: BindingTypes.SETUP_CONST_REF,
a: 'setup', a: BindingTypes.SETUP_CONST_REF,
b: 'setup', b: BindingTypes.SETUP_CONST_REF,
c: 'setup', c: BindingTypes.SETUP_LET,
d: 'setup' d: BindingTypes.SETUP_LET
}) })
}) })
@ -402,9 +403,9 @@ const { props, emit } = defineOptions({
expect(content).toMatch(`return { a, b, c }`) expect(content).toMatch(`return { a, b, c }`)
assertCode(content) assertCode(content)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
a: 'setup', a: BindingTypes.SETUP_CONST_REF,
b: 'setup', b: BindingTypes.SETUP_CONST_REF,
c: 'setup' c: BindingTypes.SETUP_CONST_REF
}) })
}) })
@ -494,12 +495,12 @@ const { props, emit } = defineOptions({
) )
expect(content).toMatch(`return { n, a, c, d, f, g }`) expect(content).toMatch(`return { n, a, c, d, f, g }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
n: 'setup', n: BindingTypes.SETUP_CONST_REF,
a: 'setup', a: BindingTypes.SETUP_CONST_REF,
c: 'setup', c: BindingTypes.SETUP_CONST_REF,
d: 'setup', d: BindingTypes.SETUP_CONST_REF,
f: 'setup', f: BindingTypes.SETUP_CONST_REF,
g: 'setup' g: BindingTypes.SETUP_CONST_REF
}) })
assertCode(content) assertCode(content)
}) })
@ -518,10 +519,10 @@ const { props, emit } = defineOptions({
expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`) expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
expect(content).toMatch(`return { n, a, b, c }`) expect(content).toMatch(`return { n, a, b, c }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
n: 'setup', n: BindingTypes.SETUP_CONST_REF,
a: 'setup', a: BindingTypes.SETUP_CONST_REF,
b: 'setup', b: BindingTypes.SETUP_CONST_REF,
c: 'setup' c: BindingTypes.SETUP_CONST_REF
}) })
assertCode(content) assertCode(content)
}) })
@ -541,9 +542,9 @@ const { props, emit } = defineOptions({
expect(content).toMatch(`\nconst e = _ref(__e);`) expect(content).toMatch(`\nconst e = _ref(__e);`)
expect(content).toMatch(`return { b, d, e }`) expect(content).toMatch(`return { b, d, e }`)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
b: 'setup', b: BindingTypes.SETUP_CONST_REF,
d: 'setup', d: BindingTypes.SETUP_CONST_REF,
e: 'setup' e: BindingTypes.SETUP_CONST_REF
}) })
assertCode(content) assertCode(content)
}) })
@ -683,7 +684,10 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'props', bar: 'props' }) expect(bindings).toStrictEqual({
foo: BindingTypes.PROPS,
bar: BindingTypes.PROPS
})
}) })
it('recognizes props object declaration', () => { it('recognizes props object declaration', () => {
@ -702,10 +706,10 @@ describe('SFC analyze <script> bindings', () => {
</script> </script>
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: 'props', foo: BindingTypes.PROPS,
bar: 'props', bar: BindingTypes.PROPS,
baz: 'props', baz: BindingTypes.PROPS,
qux: 'props' qux: BindingTypes.PROPS
}) })
}) })
@ -723,7 +727,10 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' }) expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_CONST_REF,
bar: BindingTypes.SETUP_CONST_REF
})
}) })
it('recognizes async setup return', () => { it('recognizes async setup return', () => {
@ -740,7 +747,10 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'setup', bar: 'setup' }) expect(bindings).toStrictEqual({
foo: BindingTypes.SETUP_CONST_REF,
bar: BindingTypes.SETUP_CONST_REF
})
}) })
it('recognizes data return', () => { it('recognizes data return', () => {
@ -757,7 +767,10 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'data', bar: 'data' }) expect(bindings).toStrictEqual({
foo: BindingTypes.DATA,
bar: BindingTypes.DATA
})
}) })
it('recognizes methods', () => { it('recognizes methods', () => {
@ -770,7 +783,7 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'options' }) expect(bindings).toStrictEqual({ foo: BindingTypes.OPTIONS })
}) })
it('recognizes computeds', () => { it('recognizes computeds', () => {
@ -787,7 +800,10 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS,
bar: BindingTypes.OPTIONS
})
}) })
it('recognizes injections array declaration', () => { it('recognizes injections array declaration', () => {
@ -798,7 +814,10 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS,
bar: BindingTypes.OPTIONS
})
}) })
it('recognizes injections object declaration', () => { it('recognizes injections object declaration', () => {
@ -812,7 +831,10 @@ describe('SFC analyze <script> bindings', () => {
} }
</script> </script>
`) `)
expect(bindings).toStrictEqual({ foo: 'options', bar: 'options' }) expect(bindings).toStrictEqual({
foo: BindingTypes.OPTIONS,
bar: BindingTypes.OPTIONS
})
}) })
it('works for mixed bindings', () => { it('works for mixed bindings', () => {
@ -843,12 +865,12 @@ describe('SFC analyze <script> bindings', () => {
</script> </script>
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: 'options', foo: BindingTypes.OPTIONS,
bar: 'props', bar: BindingTypes.PROPS,
baz: 'setup', baz: BindingTypes.SETUP_CONST_REF,
qux: 'data', qux: BindingTypes.DATA,
quux: 'options', quux: BindingTypes.OPTIONS,
quuz: 'options' quuz: BindingTypes.OPTIONS
}) })
}) })
@ -864,7 +886,7 @@ describe('SFC analyze <script> bindings', () => {
</script> </script>
`) `)
expect(bindings).toStrictEqual({ expect(bindings).toStrictEqual({
foo: 'props' foo: BindingTypes.PROPS
}) })
}) })
}) })

View File

@ -159,11 +159,8 @@ export function compileScript(
source: string source: string
} }
> = Object.create(null) > = Object.create(null)
const setupBindings: Record< const setupBindings: Record<string, BindingTypes> = Object.create(null)
string, const refBindings: Record<string, BindingTypes> = Object.create(null)
BindingTypes.SETUP | BindingTypes.CONST
> = Object.create(null)
const refBindings: Record<string, BindingTypes.SETUP> = Object.create(null)
const refIdentifiers: Set<Identifier> = new Set() const refIdentifiers: Set<Identifier> = new Set()
const enableRefSugar = options.refSugar !== false const enableRefSugar = options.refSugar !== false
let defaultExport: Node | undefined let defaultExport: Node | undefined
@ -311,7 +308,7 @@ export function compileScript(
if (id.name[0] === '$') { if (id.name[0] === '$') {
error(`ref variable identifiers cannot start with $.`, id) error(`ref variable identifiers cannot start with $.`, id)
} }
refBindings[id.name] = setupBindings[id.name] = BindingTypes.SETUP refBindings[id.name] = setupBindings[id.name] = BindingTypes.SETUP_CONST_REF
refIdentifiers.add(id) refIdentifiers.add(id)
} }
@ -787,8 +784,8 @@ export function compileScript(
} }
for (const [key, { source }] of Object.entries(userImports)) { for (const [key, { source }] of Object.entries(userImports)) {
bindingMetadata[key] = source.endsWith('.vue') bindingMetadata[key] = source.endsWith('.vue')
? BindingTypes.CONST ? BindingTypes.SETUP_CONST
: BindingTypes.SETUP : BindingTypes.SETUP_CONST_REF
} }
for (const key in setupBindings) { for (const key in setupBindings) {
bindingMetadata[key] = setupBindings[key] bindingMetadata[key] = setupBindings[key]
@ -966,8 +963,10 @@ function walkDeclaration(
init!.type !== 'Identifier' && // const a = b init!.type !== 'Identifier' && // const a = b
init!.type !== 'CallExpression' && // const a = ref() init!.type !== 'CallExpression' && // const a = ref()
init!.type !== 'MemberExpression') // const a = b.c init!.type !== 'MemberExpression') // const a = b.c
? BindingTypes.CONST ? BindingTypes.SETUP_CONST
: BindingTypes.SETUP : isConst
? BindingTypes.SETUP_CONST_REF
: BindingTypes.SETUP_LET
} else if (id.type === 'ObjectPattern') { } else if (id.type === 'ObjectPattern') {
walkObjectPattern(id, bindings, isConst, isUseOptionsCall) walkObjectPattern(id, bindings, isConst, isUseOptionsCall)
} else if (id.type === 'ArrayPattern') { } else if (id.type === 'ArrayPattern') {
@ -980,7 +979,7 @@ function walkDeclaration(
) { ) {
// export function foo() {} / export class Foo {} // export function foo() {} / export class Foo {}
// export declarations must be named. // export declarations must be named.
bindings[node.id!.name] = BindingTypes.CONST bindings[node.id!.name] = BindingTypes.SETUP_CONST
} }
} }
@ -997,8 +996,10 @@ function walkObjectPattern(
if (p.key === p.value) { if (p.key === p.value) {
// const { x } = ... // const { x } = ...
bindings[p.key.name] = isUseOptionsCall bindings[p.key.name] = isUseOptionsCall
? BindingTypes.CONST ? BindingTypes.SETUP_CONST
: BindingTypes.SETUP : isConst
? BindingTypes.SETUP_CONST_REF
: BindingTypes.SETUP_LET
} else { } else {
walkPattern(p.value, bindings, isConst, isUseOptionsCall) walkPattern(p.value, bindings, isConst, isUseOptionsCall)
} }
@ -1007,8 +1008,8 @@ function walkObjectPattern(
// ...rest // ...rest
// argument can only be identifer when destructuring // argument can only be identifer when destructuring
bindings[(p.argument as Identifier).name] = isConst bindings[(p.argument as Identifier).name] = isConst
? BindingTypes.CONST ? BindingTypes.SETUP_CONST
: BindingTypes.SETUP : BindingTypes.SETUP_LET
} }
} }
} }
@ -1032,13 +1033,15 @@ function walkPattern(
) { ) {
if (node.type === 'Identifier') { if (node.type === 'Identifier') {
bindings[node.name] = isUseOptionsCall bindings[node.name] = isUseOptionsCall
? BindingTypes.CONST ? BindingTypes.SETUP_CONST
: BindingTypes.SETUP : isConst
? BindingTypes.SETUP_CONST_REF
: BindingTypes.SETUP_LET
} else if (node.type === 'RestElement') { } else if (node.type === 'RestElement') {
// argument can only be identifer when destructuring // argument can only be identifer when destructuring
bindings[(node.argument as Identifier).name] = isConst bindings[(node.argument as Identifier).name] = isConst
? BindingTypes.CONST ? BindingTypes.SETUP_CONST
: BindingTypes.SETUP : BindingTypes.SETUP_LET
} else if (node.type === 'ObjectPattern') { } else if (node.type === 'ObjectPattern') {
walkObjectPattern(node, bindings, isConst) walkObjectPattern(node, bindings, isConst)
} else if (node.type === 'ArrayPattern') { } else if (node.type === 'ArrayPattern') {
@ -1046,8 +1049,10 @@ function walkPattern(
} else if (node.type === 'AssignmentPattern') { } else if (node.type === 'AssignmentPattern') {
if (node.left.type === 'Identifier') { if (node.left.type === 'Identifier') {
bindings[node.left.name] = isUseOptionsCall bindings[node.left.name] = isUseOptionsCall
? BindingTypes.CONST ? BindingTypes.SETUP_CONST
: BindingTypes.SETUP : isConst
? BindingTypes.SETUP_CONST_REF
: BindingTypes.SETUP_LET
} else { } else {
walkPattern(node.left, bindings, isConst) walkPattern(node.left, bindings, isConst)
} }
@ -1490,7 +1495,7 @@ function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {
for (const key of getObjectExpressionKeys(bodyItem.argument)) { for (const key of getObjectExpressionKeys(bodyItem.argument)) {
bindings[key] = bindings[key] =
property.key.name === 'setup' property.key.name === 'setup'
? BindingTypes.SETUP ? BindingTypes.SETUP_CONST_REF
: BindingTypes.DATA : BindingTypes.DATA
} }
} }