feat: compound expression for v-bind
This commit is contained in:
parent
3474e06542
commit
f644ed4081
|
@ -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`
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<template>
|
||||
<div :id="'hello'"></div>
|
||||
</template>
|
Loading…
Reference in New Issue