fix(compiler-sfc): fix script setup ref assignment codegen edge case (#4520)

fix #4514
This commit is contained in:
edison 2021-09-06 06:02:50 +08:00 committed by GitHub
parent e6fe751b20
commit 5594643d7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 59 additions and 14 deletions

View File

@ -99,7 +99,8 @@ export function processExpression(
// function params // function params
asParams = false, asParams = false,
// v-on handler values may contain multiple statements // v-on handler values may contain multiple statements
asRawStatements = false asRawStatements = false,
localVars: Record<string, number> = Object.create(context.identifiers)
): ExpressionNode { ): ExpressionNode {
if (__BROWSER__) { if (__BROWSER__) {
if (__DEV__) { if (__DEV__) {
@ -127,7 +128,7 @@ export function processExpression(
const isDestructureAssignment = const isDestructureAssignment =
parent && isInDestructureAssignment(parent, parentStack) parent && isInDestructureAssignment(parent, parentStack)
if (type === BindingTypes.SETUP_CONST) { if (type === BindingTypes.SETUP_CONST || localVars[raw]) {
return raw return raw
} else if (type === BindingTypes.SETUP_REF) { } else if (type === BindingTypes.SETUP_REF) {
return `${raw}.value` return `${raw}.value`
@ -149,7 +150,13 @@ export function processExpression(
const { right: rVal, operator } = parent as AssignmentExpression const { right: rVal, operator } = parent as AssignmentExpression
const rExp = rawExp.slice(rVal.start! - 1, rVal.end! - 1) const rExp = rawExp.slice(rVal.start! - 1, rVal.end! - 1)
const rExpString = stringifyExpression( const rExpString = stringifyExpression(
processExpression(createSimpleExpression(rExp, false), context) processExpression(
createSimpleExpression(rExp, false),
context,
false,
false,
knownIds
)
) )
return `${context.helperString(IS_REF)}(${raw})${ return `${context.helperString(IS_REF)}(${raw})${
context.isTS ? ` //@ts-ignore\n` : `` context.isTS ? ` //@ts-ignore\n` : ``
@ -190,6 +197,7 @@ export function processExpression(
return `$${type}.${raw}` return `$${type}.${raw}`
} }
} }
// fallback to ctx // fallback to ctx
return `_ctx.${raw}` return `_ctx.${raw}`
} }
@ -246,7 +254,6 @@ export function processExpression(
} }
type QualifiedId = Identifier & PrefixMeta type QualifiedId = Identifier & PrefixMeta
const ids: QualifiedId[] = [] const ids: QualifiedId[] = []
const parentStack: Node[] = [] const parentStack: Node[] = []
const knownIds: Record<string, number> = Object.create(context.identifiers) const knownIds: Record<string, number> = Object.create(context.identifiers)

View File

@ -157,7 +157,7 @@ return { props, bar }
}" }"
`; `;
exports[`SFC compile <script setup> defineProps/defineEmits in multi-variable decalration (full removal) 1`] = ` exports[`SFC compile <script setup> defineProps/defineEmits in multi-variable declaration (full removal) 1`] = `
"export default { "export default {
props: ['item'], props: ['item'],
emits: ['a'], emits: ['a'],
@ -173,7 +173,7 @@ return { props, emit }
}" }"
`; `;
exports[`SFC compile <script setup> defineProps/defineEmits in multi-variable decalration 1`] = ` exports[`SFC compile <script setup> defineProps/defineEmits in multi-variable declaration 1`] = `
"export default { "export default {
props: ['item'], props: ['item'],
emits: ['a'], emits: ['a'],
@ -506,7 +506,7 @@ return (_ctx, _push, _parent, _attrs) => {
`; `;
exports[`SFC compile <script setup> inlineTemplate mode template assignment expression codegen 1`] = ` exports[`SFC compile <script setup> inlineTemplate mode template assignment expression codegen 1`] = `
"import { createElementVNode as _createElementVNode, isRef as _isRef, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\" "import { createElementVNode as _createElementVNode, isRef as _isRef, unref as _unref, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
import { ref } from 'vue' import { ref } from 'vue'
@ -534,6 +534,26 @@ return (_ctx, _cache) => {
}), }),
_createElementVNode(\\"div\\", { _createElementVNode(\\"div\\", {
onClick: _cache[4] || (_cache[4] = $event => (_isRef(v) ? v.value -= 1 : v -= 1)) onClick: _cache[4] || (_cache[4] = $event => (_isRef(v) ? v.value -= 1 : v -= 1))
}),
_createElementVNode(\\"div\\", {
onClick: _cache[5] || (_cache[5] = () => {
let a = '' + _unref(lett)
_isRef(v) ? v.value = a : v = a
})
}),
_createElementVNode(\\"div\\", {
onClick: _cache[6] || (_cache[6] = () => {
// nested scopes
(()=>{
let x = _ctx.a
(()=>{
let z = x
let z2 = z
})
let lz = _ctx.z
})
_isRef(v) ? v.value = _ctx.a : v = _ctx.a
})
}) })
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
} }

View File

@ -98,7 +98,7 @@ const myEmit = defineEmits(['foo', 'bar'])
emits: ['foo', 'bar'],`) emits: ['foo', 'bar'],`)
}) })
test('defineProps/defineEmits in multi-variable decalration', () => { test('defineProps/defineEmits in multi-variable declaration', () => {
const { content } = compile(` const { content } = compile(`
<script setup> <script setup>
const props = defineProps(['item']), const props = defineProps(['item']),
@ -112,7 +112,7 @@ const myEmit = defineEmits(['foo', 'bar'])
expect(content).toMatch(`emits: ['a'],`) expect(content).toMatch(`emits: ['a'],`)
}) })
test('defineProps/defineEmits in multi-variable decalration (full removal)', () => { test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
const { content } = compile(` const { content } = compile(`
<script setup> <script setup>
const props = defineProps(['item']), const props = defineProps(['item']),
@ -517,6 +517,22 @@ defineExpose({ foo: 123 })
<div @click="lett = count"/> <div @click="lett = count"/>
<div @click="v += 1"/> <div @click="v += 1"/>
<div @click="v -= 1"/> <div @click="v -= 1"/>
<div @click="() => {
let a = '' + lett
v = a
}"/>
<div @click="() => {
// nested scopes
(()=>{
let x = a
(()=>{
let z = x
let z2 = z
})
let lz = z
})
v = a
}"/>
</template> </template>
`, `,
{ inlineTemplate: true } { inlineTemplate: true }
@ -531,6 +547,8 @@ defineExpose({ foo: 123 })
) )
expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`) expect(content).toMatch(`_isRef(v) ? v.value += 1 : v += 1`)
expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`) expect(content).toMatch(`_isRef(v) ? v.value -= 1 : v -= 1`)
expect(content).toMatch(`_isRef(v) ? v.value = a : v = a`)
expect(content).toMatch(`_isRef(v) ? v.value = _ctx.a : v = _ctx.a`)
assertCode(content) assertCode(content)
}) })

View File

@ -526,7 +526,7 @@ export function compileScript(
/** /**
* check defaults. If the default object is an object literal with only * check defaults. If the default object is an object literal with only
* static properties, we can directly generate more optimzied default * static properties, we can directly generate more optimzied default
* decalrations. Otherwise we will have to fallback to runtime merging. * declarations. Otherwise we will have to fallback to runtime merging.
*/ */
function checkStaticDefaults() { function checkStaticDefaults() {
return ( return (
@ -895,7 +895,7 @@ export function compileScript(
} }
} }
// walk decalrations to record declared bindings // walk declarations to record declared bindings
if ( if (
(node.type === 'VariableDeclaration' || (node.type === 'VariableDeclaration' ||
node.type === 'FunctionDeclaration' || node.type === 'FunctionDeclaration' ||

View File

@ -40,7 +40,7 @@ const warnRuntimeUsage = (method: string) =>
* }) * })
* ``` * ```
* *
* Equivalent type-based decalration: * Equivalent type-based declaration:
* ```ts * ```ts
* // will be compiled into equivalent runtime declarations * // will be compiled into equivalent runtime declarations
* const props = defineProps<{ * const props = defineProps<{
@ -79,7 +79,7 @@ export function defineProps() {
* const emit = defineEmits(['change', 'update']) * const emit = defineEmits(['change', 'update'])
* ``` * ```
* *
* Example type-based decalration: * Example type-based declaration:
* ```ts * ```ts
* const emit = defineEmits<{ * const emit = defineEmits<{
* (event: 'change'): void * (event: 'change'): void
@ -147,7 +147,7 @@ type PropsWithDefaults<Base, Defaults> = Base &
/** /**
* Vue `<script setup>` compiler macro for providing props default values when * Vue `<script setup>` compiler macro for providing props default values when
* using type-based `defineProps` decalration. * using type-based `defineProps` declaration.
* *
* Example usage: * Example usage:
* ```ts * ```ts