feat(reactivity-transform): support optionally importing macros
This commit is contained in:
		
							parent
							
								
									db729ce99e
								
							
						
					
					
						commit
						fbd0fe9759
					
				@ -80,6 +80,18 @@ exports[`handle TS casting syntax 1`] = `
 | 
				
			|||||||
      "
 | 
					      "
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports[`macro import alias and removal 1`] = `
 | 
				
			||||||
 | 
					"import { ref as _ref, toRef as _toRef } from 'vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let a = _ref(1)
 | 
				
			||||||
 | 
					    const __$temp_1 = (useMouse()),
 | 
				
			||||||
 | 
					  x = _toRef(__$temp_1, 'x'),
 | 
				
			||||||
 | 
					  y = _toRef(__$temp_1, 'y')
 | 
				
			||||||
 | 
					    "
 | 
				
			||||||
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports[`mixing $ref & $computed declarations 1`] = `
 | 
					exports[`mixing $ref & $computed declarations 1`] = `
 | 
				
			||||||
"import { ref as _ref, computed as _computed } from 'vue'
 | 
					"import { ref as _ref, computed as _computed } from 'vue'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -362,6 +362,22 @@ test('handle TS casting syntax', () => {
 | 
				
			|||||||
  assertCode(code)
 | 
					  assertCode(code)
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('macro import alias and removal', () => {
 | 
				
			||||||
 | 
					  const { code } = transform(
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					    import { $ as fromRefs, $ref } from 'vue/macros'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let a = $ref(1)
 | 
				
			||||||
 | 
					    const { x, y } = fromRefs(useMouse())
 | 
				
			||||||
 | 
					    `
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  // should remove imports
 | 
				
			||||||
 | 
					  expect(code).not.toMatch(`from 'vue/macros'`)
 | 
				
			||||||
 | 
					  expect(code).toMatch(`let a = _ref(1)`)
 | 
				
			||||||
 | 
					  expect(code).toMatch(`const __$temp_1 = (useMouse())`)
 | 
				
			||||||
 | 
					  assertCode(code)
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('errors', () => {
 | 
					describe('errors', () => {
 | 
				
			||||||
  test('$ref w/ destructure', () => {
 | 
					  test('$ref w/ destructure', () => {
 | 
				
			||||||
    expect(() => transform(`let { a } = $ref(1)`)).toThrow(
 | 
					    expect(() => transform(`let { a } = $ref(1)`)).toThrow(
 | 
				
			||||||
 | 
				
			|||||||
@ -22,7 +22,7 @@ import {
 | 
				
			|||||||
import { parse, ParserPlugin } from '@babel/parser'
 | 
					import { parse, ParserPlugin } from '@babel/parser'
 | 
				
			||||||
import { hasOwn, isArray, isString } from '@vue/shared'
 | 
					import { hasOwn, isArray, isString } from '@vue/shared'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TO_VAR_SYMBOL = '$'
 | 
					const CONVERT_SYMBOL = '$'
 | 
				
			||||||
const ESCAPE_SYMBOL = '$$'
 | 
					const ESCAPE_SYMBOL = '$$'
 | 
				
			||||||
const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef']
 | 
					const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef']
 | 
				
			||||||
const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/
 | 
					const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/
 | 
				
			||||||
@ -114,10 +114,44 @@ export function transformAST(
 | 
				
			|||||||
  // TODO remove when out of experimental
 | 
					  // TODO remove when out of experimental
 | 
				
			||||||
  warnExperimental()
 | 
					  warnExperimental()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let convertSymbol = CONVERT_SYMBOL
 | 
				
			||||||
 | 
					  let escapeSymbol = ESCAPE_SYMBOL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // macro import handling
 | 
				
			||||||
 | 
					  for (const node of ast.body) {
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      node.type === 'ImportDeclaration' &&
 | 
				
			||||||
 | 
					      node.source.value === 'vue/macros'
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      // remove macro imports
 | 
				
			||||||
 | 
					      s.remove(node.start! + offset, node.end! + offset)
 | 
				
			||||||
 | 
					      // check aliasing
 | 
				
			||||||
 | 
					      for (const specifier of node.specifiers) {
 | 
				
			||||||
 | 
					        if (specifier.type === 'ImportSpecifier') {
 | 
				
			||||||
 | 
					          const imported = (specifier.imported as Identifier).name
 | 
				
			||||||
 | 
					          const local = specifier.local.name
 | 
				
			||||||
 | 
					          if (local !== imported) {
 | 
				
			||||||
 | 
					            if (imported === ESCAPE_SYMBOL) {
 | 
				
			||||||
 | 
					              escapeSymbol = local
 | 
				
			||||||
 | 
					            } else if (imported === CONVERT_SYMBOL) {
 | 
				
			||||||
 | 
					              convertSymbol = local
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              error(
 | 
				
			||||||
 | 
					                `macro imports for ref-creating methods do not support aliasing.`,
 | 
				
			||||||
 | 
					                specifier
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const importedHelpers = new Set<string>()
 | 
					  const importedHelpers = new Set<string>()
 | 
				
			||||||
  const rootScope: Scope = {}
 | 
					  const rootScope: Scope = {}
 | 
				
			||||||
  const scopeStack: Scope[] = [rootScope]
 | 
					  const scopeStack: Scope[] = [rootScope]
 | 
				
			||||||
  let currentScope: Scope = rootScope
 | 
					  let currentScope: Scope = rootScope
 | 
				
			||||||
 | 
					  let escapeScope: CallExpression | undefined // inside $$()
 | 
				
			||||||
  const excludedIds = new WeakSet<Identifier>()
 | 
					  const excludedIds = new WeakSet<Identifier>()
 | 
				
			||||||
  const parentStack: Node[] = []
 | 
					  const parentStack: Node[] = []
 | 
				
			||||||
  const propsLocalToPublicMap = Object.create(null)
 | 
					  const propsLocalToPublicMap = Object.create(null)
 | 
				
			||||||
@ -135,6 +169,16 @@ export function transformAST(
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function isRefCreationCall(callee: string): string | false {
 | 
				
			||||||
 | 
					    if (callee === convertSymbol) {
 | 
				
			||||||
 | 
					      return convertSymbol
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (callee[0] === '$' && shorthands.includes(callee.slice(1))) {
 | 
				
			||||||
 | 
					      return callee
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function error(msg: string, node: Node) {
 | 
					  function error(msg: string, node: Node) {
 | 
				
			||||||
    const e = new Error(msg)
 | 
					    const e = new Error(msg)
 | 
				
			||||||
    ;(e as any).node = node
 | 
					    ;(e as any).node = node
 | 
				
			||||||
@ -174,20 +218,16 @@ export function transformAST(
 | 
				
			|||||||
      if (stmt.type === 'VariableDeclaration') {
 | 
					      if (stmt.type === 'VariableDeclaration') {
 | 
				
			||||||
        if (stmt.declare) continue
 | 
					        if (stmt.declare) continue
 | 
				
			||||||
        for (const decl of stmt.declarations) {
 | 
					        for (const decl of stmt.declarations) {
 | 
				
			||||||
          let toVarCall
 | 
					          let refCall
 | 
				
			||||||
          const isCall =
 | 
					          const isCall =
 | 
				
			||||||
            decl.init &&
 | 
					            decl.init &&
 | 
				
			||||||
            decl.init.type === 'CallExpression' &&
 | 
					            decl.init.type === 'CallExpression' &&
 | 
				
			||||||
            decl.init.callee.type === 'Identifier'
 | 
					            decl.init.callee.type === 'Identifier'
 | 
				
			||||||
          if (
 | 
					          if (
 | 
				
			||||||
            isCall &&
 | 
					            isCall &&
 | 
				
			||||||
            (toVarCall = isToVarCall((decl as any).init.callee.name))
 | 
					            (refCall = isRefCreationCall((decl as any).init.callee.name))
 | 
				
			||||||
          ) {
 | 
					          ) {
 | 
				
			||||||
            processRefDeclaration(
 | 
					            processRefDeclaration(refCall, decl.id, decl.init as CallExpression)
 | 
				
			||||||
              toVarCall,
 | 
					 | 
				
			||||||
              decl.id,
 | 
					 | 
				
			||||||
              decl.init as CallExpression
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            const isProps =
 | 
					            const isProps =
 | 
				
			||||||
              isRoot &&
 | 
					              isRoot &&
 | 
				
			||||||
@ -220,7 +260,7 @@ export function transformAST(
 | 
				
			|||||||
    call: CallExpression
 | 
					    call: CallExpression
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    excludedIds.add(call.callee as Identifier)
 | 
					    excludedIds.add(call.callee as Identifier)
 | 
				
			||||||
    if (method === TO_VAR_SYMBOL) {
 | 
					    if (method === convertSymbol) {
 | 
				
			||||||
      // $
 | 
					      // $
 | 
				
			||||||
      // remove macro
 | 
					      // remove macro
 | 
				
			||||||
      s.remove(call.callee.start! + offset, call.callee.end! + offset)
 | 
					      s.remove(call.callee.start! + offset, call.callee.end! + offset)
 | 
				
			||||||
@ -491,9 +531,6 @@ export function transformAST(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  // check root scope first
 | 
					  // check root scope first
 | 
				
			||||||
  walkScope(ast, true)
 | 
					  walkScope(ast, true)
 | 
				
			||||||
 | 
					 | 
				
			||||||
  // inside $$()
 | 
					 | 
				
			||||||
  let escapeScope: CallExpression | undefined
 | 
					 | 
				
			||||||
  ;(walk as any)(ast, {
 | 
					  ;(walk as any)(ast, {
 | 
				
			||||||
    enter(node: Node, parent?: Node) {
 | 
					    enter(node: Node, parent?: Node) {
 | 
				
			||||||
      parent && parentStack.push(parent)
 | 
					      parent && parentStack.push(parent)
 | 
				
			||||||
@ -544,16 +581,16 @@ export function transformAST(
 | 
				
			|||||||
      if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
 | 
					      if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
 | 
				
			||||||
        const callee = node.callee.name
 | 
					        const callee = node.callee.name
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const toVarCall = isToVarCall(callee)
 | 
					        const refCall = isRefCreationCall(callee)
 | 
				
			||||||
        if (toVarCall && (!parent || parent.type !== 'VariableDeclarator')) {
 | 
					        if (refCall && (!parent || parent.type !== 'VariableDeclarator')) {
 | 
				
			||||||
          return error(
 | 
					          return error(
 | 
				
			||||||
            `${toVarCall} can only be used as the initializer of ` +
 | 
					            `${refCall} can only be used as the initializer of ` +
 | 
				
			||||||
              `a variable declaration.`,
 | 
					              `a variable declaration.`,
 | 
				
			||||||
            node
 | 
					            node
 | 
				
			||||||
          )
 | 
					          )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (callee === ESCAPE_SYMBOL) {
 | 
					        if (callee === escapeSymbol) {
 | 
				
			||||||
          s.remove(node.callee.start! + offset, node.callee.end! + offset)
 | 
					          s.remove(node.callee.start! + offset, node.callee.end! + offset)
 | 
				
			||||||
          escapeScope = node
 | 
					          escapeScope = node
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -596,16 +633,6 @@ export function transformAST(
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isToVarCall(callee: string): string | false {
 | 
					 | 
				
			||||||
  if (callee === TO_VAR_SYMBOL) {
 | 
					 | 
				
			||||||
    return TO_VAR_SYMBOL
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  if (callee[0] === TO_VAR_SYMBOL && shorthands.includes(callee.slice(1))) {
 | 
					 | 
				
			||||||
    return callee
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  return false
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const RFC_LINK = `https://github.com/vuejs/rfcs/discussions/369`
 | 
					const RFC_LINK = `https://github.com/vuejs/rfcs/discussions/369`
 | 
				
			||||||
const hasWarned: Record<string, boolean> = {}
 | 
					const hasWarned: Record<string, boolean> = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user