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-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`
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 => {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
<template>
|
||||||
|
<div :id="'hello'"></div>
|
||||||
|
</template>
|
Loading…
Reference in New Issue