Skip to content

Commit

Permalink
wip: support v-bind="{}"
Browse files Browse the repository at this point in the history
  • Loading branch information
ygj6 committed Jan 31, 2024
1 parent d7ab873 commit 7742b06
Show file tree
Hide file tree
Showing 8 changed files with 330 additions and 34 deletions.
6 changes: 5 additions & 1 deletion packages/compiler-vapor/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { SourceMapGenerator } from 'source-map-js'
import { isString } from '@vue/shared'
import type { ParserPlugin } from '@babel/parser'
import { genSetProp } from './generators/prop'
import { genSetArrProps, genSetObjProps, genSetProp } from './generators/prop'
import { genCreateTextNode, genSetText } from './generators/text'
import { genSetEvent } from './generators/event'
import { genSetHtml } from './generators/html'
Expand Down Expand Up @@ -348,6 +348,10 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
switch (oper.type) {
case IRNodeTypes.SET_PROP:
return genSetProp(oper, context)
case IRNodeTypes.SET_OBJ_PROPS:
return genSetObjProps(oper, context)
case IRNodeTypes.SET_ARR_PROPS:
return genSetArrProps(oper, context)
case IRNodeTypes.SET_TEXT:
return genSetText(oper, context)
case IRNodeTypes.SET_EVENT:
Expand Down
67 changes: 66 additions & 1 deletion packages/compiler-vapor/src/generators/prop.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { CodegenContext } from '../generate'
import type { SetPropIRNode } from '../ir'
import type { SetArrPropsIRNode, SetObjPropsIRNode, SetPropIRNode } from '../ir'
import { genExpression } from './expression'
import { isString } from '@vue/shared'
import { NewlineType } from '@vue/compiler-core'

export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
Expand Down Expand Up @@ -64,3 +65,67 @@ export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
() => genExpression(oper.value, context),
)
}

export function genSetObjProps(
oper: SetObjPropsIRNode,
context: CodegenContext,
) {
const { push, pushFnCall, newline, vaporHelper } = context

newline()

pushFnCall(vaporHelper('setObjProps'), `n${oper.element}`, () => {
// TODO genObjectExpression
if (!oper.value) {
push('{}', NewlineType.None, oper.loc)
}
push('{')
for (let i = 0; i < oper.value.length; i++) {
const { key, value } = oper.value[i]
push('[')
genExpression(key, context)
push(']:')
genExpression(value, context)
push(',')
}
push('}')
})
}

export function genSetArrProps(
oper: SetArrPropsIRNode,
context: CodegenContext,
) {
const { push, pushFnCall, newline, vaporHelper } = context

newline()
pushFnCall(
vaporHelper('setArrProps'),
`n${oper.element}`,
() => {
// TODO genArrayExpression
push('[')
for (let i = 0; i < oper.value.length; i++) {
const props = oper.value[i]
for (const prop of props) {
// TODO genObjectExpression
if ('key' in prop) {
push('{')
const { key, value } = prop
push('[')
genExpression(key, context)
push(']:')
genExpression(value, context)
push(',')
push('}')
} else {
genExpression(prop, context)
}
push(',')
}
}
push(']')
},
`${oper.needMerge}`,
)
}
17 changes: 17 additions & 0 deletions packages/compiler-vapor/src/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export enum IRNodeTypes {
FRAGMENT_FACTORY,

SET_PROP,
SET_OBJ_PROPS,
SET_ARR_PROPS,
SET_TEXT,
SET_EVENT,
SET_HTML,
Expand Down Expand Up @@ -83,6 +85,19 @@ export interface SetPropIRNode extends BaseIRNode {
runtimeCamelize: boolean
}

export interface SetObjPropsIRNode extends BaseIRNode {
type: IRNodeTypes.SET_OBJ_PROPS
element: number
value: any
}

export interface SetArrPropsIRNode extends BaseIRNode {
type: IRNodeTypes.SET_ARR_PROPS
element: number
value: any
needMerge: boolean
}

export interface SetTextIRNode extends BaseIRNode {
type: IRNodeTypes.SET_TEXT
element: number
Expand Down Expand Up @@ -166,6 +181,8 @@ export type IRNode =
| FragmentFactoryIRNode
export type OperationNode =
| SetPropIRNode
| SetObjPropsIRNode
| SetArrPropsIRNode
| SetTextIRNode
| SetEventIRNode
| SetHtmlIRNode
Expand Down
10 changes: 8 additions & 2 deletions packages/compiler-vapor/src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type RootNode,
type TemplateChildNode,
defaultOnError,
defaultOnWarn,
defaultOnWarn, type Property,

Check failure on line 12 in packages/compiler-vapor/src/transform.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

Member 'Property' of the import declaration should be sorted alphabetically
isVSlot,
} from '@vue/compiler-dom'
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
Expand All @@ -36,7 +36,13 @@ export type DirectiveTransform = (
dir: VaporDirectiveNode,
node: ElementNode,
context: TransformContext<ElementNode>,
) => void
) => void | DirectiveTransformResult

export interface DirectiveTransformResult {
props: Property[]
modifier?: '.' | '^'
runtimeCamelize: boolean
}

// A structural directive transform is technically also a NodeTransform;
// Only v-if and v-for fall into this category.
Expand Down
121 changes: 117 additions & 4 deletions packages/compiler-vapor/src/transforms/transformElement.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import {

Check failure on line 1 in packages/compiler-vapor/src/transforms/transformElement.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

Import "ExpressionNode" is only used as types
type AttributeNode,
createCompilerError,
type ElementNode,

Check failure on line 4 in packages/compiler-vapor/src/transforms/transformElement.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

Member 'ElementNode' of the import declaration should be sorted alphabetically
ElementTypes,
ErrorCodes,
ExpressionNode,
isStaticExp,
NodeTypes,
type ObjectExpression,
} from '@vue/compiler-dom'
import { isBuiltInDirective, isReservedProp, isVoidTag } from '@vue/shared'
import type { NodeTransform, TransformContext } from '../transform'
import {

Check failure on line 13 in packages/compiler-vapor/src/transforms/transformElement.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

All imports in the declaration are only used as types. Use `import type`
DirectiveTransformResult,
NodeTransform,
TransformContext,
} from '../transform'
import { IRNodeTypes, type VaporDirectiveNode } from '../ir'

export const transformElement: NodeTransform = (node, ctx) => {
Expand Down Expand Up @@ -49,16 +58,120 @@ function buildProps(
props: ElementNode['props'] = node.props,
isComponent: boolean,
) {
const expressions = []
const properties: ObjectExpression['properties'] = []
const mergeArgs: ExpressionNode[] = []

const pushMergeArg = (arg?: ExpressionNode) => {
if (properties.length) {
// TODO dedupe properties
mergeArgs.push([...properties])
properties.length = 0
}
}

for (const prop of props) {
transformProp(prop as VaporDirectiveNode | AttributeNode, node, context)
if (prop.type === NodeTypes.DIRECTIVE) {
const isVBind = prop.name === 'bind'
if (!prop.arg && isVBind) {
if (prop.exp) {
if (isVBind) {
pushMergeArg()
expressions.push(prop.exp)
mergeArgs.push([prop.exp])
}
} else {
context.options.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, prop.loc),
)
}
continue
}
}

const result = transformProp(
prop as VaporDirectiveNode | AttributeNode,
node,
context,
)
if (result) {
const { props: propsObj } = result
for (const prop of propsObj) {
!isStaticExp(prop.key) && expressions.push(prop.key)
!isStaticExp(prop.value) && expressions.push(prop.value)
}
properties.push(...propsObj)
}
}

if (mergeArgs.length) {
pushMergeArg()
if (mergeArgs.length > 1) {
context.registerEffect(expressions, [
{
type: IRNodeTypes.SET_ARR_PROPS,
loc: node.loc,
element: context.reference(),
value: mergeArgs,
needMerge: true,
},
])
} else {
context.registerEffect(expressions, [
{
type: IRNodeTypes.SET_ARR_PROPS,
loc: node.loc,
element: context.reference(),
value: mergeArgs,
needMerge: false,
},
])
}
} else if (properties.length) {
let hasDynamicKey = false
for (let i = 0; i < properties.length; i++) {
const key = properties[i].key
if (isStaticExp(key)) {
// todo
} else if (!key.isHandlerKey) {
hasDynamicKey = true
}
}
if (!hasDynamicKey) {
// TODO handle class/style prop
for (const prop of properties) {
context.registerEffect(
[prop.value],
[
{
...prop,

Check failure on line 147 in packages/compiler-vapor/src/transforms/transformElement.ts

View workflow job for this annotation

GitHub Actions / lint-and-test-dts

Using 'ObjectExpression > SpreadElement' is not allowed
type: IRNodeTypes.SET_PROP,
loc: prop.loc,
element: context.reference(),
key: prop.key,
value: prop.value,
},
],
)
}
} else {
context.registerEffect(expressions, [
{
type: IRNodeTypes.SET_OBJ_PROPS,
loc: node.loc,
element: context.reference(),
value: properties,
},
])
}
}
}

function transformProp(
prop: VaporDirectiveNode | AttributeNode,
node: ElementNode,
context: TransformContext<ElementNode>,
): void {
): void | DirectiveTransformResult {
const { name, loc } = prop
if (isReservedProp(name)) return

Expand All @@ -70,7 +183,7 @@ function transformProp(

const directiveTransform = context.options.directiveTransforms[name]
if (directiveTransform) {
directiveTransform(prop, node, context)
return directiveTransform(prop, node, context)
} else if (!isBuiltInDirective(name)) {
context.registerOperation({
type: IRNodeTypes.WITH_DIRECTIVE,
Expand Down
38 changes: 14 additions & 24 deletions packages/compiler-vapor/src/transforms/vBind.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
createSimpleExpression,
} from '@vue/compiler-dom'
import { camelize, isReservedProp } from '@vue/shared'
import { IRNodeTypes } from '../ir'
import type { DirectiveTransform } from '../transform'

export function normalizeBindShorthand(
Expand All @@ -19,12 +18,9 @@ export function normalizeBindShorthand(
}

export const transformVBind: DirectiveTransform = (dir, node, context) => {
let { arg, exp, loc, modifiers } = dir
let { exp, loc, modifiers } = dir
const arg = dir.arg!

if (!arg) {
// TODO support v-bind="{}"
return
}
if (arg.isStatic && isReservedProp(arg.content)) return

if (!exp) exp = normalizeBindShorthand(arg)
Expand All @@ -46,22 +42,16 @@ export const transformVBind: DirectiveTransform = (dir, node, context) => {
return
}

context.registerEffect(
[exp],
[
{
type: IRNodeTypes.SET_PROP,
loc: dir.loc,
element: context.reference(),
key: arg,
value: exp,
runtimeCamelize: camel,
modifier: modifiers.includes('prop')
? '.'
: modifiers.includes('attr')
? '^'
: undefined,
},
],
)
return {
props: [{
key: arg,
value: exp,
runtimeCamelize: camel,
modifier: modifiers.includes('prop')
? '.'
: modifiers.includes('attr')
? '^'
: undefined,
}]
}
}

0 comments on commit 7742b06

Please sign in to comment.