From db8dc753c0647edfb878d3b0f7b5b16bcfd2c23c Mon Sep 17 00:00:00 2001 From: Evan You Date: Sun, 22 Aug 2021 22:21:42 -0400 Subject: [PATCH] feat(experimental): standalone ref transform --- .eslintrc.js | 4 +- packages/compiler-core/src/babelUtils.ts | 104 +++-- .../src/transforms/transformExpression.ts | 6 +- .../compileScriptRefSugar.spec.ts.snap | 262 +----------- .../__tests__/compileScriptRefSugar.spec.ts | 360 ++-------------- packages/compiler-sfc/package.json | 1 + packages/compiler-sfc/src/compileScript.ts | 286 ++----------- packages/compiler-sfc/src/index.ts | 4 + packages/ref-transform/README.md | 84 ++++ .../__snapshots__/refTransform.spec.ts.snap | 211 ++++++++++ .../__tests__/refTransform.spec.ts | 390 ++++++++++++++++++ packages/ref-transform/api-extractor.json | 7 + packages/ref-transform/package.json | 39 ++ packages/ref-transform/src/babelPlugin.ts | 3 + packages/ref-transform/src/index.ts | 368 +++++++++++++++++ yarn.lock | 2 +- 16 files changed, 1258 insertions(+), 873 deletions(-) create mode 100644 packages/ref-transform/README.md create mode 100644 packages/ref-transform/__tests__/__snapshots__/refTransform.spec.ts.snap create mode 100644 packages/ref-transform/__tests__/refTransform.spec.ts create mode 100644 packages/ref-transform/api-extractor.json create mode 100644 packages/ref-transform/package.json create mode 100644 packages/ref-transform/src/babelPlugin.ts create mode 100644 packages/ref-transform/src/index.ts diff --git a/.eslintrc.js b/.eslintrc.js index 88034d23..ea44a000 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -49,7 +49,9 @@ module.exports = { }, // Packages targeting Node { - files: ['packages/{compiler-sfc,compiler-ssr,server-renderer}/**'], + files: [ + 'packages/{compiler-sfc,compiler-ssr,server-renderer,ref-transform}/**' + ], rules: { 'no-restricted-globals': ['error', ...DOMGlobals], 'no-restricted-syntax': 'off' diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts index 6586c446..6f19f305 100644 --- a/packages/compiler-core/src/babelUtils.ts +++ b/packages/compiler-core/src/babelUtils.ts @@ -3,7 +3,8 @@ import { Identifier, Node, Function, - ObjectProperty + ObjectProperty, + BlockStatement } from '@babel/types' import { walk } from 'estree-walker' @@ -17,9 +18,10 @@ export function walkIdentifiers( isLocal: boolean ) => void, onNode?: (node: Node, parent: Node, parentStack: Node[]) => void | boolean, + includeAll = false, + analyzeScope = true, parentStack: Node[] = [], - knownIds: Record = Object.create(null), - includeAll = false + knownIds: Record = Object.create(null) ) { const rootExp = root.type === 'Program' && @@ -42,62 +44,41 @@ export function walkIdentifiers( return this.skip() } if (node.type === 'Identifier') { - const isLocal = !!knownIds[node.name] + const isLocal = analyzeScope && !!knownIds[node.name] const isRefed = isReferencedIdentifier(node, parent!, parentStack) if (includeAll || (isRefed && !isLocal)) { onIdentifier(node, parent!, parentStack, isRefed, isLocal) } - } else if (isFunctionType(node)) { - // walk function expressions and add its arguments to known identifiers - // so that we don't prefix them - for (const p of node.params) { - ;(walk as any)(p, { - enter(child: Node, parent: Node) { - if ( - child.type === 'Identifier' && - // do not record as scope variable if is a destructured key - !isStaticPropertyKey(child, parent) && - // do not record if this is a default value - // assignment of a destructured variable - !( - parent && - parent.type === 'AssignmentPattern' && - parent.right === child - ) - ) { - markScopeIdentifier(node, child, knownIds) - } - } - }) - } - } else if (node.type === 'BlockStatement') { - // #3445 record block-level local variables - for (const stmt of node.body) { - if (stmt.type === 'VariableDeclaration') { - for (const decl of stmt.declarations) { - for (const id of extractIdentifiers(decl.id)) { - markScopeIdentifier(node, id, knownIds) - } - } - } - } } else if ( node.type === 'ObjectProperty' && parent!.type === 'ObjectPattern' ) { // mark property in destructure pattern ;(node as any).inPattern = true + } else if (analyzeScope) { + if (isFunctionType(node)) { + // walk function expressions and add its arguments to known identifiers + // so that we don't prefix them + walkFunctionParams(node, id => + markScopeIdentifier(node, id, knownIds) + ) + } else if (node.type === 'BlockStatement') { + // #3445 record block-level local variables + walkBlockDeclarations(node, id => + markScopeIdentifier(node, id, knownIds) + ) + } } }, leave(node: Node & { scopeIds?: Set }, parent: Node | undefined) { parent && parentStack.pop() - if (node !== rootExp && node.scopeIds) { - node.scopeIds.forEach((id: string) => { + if (analyzeScope && node !== rootExp && node.scopeIds) { + for (const id of node.scopeIds) { knownIds[id]-- if (knownIds[id] === 0) { delete knownIds[id] } - }) + } } } }) @@ -156,6 +137,47 @@ export function isInDestructureAssignment( return false } +export function walkFunctionParams( + node: Function, + onIdent: (id: Identifier) => void +) { + for (const p of node.params) { + ;(walk as any)(p, { + enter(child: Node, parent: Node) { + if ( + child.type === 'Identifier' && + // do not record as scope variable if is a destructured key + !isStaticPropertyKey(child, parent) && + // do not record if this is a default value + // assignment of a destructured variable + !( + parent && + parent.type === 'AssignmentPattern' && + parent.right === child + ) + ) { + onIdent(child) + } + } + }) + } +} + +export function walkBlockDeclarations( + block: BlockStatement, + onIdent: (node: Identifier) => void +) { + for (const stmt of block.body) { + if (stmt.type === 'VariableDeclaration') { + for (const decl of stmt.declarations) { + for (const id of extractIdentifiers(decl.id)) { + onIdent(id) + } + } + } + } +} + function extractIdentifiers( param: Node, nodes: Identifier[] = [] diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts index 4c30707f..d20fe735 100644 --- a/packages/compiler-core/src/transforms/transformExpression.ts +++ b/packages/compiler-core/src/transforms/transformExpression.ts @@ -283,10 +283,10 @@ export function processExpression( } }, undefined, + true, // invoke on ALL identifiers + true, // isLocal scope analysis parentStack, - knownIds, - // invoke on ALL identifiers - true + knownIds ) // We break up the compound expression into an array of strings and sub diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptRefSugar.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptRefSugar.spec.ts.snap index dbe6aa1d..ed4605c1 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScriptRefSugar.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScriptRefSugar.spec.ts.snap @@ -1,33 +1,21 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[``) + expect(content).not.toMatch(`$(ref())`) + expect(content).not.toMatch(`$(ref(1))`) + expect(content).not.toMatch(`$(shallowRef({`) + expect(content).toMatch(`let foo = (ref())`) + expect(content).toMatch(`let a = (ref(1))`) + expect(content).toMatch(` + let b = (shallowRef({ + count: 0 + })) + `) + // normal declarations left untouched + expect(content).toMatch(`let c = () => {}`) + expect(content).toMatch(`let d`) + expect(content).toMatch(`return { foo, a, b, c, d, ref, shallowRef }`) + assertCode(content) + expect(bindings).toStrictEqual({ + foo: BindingTypes.SETUP_REF, + a: BindingTypes.SETUP_REF, + b: BindingTypes.SETUP_REF, + c: BindingTypes.SETUP_LET, + d: BindingTypes.SETUP_LET, + ref: BindingTypes.SETUP_CONST, + shallowRef: BindingTypes.SETUP_CONST + }) + }) + test('$ref & $shallowRef declarations', () => { const { content, bindings } = compileWithRefSugar(``) - expect(content).toMatch(` - let a = _ref(1), b = _ref(2), c = _ref({ - count: 0 - }) - `) - expect(content).toMatch(`return { a, b, c }`) - assertCode(content) - expect(bindings).toStrictEqual({ - a: BindingTypes.SETUP_REF, - b: BindingTypes.SETUP_REF, - c: BindingTypes.SETUP_REF - }) - }) - - test('$computed declaration', () => { - const { content, bindings } = compileWithRefSugar(``) - expect(content).toMatch(` - const a = _computed(() => 1) - `) - expect(content).toMatch(`return { a }`) - assertCode(content) - expect(bindings).toStrictEqual({ - a: BindingTypes.SETUP_REF - }) - }) - - test('mixing $ref & $computed declarations', () => { - const { content, bindings } = compileWithRefSugar(``) - expect(content).toMatch(` - let a = _ref(1), b = _computed(() => a.value + 1) - `) - expect(content).toMatch(`return { a, b }`) - assertCode(content) - expect(bindings).toStrictEqual({ - a: BindingTypes.SETUP_REF, - b: BindingTypes.SETUP_REF - }) - }) - - test('accessing ref binding', () => { - const { content } = compileWithRefSugar(``) - 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 } = compileWithRefSugar(``) - expect(content).not.toMatch(`a.value`) - }) - - test('mutating ref binding', () => { - const { content } = compileWithRefSugar(``) - 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`) - expect(content).toMatch(`;({ a: a.value } = { a: 2 })`) - expect(content).toMatch(`;[a.value] = [1]`) - assertCode(content) - }) - - test('using ref binding in property shorthand', () => { - const { content } = compileWithRefSugar(``) - expect(content).toMatch(`const b = { a: a.value }`) - // should not convert destructure - expect(content).toMatch(`const { a } = b`) - assertCode(content) - }) - - test('should not rewrite scope variable', () => { - const { content } = compileWithRefSugar(` - `) - expect(content).toMatch('console.log(a)') - expect(content).toMatch('console.log(b.value)') - expect(content).toMatch('console.log(c)') - expect(content).toMatch('console.log(d.value)') - expect(content).toMatch('console.log(e)') - assertCode(content) - }) - - test('object destructure', () => { - const { content, bindings } = compileWithRefSugar(``) - expect(content).toMatch( - `let n = _ref(1), { a: __a, b: __c, d: __d = 1, e: __f = 2, ...__g } = (useFoo())` - ) - expect(content).toMatch(`let { foo: __foo } = (useSomthing(() => 1))`) - expect(content).toMatch(`\nconst a = _shallowRef(__a);`) - expect(content).not.toMatch(`\nconst b = _shallowRef(__b);`) - expect(content).toMatch(`\nconst c = _shallowRef(__c);`) - expect(content).toMatch(`\nconst d = _shallowRef(__d);`) - expect(content).not.toMatch(`\nconst e = _shallowRef(__e);`) - expect(content).toMatch(`\nconst f = _shallowRef(__f);`) - expect(content).toMatch(`\nconst g = _shallowRef(__g);`) - expect(content).toMatch(`\nconst foo = _shallowRef(__foo);`) - expect(content).toMatch( - `console.log(n.value, a.value, c.value, d.value, f.value, g.value, foo.value)` - ) - expect(content).toMatch(`return { n, a, c, d, f, g, foo }`) - expect(bindings).toStrictEqual({ - n: BindingTypes.SETUP_REF, - a: BindingTypes.SETUP_REF, - c: BindingTypes.SETUP_REF, - d: BindingTypes.SETUP_REF, - f: BindingTypes.SETUP_REF, - g: BindingTypes.SETUP_REF, - foo: BindingTypes.SETUP_REF - }) - assertCode(content) - }) - - test('array destructure', () => { - const { content, bindings } = compileWithRefSugar(``) - expect(content).toMatch( - `let n = _ref(1), [__a, __b = 1, ...__c] = (useFoo())` - ) - expect(content).toMatch(`\nconst a = _shallowRef(__a);`) - expect(content).toMatch(`\nconst b = _shallowRef(__b);`) - expect(content).toMatch(`\nconst c = _shallowRef(__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: BindingTypes.SETUP_REF, - a: BindingTypes.SETUP_REF, - b: BindingTypes.SETUP_REF, - c: BindingTypes.SETUP_REF - }) - assertCode(content) - }) - - test('nested destructure', () => { - const { content, bindings } = compileWithRefSugar(``) - expect(content).toMatch(`let [{ a: { b: __b }}] = (useFoo())`) - expect(content).toMatch(`let { c: [__d, __e] } = (useBar())`) - expect(content).not.toMatch(`\nconst a = _shallowRef(__a);`) - expect(content).not.toMatch(`\nconst c = _shallowRef(__c);`) - expect(content).toMatch(`\nconst b = _shallowRef(__b);`) - expect(content).toMatch(`\nconst d = _shallowRef(__d);`) - expect(content).toMatch(`\nconst e = _shallowRef(__e);`) - expect(content).toMatch(`return { b, d, e }`) - expect(bindings).toStrictEqual({ - b: BindingTypes.SETUP_REF, - d: BindingTypes.SETUP_REF, - e: BindingTypes.SETUP_REF - }) - assertCode(content) - }) - - test('$raw', () => { - const { content } = compileWithRefSugar(``) - expect(content).toMatch(`const b = (a)`) - expect(content).toMatch(`const c = ({ a })`) - expect(content).toMatch(`callExternal((a))`) - assertCode(content) - }) - - //#4062 - test('should not rewrite type identifiers', () => { - const { content } = compile( - ` - `, - { - refSugar: true - } - ) - assertCode(content) - expect(content).not.toMatch('.value') - }) - - // #4254 - test('handle TS casting syntax', () => { - const { content } = compile( - ` - `, - { - refSugar: true - } - ) - assertCode(content) - expect(content).toMatch('console.log(a.value!)') - expect(content).toMatch('console.log(a.value as number)') - expect(content).toMatch('console.log(a.value)') - }) - describe('errors', () => { - test('non-let $ref declaration', () => { - expect(() => - compile( - ``, - { refSugar: true } - ) - ).toThrow(`$ref() bindings can only be declared with let`) - }) - - test('$ref w/ destructure', () => { - expect(() => - compile( - ``, - { refSugar: true } - ) - ).toThrow(`$ref() bindings cannot be used with destructuring`) - }) - - test('$computed w/ destructure', () => { - expect(() => - compile( - ``, - { refSugar: true } - ) - ).toThrow(`$computed() bindings cannot be used with destructuring`) - }) - test('defineProps/Emit() referencing ref declarations', () => { expect(() => compile( @@ -368,26 +107,5 @@ describe('`, - { refSugar: true } - ) - ).toThrow(`$ref can only be used directly as a variable initializer`) - - expect(() => - compile( - ``, - { refSugar: true } - ) - ).toThrow(`$computed can only be used directly as a variable initializer`) - }) }) }) diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index 58bd4c12..5b8defac 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -37,6 +37,7 @@ "@vue/compiler-core": "3.2.4", "@vue/compiler-dom": "3.2.4", "@vue/compiler-ssr": "3.2.4", + "@vue/ref-transform": "3.2.4", "@vue/shared": "3.2.4", "consolidate": "^0.16.0", "estree-walker": "^2.0.2", diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 7857a125..8356d717 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -10,7 +10,6 @@ import { UNREF, SimpleExpressionNode, isFunctionType, - isStaticProperty, walkIdentifiers } from '@vue/compiler-dom' import { @@ -45,8 +44,7 @@ import { RestElement, TSInterfaceBody, AwaitExpression, - VariableDeclarator, - VariableDeclaration + Program } from '@babel/types' import { walk } from 'estree-walker' import { RawSourceMap } from 'source-map' @@ -59,6 +57,7 @@ import { compileTemplate, SFCTemplateCompileOptions } from './compileTemplate' import { warnExperimental, warnOnce } from './warn' import { rewriteDefault } from './rewriteDefault' import { createCache } from './cache' +import { transformAST as transformWithRefSugar } from '@vue/ref-transform' // Special compiler macros const DEFINE_PROPS = 'defineProps' @@ -66,12 +65,6 @@ const DEFINE_EMITS = 'defineEmits' const DEFINE_EXPOSE = 'defineExpose' const WITH_DEFAULTS = 'withDefaults' -const $REF = `$ref` -const $SHALLOW_REF = '$shallowRef' -const $COMPUTED = `$computed` -const $FROM_REFS = `$fromRefs` -const $RAW = `$raw` - const isBuiltInDir = makeMap( `once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is` ) @@ -144,6 +137,7 @@ export function compileScript( let { script, scriptSetup, source, filename } = sfc // feature flags const enableRefSugar = !!options.refSugar + let refBindings: string[] | undefined const parseOnly = !!options.parseOnly if (parseOnly && !scriptSetup) { @@ -250,8 +244,7 @@ export function compileScript( const userImports: Record = Object.create(null) const userImportAlias: Record = Object.create(null) const setupBindings: Record = Object.create(null) - const refBindings: Record = Object.create(null) - const refIdentifiers: Set = new Set() + let defaultExport: Node | undefined let hasDefinePropsCall = false let hasDefineEmitCall = false @@ -293,10 +286,10 @@ export function compileScript( input: string, options: ParserOptions, offset: number - ): Statement[] { + ): Program { try { options.errorRecovery = parseOnly - return _parse(input, options).program.body + return _parse(input, options).program } catch (e) { e.message = `[@vue/compiler-sfc] ${e.message}\n\n${ sfc.filename @@ -476,7 +469,7 @@ export function compileScript( } } - for (const node of scriptSetupAst) { + for (const node of scriptSetupAst.body) { const qualified = isQualifiedType(node) if (qualified) { return qualified @@ -531,173 +524,6 @@ export function compileScript( ) } - function isRefSugarCall(callee: string) { - return ( - callee === $REF || - callee === $COMPUTED || - callee === $FROM_REFS || - callee === $SHALLOW_REF - ) - } - - function processRefSugar( - decl: VariableDeclarator, - statement: VariableDeclaration - ) { - if (!isCallOf(decl.init, isRefSugarCall)) { - return - } - - if (!enableRefSugar) { - error( - `ref sugar is an experimental proposal and must be explicitly ` + - `enabled via @vue/compiler-sfc options.`, - // TODO link to RFC details - decl.init - ) - } else { - warnExperimental( - `ref sugar`, - `https://github.com/vuejs/rfcs/discussions/369` - ) - } - - const callee = (decl.init.callee as Identifier).name - const start = decl.init.start! + startOffset - if (callee === $REF || callee === $SHALLOW_REF) { - if (statement.kind !== 'let') { - error(`${callee}() bindings can only be declared with let.`, decl) - } - if (decl.id.type !== 'Identifier') { - error( - `${callee}() bindings cannot be used with destructuring. ` + - `If you are trying to destructure from an object of refs, ` + - `use \`let { x } = $fromRefs(obj)\`.`, - decl.id - ) - } - registerRefBinding(decl.id) - s.overwrite( - start, - start + callee.length, - helper(callee === $REF ? 'ref' : 'shallowRef') - ) - } else if (callee === $COMPUTED) { - if (decl.id.type !== 'Identifier') { - error( - `${callee}() bindings cannot be used with destructuring.`, - decl.id - ) - } - registerRefBinding(decl.id) - s.overwrite(start, start + $COMPUTED.length, helper('computed')) - } else if (callee === $FROM_REFS) { - if (!decl.id.type.endsWith('Pattern')) { - error( - `${callee}() declaration must be used with destructure patterns.`, - decl - ) - } - if (decl.id.type === 'ObjectPattern') { - processRefObjectPattern(decl.id, statement) - } else if (decl.id.type === 'ArrayPattern') { - processRefArrayPattern(decl.id, statement) - } - s.remove(start, start + callee.length) - } - } - - function registerRefBinding(id: Identifier) { - if (id.name[0] === '$') { - error(`ref variable identifiers cannot start with $.`, id) - } - refBindings[id.name] = setupBindings[id.name] = { - type: BindingTypes.SETUP_REF, - rangeNode: id - } - refIdentifiers.add(id) - } - - function processRefObjectPattern( - pattern: ObjectPattern, - statement: VariableDeclaration - ) { - for (const p of pattern.properties) { - let nameId: Identifier | undefined - if (p.type === 'ObjectProperty') { - if (p.key.start! === p.value.start!) { - // shorthand { foo } --> { foo: __foo } - nameId = p.key as Identifier - s.appendLeft(nameId.end! + startOffset, `: __${nameId.name}`) - if (p.value.type === 'AssignmentPattern') { - // { foo = 1 } - refIdentifiers.add(p.value.left as Identifier) - } - } else { - if (p.value.type === 'Identifier') { - // { foo: bar } --> { foo: __bar } - nameId = p.value - s.prependRight(nameId.start! + startOffset, `__`) - } else if (p.value.type === 'ObjectPattern') { - processRefObjectPattern(p.value, statement) - } else if (p.value.type === 'ArrayPattern') { - processRefArrayPattern(p.value, statement) - } else if (p.value.type === 'AssignmentPattern') { - // { foo: bar = 1 } --> { foo: __bar = 1 } - nameId = p.value.left as Identifier - s.prependRight(nameId.start! + startOffset, `__`) - } - } - } else { - // rest element { ...foo } --> { ...__foo } - nameId = p.argument as Identifier - s.prependRight(nameId.start! + startOffset, `__`) - } - if (nameId) { - registerRefBinding(nameId) - // append binding declarations after the parent statement - s.appendLeft( - statement.end! + startOffset, - `\nconst ${nameId.name} = ${helper('shallowRef')}(__${nameId.name});` - ) - } - } - } - - function processRefArrayPattern( - pattern: ArrayPattern, - statement: VariableDeclaration - ) { - for (const e of pattern.elements) { - if (!e) continue - let nameId: Identifier | undefined - if (e.type === 'Identifier') { - // [a] --> [__a] - nameId = e - } else if (e.type === 'AssignmentPattern') { - // [a = 1] --> [__a = 1] - nameId = e.left as Identifier - } else if (e.type === 'RestElement') { - // [...a] --> [...__a] - nameId = e.argument as Identifier - } else if (e.type === 'ObjectPattern') { - processRefObjectPattern(e, statement) - } else if (e.type === 'ArrayPattern') { - processRefArrayPattern(e, statement) - } - if (nameId) { - registerRefBinding(nameId) - // prefix original - s.prependRight(nameId.start! + startOffset, `__`) - // append binding declarations after the parent statement - s.appendLeft( - statement.end! + startOffset, - `\nconst ${nameId.name} = ${helper('shallowRef')}(__${nameId.name});` - ) - } - } - } - function genRuntimeProps(props: Record) { const keys = Object.keys(props) if (!keys.length) { @@ -770,7 +596,7 @@ export function compileScript( scriptStartOffset! ) - for (const node of scriptAst) { + for (const node of scriptAst.body) { if (node.type === 'ImportDeclaration') { // record imports for dedupe for (const specifier of node.specifiers) { @@ -854,7 +680,7 @@ export function compileScript( startOffset ) - for (const node of scriptSetupAst) { + for (const node of scriptSetupAst.body) { const start = node.start! + startOffset let end = node.end! + startOffset // locate comment @@ -1005,8 +831,6 @@ export function compileScript( s.remove(start, end) left-- } - } else { - processRefSugar(decl, node) } } } @@ -1106,54 +930,25 @@ export function compileScript( return { ...scriptSetup, ranges, - scriptAst, - scriptSetupAst + scriptAst: scriptAst?.body, + scriptSetupAst: scriptSetupAst?.body } } - // 3. Do a full walk to rewrite identifiers referencing let exports with ref - // value access + // 3. Apply ref sugar transform if (enableRefSugar) { - const onIdent = (id: Identifier, parent: Node, parentStack: Node[]) => { - if (refBindings[id.name] && !refIdentifiers.has(id)) { - if (isStaticProperty(parent) && parent.shorthand) { - // let binding used in a property shorthand - // { foo } -> { foo: foo.value } - // skip for destructure patterns - if ( - !(parent as any).inPattern || - isInDestructureAssignment(parent, parentStack) - ) { - s.appendLeft(id.end! + startOffset, `: ${id.name}.value`) - } - } else { - s.appendLeft(id.end! + startOffset, '.value') - } - } - } - - const onNode = (node: Node, parent: Node) => { - if (isCallOf(node, $RAW)) { - s.remove( - node.callee.start! + startOffset, - node.callee.end! + startOffset - ) - return false // skip walk - } else if ( - parent && - isCallOf(node, isRefSugarCall) && - (parent.type !== 'VariableDeclarator' || node !== parent.init) - ) { - error( - // @ts-ignore - `${node.callee.name} can only be used directly as a variable initializer.`, - node - ) - } - } - - for (const node of scriptSetupAst) { - walkIdentifiers(node, onIdent, onNode) + warnExperimental( + `ref sugar`, + `https://github.com/vuejs/rfcs/discussions/369` + ) + const { rootVars, importedHelpers } = transformWithRefSugar( + scriptSetupAst, + s, + startOffset + ) + refBindings = rootVars + for (const h of importedHelpers) { + helperImports.add(h) } } @@ -1192,7 +987,7 @@ export function compileScript( // 7. analyze binding metadata if (scriptAst) { - Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst)) + Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body)) } if (propsRuntimeDecl) { for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) { @@ -1215,8 +1010,10 @@ export function compileScript( bindingMetadata[key] = setupBindings[key].type } // known ref bindings - for (const key in refBindings) { - bindingMetadata[key] = BindingTypes.SETUP_REF + if (refBindings) { + for (const key of refBindings) { + bindingMetadata[key] = BindingTypes.SETUP_REF + } } // 8. inject `useCssVars` calls @@ -1437,8 +1234,8 @@ export function compileScript( hires: true, includeContent: true }) as unknown as RawSourceMap, - scriptAst, - scriptSetupAst + scriptAst: scriptAst?.body, + scriptSetupAst: scriptSetupAst?.body } } @@ -1818,27 +1615,6 @@ function canNeverBeRef(node: Node, userReactiveImport: string): boolean { } } -function isInDestructureAssignment(parent: Node, parentStack: Node[]): boolean { - if ( - parent && - (parent.type === 'ObjectProperty' || parent.type === 'ArrayPattern') - ) { - let i = parentStack.length - while (i--) { - const p = parentStack[i] - if (p.type === 'AssignmentExpression') { - const root = parentStack[0] - // if this is a ref: destructure, it should be treated like a - // variable decalration! - return !(root.type === 'LabeledStatement' && root.label.name === 'ref') - } else if (p.type !== 'ObjectProperty' && !p.type.endsWith('Pattern')) { - break - } - } - } - return false -} - /** * Analyze bindings in normal `