diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts
index a671c563f70..c2f00a4ce27 100644
--- a/packages/compiler-core/src/transforms/transformExpression.ts
+++ b/packages/compiler-core/src/transforms/transformExpression.ts
@@ -161,9 +161,9 @@ export function processExpression(
if (!isDuplicate(node)) {
const needPrefix = shouldPrefix(node, parent)
if (!knownIds[node.name] && needPrefix) {
- if (isPropertyShorthand(node, parent)) {
- // property shorthand like { foo }, we need to add the key since we
- // rewrite the value
+ if (isStaticProperty(parent) && parent.shorthand) {
+ // property shorthand like { foo }, we need to add the key since
+ // we rewrite the value
node.prefix = `${node.name}: `
}
node.name = prefix(node.name)
@@ -278,46 +278,65 @@ const isStaticProperty = (node: Node): node is ObjectProperty =>
(node.type === 'ObjectProperty' || node.type === 'ObjectMethod') &&
!node.computed
-const isPropertyShorthand = (node: Node, parent: Node) => {
- return (
- isStaticProperty(parent) &&
- parent.value === node &&
- parent.key.type === 'Identifier' &&
- parent.key.name === (node as Identifier).name &&
- parent.key.start === node.start
- )
-}
-
const isStaticPropertyKey = (node: Node, parent: Node) =>
isStaticProperty(parent) && parent.key === node
-function shouldPrefix(identifier: Identifier, parent: Node) {
+function shouldPrefix(id: Identifier, parent: Node) {
+ // declaration id
if (
- !(
- isFunction(parent) &&
- // not id of a FunctionDeclaration
- ((parent as any).id === identifier ||
- // not a params of a function
- parent.params.includes(identifier))
- ) &&
- // not a key of Property
- !isStaticPropertyKey(identifier, parent) &&
- // not a property of a MemberExpression
- !(
- (parent.type === 'MemberExpression' ||
- parent.type === 'OptionalMemberExpression') &&
- parent.property === identifier &&
- !parent.computed
- ) &&
- // not in an Array destructure pattern
- !(parent.type === 'ArrayPattern') &&
- // skip whitelisted globals
- !isGloballyWhitelisted(identifier.name) &&
- // special case for webpack compilation
- identifier.name !== `require` &&
- // is a special keyword but parsed as identifier
- identifier.name !== `arguments`
+ (parent.type === 'VariableDeclarator' ||
+ parent.type === 'ClassDeclaration') &&
+ parent.id === id
) {
- return true
+ return false
+ }
+
+ if (isFunction(parent)) {
+ // function decalration/expression id
+ if ((parent as any).id === id) {
+ return false
+ }
+ // params list
+ if (parent.params.includes(id)) {
+ return false
+ }
}
+
+ // property key
+ // this also covers object destructure pattern
+ if (isStaticPropertyKey(id, parent)) {
+ return false
+ }
+
+ // array destructure pattern
+ if (parent.type === 'ArrayPattern') {
+ return false
+ }
+
+ // member expression property
+ if (
+ (parent.type === 'MemberExpression' ||
+ parent.type === 'OptionalMemberExpression') &&
+ parent.property === id &&
+ !parent.computed
+ ) {
+ return false
+ }
+
+ // is a special keyword but parsed as identifier
+ if (id.name === 'arguments') {
+ return false
+ }
+
+ // skip whitelisted globals
+ if (isGloballyWhitelisted(id.name)) {
+ return false
+ }
+
+ // special case for webpack compilation
+ if (id.name === 'require') {
+ return false
+ }
+
+ return true
}
diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
index a6bb93e7405..69937bc9e79 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap
@@ -1,8 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SFC compile `).content
- )
- })
-
- test('should extract comment for import or type declarations', () => {
- assertCode(
- compile(``).content
- )
- })
-
test('explicit setup signature', () => {
assertCode(
compile(``).content
)
})
- test('import dedupe between
`)
assertCode(content)
- expect(content.indexOf(`import { x }`)).toEqual(
- content.lastIndexOf(`import { x }`)
- )
+ expect(content).toMatch('return { x, a, b, c, d }')
})
- describe('exports', () => {
- test('export const x = ...', () => {
- const { content, bindings } = compile(
- ``
- )
- assertCode(content)
- expect(bindings).toStrictEqual({
- x: 'setup'
- })
- })
-
- test('export const { x } = ... (destructuring)', () => {
- const { content, bindings } = compile(``)
- assertCode(content)
- expect(bindings).toStrictEqual({
- a: 'setup',
- b: 'setup',
- c: 'setup',
- d: 'setup',
- e: 'setup',
- f: 'setup'
- })
- })
-
- test('export function x() {}', () => {
- const { content, bindings } = compile(
- ``
- )
- assertCode(content)
- expect(bindings).toStrictEqual({
- x: 'setup'
- })
- })
-
- test('export class X() {}', () => {
- const { content, bindings } = compile(
- ``
- )
- assertCode(content)
- expect(bindings).toStrictEqual({
- X: 'setup'
- })
- })
-
- test('export { x }', () => {
- const { content, bindings } = compile(
- ``
- )
- assertCode(content)
- expect(bindings).toStrictEqual({
- x: 'setup',
- y: 'setup'
- })
- })
-
- test(`export { x } from './x'`, () => {
- const { content, bindings } = compile(
- ``
- )
- assertCode(content)
- expect(bindings).toStrictEqual({
- x: 'setup',
- y: 'setup'
- })
- })
-
- test(`export default from './x'`, () => {
- const { content, bindings } = compile(
- ``,
- {
- babelParserPlugins: ['exportDefaultFrom']
- }
+ describe('imports', () => {
+ test('should hoist and expose imports', () => {
+ assertCode(
+ compile(``).content
)
- assertCode(content)
- expect(bindings).toStrictEqual({})
})
- test(`export { x as default }`, () => {
- const { content, bindings } = compile(
- ``
+ test('should extract comment for import or type declarations', () => {
+ assertCode(
+ compile(``).content
)
- assertCode(content)
- expect(bindings).toStrictEqual({
- y: 'setup'
- })
})
- test(`export { x as default } from './x'`, () => {
- const { content, bindings } = compile(
- ``
- )
+ test('dedupe between user & helper', () => {
+ const { content } = compile(``)
assertCode(content)
- expect(bindings).toStrictEqual({
- y: 'setup'
- })
+ expect(content).toMatch(`import { ref } from 'vue'`)
})
- test(`export * from './x'`, () => {
- const { content, bindings } = compile(
- ``
- )
+ test('import dedupe between
+
+ `)
assertCode(content)
- expect(bindings).toStrictEqual({
- y: 'setup'
- // in this case we cannot extract bindings from ./x so it falls back
- // to runtime proxy dispatching
- })
- })
-
- test('export default in `
+ expect(content.indexOf(`import { x }`)).toEqual(
+ content.lastIndexOf(`import { x }`)
)
- assertCode(content)
- expect(bindings).toStrictEqual({
- foo: 'props',
- y: 'setup'
- })
})
})
describe('`)
assertCode(content)
- expect(bindings).toStrictEqual({ a: 'setup' })
})
test('extract props', () => {
@@ -333,7 +210,7 @@ import b from 'b'
test('w/ \n` +
+ `\n` +
``
).content
)
@@ -356,8 +233,8 @@ import b from 'b'
assertAwaitDetection(`const a = 1 + (await foo)`)
})
- test('export', () => {
- assertAwaitDetection(`export const a = 1 + (await foo)`)
+ test('ref', () => {
+ assertAwaitDetection(`ref: a = 1 + (await foo)`)
})
test('nested statements', () => {
@@ -366,7 +243,7 @@ import b from 'b'
test('should ignore await inside functions', () => {
// function declaration
- assertAwaitDetection(`export async function foo() { await bar }`, false)
+ assertAwaitDetection(`async function foo() { await bar }`, false)
// function expression
assertAwaitDetection(`const foo = async () => { await bar }`, false)
// object method
@@ -379,6 +256,197 @@ import b from 'b'
})
})
+ describe('ref: syntax sugar', () => {
+ test('convert ref declarations', () => {
+ const { content, bindings } = compile(``)
+ expect(content).toMatch(`import { ref } from 'vue'`)
+ expect(content).not.toMatch(`ref: a`)
+ expect(content).toMatch(`const a = ref(1)`)
+ expect(content).toMatch(`
+ const b = ref({
+ count: 0
+ })
+ `)
+ // normal declarations left untouched
+ expect(content).toMatch(`let c = () => {}`)
+ expect(content).toMatch(`let d`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ a: 'setup',
+ b: 'setup',
+ c: 'setup',
+ d: 'setup'
+ })
+ })
+
+ test('multi ref declarations', () => {
+ const { content, bindings } = compile(``)
+ expect(content).toMatch(`
+ const a = ref(1), b = ref(2), c = ref({
+ count: 0
+ })
+ `)
+ expect(content).toMatch(`return { a, b, c }`)
+ assertCode(content)
+ expect(bindings).toStrictEqual({
+ a: 'setup',
+ b: 'setup',
+ c: 'setup'
+ })
+ })
+
+ test('should not convert non ref labels', () => {
+ const { content } = compile(``)
+ expect(content).toMatch(`foo: a = 1, b = 2`)
+ assertCode(content)
+ })
+
+ test('accessing ref binding', () => {
+ const { content } = compile(``)
+ expect(content).toMatch(`console.log(a.value)`)
+ expect(content).toMatch(`return a.value + 1`)
+ assertCode(content)
+ })
+
+ test('cases that should not append .value', () => {
+ const { content } = compile(``)
+ expect(content).not.toMatch(`a.value`)
+ })
+
+ test('mutating ref binding', () => {
+ const { content } = compile(``)
+ expect(content).toMatch(`a.value++`)
+ expect(content).toMatch(`a.value = a.value + 1`)
+ expect(content).toMatch(`b.value.count++`)
+ expect(content).toMatch(`b.value.count = b.value.count + 1`)
+ assertCode(content)
+ })
+
+ test('using ref binding in property shorthand', () => {
+ const { content } = compile(``)
+ expect(content).toMatch(`const b = { a: a.value }`)
+ // should not convert destructure
+ expect(content).toMatch(`const { a } = b`)
+ assertCode(content)
+ })
+
+ test('object destructure', () => {
+ const { content, bindings } = compile(``)
+ expect(content).toMatch(
+ `const n = ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = useFoo()`
+ )
+ expect(content).toMatch(`\nconst a = ref(__a);`)
+ expect(content).not.toMatch(`\nconst b = ref(__b);`)
+ expect(content).toMatch(`\nconst c = ref(__c);`)
+ expect(content).toMatch(`\nconst d = ref(__d);`)
+ expect(content).not.toMatch(`\nconst e = ref(__e);`)
+ expect(content).toMatch(`\nconst f = ref(__f);`)
+ expect(content).toMatch(`\nconst g = ref(__g);`)
+ expect(content).toMatch(
+ `console.log(n.value, a.value, c.value, d.value, f.value, g.value)`
+ )
+ expect(content).toMatch(`return { n, a, c, d, f, g }`)
+ expect(bindings).toStrictEqual({
+ n: 'setup',
+ a: 'setup',
+ c: 'setup',
+ d: 'setup',
+ f: 'setup',
+ g: 'setup'
+ })
+ assertCode(content)
+ })
+
+ test('array destructure', () => {
+ const { content, bindings } = compile(``)
+ expect(content).toMatch(
+ `const n = ref(1), [__a, __b = 1, ...__c] = useFoo()`
+ )
+ expect(content).toMatch(`\nconst a = ref(__a);`)
+ expect(content).toMatch(`\nconst b = ref(__b);`)
+ expect(content).toMatch(`\nconst c = ref(__c);`)
+ expect(content).toMatch(`console.log(n.value, a.value, b.value, c.value)`)
+ expect(content).toMatch(`return { n, a, b, c }`)
+ expect(bindings).toStrictEqual({
+ n: 'setup',
+ a: 'setup',
+ b: 'setup',
+ c: 'setup'
+ })
+ assertCode(content)
+ })
+
+ test('nested destructure', () => {
+ const { content, bindings } = compile(``)
+ expect(content).toMatch(`const [{ a: { b: __b }}] = useFoo()`)
+ expect(content).toMatch(`const { c: [__d, __e] } = useBar()`)
+ expect(content).not.toMatch(`\nconst a = ref(__a);`)
+ expect(content).not.toMatch(`\nconst c = ref(__c);`)
+ expect(content).toMatch(`\nconst b = ref(__b);`)
+ expect(content).toMatch(`\nconst d = ref(__d);`)
+ expect(content).toMatch(`\nconst e = ref(__e);`)
+ expect(content).toMatch(`return { b, d, e }`)
+ expect(bindings).toStrictEqual({
+ b: 'setup',
+ d: 'setup',
+ e: 'setup'
+ })
+ assertCode(content)
+ })
+ })
+
describe('errors', () => {
test('`)
+ ).toThrow(`cannot contain non-type named exports`)
+
expect(() =>
compile(``)
- ).toThrow(`Cannot export locally defined variable as default`)
+ ).toThrow(`cannot contain non-type named exports`)
+ })
+
+ test('ref: non-assignment expressions', () => {
+ expect(() =>
+ compile(``)
+ ).toThrow(`ref: statements can only contain assignment expressions`)
})
test('export default referencing local var', () => {
@@ -410,10 +492,10 @@ import b from 'b'
).toThrow(`cannot reference locally declared variables`)
})
- test('export default referencing exports', () => {
+ test('export default referencing ref declarations', () => {
expect(() =>
compile(``).content
- )
- })
-
- test('should allow export default referencing re-exported binding', () => {
- assertCode(
- compile(`
-
- `)
- ).toThrow(`Default export is already declared`)
-
- expect(() =>
- compile(`
-
-
- `)
- ).toThrow(`Default export is already declared`)
-
expect(() =>
compile(`