feat: compound expression for v-bind

This commit is contained in:
三咲智子 Kevin Deng 2023-12-06 00:15:57 +08:00
parent 3474e06542
commit f644ed4081
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
8 changed files with 109 additions and 51 deletions

View File

@ -25,13 +25,13 @@ PR are welcome! However, please create an issue before you start to work on it,
- [x] `v-text`
- [x] `v-pre`
- [x] `v-cloak`
- [x] `v-bind`
- [x] simple expression
- [x] compound expression
- [ ] `v-on`
- [x] simple expression
- [ ] compound expression
- [x] modifiers
- [ ] `v-bind`
- [x] simple expression
- [ ] compound expression
- [ ] runtime directives
- #19
- [ ] `v-memo`

View File

@ -70,9 +70,13 @@ export interface CodegenResult {
}
export enum NewlineType {
/** Start with `\n` */
Start = 0,
/** Ends with `\n` */
End = -1,
/** No `\n` included */
None = -2,
/** Don't know, calc it */
Unknown = -3
}

View File

@ -7,8 +7,9 @@ import {
advancePositionWithMutation,
locStub,
BindingTypes,
isSimpleIdentifier,
createSimpleExpression,
walkIdentifiers,
advancePositionWithClone,
} from '@vue/compiler-dom'
import {
type IRDynamicChildren,
@ -20,15 +21,16 @@ import {
type SetEventIRNode,
type WithDirectiveIRNode,
type SetTextIRNode,
type SetHtmlIRNode,
type CreateTextNodeIRNode,
type InsertNodeIRNode,
type PrependNodeIRNode,
type AppendNodeIRNode,
IRNodeTypes,
SetHtmlIRNode,
CreateTextNodeIRNode,
InsertNodeIRNode,
PrependNodeIRNode,
AppendNodeIRNode,
} from './ir'
import { SourceMapGenerator } from 'source-map-js'
import { camelize, isString } from '@vue/shared'
import type { Identifier } from '@babel/types'
// remove when stable
// @ts-expect-error
@ -467,7 +469,9 @@ function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
// TODO resolve directive
const directiveReference = camelize(`v-${oper.name}`)
if (bindingMetadata[directiveReference]) {
genExpression(createSimpleExpression(directiveReference), context)
const directiveExpression = createSimpleExpression(directiveReference)
directiveExpression.ast = null
genExpression(directiveExpression, context)
}
if (oper.binding) {
@ -483,37 +487,82 @@ function genArrayExpression(elements: string[]) {
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
}
function genExpression(
exp: IRExpression,
{
inline,
prefixIdentifiers,
bindingMetadata,
vaporHelper,
push,
}: CodegenContext,
) {
if (isString(exp)) return push(exp)
function genExpression(node: IRExpression, context: CodegenContext): void {
const { push } = context
if (isString(node)) return push(node)
let { content } = exp
let name: string | undefined
if (exp.isStatic) {
content = JSON.stringify(content)
} else {
switch (bindingMetadata[content]) {
case BindingTypes.SETUP_REF:
content += '.value'
break
case BindingTypes.SETUP_MAYBE_REF:
content = `${vaporHelper('unref')}(${content})`
break
}
if (prefixIdentifiers && !inline) {
if (isSimpleIdentifier(content)) name = content
content = `_ctx.${content}`
}
const { content: rawExpr, ast, isStatic, loc } = node
if (__BROWSER__) {
return push(rawExpr)
}
push(content, NewlineType.None, exp.loc, name)
if (
!context.prefixIdentifiers ||
!node.content.trim() ||
// there was a parsing error
ast === false
) {
return push(rawExpr, NewlineType.None, node.loc)
}
if (isStatic) {
// TODO
return push(JSON.stringify(rawExpr))
}
if (ast === null) {
// the expression is a simple identifier
return genIdentifier(rawExpr, context, loc)
}
const ids: Identifier[] = []
walkIdentifiers(
ast!,
(id) => {
ids.push(id)
},
true,
)
ids.sort((a, b) => a.start! - b.start!)
ids.forEach((id, i) => {
// range is offset by -1 due to the wrapping parens when parsed
const start = id.start! - 1
const end = id.end! - 1
const last = ids[i - 1]
const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start)
if (leadingText.length) push(leadingText, NewlineType.Unknown)
const source = rawExpr.slice(start, end)
genIdentifier(source, context, {
start: advancePositionWithClone(node.loc.start, source, start),
end: advancePositionWithClone(node.loc.start, source, end),
source,
})
if (i === ids.length - 1 && end < rawExpr.length) {
push(rawExpr.slice(end), NewlineType.Unknown)
}
})
}
function genIdentifier(
id: string,
{ inline, bindingMetadata, vaporHelper, push }: CodegenContext,
loc?: SourceLocation,
): void {
let name: string | undefined = id
if (inline) {
switch (bindingMetadata[id]) {
case BindingTypes.SETUP_REF:
name = id += '.value'
break
case BindingTypes.SETUP_MAYBE_REF:
id = `${vaporHelper('unref')}(${id})`
name = undefined
break
}
} else {
id = `_ctx.${id}`
}
push(id, NewlineType.None, loc, name)
}

View File

@ -1,4 +1,5 @@
import type {
CompoundExpressionNode,
DirectiveNode,
RootNode,
SimpleExpressionNode,
@ -166,10 +167,10 @@ export type HackOptions<T> = Prettify<
>
>
export type HackDirectiveNode = Overwrite<
export type VaporDirectiveNode = Overwrite<
DirectiveNode,
{
exp: SimpleExpressionNode | undefined
arg: SimpleExpressionNode | undefined
exp: Exclude<DirectiveNode['exp'], CompoundExpressionNode>
arg: Exclude<DirectiveNode['arg'], CompoundExpressionNode>
}
>

View File

@ -18,7 +18,7 @@ import {
type IRExpression,
IRNodeTypes,
} from './ir'
import type { HackDirectiveNode, HackOptions } from './ir'
import type { VaporDirectiveNode, HackOptions } from './ir'
export type NodeTransform = (
node: RootNode | TemplateChildNode,
@ -26,12 +26,9 @@ export type NodeTransform = (
) => void | (() => void) | (() => void)[]
export type DirectiveTransform = (
dir: HackDirectiveNode,
dir: VaporDirectiveNode,
node: ElementNode,
context: TransformContext<ElementNode>,
// a platform specific compiler can import the base transform and augment
// it by passing in this optional argument.
// augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,
) => void
export type TransformOptions = HackOptions<BaseTransformOptions>

View File

@ -8,7 +8,7 @@ import {
} from '@vue/compiler-dom'
import { isBuiltInDirective, isVoidTag } from '@vue/shared'
import { NodeTransform, TransformContext } from '../transform'
import { HackDirectiveNode, IRNodeTypes } from '../ir'
import { VaporDirectiveNode, IRNodeTypes } from '../ir'
export const transformElement: NodeTransform = (node, ctx) => {
return function postTransformElement() {
@ -52,12 +52,12 @@ function buildProps(
isComponent: boolean,
) {
for (const prop of props) {
transformProp(prop as HackDirectiveNode | AttributeNode, node, context)
transformProp(prop as VaporDirectiveNode | AttributeNode, node, context)
}
}
function transformProp(
prop: HackDirectiveNode | AttributeNode,
prop: VaporDirectiveNode | AttributeNode,
node: ElementNode,
context: TransformContext<ElementNode>,
): void {

View File

@ -78,6 +78,10 @@ window.init = () => {
const start = performance.now()
const { code, ast, map } = compileFn(source, {
...compilerOptions,
prefixIdentifiers:
compilerOptions.prefixIdentifiers ||
compilerOptions.mode === 'module' ||
compilerOptions.ssr,
filename: 'ExampleTemplate.vue',
sourceMap: true,
onError: err => {

View File

@ -0,0 +1,3 @@
<template>
<div :id="'hello'"></div>
</template>