feat(compiler-vapor): resolve directive

This commit is contained in:
三咲智子 Kevin Deng 2024-05-13 02:08:58 +08:00
parent 30f98942db
commit e2b51d6e7a
No known key found for this signature in database
11 changed files with 143 additions and 115 deletions

View File

@ -16,30 +16,34 @@ exports[`compile > custom directive > basic 1`] = `
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const _directive_test = _resolveDirective("test")
const _directive_hello = _resolveDirective("hello")
const n0 = t0() const n0 = t0()
_withDirectives(n0, [[_resolveDirective("vTest")], [_resolveDirective("vHello"), void 0, void 0, { world: true }]]) _withDirectives(n0, [[_directive_test], [_directive_hello, void 0, void 0, { world: true }]])
return n0 return n0
}" }"
`; `;
exports[`compile > custom directive > component 1`] = ` exports[`compile > custom directive > component 1`] = `
"import { resolveComponent as _resolveComponent, createComponent as _createComponent, resolveDirective as _resolveDirective, withDirectives as _withDirectives, insert as _insert, createIf as _createIf, template as _template } from 'vue/vapor'; "import { resolveComponent as _resolveComponent, resolveDirective as _resolveDirective, createComponent as _createComponent, withDirectives as _withDirectives, insert as _insert, createIf as _createIf, template as _template } from 'vue/vapor';
const t0 = _template("<div></div>") const t0 = _template("<div></div>")
export function render(_ctx) { export function render(_ctx) {
const _component_Bar = _resolveComponent("Bar") const _component_Bar = _resolveComponent("Bar")
const _component_Comp = _resolveComponent("Comp") const _component_Comp = _resolveComponent("Comp")
const _directive_hello = _resolveDirective("hello")
const _directive_test = _resolveDirective("test")
const n4 = _createComponent(_component_Comp, null, { default: () => { const n4 = _createComponent(_component_Comp, null, { default: () => {
const n0 = _createIf(() => (true), () => { const n0 = _createIf(() => (true), () => {
const n3 = t0() const n3 = t0()
const n2 = _createComponent(_component_Bar) const n2 = _createComponent(_component_Bar)
_withDirectives(n2, [[_resolveDirective("vHello"), void 0, void 0, { world: true }]]) _withDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
_insert(n2, n3) _insert(n2, n3)
return n3 return n3
}) })
return n0 return n0
} }, null, true) } }, null, true)
_withDirectives(n4, [[_resolveDirective("vTest")]]) _withDirectives(n4, [[_directive_test]])
return n4 return n4
}" }"
`; `;

View File

@ -71,9 +71,10 @@ export function render(_ctx) {
`; `;
exports[`compiler: transform <slot> outlets > error on unexpected custom directive on <slot> 1`] = ` exports[`compiler: transform <slot> outlets > error on unexpected custom directive on <slot> 1`] = `
"import { createSlot as _createSlot } from 'vue/vapor'; "import { resolveDirective as _resolveDirective, createSlot as _createSlot } from 'vue/vapor';
export function render(_ctx) { export function render(_ctx) {
const _directive_foo = _resolveDirective("foo")
const n0 = _createSlot("default", null) const n0 = _createSlot("default", null)
return n0 return n0
}" }"

View File

@ -36,7 +36,7 @@ describe('compiler: element transform', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0, id: 0,
tag: 'Foo', tag: 'Foo',
resolve: true, asset: true,
root: true, root: true,
props: [[]], props: [[]],
}, },
@ -66,7 +66,7 @@ describe('compiler: element transform', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Example', tag: 'Example',
resolve: false, asset: false,
}, },
]) ])
}) })
@ -172,7 +172,7 @@ describe('compiler: element transform', () => {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
id: 0, id: 0,
tag: 'Example', tag: 'Example',
resolve: true, asset: true,
}, },
]) ])
}) })
@ -212,7 +212,7 @@ describe('compiler: element transform', () => {
{ {
type: IRNodeTypes.CREATE_COMPONENT_NODE, type: IRNodeTypes.CREATE_COMPONENT_NODE,
tag: 'Foo', tag: 'Foo',
resolve: true, asset: true,
root: true, root: true,
props: [ props: [
[ [

View File

@ -1,4 +1,4 @@
import type { BlockIRNode } from '../ir' import type { BlockIRNode, VaporHelper } from '../ir'
import { import {
type CodeFragment, type CodeFragment,
DELIMITERS_ARRAY, DELIMITERS_ARRAY,
@ -12,6 +12,7 @@ import {
import type { CodegenContext } from '../generate' import type { CodegenContext } from '../generate'
import { genEffects, genOperations } from './operation' import { genEffects, genOperations } from './operation'
import { genChildren } from './template' import { genChildren } from './template'
import { toValidAssetId } from '@vue/compiler-dom'
export function genBlock( export function genBlock(
oper: BlockIRNode, oper: BlockIRNode,
@ -43,16 +44,8 @@ export function genBlockContent(
const resetBlock = context.enterBlock(block) const resetBlock = context.enterBlock(block)
if (root) { if (root) {
for (const name of context.ir.component) { genResolveAssets('component', 'resolveComponent')
push( genResolveAssets('directive', 'resolveDirective')
NEWLINE,
`const _component_${name} = `,
...genCall(
context.vaporHelper('resolveComponent'),
JSON.stringify(name),
),
)
}
} }
for (const child of dynamic.children) { for (const child of dynamic.children) {
@ -77,4 +70,17 @@ export function genBlockContent(
resetBlock() resetBlock()
return frag return frag
function genResolveAssets(
kind: 'component' | 'directive',
helper: VaporHelper,
) {
for (const name of context.ir[kind]) {
push(
NEWLINE,
`const ${toValidAssetId(name, kind)} = `,
...genCall(context.vaporHelper(helper), JSON.stringify(name)),
)
}
}
} }

View File

@ -21,7 +21,7 @@ import {
} from './utils' } from './utils'
import { genExpression } from './expression' import { genExpression } from './expression'
import { genPropKey } from './prop' import { genPropKey } from './prop'
import { createSimpleExpression } from '@vue/compiler-dom' import { createSimpleExpression, toValidAssetId } from '@vue/compiler-dom'
import { genEventHandler } from './event' import { genEventHandler } from './event'
import { genDirectiveModifiers, genDirectivesForElement } from './directive' import { genDirectiveModifiers, genDirectivesForElement } from './directive'
import { genModelHandler } from './modelValue' import { genModelHandler } from './modelValue'
@ -52,8 +52,8 @@ export function genCreateComponent(
] ]
function genTag() { function genTag() {
if (oper.resolve) { if (oper.asset) {
return [`_component_${oper.tag}`] return toValidAssetId(oper.tag, 'component')
} else { } else {
return genExpression( return genExpression(
extend(createSimpleExpression(oper.tag, false), { ast: null }), extend(createSimpleExpression(oper.tag, false), { ast: null }),

View File

@ -1,5 +1,9 @@
import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom' import {
import { camelize } from '@vue/shared' createSimpleExpression,
isSimpleIdentifier,
toValidAssetId,
} from '@vue/compiler-dom'
import { extend } from '@vue/shared'
import { genExpression } from './expression' import { genExpression } from './expression'
import type { CodegenContext } from '../generate' import type { CodegenContext } from '../generate'
import { import {
@ -36,7 +40,12 @@ export function genWithDirective(
...genCall(vaporHelper('withDirectives'), element, directives), ...genCall(vaporHelper('withDirectives'), element, directives),
] ]
function genDirective({ dir, builtin }: WithDirectiveIRNode): CodeFragment[] { function genDirective({
dir,
name,
builtin,
asset,
}: WithDirectiveIRNode): CodeFragment[] {
const directive = genDirective() const directive = genDirective()
const value = dir.exp && ['() => ', ...genExpression(dir.exp, context)] const value = dir.exp && ['() => ', ...genExpression(dir.exp, context)]
const argument = dir.arg && genExpression(dir.arg, context) const argument = dir.arg && genExpression(dir.arg, context)
@ -55,24 +64,15 @@ export function genWithDirective(
) )
function genDirective() { function genDirective() {
const { if (builtin) {
vaporHelper, return vaporHelper(name as any)
options: { bindingMetadata }, } else if (asset) {
} = context return toValidAssetId(name, 'directive')
if (dir.name === 'show') {
return [vaporHelper('vShow')]
} else if (builtin) {
return [vaporHelper(builtin)]
} else { } else {
const directiveReference = camelize(`v-${dir.name}`) return genExpression(
// TODO resolve directive extend(createSimpleExpression(name, false), { ast: null }),
if (bindingMetadata[directiveReference]) { context,
const directiveExpression = createSimpleExpression(directiveReference) )
directiveExpression.ast = null
return genExpression(directiveExpression, context)
} else {
return `${vaporHelper('resolveDirective')}("${directiveReference}")`
}
} }
} }
} }

View File

@ -60,6 +60,7 @@ export interface RootIRNode {
source: string source: string
template: string[] template: string[]
component: Set<string> component: Set<string>
directive: Set<string>
block: BlockIRNode block: BlockIRNode
} }
@ -197,7 +198,9 @@ export interface WithDirectiveIRNode extends BaseIRNode {
type: IRNodeTypes.WITH_DIRECTIVE type: IRNodeTypes.WITH_DIRECTIVE
element: number element: number
dir: VaporDirectiveNode dir: VaporDirectiveNode
builtin?: VaporHelper name: string
builtin?: boolean
asset?: boolean
} }
export interface ComponentSlotBlockIRNode extends BlockIRNode { export interface ComponentSlotBlockIRNode extends BlockIRNode {
@ -219,7 +222,7 @@ export interface CreateComponentIRNode extends BaseIRNode {
slots?: ComponentSlots slots?: ComponentSlots
dynamicSlots?: ComponentDynamicSlot[] dynamicSlots?: ComponentDynamicSlot[]
resolve: boolean asset: boolean
root: boolean root: boolean
} }

View File

@ -79,6 +79,8 @@ export class TransformContext<T extends AllNode = AllNode> {
comment: CommentNode[] = [] comment: CommentNode[] = []
component: Set<string> = this.ir.component component: Set<string> = this.ir.component
directive: Set<string> = this.ir.directive
slots?: ComponentSlots slots?: ComponentSlots
dynamicSlots?: ComponentDynamicSlot[] dynamicSlots?: ComponentDynamicSlot[]
@ -220,6 +222,7 @@ export function transform(
source: node.source, source: node.source,
template: [], template: [],
component: new Set(), component: new Set(),
directive: new Set(),
block: newBlock(node), block: newBlock(node),
} }

View File

@ -72,24 +72,24 @@ function transformComponentElement(
propsResult: PropsResult, propsResult: PropsResult,
context: TransformContext, context: TransformContext,
) { ) {
let resolve = true let asset = true
if (!__BROWSER__) { if (!__BROWSER__) {
const fromSetup = resolveSetupReference(tag, context) const fromSetup = resolveSetupReference(tag, context)
if (fromSetup) { if (fromSetup) {
tag = fromSetup tag = fromSetup
resolve = false asset = false
} }
const dotIndex = tag.indexOf('.') const dotIndex = tag.indexOf('.')
if (dotIndex > 0) { if (dotIndex > 0) {
const ns = resolveSetupReference(tag.slice(0, dotIndex), context) const ns = resolveSetupReference(tag.slice(0, dotIndex), context)
if (ns) { if (ns) {
tag = ns + tag.slice(dotIndex) tag = ns + tag.slice(dotIndex)
resolve = false asset = false
} }
} }
} }
if (resolve) { if (asset) {
context.component.add(tag) context.component.add(tag)
} }
@ -102,7 +102,7 @@ function transformComponentElement(
id: context.reference(), id: context.reference(),
tag, tag,
props: propsResult[0] ? propsResult[1] : [propsResult[1]], props: propsResult[0] ? propsResult[1] : [propsResult[1]],
resolve, asset,
root, root,
slots: context.slots, slots: context.slots,
dynamicSlots: context.dynamicSlots, dynamicSlots: context.dynamicSlots,
@ -287,7 +287,7 @@ function transformProp(
node: ElementNode, node: ElementNode,
context: TransformContext<ElementNode>, context: TransformContext<ElementNode>,
): DirectiveTransformResult | void { ): DirectiveTransformResult | void {
const { name } = prop let { name } = prop
if (prop.type === NodeTypes.ATTRIBUTE) { if (prop.type === NodeTypes.ATTRIBUTE) {
if (isReservedProp(name)) return if (isReservedProp(name)) return
@ -305,10 +305,20 @@ function transformProp(
} }
if (!isBuiltInDirective(name)) { if (!isBuiltInDirective(name)) {
const fromSetup =
!__BROWSER__ && resolveSetupReference(`v-${name}`, context)
if (fromSetup) {
name = fromSetup
} else {
context.directive.add(name)
}
context.registerOperation({ context.registerOperation({
type: IRNodeTypes.WITH_DIRECTIVE, type: IRNodeTypes.WITH_DIRECTIVE,
element: context.reference(), element: context.reference(),
dir: prop, dir: prop,
name,
asset: !fromSetup,
}) })
} }
} }

View File

@ -62,8 +62,6 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
} }
const isComponent = node.tagType === ElementTypes.COMPONENT const isComponent = node.tagType === ElementTypes.COMPONENT
let runtimeDirective: VaporHelper | undefined
if (isComponent) { if (isComponent) {
return { return {
key: arg ? arg : createSimpleExpression('modelValue', true), key: arg ? arg : createSimpleExpression('modelValue', true),
@ -71,74 +69,74 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
model: true, model: true,
modelModifiers: dir.modifiers, modelModifiers: dir.modifiers,
} }
} else { }
if (dir.arg)
context.options.onError( if (dir.arg)
createDOMCompilerError( context.options.onError(
DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT, createDOMCompilerError(
dir.arg.loc, DOMErrorCodes.X_V_MODEL_ARG_ON_ELEMENT,
), dir.arg.loc,
) ),
const { tag } = node )
const isCustomElement = context.options.isCustomElement(tag) const { tag } = node
runtimeDirective = 'vModelText' const isCustomElement = context.options.isCustomElement(tag)
if ( let runtimeDirective: VaporHelper | undefined = 'vModelText'
tag === 'input' || if (
tag === 'textarea' || tag === 'input' ||
tag === 'select' || tag === 'textarea' ||
isCustomElement tag === 'select' ||
) { isCustomElement
if (tag === 'input' || isCustomElement) { ) {
const type = findProp(node, 'type') if (tag === 'input' || isCustomElement) {
if (type) { const type = findProp(node, 'type')
if (type.type === NodeTypes.DIRECTIVE) { if (type) {
// :type="foo" if (type.type === NodeTypes.DIRECTIVE) {
runtimeDirective = 'vModelDynamic' // :type="foo"
} else if (type.value) {
switch (type.value.content) {
case 'radio':
runtimeDirective = 'vModelRadio'
break
case 'checkbox':
runtimeDirective = 'vModelCheckbox'
break
case 'file':
runtimeDirective = undefined
context.options.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
dir.loc,
),
)
break
default:
// text type
__DEV__ && checkDuplicatedValue()
break
}
}
} else if (hasDynamicKeyVBind(node)) {
// element has bindings with dynamic keys, which can possibly contain
// "type".
runtimeDirective = 'vModelDynamic' runtimeDirective = 'vModelDynamic'
} else { } else if (type.value) {
// text type switch (type.value.content) {
__DEV__ && checkDuplicatedValue() case 'radio':
runtimeDirective = 'vModelRadio'
break
case 'checkbox':
runtimeDirective = 'vModelCheckbox'
break
case 'file':
runtimeDirective = undefined
context.options.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
dir.loc,
),
)
break
default:
// text type
__DEV__ && checkDuplicatedValue()
break
}
} }
} else if (tag === 'select') { } else if (hasDynamicKeyVBind(node)) {
runtimeDirective = 'vModelSelect' // element has bindings with dynamic keys, which can possibly contain
// "type".
runtimeDirective = 'vModelDynamic'
} else { } else {
// textarea // text type
__DEV__ && checkDuplicatedValue() __DEV__ && checkDuplicatedValue()
} }
} else if (tag === 'select') {
runtimeDirective = 'vModelSelect'
} else { } else {
context.options.onError( // textarea
createDOMCompilerError( __DEV__ && checkDuplicatedValue()
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
dir.loc,
),
)
} }
} else {
context.options.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_INVALID_ELEMENT,
dir.loc,
),
)
} }
context.registerOperation({ context.registerOperation({
@ -154,7 +152,8 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => {
type: IRNodeTypes.WITH_DIRECTIVE, type: IRNodeTypes.WITH_DIRECTIVE,
element: context.reference(), element: context.reference(),
dir, dir,
builtin: runtimeDirective, name: runtimeDirective,
builtin: true,
}) })
function checkDuplicatedValue() { function checkDuplicatedValue() {

View File

@ -14,5 +14,7 @@ export const transformVShow: DirectiveTransform = (dir, node, context) => {
type: IRNodeTypes.WITH_DIRECTIVE, type: IRNodeTypes.WITH_DIRECTIVE,
element: context.reference(), element: context.reference(),
dir, dir,
name: 'vShow',
builtin: true,
}) })
} }