feat(reactivity-transform): use toRef() for $() destructure codegen
- now supports destructuring reactive objects - no longer supports rest elements
This commit is contained in:
parent
2db9c909c2
commit
93ba6b974e
@ -81,15 +81,22 @@ export interface SFCScriptCompileOptions {
|
|||||||
* https://babeljs.io/docs/en/babel-parser#plugins
|
* https://babeljs.io/docs/en/babel-parser#plugins
|
||||||
*/
|
*/
|
||||||
babelParserPlugins?: ParserPlugin[]
|
babelParserPlugins?: ParserPlugin[]
|
||||||
|
/**
|
||||||
|
* (Experimental) Enable syntax transform for using refs without `.value` and
|
||||||
|
* using destructured props with reactivity
|
||||||
|
*/
|
||||||
|
reactivityTransform?: boolean
|
||||||
/**
|
/**
|
||||||
* (Experimental) Enable syntax transform for using refs without `.value`
|
* (Experimental) Enable syntax transform for using refs without `.value`
|
||||||
* https://github.com/vuejs/rfcs/discussions/369
|
* https://github.com/vuejs/rfcs/discussions/369
|
||||||
|
* @deprecated now part of `reactivityTransform`
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
refTransform?: boolean
|
refTransform?: boolean
|
||||||
/**
|
/**
|
||||||
* (Experimental) Enable syntax transform for destructuring from defineProps()
|
* (Experimental) Enable syntax transform for destructuring from defineProps()
|
||||||
* https://github.com/vuejs/rfcs/discussions/394
|
* https://github.com/vuejs/rfcs/discussions/394
|
||||||
|
* @deprecated now part of `reactivityTransform`
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
propsDestructureTransform?: boolean
|
propsDestructureTransform?: boolean
|
||||||
@ -132,8 +139,13 @@ export function compileScript(
|
|||||||
): SFCScriptBlock {
|
): SFCScriptBlock {
|
||||||
let { script, scriptSetup, source, filename } = sfc
|
let { script, scriptSetup, source, filename } = sfc
|
||||||
// feature flags
|
// feature flags
|
||||||
const enableRefTransform = !!options.refSugar || !!options.refTransform
|
// TODO remove support for deprecated options when out of experimental
|
||||||
const enablePropsTransform = !!options.propsDestructureTransform
|
const enableRefTransform =
|
||||||
|
!!options.reactivityTransform ||
|
||||||
|
!!options.refSugar ||
|
||||||
|
!!options.refTransform
|
||||||
|
const enablePropsTransform =
|
||||||
|
!!options.reactivityTransform || !!options.propsDestructureTransform
|
||||||
const isProd = !!options.isProd
|
const isProd = !!options.isProd
|
||||||
const genSourceMap = options.sourceMap !== false
|
const genSourceMap = options.sourceMap !== false
|
||||||
let refBindings: string[] | undefined
|
let refBindings: string[] | undefined
|
||||||
@ -1097,8 +1109,7 @@ export function compileScript(
|
|||||||
s,
|
s,
|
||||||
startOffset,
|
startOffset,
|
||||||
refBindings,
|
refBindings,
|
||||||
propsDestructuredBindings,
|
propsDestructuredBindings
|
||||||
!enableRefTransform
|
|
||||||
)
|
)
|
||||||
refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
|
refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
|
||||||
for (const h of importedHelpers) {
|
for (const h of importedHelpers) {
|
||||||
|
@ -55,13 +55,12 @@ exports[`accessing ref binding 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`array destructure 1`] = `
|
exports[`array destructure 1`] = `
|
||||||
"import { ref as _ref, shallowRef as _shallowRef } from 'vue'
|
"import { ref as _ref, toRef as _toRef } from 'vue'
|
||||||
|
|
||||||
let n = _ref(1), [__a, __b = 1, ...__c] = (useFoo())
|
let n = _ref(1), __$temp_1 = (useFoo()),
|
||||||
const a = _shallowRef(__a);
|
a = _toRef(__$temp_1, 0),
|
||||||
const b = _shallowRef(__b);
|
b = _toRef(__$temp_1, 1, 1)
|
||||||
const c = _shallowRef(__c);
|
console.log(n.value, a.value, b.value)
|
||||||
console.log(n.value, a.value, b.value, c.value)
|
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -114,13 +113,13 @@ exports[`mutating ref binding 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`nested destructure 1`] = `
|
exports[`nested destructure 1`] = `
|
||||||
"import { shallowRef as _shallowRef } from 'vue'
|
"import { toRef as _toRef } from 'vue'
|
||||||
|
|
||||||
let [{ a: { b: __b }}] = (useFoo())
|
let __$temp_1 = (useFoo()),
|
||||||
const b = _shallowRef(__b);
|
b = _toRef(__$temp_1[0].a, 'b')
|
||||||
let { c: [__d, __e] } = (useBar())
|
let __$temp_2 = (useBar()),
|
||||||
const d = _shallowRef(__d);
|
d = _toRef(__$temp_2.c, 0),
|
||||||
const e = _shallowRef(__e);
|
e = _toRef(__$temp_2.c, 1)
|
||||||
console.log(b.value, d.value, e.value)
|
console.log(b.value, d.value, e.value)
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
@ -163,20 +162,29 @@ exports[`nested scopes 1`] = `
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`object destructure 1`] = `
|
exports[`object destructure 1`] = `
|
||||||
"import { ref as _ref, shallowRef as _shallowRef } from 'vue'
|
"import { ref as _ref, toRef as _toRef } from 'vue'
|
||||||
|
|
||||||
let n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = (useFoo())
|
let n = _ref(1), __$temp_1 = (useFoo()),
|
||||||
const a = _shallowRef(__a);
|
a = _toRef(__$temp_1, 'a'),
|
||||||
const c = _shallowRef(__c);
|
c = _toRef(__$temp_1, 'b'),
|
||||||
const d = _shallowRef(__d);
|
d = _toRef(__$temp_1, 'd', 1),
|
||||||
const f = _shallowRef(__f);
|
f = _toRef(__$temp_1, 'e', 2),
|
||||||
const g = _shallowRef(__g);
|
h = _toRef(__$temp_1, g)
|
||||||
let { foo: __foo } = (useSomthing(() => 1));
|
let __$temp_2 = (useSomthing(() => 1)),
|
||||||
const foo = _shallowRef(__foo);
|
foo = _toRef(__$temp_2, 'foo');
|
||||||
console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)
|
console.log(n.value, a.value, c.value, d.value, f.value, h.value, foo.value)
|
||||||
"
|
"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`object destructure w/ mid-path default values 1`] = `
|
||||||
|
"import { toRef as _toRef } from 'vue'
|
||||||
|
|
||||||
|
const __$temp_1 = (useFoo()),
|
||||||
|
b = _toRef((__$temp_1.a || { b: 123 }), 'b')
|
||||||
|
console.log(b.value)
|
||||||
|
"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`should not rewrite scope variable 1`] = `
|
exports[`should not rewrite scope variable 1`] = `
|
||||||
"import { ref as _ref } from 'vue'
|
"import { ref as _ref } from 'vue'
|
||||||
|
|
||||||
|
@ -201,40 +201,43 @@ test('should not rewrite scope variable', () => {
|
|||||||
|
|
||||||
test('object destructure', () => {
|
test('object destructure', () => {
|
||||||
const { code, rootRefs } = transform(`
|
const { code, rootRefs } = transform(`
|
||||||
let n = $ref(1), { a, b: c, d = 1, e: f = 2, ...g } = $(useFoo())
|
let n = $ref(1), { a, b: c, d = 1, e: f = 2, [g]: h } = $(useFoo())
|
||||||
let { foo } = $(useSomthing(() => 1));
|
let { foo } = $(useSomthing(() => 1));
|
||||||
console.log(n, a, c, d, f, g, foo)
|
console.log(n, a, c, d, f, h, foo)
|
||||||
`)
|
`)
|
||||||
|
expect(code).toMatch(`a = _toRef(__$temp_1, 'a')`)
|
||||||
|
expect(code).toMatch(`c = _toRef(__$temp_1, 'b')`)
|
||||||
|
expect(code).toMatch(`d = _toRef(__$temp_1, 'd', 1)`)
|
||||||
|
expect(code).toMatch(`f = _toRef(__$temp_1, 'e', 2)`)
|
||||||
|
expect(code).toMatch(`h = _toRef(__$temp_1, g)`)
|
||||||
|
expect(code).toMatch(`foo = _toRef(__$temp_2, 'foo')`)
|
||||||
expect(code).toMatch(
|
expect(code).toMatch(
|
||||||
`let n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = (useFoo())`
|
`console.log(n.value, a.value, c.value, d.value, f.value, h.value, foo.value)`
|
||||||
)
|
)
|
||||||
expect(code).toMatch(`let { foo: __foo } = (useSomthing(() => 1))`)
|
expect(rootRefs).toStrictEqual(['n', 'a', 'c', 'd', 'f', 'h', 'foo'])
|
||||||
expect(code).toMatch(`\nconst a = _shallowRef(__a);`)
|
assertCode(code)
|
||||||
expect(code).not.toMatch(`\nconst b = _shallowRef(__b);`)
|
})
|
||||||
expect(code).toMatch(`\nconst c = _shallowRef(__c);`)
|
|
||||||
expect(code).toMatch(`\nconst d = _shallowRef(__d);`)
|
test('object destructure w/ mid-path default values', () => {
|
||||||
expect(code).not.toMatch(`\nconst e = _shallowRef(__e);`)
|
const { code, rootRefs } = transform(`
|
||||||
expect(code).toMatch(`\nconst f = _shallowRef(__f);`)
|
const { a: { b } = { b: 123 }} = $(useFoo())
|
||||||
expect(code).toMatch(`\nconst g = _shallowRef(__g);`)
|
console.log(b)
|
||||||
expect(code).toMatch(`\nconst foo = _shallowRef(__foo);`)
|
`)
|
||||||
expect(code).toMatch(
|
expect(code).toMatch(`b = _toRef((__$temp_1.a || { b: 123 }), 'b')`)
|
||||||
`console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)`
|
expect(code).toMatch(`console.log(b.value)`)
|
||||||
)
|
expect(rootRefs).toStrictEqual(['b'])
|
||||||
expect(rootRefs).toStrictEqual(['n', 'a', 'c', 'd', 'f', 'g', 'foo'])
|
|
||||||
assertCode(code)
|
assertCode(code)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('array destructure', () => {
|
test('array destructure', () => {
|
||||||
const { code, rootRefs } = transform(`
|
const { code, rootRefs } = transform(`
|
||||||
let n = $ref(1), [a, b = 1, ...c] = $(useFoo())
|
let n = $ref(1), [a, b = 1] = $(useFoo())
|
||||||
console.log(n, a, b, c)
|
console.log(n, a, b)
|
||||||
`)
|
`)
|
||||||
expect(code).toMatch(`let n = _ref(1), [__a, __b = 1, ...__c] = (useFoo())`)
|
expect(code).toMatch(`a = _toRef(__$temp_1, 0)`)
|
||||||
expect(code).toMatch(`\nconst a = _shallowRef(__a);`)
|
expect(code).toMatch(`b = _toRef(__$temp_1, 1, 1)`)
|
||||||
expect(code).toMatch(`\nconst b = _shallowRef(__b);`)
|
expect(code).toMatch(`console.log(n.value, a.value, b.value)`)
|
||||||
expect(code).toMatch(`\nconst c = _shallowRef(__c);`)
|
expect(rootRefs).toStrictEqual(['n', 'a', 'b'])
|
||||||
expect(code).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
|
|
||||||
expect(rootRefs).toStrictEqual(['n', 'a', 'b', 'c'])
|
|
||||||
assertCode(code)
|
assertCode(code)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -244,13 +247,9 @@ test('nested destructure', () => {
|
|||||||
let { c: [d, e] } = $(useBar())
|
let { c: [d, e] } = $(useBar())
|
||||||
console.log(b, d, e)
|
console.log(b, d, e)
|
||||||
`)
|
`)
|
||||||
expect(code).toMatch(`let [{ a: { b: __b }}] = (useFoo())`)
|
expect(code).toMatch(`b = _toRef(__$temp_1[0].a, 'b')`)
|
||||||
expect(code).toMatch(`let { c: [__d, __e] } = (useBar())`)
|
expect(code).toMatch(`d = _toRef(__$temp_2.c, 0)`)
|
||||||
expect(code).not.toMatch(`\nconst a = _shallowRef(__a);`)
|
expect(code).toMatch(`e = _toRef(__$temp_2.c, 1)`)
|
||||||
expect(code).not.toMatch(`\nconst c = _shallowRef(__c);`)
|
|
||||||
expect(code).toMatch(`\nconst b = _shallowRef(__b);`)
|
|
||||||
expect(code).toMatch(`\nconst d = _shallowRef(__d);`)
|
|
||||||
expect(code).toMatch(`\nconst e = _shallowRef(__e);`)
|
|
||||||
expect(rootRefs).toStrictEqual(['b', 'd', 'e'])
|
expect(rootRefs).toStrictEqual(['b', 'd', 'e'])
|
||||||
assertCode(code)
|
assertCode(code)
|
||||||
})
|
})
|
||||||
@ -396,4 +395,13 @@ describe('errors', () => {
|
|||||||
`)
|
`)
|
||||||
expect(code).not.toMatch('.value')
|
expect(code).not.toMatch('.value')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('rest element in $() destructure', () => {
|
||||||
|
expect(() => transform(`let { a, ...b } = $(foo())`)).toThrow(
|
||||||
|
`does not support rest element`
|
||||||
|
)
|
||||||
|
expect(() => transform(`let [a, ...b] = $(foo())`)).toThrow(
|
||||||
|
`does not support rest element`
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -4,10 +4,10 @@ import {
|
|||||||
BlockStatement,
|
BlockStatement,
|
||||||
CallExpression,
|
CallExpression,
|
||||||
ObjectPattern,
|
ObjectPattern,
|
||||||
VariableDeclaration,
|
|
||||||
ArrayPattern,
|
ArrayPattern,
|
||||||
Program,
|
Program,
|
||||||
VariableDeclarator
|
VariableDeclarator,
|
||||||
|
Expression
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import MagicString, { SourceMap } from 'magic-string'
|
import MagicString, { SourceMap } from 'magic-string'
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
@ -20,7 +20,7 @@ import {
|
|||||||
walkFunctionParams
|
walkFunctionParams
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import { parse, ParserPlugin } from '@babel/parser'
|
import { parse, ParserPlugin } from '@babel/parser'
|
||||||
import { hasOwn } from '@vue/shared'
|
import { hasOwn, isArray, isString } from '@vue/shared'
|
||||||
|
|
||||||
const TO_VAR_SYMBOL = '$'
|
const TO_VAR_SYMBOL = '$'
|
||||||
const TO_REF_SYMBOL = '$$'
|
const TO_REF_SYMBOL = '$$'
|
||||||
@ -71,7 +71,7 @@ export function transform(
|
|||||||
plugins
|
plugins
|
||||||
})
|
})
|
||||||
const s = new MagicString(src)
|
const s = new MagicString(src)
|
||||||
const res = transformAST(ast.program, s)
|
const res = transformAST(ast.program, s, 0)
|
||||||
|
|
||||||
// inject helper imports
|
// inject helper imports
|
||||||
if (res.importedHelpers.length) {
|
if (res.importedHelpers.length) {
|
||||||
@ -106,16 +106,13 @@ export function transformAST(
|
|||||||
local: string // local identifier, may be different
|
local: string // local identifier, may be different
|
||||||
default?: any
|
default?: any
|
||||||
}
|
}
|
||||||
>,
|
>
|
||||||
rewritePropsOnly = false
|
|
||||||
): {
|
): {
|
||||||
rootRefs: string[]
|
rootRefs: string[]
|
||||||
importedHelpers: string[]
|
importedHelpers: string[]
|
||||||
} {
|
} {
|
||||||
// TODO remove when out of experimental
|
// TODO remove when out of experimental
|
||||||
if (!rewritePropsOnly) {
|
warnExperimental()
|
||||||
warnExperimental()
|
|
||||||
}
|
|
||||||
|
|
||||||
const importedHelpers = new Set<string>()
|
const importedHelpers = new Set<string>()
|
||||||
const rootScope: Scope = {}
|
const rootScope: Scope = {}
|
||||||
@ -139,7 +136,6 @@ export function transformAST(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function error(msg: string, node: Node) {
|
function error(msg: string, node: Node) {
|
||||||
if (rewritePropsOnly) return
|
|
||||||
const e = new Error(msg)
|
const e = new Error(msg)
|
||||||
;(e as any).node = node
|
;(e as any).node = node
|
||||||
throw e
|
throw e
|
||||||
@ -164,6 +160,15 @@ export function transformAST(
|
|||||||
|
|
||||||
const registerRefBinding = (id: Identifier) => registerBinding(id, true)
|
const registerRefBinding = (id: Identifier) => registerBinding(id, true)
|
||||||
|
|
||||||
|
let tempVarCount = 0
|
||||||
|
function genTempVar() {
|
||||||
|
return `__$temp_${++tempVarCount}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function snip(node: Node) {
|
||||||
|
return s.original.slice(node.start! + offset, node.end! + offset)
|
||||||
|
}
|
||||||
|
|
||||||
function walkScope(node: Program | BlockStatement, isRoot = false) {
|
function walkScope(node: Program | BlockStatement, isRoot = false) {
|
||||||
for (const stmt of node.body) {
|
for (const stmt of node.body) {
|
||||||
if (stmt.type === 'VariableDeclaration') {
|
if (stmt.type === 'VariableDeclaration') {
|
||||||
@ -180,9 +185,8 @@ export function transformAST(
|
|||||||
) {
|
) {
|
||||||
processRefDeclaration(
|
processRefDeclaration(
|
||||||
toVarCall,
|
toVarCall,
|
||||||
decl.init as CallExpression,
|
|
||||||
decl.id,
|
decl.id,
|
||||||
stmt
|
decl.init as CallExpression
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
const isProps =
|
const isProps =
|
||||||
@ -212,9 +216,8 @@ export function transformAST(
|
|||||||
|
|
||||||
function processRefDeclaration(
|
function processRefDeclaration(
|
||||||
method: string,
|
method: string,
|
||||||
call: CallExpression,
|
|
||||||
id: VariableDeclarator['id'],
|
id: VariableDeclarator['id'],
|
||||||
statement: VariableDeclaration
|
call: CallExpression
|
||||||
) {
|
) {
|
||||||
excludedIds.add(call.callee as Identifier)
|
excludedIds.add(call.callee as Identifier)
|
||||||
if (method === TO_VAR_SYMBOL) {
|
if (method === TO_VAR_SYMBOL) {
|
||||||
@ -225,9 +228,9 @@ export function transformAST(
|
|||||||
// single variable
|
// single variable
|
||||||
registerRefBinding(id)
|
registerRefBinding(id)
|
||||||
} else if (id.type === 'ObjectPattern') {
|
} else if (id.type === 'ObjectPattern') {
|
||||||
processRefObjectPattern(id, statement)
|
processRefObjectPattern(id, call)
|
||||||
} else if (id.type === 'ArrayPattern') {
|
} else if (id.type === 'ArrayPattern') {
|
||||||
processRefArrayPattern(id, statement)
|
processRefArrayPattern(id, call)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// shorthands
|
// shorthands
|
||||||
@ -247,15 +250,24 @@ export function transformAST(
|
|||||||
|
|
||||||
function processRefObjectPattern(
|
function processRefObjectPattern(
|
||||||
pattern: ObjectPattern,
|
pattern: ObjectPattern,
|
||||||
statement: VariableDeclaration
|
call: CallExpression,
|
||||||
|
tempVar?: string,
|
||||||
|
path: PathSegment[] = []
|
||||||
) {
|
) {
|
||||||
|
if (!tempVar) {
|
||||||
|
tempVar = genTempVar()
|
||||||
|
// const { x } = $(useFoo()) --> const __$temp_1 = useFoo()
|
||||||
|
s.overwrite(pattern.start! + offset, pattern.end! + offset, tempVar)
|
||||||
|
}
|
||||||
|
|
||||||
for (const p of pattern.properties) {
|
for (const p of pattern.properties) {
|
||||||
let nameId: Identifier | undefined
|
let nameId: Identifier | undefined
|
||||||
|
let key: Expression | string | undefined
|
||||||
|
let defaultValue: Expression | undefined
|
||||||
if (p.type === 'ObjectProperty') {
|
if (p.type === 'ObjectProperty') {
|
||||||
if (p.key.start! === p.value.start!) {
|
if (p.key.start! === p.value.start!) {
|
||||||
// shorthand { foo } --> { foo: __foo }
|
// shorthand { foo }
|
||||||
nameId = p.key as Identifier
|
nameId = p.key as Identifier
|
||||||
s.appendLeft(nameId.end! + offset, `: __${nameId.name}`)
|
|
||||||
if (p.value.type === 'Identifier') {
|
if (p.value.type === 'Identifier') {
|
||||||
// avoid shorthand value identifier from being processed
|
// avoid shorthand value identifier from being processed
|
||||||
excludedIds.add(p.value)
|
excludedIds.add(p.value)
|
||||||
@ -265,33 +277,56 @@ export function transformAST(
|
|||||||
) {
|
) {
|
||||||
// { foo = 1 }
|
// { foo = 1 }
|
||||||
excludedIds.add(p.value.left)
|
excludedIds.add(p.value.left)
|
||||||
|
defaultValue = p.value.right
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
key = p.computed ? p.key : (p.key as Identifier).name
|
||||||
if (p.value.type === 'Identifier') {
|
if (p.value.type === 'Identifier') {
|
||||||
// { foo: bar } --> { foo: __bar }
|
// { foo: bar }
|
||||||
nameId = p.value
|
nameId = p.value
|
||||||
s.prependRight(nameId.start! + offset, `__`)
|
|
||||||
} else if (p.value.type === 'ObjectPattern') {
|
} else if (p.value.type === 'ObjectPattern') {
|
||||||
processRefObjectPattern(p.value, statement)
|
processRefObjectPattern(p.value, call, tempVar, [...path, key])
|
||||||
} else if (p.value.type === 'ArrayPattern') {
|
} else if (p.value.type === 'ArrayPattern') {
|
||||||
processRefArrayPattern(p.value, statement)
|
processRefArrayPattern(p.value, call, tempVar, [...path, key])
|
||||||
} else if (p.value.type === 'AssignmentPattern') {
|
} else if (p.value.type === 'AssignmentPattern') {
|
||||||
// { foo: bar = 1 } --> { foo: __bar = 1 }
|
if (p.value.left.type === 'Identifier') {
|
||||||
nameId = p.value.left as Identifier
|
// { foo: bar = 1 }
|
||||||
s.prependRight(nameId.start! + offset, `__`)
|
nameId = p.value.left
|
||||||
|
defaultValue = p.value.right
|
||||||
|
} else if (p.value.left.type === 'ObjectPattern') {
|
||||||
|
processRefObjectPattern(p.value.left, call, tempVar, [
|
||||||
|
...path,
|
||||||
|
[key, p.value.right]
|
||||||
|
])
|
||||||
|
} else if (p.value.left.type === 'ArrayPattern') {
|
||||||
|
processRefArrayPattern(p.value.left, call, tempVar, [
|
||||||
|
...path,
|
||||||
|
[key, p.value.right]
|
||||||
|
])
|
||||||
|
} else {
|
||||||
|
// MemberExpression case is not possible here, ignore
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// rest element { ...foo } --> { ...__foo }
|
// rest element { ...foo }
|
||||||
nameId = p.argument as Identifier
|
error(`reactivity destructure does not support rest elements.`, p)
|
||||||
s.prependRight(nameId.start! + offset, `__`)
|
|
||||||
}
|
}
|
||||||
if (nameId) {
|
if (nameId) {
|
||||||
registerRefBinding(nameId)
|
registerRefBinding(nameId)
|
||||||
// append binding declarations after the parent statement
|
// inject toRef() after original replaced pattern
|
||||||
|
const source = pathToString(tempVar, path)
|
||||||
|
const keyStr = isString(key)
|
||||||
|
? `'${key}'`
|
||||||
|
: key
|
||||||
|
? snip(key)
|
||||||
|
: `'${nameId.name}'`
|
||||||
|
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
||||||
s.appendLeft(
|
s.appendLeft(
|
||||||
statement.end! + offset,
|
call.end! + offset,
|
||||||
`\nconst ${nameId.name} = ${helper('shallowRef')}(__${nameId.name});`
|
`,\n ${nameId.name} = ${helper(
|
||||||
|
'toRef'
|
||||||
|
)}(${source}, ${keyStr}${defaultStr})`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -299,38 +334,80 @@ export function transformAST(
|
|||||||
|
|
||||||
function processRefArrayPattern(
|
function processRefArrayPattern(
|
||||||
pattern: ArrayPattern,
|
pattern: ArrayPattern,
|
||||||
statement: VariableDeclaration
|
call: CallExpression,
|
||||||
|
tempVar?: string,
|
||||||
|
path: PathSegment[] = []
|
||||||
) {
|
) {
|
||||||
for (const e of pattern.elements) {
|
if (!tempVar) {
|
||||||
|
// const [x] = $(useFoo()) --> const __$temp_1 = useFoo()
|
||||||
|
tempVar = genTempVar()
|
||||||
|
s.overwrite(pattern.start! + offset, pattern.end! + offset, tempVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < pattern.elements.length; i++) {
|
||||||
|
const e = pattern.elements[i]
|
||||||
if (!e) continue
|
if (!e) continue
|
||||||
let nameId: Identifier | undefined
|
let nameId: Identifier | undefined
|
||||||
|
let defaultValue: Expression | undefined
|
||||||
if (e.type === 'Identifier') {
|
if (e.type === 'Identifier') {
|
||||||
// [a] --> [__a]
|
// [a] --> [__a]
|
||||||
nameId = e
|
nameId = e
|
||||||
} else if (e.type === 'AssignmentPattern') {
|
} else if (e.type === 'AssignmentPattern') {
|
||||||
// [a = 1] --> [__a = 1]
|
// [a = 1]
|
||||||
nameId = e.left as Identifier
|
nameId = e.left as Identifier
|
||||||
|
defaultValue = e.right
|
||||||
} else if (e.type === 'RestElement') {
|
} else if (e.type === 'RestElement') {
|
||||||
// [...a] --> [...__a]
|
// [...a]
|
||||||
nameId = e.argument as Identifier
|
error(`reactivity destructure does not support rest elements.`, e)
|
||||||
} else if (e.type === 'ObjectPattern') {
|
} else if (e.type === 'ObjectPattern') {
|
||||||
processRefObjectPattern(e, statement)
|
processRefObjectPattern(e, call, tempVar, [...path, i])
|
||||||
} else if (e.type === 'ArrayPattern') {
|
} else if (e.type === 'ArrayPattern') {
|
||||||
processRefArrayPattern(e, statement)
|
processRefArrayPattern(e, call, tempVar, [...path, i])
|
||||||
}
|
}
|
||||||
if (nameId) {
|
if (nameId) {
|
||||||
registerRefBinding(nameId)
|
registerRefBinding(nameId)
|
||||||
// prefix original
|
// inject toRef() after original replaced pattern
|
||||||
s.prependRight(nameId.start! + offset, `__`)
|
const source = pathToString(tempVar, path)
|
||||||
// append binding declarations after the parent statement
|
const defaultStr = defaultValue ? `, ${snip(defaultValue)}` : ``
|
||||||
s.appendLeft(
|
s.appendLeft(
|
||||||
statement.end! + offset,
|
call.end! + offset,
|
||||||
`\nconst ${nameId.name} = ${helper('shallowRef')}(__${nameId.name});`
|
`,\n ${nameId.name} = ${helper(
|
||||||
|
'toRef'
|
||||||
|
)}(${source}, ${i}${defaultStr})`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PathSegmentAtom = Expression | string | number
|
||||||
|
|
||||||
|
type PathSegment =
|
||||||
|
| PathSegmentAtom
|
||||||
|
| [PathSegmentAtom, Expression /* default value */]
|
||||||
|
|
||||||
|
function pathToString(source: string, path: PathSegment[]): string {
|
||||||
|
if (path.length) {
|
||||||
|
for (const seg of path) {
|
||||||
|
if (isArray(seg)) {
|
||||||
|
source = `(${source}${segToString(seg[0])} || ${snip(seg[1])})`
|
||||||
|
} else {
|
||||||
|
source += segToString(seg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return source
|
||||||
|
}
|
||||||
|
|
||||||
|
function segToString(seg: PathSegmentAtom): string {
|
||||||
|
if (typeof seg === 'number') {
|
||||||
|
return `[${seg}]`
|
||||||
|
} else if (typeof seg === 'string') {
|
||||||
|
return `.${seg}`
|
||||||
|
} else {
|
||||||
|
return snip(seg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function rewriteId(
|
function rewriteId(
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
id: Identifier,
|
id: Identifier,
|
||||||
@ -341,10 +418,6 @@ export function transformAST(
|
|||||||
const bindingType = scope[id.name]
|
const bindingType = scope[id.name]
|
||||||
if (bindingType) {
|
if (bindingType) {
|
||||||
const isProp = bindingType === 'prop'
|
const isProp = bindingType === 'prop'
|
||||||
if (rewritePropsOnly && !isProp) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
// ref
|
|
||||||
if (isStaticProperty(parent) && parent.shorthand) {
|
if (isStaticProperty(parent) && parent.shorthand) {
|
||||||
// let binding used in a property shorthand
|
// let binding used in a property shorthand
|
||||||
// { foo } -> { foo: foo.value }
|
// { foo } -> { foo: foo.value }
|
||||||
@ -498,7 +571,7 @@ function warnExperimental() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
warnOnce(
|
warnOnce(
|
||||||
`@vue/ref-transform is an experimental feature.\n` +
|
`Reactivity transform is an experimental feature.\n` +
|
||||||
`Experimental features may change behavior between patch versions.\n` +
|
`Experimental features may change behavior between patch versions.\n` +
|
||||||
`It is recommended to pin your vue dependencies to exact versions to avoid breakage.\n` +
|
`It is recommended to pin your vue dependencies to exact versions to avoid breakage.\n` +
|
||||||
`You can follow the proposal's status at ${RFC_LINK}.`
|
`You can follow the proposal's status at ${RFC_LINK}.`
|
||||||
|
Loading…
Reference in New Issue
Block a user