feat(reactivity-transform): support optionally importing macros

This commit is contained in:
Evan You 2021-12-11 18:28:03 +08:00
parent db729ce99e
commit fbd0fe9759
3 changed files with 81 additions and 26 deletions

View File

@ -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'

View File

@ -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(

View File

@ -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> = {}