// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compile > bindings 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div>count is <!>.</div>\\")
const n0 = t0()
const { 0: [n3, { 1: [n2],}],} = _children(n0)
`;
exports[`compile > directives > v-bind > simple expression 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
`;
exports[`compile > directives > v-html > no expression 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _effect(() => {
- _setHtml(n1, undefined, \\"\\")
- })
+ _setHtml(n1, undefined, \\"\\")
return n0
}
-import { template as _template, children as _children, effect as _effect, setHtml as _setHtml } from 'vue/vapor'
+import { template as _template, children as _children, setHtml as _setHtml } from 'vue/vapor'
"
`;
exports[`compile > directives > v-html > simple expression 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
`;
exports[`compile > directives > v-on > event modifier 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
`;
exports[`compile > directives > v-on > simple expression 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
`;
exports[`compile > directives > v-once > as root node 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
`;
exports[`compile > directives > v-once > basic 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div> <span></span></div>\\")
const n0 = t0()
const { 0: [n3, { 1: [n2],}],} = _children(n0)
`;
exports[`compile > directives > v-pre > basic 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div :id=\\\\\\"foo\\\\\\"><Comp></Comp>{{ bar }}</div>\\")
const n0 = t0()
return n0
`;
exports[`compile > directives > v-pre > self-closing v-pre 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div><div><Comp></Comp></div>\\")
const n0 = t0()
const { 1: [n1],} = _children(n0)
`;
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div :id=\\\\\\"foo\\\\\\"><Comp></Comp>{{ bar }}</div><div><Comp></Comp></div>\\")
const n0 = t0()
const { 1: [n1],} = _children(n0)
`;
exports[`compile > directives > v-text > no expression 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
- _effect(() => {
- _setText(n1, undefined, \\"\\")
- })
+ _setText(n1, undefined, \\"\\")
return n0
}
-import { template as _template, children as _children, effect as _effect, setText as _setText } from 'vue/vapor'
+import { template as _template, children as _children, setText as _setText } from 'vue/vapor'
"
`;
exports[`compile > directives > v-text > simple expression 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div></div>\\")
const n0 = t0()
const { 0: [n1],} = _children(n0)
`;
exports[`compile > dynamic root 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _fragment()
const n0 = t0()
`;
exports[`compile > dynamic root nodes and interpolation 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<button>foo<!>foo</button>\\")
const n0 = t0()
const { 0: [n1, { 1: [n5],}],} = _children(n0)
})
_effect(() => {
_setAttr(n1, \\"id\\", undefined, count)
+ })
+ _effect(() => {
_setText(n2, undefined, count)
+ })
+ _effect(() => {
_setText(n3, undefined, count)
+ })
+ _effect(() => {
_setText(n4, undefined, count)
})
return n0
`;
exports[`compile > fragment 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<p></p><span></span><div></div>\\")
const n0 = t0()
return n0
`;
exports[`compile > static + dynamic root 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"3<!>6<!>9\\")
const n0 = t0()
const { 1: [n9], 3: [n10],} = _children(n0)
`;
exports[`compile > static template 1`] = `
-"export function render(_ctx) {
+"
+export function render(_ctx) {
const t0 = _template(\\"<div><p>hello</p><input><span></span></div>\\")
const n0 = t0()
return n0
NewlineType,
advancePositionWithMutation,
locStub,
+ NodeTypes,
+ BindingTypes,
} from '@vue/compiler-dom'
import {
- type DynamicChildren,
+ type IRDynamicChildren,
type RootIRNode,
IRNodeTypes,
OperationNode,
VaporHelper,
- IRNode,
+ BaseIRNode,
+ IRExpression,
} from './ir'
import { SourceMapGenerator } from 'source-map-js'
+import { isString } from '@vue/shared'
// remove when stable
// @ts-expect-error
function checkNever(x: never): never {}
-export interface CodegenContext
- extends Omit<Required<CodegenOptions>, 'bindingMetadata' | 'inline'> {
+export interface CodegenContext extends Required<CodegenOptions> {
source: string
code: string
line: number
indentLevel: number
map?: SourceMapGenerator
- push(code: string, newlineIndex?: number, node?: IRNode): void
- pushWithNewline(code: string, newlineIndex?: number, node?: IRNode): void
+ push(code: string, newlineIndex?: number, node?: BaseIRNode): void
+ pushWithNewline(code: string, newlineIndex?: number, node?: BaseIRNode): void
indent(): void
deindent(): void
newline(): void
ssr = false,
isTS = false,
inSSR = false,
+ inline = false,
+ bindingMetadata = {},
}: CodegenOptions,
) {
const { helpers, vaporHelpers } = ir
ssr,
isTS,
inSSR,
+ bindingMetadata,
+ inline,
source: ir.source,
code: ``,
if (isSetupInlined) {
push(`(() => {`)
} else {
- push(`export function ${functionName}(_ctx) {`)
+ pushWithNewline(`export function ${functionName}(_ctx) {`)
}
indent()
for (const operation of ir.operation) {
genOperation(operation, ctx)
}
- for (const [_expr, operations] of Object.entries(ir.effect)) {
+ for (const { operations } of ir.effect) {
pushWithNewline(`${vaporHelper('effect')}(() => {`)
indent()
for (const operation of operations) {
NewlineType.End,
)
+ console.log(ctx.code)
+
return {
code: ctx.code,
ast: ir as any,
}
}
-function genOperation(
- oper: OperationNode,
- { vaporHelper, pushWithNewline }: CodegenContext,
-) {
+function genOperation(oper: OperationNode, context: CodegenContext) {
+ const { vaporHelper, pushWithNewline } = context
// TODO: cache old value
switch (oper.type) {
case IRNodeTypes.SET_PROP: {
pushWithNewline(
- `${vaporHelper('setAttr')}(n${oper.element}, ${JSON.stringify(
+ `${vaporHelper('setAttr')}(n${oper.element}, ${processExpression(
oper.name,
- )}, undefined, ${oper.value})`,
+ context,
+ )}, undefined, ${processExpression(oper.value, context)})`,
)
return
}
case IRNodeTypes.SET_TEXT: {
pushWithNewline(
- `${vaporHelper('setText')}(n${oper.element}, undefined, ${oper.value})`,
+ `${vaporHelper('setText')}(n${
+ oper.element
+ }, undefined, ${processExpression(oper.value, context)})`,
)
return
}
case IRNodeTypes.SET_EVENT: {
let value = oper.value
if (oper.modifiers.length) {
- value = `${vaporHelper('withModifiers')}(${value}, ${genArrayExpression(
- oper.modifiers,
- )})`
+ value = `${vaporHelper('withModifiers')}(${processExpression(
+ value,
+ context,
+ )}, ${genArrayExpression(oper.modifiers)})`
}
pushWithNewline(
- `${vaporHelper('on')}(n${oper.element}, ${JSON.stringify(
+ `${vaporHelper('on')}(n${oper.element}, ${processExpression(
oper.name,
- )}, ${value})`,
+ context,
+ )}, ${processExpression(value, context)})`,
)
return
}
case IRNodeTypes.SET_HTML: {
pushWithNewline(
- `${vaporHelper('setHtml')}(n${oper.element}, undefined, ${oper.value})`,
+ `${vaporHelper('setHtml')}(n${
+ oper.element
+ }, undefined, ${processExpression(oper.value, context)})`,
)
return
}
case IRNodeTypes.CREATE_TEXT_NODE: {
pushWithNewline(
- `const n${oper.id} = ${vaporHelper('createTextNode')}(${oper.value})`,
+ `const n${oper.id} = ${vaporHelper(
+ 'createTextNode',
+ )}(${processExpression(oper.value, context)})`,
)
return
}
}
}
-function genChildren(children: DynamicChildren) {
+function genChildren(children: IRDynamicChildren) {
let code = ''
// TODO
let offset = 0
function genArrayExpression(elements: string[]) {
return `[${elements.map((it) => `"${it}"`).join(', ')}]`
}
+
+function processExpression(
+ exp: IRExpression,
+ { inline, prefixIdentifiers, bindingMetadata, vaporHelper }: CodegenContext,
+) {
+ if (isString(exp)) return exp
+
+ let content = ''
+ if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
+ content = exp.content
+ if (exp.isStatic) {
+ return JSON.stringify(content)
+ }
+ } else {
+ // TODO NodeTypes.COMPOUND_EXPRESSION
+ }
+
+ switch (bindingMetadata[content]) {
+ case BindingTypes.SETUP_REF:
+ content += '.value'
+ break
+ case BindingTypes.SETUP_MAYBE_REF:
+ content = `${vaporHelper('unref')}(${content})`
+ break
+ }
+
+ if (prefixIdentifiers && !inline) {
+ content = `_ctx.${content}`
+ }
+
+ return content
+}
-import type { SourceLocation } from '@vue/compiler-dom'
+import type {
+ ExpressionNode,
+ RootNode,
+ SourceLocation,
+} from '@vue/compiler-dom'
export enum IRNodeTypes {
ROOT,
CREATE_TEXT_NODE,
}
-export interface IRNode {
+export interface BaseIRNode {
type: IRNodeTypes
loc: SourceLocation
}
// TODO refactor
export type VaporHelper = keyof typeof import('../../runtime-vapor/src')
-export interface RootIRNode extends IRNode {
+export interface RootIRNode extends BaseIRNode {
type: IRNodeTypes.ROOT
source: string
+ node: RootNode
template: Array<TemplateFactoryIRNode | FragmentFactoryIRNode>
- dynamic: DynamicInfo
- // TODO multi-expression effect
- effect: Record<string /* expr */, OperationNode[]>
+ dynamic: IRDynamicInfo
+ effect: IREffect[]
operation: OperationNode[]
helpers: Set<string>
vaporHelpers: Set<VaporHelper>
}
-export interface TemplateFactoryIRNode extends IRNode {
+export interface TemplateFactoryIRNode extends BaseIRNode {
type: IRNodeTypes.TEMPLATE_FACTORY
template: string
}
-export interface FragmentFactoryIRNode extends IRNode {
+export interface FragmentFactoryIRNode extends BaseIRNode {
type: IRNodeTypes.FRAGMENT_FACTORY
}
-export interface SetPropIRNode extends IRNode {
+export interface SetPropIRNode extends BaseIRNode {
type: IRNodeTypes.SET_PROP
element: number
- name: string
- value: string
+ name: IRExpression
+ value: IRExpression
}
-export interface SetTextIRNode extends IRNode {
+export interface SetTextIRNode extends BaseIRNode {
type: IRNodeTypes.SET_TEXT
element: number
- value: string
+ value: IRExpression
}
-export interface SetEventIRNode extends IRNode {
+export interface SetEventIRNode extends BaseIRNode {
type: IRNodeTypes.SET_EVENT
element: number
- name: string
- value: string
+ name: IRExpression
+ value: IRExpression
modifiers: string[]
}
-export interface SetHtmlIRNode extends IRNode {
+export interface SetHtmlIRNode extends BaseIRNode {
type: IRNodeTypes.SET_HTML
element: number
- value: string
+ value: IRExpression
}
-export interface CreateTextNodeIRNode extends IRNode {
+export interface CreateTextNodeIRNode extends BaseIRNode {
type: IRNodeTypes.CREATE_TEXT_NODE
id: number
- value: string
+ value: IRExpression
}
-export interface InsertNodeIRNode extends IRNode {
+export interface InsertNodeIRNode extends BaseIRNode {
type: IRNodeTypes.INSERT_NODE
element: number | number[]
parent: number
anchor: number
}
-export interface PrependNodeIRNode extends IRNode {
+export interface PrependNodeIRNode extends BaseIRNode {
type: IRNodeTypes.PREPEND_NODE
elements: number[]
parent: number
}
-export interface AppendNodeIRNode extends IRNode {
+export interface AppendNodeIRNode extends BaseIRNode {
type: IRNodeTypes.APPEND_NODE
elements: number[]
parent: number
}
+export type IRNode =
+ | OperationNode
+ | RootIRNode
+ | TemplateFactoryIRNode
+ | FragmentFactoryIRNode
export type OperationNode =
| SetPropIRNode
| SetTextIRNode
| PrependNodeIRNode
| AppendNodeIRNode
-export interface DynamicInfo {
+export interface IRDynamicInfo {
id: number | null
referenced: boolean
/** created by DOM API */
ghost: boolean
placeholder: number | null
- children: DynamicChildren
+ children: IRDynamicChildren
+}
+export type IRDynamicChildren = Record<number, IRDynamicInfo>
+
+export type IRExpression = ExpressionNode | string
+export interface IREffect {
+ // TODO multi-expression effect
+ expressions: IRExpression[]
+ operations: OperationNode[]
}
-export type DynamicChildren = Record<number, DynamicInfo>
type InterpolationNode,
type TransformOptions as BaseTransformOptions,
type DirectiveNode,
- type ExpressionNode,
type ParentNode,
type AllNode,
type CompilerCompatOptions,
NodeTypes,
- BindingTypes,
defaultOnError,
defaultOnWarn,
ErrorCodes,
import {
type OperationNode,
type RootIRNode,
+ type IRDynamicInfo,
+ type IRExpression,
IRNodeTypes,
- DynamicInfo,
} from './ir'
import type { HackOptions } from './hack'
>
template: string
- dynamic: DynamicInfo
+ dynamic: IRDynamicInfo
inVOnce: boolean
reference(): number
increaseId(): number
registerTemplate(): number
- registerEffect(expr: string, operation: OperationNode): void
+ registerEffect(
+ expressions: Array<IRExpression | null | undefined>,
+ operation: OperationNode[],
+ ): void
registerOperation(...operations: OperationNode[]): void
helper(name: string): string
}
this.dynamic.referenced = true
return (this.dynamic.id = this.increaseId())
},
- registerEffect(expr, operation) {
- if (this.inVOnce) {
- return this.registerOperation(operation)
+ registerEffect(expressions, operations) {
+ if (
+ this.inVOnce ||
+ (expressions = expressions.filter(Boolean)).length === 0
+ ) {
+ return this.registerOperation(...operations)
}
- if (!effect[expr]) effect[expr] = []
- effect[expr].push(operation)
+ // TODO combine effects
+ effect.push({
+ expressions: expressions as IRExpression[],
+ operations,
+ })
},
template: '',
const ir: RootIRNode = {
type: IRNodeTypes.ROOT,
+ node: root,
source: root.source,
loc: root.loc,
template: [],
placeholder: null,
children: {},
},
- effect: Object.create(null),
+ effect: [],
operation: [],
helpers: new Set([]),
vaporHelpers: new Set([]),
if (ctx.node.type === NodeTypes.ROOT) ctx.registerTemplate()
function processDynamicChildren() {
- let prevChildren: DynamicInfo[] = []
+ let prevChildren: IRDynamicInfo[] = []
let hasStatic = false
for (let index = 0; index < children.length; index++) {
const child = ctx.dynamic.children[index]
) {
const { node } = ctx
- if (node.content.type === NodeTypes.COMPOUND_EXPRESSION) {
- // TODO: CompoundExpressionNode: {{ count + 1 }}
- return
- }
-
- const expr = processExpression(ctx, node.content)!
+ const expr = node.content
if (isFirst && isLast) {
const parent = ctx.parent!
const parentId = parent.reference()
- ctx.registerEffect(expr, {
- type: IRNodeTypes.SET_TEXT,
- loc: node.loc,
- element: parentId,
- value: expr,
- })
+ ctx.registerEffect(
+ [expr],
+ [
+ {
+ type: IRNodeTypes.SET_TEXT,
+ loc: node.loc,
+ element: parentId,
+ value: expr,
+ },
+ ],
+ )
} else {
const id = ctx.reference()
ctx.dynamic.ghost = true
id,
value: expr,
})
- ctx.registerEffect(expr, {
- type: IRNodeTypes.SET_TEXT,
- loc: node.loc,
- element: id,
- value: expr,
- })
+ ctx.registerEffect(
+ [expr],
+ [
+ {
+ type: IRNodeTypes.SET_TEXT,
+ loc: node.loc,
+ element: id,
+ value: expr,
+ },
+ ],
+ )
}
}
return
}
- const { exp, loc, modifiers } = node
+ const { arg, exp, loc, modifiers } = node
- const expr = processExpression(ctx, exp)
switch (name) {
case 'bind': {
if (
return
}
- if (expr === null) {
+ if (exp === null) {
// TODO: Vue 3.4 supported shorthand syntax
// https://github.com/vuejs/core/pull/9451
return
- } else if (!node.arg) {
+ } else if (!arg) {
// TODO support v-bind="{}"
return
- } else if (node.arg.type === NodeTypes.COMPOUND_EXPRESSION) {
- // TODO support :[foo]="bar"
- return
}
- ctx.registerEffect(expr, {
- type: IRNodeTypes.SET_PROP,
- loc: node.loc,
- element: ctx.reference(),
- name: node.arg.content,
- value: expr,
- })
+ ctx.registerEffect(
+ [exp],
+ [
+ {
+ type: IRNodeTypes.SET_PROP,
+ loc: node.loc,
+ element: ctx.reference(),
+ name: arg,
+ value: exp,
+ },
+ ],
+ )
break
}
case 'on': {
return
}
- if (!node.arg) {
+ if (!arg) {
// TODO support v-on="{}"
return
- } else if (node.arg.type === NodeTypes.COMPOUND_EXPRESSION) {
- // TODO support @[foo]="bar"
- return
- } else if (expr === null) {
+ } else if (exp === undefined) {
// TODO: support @foo
// https://github.com/vuejs/core/pull/9451
return
}
- ctx.registerEffect(expr, {
- type: IRNodeTypes.SET_EVENT,
- loc: node.loc,
- element: ctx.reference(),
- name: node.arg.content,
- value: expr,
- modifiers,
- })
+ ctx.registerEffect(
+ [exp],
+ [
+ {
+ type: IRNodeTypes.SET_EVENT,
+ loc: node.loc,
+ element: ctx.reference(),
+ name: arg,
+ value: exp,
+ modifiers,
+ },
+ ],
+ )
break
}
case 'html': {
- const value = expr || '""'
- ctx.registerEffect(value, {
- type: IRNodeTypes.SET_HTML,
- loc: node.loc,
- element: ctx.reference(),
- value,
- })
+ ctx.registerEffect(
+ [exp],
+ [
+ {
+ type: IRNodeTypes.SET_HTML,
+ loc: node.loc,
+ element: ctx.reference(),
+ value: exp || '""',
+ },
+ ],
+ )
break
}
case 'text': {
- const value = expr || '""'
- ctx.registerEffect(value, {
- type: IRNodeTypes.SET_TEXT,
- loc: node.loc,
- element: ctx.reference(),
- value,
- })
+ ctx.registerEffect(
+ [exp],
+ [
+ {
+ type: IRNodeTypes.SET_TEXT,
+ loc: node.loc,
+ element: ctx.reference(),
+ value: exp || '""',
+ },
+ ],
+ )
break
}
case 'cloak': {
}
}
}
-
-// TODO: reuse packages/compiler-core/src/transforms/transformExpression.ts
-function processExpression(
- ctx: TransformContext,
- expr: ExpressionNode | undefined,
-): string | null {
- if (!expr) return null
- if (expr.type === NodeTypes.COMPOUND_EXPRESSION) {
- // TODO
- return ''
- }
-
- let { content } = expr
- if (ctx.options.bindingMetadata?.[content] === BindingTypes.SETUP_REF) {
- content += '.value'
- }
- if (ctx.options.prefixIdentifiers && !ctx.options.inline) {
- content = `_ctx.${content}`
- }
- return content
-}