Skip to content

Commit cd9cf84

Browse files
committed
feat(compiler-sfc): support reactive let bindings in <script setup>
1 parent 4421c00 commit cd9cf84

File tree

4 files changed

+434
-86
lines changed

4 files changed

+434
-86
lines changed

packages/compiler-core/src/transforms/transformExpression.ts

Lines changed: 58 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@ export function processExpression(
161161
if (!isDuplicate(node)) {
162162
const needPrefix = shouldPrefix(node, parent)
163163
if (!knownIds[node.name] && needPrefix) {
164-
if (isPropertyShorthand(node, parent)) {
165-
// property shorthand like { foo }, we need to add the key since we
166-
// rewrite the value
164+
if (isStaticProperty(parent) && parent.shorthand) {
165+
// property shorthand like { foo }, we need to add the key since
166+
// we rewrite the value
167167
node.prefix = `${node.name}: `
168168
}
169169
node.name = prefix(node.name)
@@ -278,46 +278,65 @@ const isStaticProperty = (node: Node): node is ObjectProperty =>
278278
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
279279
!node.computed
280280

281-
const isPropertyShorthand = (node: Node, parent: Node) => {
282-
return (
283-
isStaticProperty(parent) &&
284-
parent.value === node &&
285-
parent.key.type === 'Identifier' &&
286-
parent.key.name === (node as Identifier).name &&
287-
parent.key.start === node.start
288-
)
289-
}
290-
291281
const isStaticPropertyKey = (node: Node, parent: Node) =>
292282
isStaticProperty(parent) && parent.key === node
293283

294-
function shouldPrefix(identifier: Identifier, parent: Node) {
284+
function shouldPrefix(id: Identifier, parent: Node) {
285+
// declaration id
295286
if (
296-
!(
297-
isFunction(parent) &&
298-
// not id of a FunctionDeclaration
299-
((parent as any).id === identifier ||
300-
// not a params of a function
301-
parent.params.includes(identifier))
302-
) &&
303-
// not a key of Property
304-
!isStaticPropertyKey(identifier, parent) &&
305-
// not a property of a MemberExpression
306-
!(
307-
(parent.type === 'MemberExpression' ||
308-
parent.type === 'OptionalMemberExpression') &&
309-
parent.property === identifier &&
310-
!parent.computed
311-
) &&
312-
// not in an Array destructure pattern
313-
!(parent.type === 'ArrayPattern') &&
314-
// skip whitelisted globals
315-
!isGloballyWhitelisted(identifier.name) &&
316-
// special case for webpack compilation
317-
identifier.name !== `require` &&
318-
// is a special keyword but parsed as identifier
319-
identifier.name !== `arguments`
287+
(parent.type === 'VariableDeclarator' ||
288+
parent.type === 'ClassDeclaration') &&
289+
parent.id === id
320290
) {
321-
return true
291+
return false
292+
}
293+
294+
if (isFunction(parent)) {
295+
// function decalration/expression id
296+
if ((parent as any).id === id) {
297+
return false
298+
}
299+
// params list
300+
if (parent.params.includes(id)) {
301+
return false
302+
}
322303
}
304+
305+
// property key
306+
// this also covers object destructure pattern
307+
if (isStaticPropertyKey(id, parent)) {
308+
return false
309+
}
310+
311+
// array destructure pattern
312+
if (parent.type === 'ArrayPattern') {
313+
return false
314+
}
315+
316+
// member expression property
317+
if (
318+
(parent.type === 'MemberExpression' ||
319+
parent.type === 'OptionalMemberExpression') &&
320+
parent.property === id &&
321+
!parent.computed
322+
) {
323+
return false
324+
}
325+
326+
// is a special keyword but parsed as identifier
327+
if (id.name === 'arguments') {
328+
return false
329+
}
330+
331+
// skip whitelisted globals
332+
if (isGloballyWhitelisted(id.name)) {
333+
return false
334+
}
335+
336+
// special case for webpack compilation
337+
if (id.name === 'require') {
338+
return false
339+
}
340+
341+
return true
323342
}

packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

33
exports[`SFC compile <script setup> <script setup lang="ts"> extract emits 1`] = `
4-
"import { defineComponent as __define__ } from 'vue'
5-
import { Slots as __Slots__ } from 'vue'
4+
"import { Slots as __Slots__, defineComponent as __defineComponent__ } from 'vue'
65
declare function __emit__(e: 'foo' | 'bar'): void
76
declare function __emit__(e: 'baz', id: number): void
87
@@ -16,15 +15,14 @@ export function setup(_: {}, { emit: myEmit }: {
1615
return { }
1716
}
1817
19-
export default __define__({
18+
export default __defineComponent__({
2019
emits: [\\"foo\\", \\"bar\\", \\"baz\\"] as unknown as undefined,
2120
setup
2221
})"
2322
`;
2423
2524
exports[`SFC compile <script setup> <script setup lang="ts"> extract props 1`] = `
26-
"import { defineComponent as __define__ } from 'vue'
27-
import { Slots as __Slots__ } from 'vue'
25+
"import { Slots as __Slots__, defineComponent as __defineComponent__ } from 'vue'
2826
interface Test {}
2927
3028
type Alias = number[]
@@ -59,7 +57,7 @@ export function setup(myProps: {
5957
return { }
6058
}
6159
62-
export default __define__({
60+
export default __defineComponent__({
6361
props: {
6462
string: { type: String, required: true },
6563
number: { type: Number, required: true },
@@ -88,8 +86,7 @@ export default __define__({
8886
`;
8987
9088
exports[`SFC compile <script setup> <script setup lang="ts"> hoist type declarations 1`] = `
91-
"import { defineComponent as __define__ } from 'vue'
92-
import { Slots as __Slots__ } from 'vue'
89+
"import { Slots as __Slots__, defineComponent as __defineComponent__ } from 'vue'
9390
export interface Foo {}
9491
type Bar = {}
9592
@@ -100,7 +97,7 @@ export function setup() {
10097
return { a }
10198
}
10299
103-
export default __define__({
100+
export default __defineComponent__({
104101
setup
105102
})"
106103
`;
@@ -379,6 +376,93 @@ return { }
379376
export default { setup }"
380377
`;
381378
379+
exports[`SFC compile <script setup> reactive let bindings accessing let binding 1`] = `
380+
"import { ref as __ref__ } from 'vue'
381+
382+
export function setup() {
383+
384+
let a = __ref__(1)
385+
console.log(a.value)
386+
function get() {
387+
return a.value + 1
388+
}
389+
390+
return { }
391+
}
392+
393+
export default { setup }"
394+
`;
395+
396+
exports[`SFC compile <script setup> reactive let bindings convert let values 1`] = `
397+
"import { ref as __ref__ } from 'vue'
398+
399+
export function setup() {
400+
401+
let a = __ref__(1)
402+
let b = __ref__({
403+
count: 0
404+
})
405+
let c = __ref__(() => {})
406+
let d = __ref__()
407+
408+
return { a }
409+
}
410+
411+
export default { setup }"
412+
`;
413+
414+
exports[`SFC compile <script setup> reactive let bindings multi let declarations 1`] = `
415+
"import { ref as __ref__ } from 'vue'
416+
417+
export function setup() {
418+
419+
let a = __ref__(1), b = __ref__(2), c = __ref__({
420+
count: 0
421+
})
422+
423+
return { a, b, c }
424+
}
425+
426+
export default { setup }"
427+
`;
428+
429+
exports[`SFC compile <script setup> reactive let bindings mutating let binding 1`] = `
430+
"import { ref as __ref__ } from 'vue'
431+
432+
export function setup() {
433+
434+
let a = __ref__(1)
435+
let b = __ref__({ count: 0 })
436+
function inc() {
437+
a.value++
438+
a.value = a.value + 1
439+
b.value.count++
440+
b.value.count = b.value.count + 1
441+
}
442+
443+
return { }
444+
}
445+
446+
export default { setup }"
447+
`;
448+
449+
exports[`SFC compile <script setup> reactive let bindings using let binding in property shorthand 1`] = `
450+
"import { ref as __ref__ } from 'vue'
451+
452+
export function setup() {
453+
454+
let a = __ref__(1)
455+
const b = { a: a.value }
456+
function test() {
457+
const { a } = b
458+
}
459+
460+
return { }
461+
}
462+
463+
export default { setup }"
464+
`;
465+
382466
exports[`SFC compile <script setup> should hoist imports 1`] = `
383467
"import { ref } from 'vue'
384468
export function setup() {

packages/compiler-sfc/__tests__/compileScript.spec.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,109 @@ describe('SFC compile <script setup>', () => {
370370
})
371371
})
372372

373+
describe('reactive let bindings', () => {
374+
test('convert let values', () => {
375+
const { content } = compile(`<script setup>
376+
export let a = 1
377+
let b = {
378+
count: 0
379+
}
380+
let c = () => {}
381+
let d
382+
</script>`)
383+
expect(content).toMatch(`import { ref as __ref__ } from 'vue'`)
384+
expect(content).not.toMatch(`export let a`)
385+
expect(content).toMatch(`let a = __ref__(1)`)
386+
expect(content).toMatch(`
387+
let b = __ref__({
388+
count: 0
389+
})
390+
`)
391+
expect(content).toMatch(`let c = __ref__(() => {})`)
392+
expect(content).toMatch(`let d = __ref__()`)
393+
assertCode(content)
394+
})
395+
396+
test('multi let declarations', () => {
397+
const { content } = compile(`<script setup>
398+
export let a = 1, b = 2, c = {
399+
count: 0
400+
}
401+
</script>`)
402+
expect(content).toMatch(`
403+
let a = __ref__(1), b = __ref__(2), c = __ref__({
404+
count: 0
405+
})
406+
`)
407+
assertCode(content)
408+
})
409+
410+
test('should not convert const values', () => {
411+
const { content } = compile(`<script setup>
412+
export const a = 1
413+
const b = 2
414+
</script>`)
415+
expect(content).toMatch(`const a = 1`)
416+
expect(content).toMatch(`b = 2`)
417+
})
418+
419+
test('accessing let binding', () => {
420+
const { content } = compile(`<script setup>
421+
let a = 1
422+
console.log(a)
423+
function get() {
424+
return a + 1
425+
}
426+
</script>`)
427+
expect(content).toMatch(`console.log(a.value)`)
428+
expect(content).toMatch(`return a.value + 1`)
429+
assertCode(content)
430+
})
431+
432+
test('cases that should not append .value', () => {
433+
const { content } = compile(`<script setup>
434+
let a = 1
435+
console.log(b.a)
436+
function get(a) {
437+
return a + 1
438+
}
439+
</script>`)
440+
expect(content).not.toMatch(`a.value`)
441+
})
442+
443+
test('mutating let binding', () => {
444+
const { content } = compile(`<script setup>
445+
let a = 1
446+
let b = { count: 0 }
447+
function inc() {
448+
a++
449+
a = a + 1
450+
b.count++
451+
b.count = b.count + 1
452+
}
453+
</script>`)
454+
expect(content).toMatch(`a.value++`)
455+
expect(content).toMatch(`a.value = a.value + 1`)
456+
expect(content).toMatch(`b.value.count++`)
457+
expect(content).toMatch(`b.value.count = b.value.count + 1`)
458+
assertCode(content)
459+
})
460+
461+
test('using let binding in property shorthand', () => {
462+
const { content } = compile(`<script setup>
463+
let a = 1
464+
const b = { a }
465+
function test() {
466+
const { a } = b
467+
}
468+
</script>`)
469+
expect(content).toMatch(`const b = { a: a.value }`)
470+
// should not convert destructure
471+
expect(content).toMatch(`const { a } = b`)
472+
assertCode(content)
473+
})
474+
})
475+
373476
describe('errors', () => {
374477
test('<script> and <script setup> must have same lang', () => {
375478
expect(() =>

0 commit comments

Comments
 (0)