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-text`
- [x] `v-pre` - [x] `v-pre`
- [x] `v-cloak` - [x] `v-cloak`
- [x] `v-bind`
- [x] simple expression
- [x] compound expression
- [ ] `v-on` - [ ] `v-on`
- [x] simple expression - [x] simple expression
- [ ] compound expression - [ ] compound expression
- [x] modifiers - [x] modifiers
- [ ] `v-bind`
- [x] simple expression
- [ ] compound expression
- [ ] runtime directives - [ ] runtime directives
- #19 - #19
- [ ] `v-memo` - [ ] `v-memo`

View File

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

View File

@ -7,8 +7,9 @@ import {
advancePositionWithMutation, advancePositionWithMutation,
locStub, locStub,
BindingTypes, BindingTypes,
isSimpleIdentifier,
createSimpleExpression, createSimpleExpression,
walkIdentifiers,
advancePositionWithClone,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { import {
type IRDynamicChildren, type IRDynamicChildren,
@ -20,15 +21,16 @@ import {
type SetEventIRNode, type SetEventIRNode,
type WithDirectiveIRNode, type WithDirectiveIRNode,
type SetTextIRNode, type SetTextIRNode,
type SetHtmlIRNode,
type CreateTextNodeIRNode,
type InsertNodeIRNode,
type PrependNodeIRNode,
type AppendNodeIRNode,
IRNodeTypes, IRNodeTypes,
SetHtmlIRNode,
CreateTextNodeIRNode,
InsertNodeIRNode,
PrependNodeIRNode,
AppendNodeIRNode,
} from './ir' } from './ir'
import { SourceMapGenerator } from 'source-map-js' import { SourceMapGenerator } from 'source-map-js'
import { camelize, isString } from '@vue/shared' import { camelize, isString } from '@vue/shared'
import type { Identifier } from '@babel/types'
// remove when stable // remove when stable
// @ts-expect-error // @ts-expect-error
@ -467,7 +469,9 @@ function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) {
// TODO resolve directive // TODO resolve directive
const directiveReference = camelize(`v-${oper.name}`) const directiveReference = camelize(`v-${oper.name}`)
if (bindingMetadata[directiveReference]) { if (bindingMetadata[directiveReference]) {
genExpression(createSimpleExpression(directiveReference), context) const directiveExpression = createSimpleExpression(directiveReference)
directiveExpression.ast = null
genExpression(directiveExpression, context)
} }
if (oper.binding) { if (oper.binding) {
@ -483,37 +487,82 @@ function genArrayExpression(elements: string[]) {
return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]` return `[${elements.map((it) => JSON.stringify(it)).join(', ')}]`
} }
function genExpression( function genExpression(node: IRExpression, context: CodegenContext): void {
exp: IRExpression, const { push } = context
{ if (isString(node)) return push(node)
inline,
prefixIdentifiers,
bindingMetadata,
vaporHelper,
push,
}: CodegenContext,
) {
if (isString(exp)) return push(exp)
let { content } = exp const { content: rawExpr, ast, isStatic, loc } = node
let name: string | undefined if (__BROWSER__) {
return push(rawExpr)
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}`
}
} }
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 { import type {
CompoundExpressionNode,
DirectiveNode, DirectiveNode,
RootNode, RootNode,
SimpleExpressionNode, SimpleExpressionNode,
@ -166,10 +167,10 @@ export type HackOptions<T> = Prettify<
> >
> >
export type HackDirectiveNode = Overwrite< export type VaporDirectiveNode = Overwrite<
DirectiveNode, DirectiveNode,
{ {
exp: SimpleExpressionNode | undefined exp: Exclude<DirectiveNode['exp'], CompoundExpressionNode>
arg: SimpleExpressionNode | undefined arg: Exclude<DirectiveNode['arg'], CompoundExpressionNode>
} }
> >

View File

@ -18,7 +18,7 @@ import {
type IRExpression, type IRExpression,
IRNodeTypes, IRNodeTypes,
} from './ir' } from './ir'
import type { HackDirectiveNode, HackOptions } from './ir' import type { VaporDirectiveNode, HackOptions } from './ir'
export type NodeTransform = ( export type NodeTransform = (
node: RootNode | TemplateChildNode, node: RootNode | TemplateChildNode,
@ -26,12 +26,9 @@ export type NodeTransform = (
) => void | (() => void) | (() => void)[] ) => void | (() => void) | (() => void)[]
export type DirectiveTransform = ( export type DirectiveTransform = (
dir: HackDirectiveNode, dir: VaporDirectiveNode,
node: ElementNode, node: ElementNode,
context: TransformContext<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 ) => void
export type TransformOptions = HackOptions<BaseTransformOptions> export type TransformOptions = HackOptions<BaseTransformOptions>

View File

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

View File

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

View File

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