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`] = `
 | 
			
		||||
"import { ref as _ref, computed as _computed } from 'vue'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -362,6 +362,22 @@ test('handle TS casting syntax', () => {
 | 
			
		||||
  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', () => {
 | 
			
		||||
  test('$ref w/ destructure', () => {
 | 
			
		||||
    expect(() => transform(`let { a } = $ref(1)`)).toThrow(
 | 
			
		||||
 | 
			
		||||
@ -22,7 +22,7 @@ import {
 | 
			
		||||
import { parse, ParserPlugin } from '@babel/parser'
 | 
			
		||||
import { hasOwn, isArray, isString } from '@vue/shared'
 | 
			
		||||
 | 
			
		||||
const TO_VAR_SYMBOL = '$'
 | 
			
		||||
const CONVERT_SYMBOL = '$'
 | 
			
		||||
const ESCAPE_SYMBOL = '$$'
 | 
			
		||||
const shorthands = ['ref', 'computed', 'shallowRef', 'toRef', 'customRef']
 | 
			
		||||
const transformCheckRE = /[^\w]\$(?:\$|ref|computed|shallowRef)?\s*(\(|\<)/
 | 
			
		||||
@ -114,10 +114,44 @@ export function transformAST(
 | 
			
		||||
  // TODO remove when out of experimental
 | 
			
		||||
  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 rootScope: Scope = {}
 | 
			
		||||
  const scopeStack: Scope[] = [rootScope]
 | 
			
		||||
  let currentScope: Scope = rootScope
 | 
			
		||||
  let escapeScope: CallExpression | undefined // inside $$()
 | 
			
		||||
  const excludedIds = new WeakSet<Identifier>()
 | 
			
		||||
  const parentStack: Node[] = []
 | 
			
		||||
  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) {
 | 
			
		||||
    const e = new Error(msg)
 | 
			
		||||
    ;(e as any).node = node
 | 
			
		||||
@ -174,20 +218,16 @@ export function transformAST(
 | 
			
		||||
      if (stmt.type === 'VariableDeclaration') {
 | 
			
		||||
        if (stmt.declare) continue
 | 
			
		||||
        for (const decl of stmt.declarations) {
 | 
			
		||||
          let toVarCall
 | 
			
		||||
          let refCall
 | 
			
		||||
          const isCall =
 | 
			
		||||
            decl.init &&
 | 
			
		||||
            decl.init.type === 'CallExpression' &&
 | 
			
		||||
            decl.init.callee.type === 'Identifier'
 | 
			
		||||
          if (
 | 
			
		||||
            isCall &&
 | 
			
		||||
            (toVarCall = isToVarCall((decl as any).init.callee.name))
 | 
			
		||||
            (refCall = isRefCreationCall((decl as any).init.callee.name))
 | 
			
		||||
          ) {
 | 
			
		||||
            processRefDeclaration(
 | 
			
		||||
              toVarCall,
 | 
			
		||||
              decl.id,
 | 
			
		||||
              decl.init as CallExpression
 | 
			
		||||
            )
 | 
			
		||||
            processRefDeclaration(refCall, decl.id, decl.init as CallExpression)
 | 
			
		||||
          } else {
 | 
			
		||||
            const isProps =
 | 
			
		||||
              isRoot &&
 | 
			
		||||
@ -220,7 +260,7 @@ export function transformAST(
 | 
			
		||||
    call: CallExpression
 | 
			
		||||
  ) {
 | 
			
		||||
    excludedIds.add(call.callee as Identifier)
 | 
			
		||||
    if (method === TO_VAR_SYMBOL) {
 | 
			
		||||
    if (method === convertSymbol) {
 | 
			
		||||
      // $
 | 
			
		||||
      // remove macro
 | 
			
		||||
      s.remove(call.callee.start! + offset, call.callee.end! + offset)
 | 
			
		||||
@ -491,9 +531,6 @@ export function transformAST(
 | 
			
		||||
 | 
			
		||||
  // check root scope first
 | 
			
		||||
  walkScope(ast, true)
 | 
			
		||||
 | 
			
		||||
  // inside $$()
 | 
			
		||||
  let escapeScope: CallExpression | undefined
 | 
			
		||||
  ;(walk as any)(ast, {
 | 
			
		||||
    enter(node: Node, parent?: Node) {
 | 
			
		||||
      parent && parentStack.push(parent)
 | 
			
		||||
@ -544,16 +581,16 @@ export function transformAST(
 | 
			
		||||
      if (node.type === 'CallExpression' && node.callee.type === 'Identifier') {
 | 
			
		||||
        const callee = node.callee.name
 | 
			
		||||
 | 
			
		||||
        const toVarCall = isToVarCall(callee)
 | 
			
		||||
        if (toVarCall && (!parent || parent.type !== 'VariableDeclarator')) {
 | 
			
		||||
        const refCall = isRefCreationCall(callee)
 | 
			
		||||
        if (refCall && (!parent || parent.type !== 'VariableDeclarator')) {
 | 
			
		||||
          return error(
 | 
			
		||||
            `${toVarCall} can only be used as the initializer of ` +
 | 
			
		||||
            `${refCall} can only be used as the initializer of ` +
 | 
			
		||||
              `a variable declaration.`,
 | 
			
		||||
            node
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (callee === ESCAPE_SYMBOL) {
 | 
			
		||||
        if (callee === escapeSymbol) {
 | 
			
		||||
          s.remove(node.callee.start! + offset, node.callee.end! + offset)
 | 
			
		||||
          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 hasWarned: Record<string, boolean> = {}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user