`;
exports[`compile > custom directive > basic 1`] = `
-"import { template as _template, children as _children, withDirectives as _withDirectives, resolveDirective("vTest") as _resolveDirective("vTest"), resolveDirective("vHello") as _resolveDirective("vHello") } from 'vue/vapor';
+"import { template as _template, children as _children, resolveDirective("vTest") as _resolveDirective("vTest"), resolveDirective("vHello") as _resolveDirective("vHello"), withDirectives as _withDirectives } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-if > basic v-if 1`] = `
-"import { template as _template, fragment as _fragment, createIf as _createIf, children as _children, renderEffect as _renderEffect, setText as _setText, append as _append } from 'vue/vapor';
+"import { template as _template, fragment as _fragment, children as _children, renderEffect as _renderEffect, setText as _setText, createIf as _createIf, append as _append } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`compiler: v-if > template v-if 1`] = `
-"import { template as _template, fragment as _fragment, createIf as _createIf, children as _children, renderEffect as _renderEffect, setText as _setText, append as _append } from 'vue/vapor';
+"import { template as _template, fragment as _fragment, children as _children, renderEffect as _renderEffect, setText as _setText, createIf as _createIf, append as _append } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>hello<p></p>")
`;
exports[`v-on > event modifier 1`] = `
-"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers, withKeys as _withKeys } from 'vue/vapor';
+"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on, withKeys as _withKeys } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<a></a><form></form><a></a><div></div><div></div><a></a><div></div><input><input><input><input><input><input><input><input><input><input><input><input><input><input><input>")
`;
exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = `
-"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = `
-"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers, withKeys as _withKeys } from 'vue/vapor';
+"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on, withKeys as _withKeys } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should support multiple modifiers and event options w/ prefixIdentifiers: true 1`] = `
-"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should transform click.middle 1`] = `
-"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should transform click.middle 2`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should transform click.right 1`] = `
-"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should transform click.right 2`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should wrap both for dynamic key event w/ left/right modifiers 1`] = `
-"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, renderEffect as _renderEffect, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
-"import { template as _template, children as _children, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
+"import { template as _template, children as _children, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
`;
exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = `
-"import { template as _template, children as _children, on as _on, withKeys as _withKeys } from 'vue/vapor';
+"import { template as _template, children as _children, withKeys as _withKeys, on as _on } from 'vue/vapor';
export function render(_ctx) {
const t0 = _template("<div></div>")
import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
import { genWithDirective } from './generators/directive'
import { genIf } from './generators/if'
+import { genTemplate } from './generators/template'
interface CodegenOptions extends BaseCodegenOptions {
expressionPlugins?: ParserPlugin[]
// @ts-expect-error
function checkNever(x: never): never {}
+export type CodeFragment =
+ | string
+ | [code: string, newlineIndex?: number, loc?: SourceLocation, name?: string]
+ | undefined
+
export interface CodegenContext {
options: Required<CodegenOptions>
source: string
- code: string
- line: number
- column: number
- offset: number
+ code: CodeFragment[]
indentLevel: number
map?: SourceMapGenerator
- push(
- code: string,
- newlineIndex?: number,
- loc?: SourceLocation,
- name?: string,
- ): void
- newline(
- code?: string,
- newlineIndex?: number,
- loc?: SourceLocation,
- name?: string,
- ): void
- pushMulti(
- codes: [left: string, right: string, segment?: string],
- ...fn: Array<false | string | (() => void)>
- ): void
- pushCall(name: string, ...args: Array<false | string | (() => void)>): void
- withIndent(fn: () => void): void
+ push(...args: CodeFragment[]): void
+ newline(): CodeFragment
+ multi(
+ codes: [left: string, right: string, segment: string],
+ ...fn: Array<false | CodeFragment[]>
+ ): CodeFragment[]
+ call(name: string, ...args: Array<false | CodeFragment[]>): CodeFragment[]
+ withIndent<T>(fn: () => T): T
helpers: Set<string>
vaporHelpers: Set<string>
function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
const helpers = new Set<string>([])
const vaporHelpers = new Set<string>([])
+ const [code, push] = buildCodeFragment()
const context: CodegenContext = {
options: extend(
{
),
source: ir.source,
- code: '',
- column: 1,
- line: 1,
- offset: 0,
+ code,
indentLevel: 0,
helpers,
return `_${name}`
},
- push(code, newlineIndex = NewlineType.None, loc, name) {
- context.code += code
- if (!__BROWSER__ && context.map) {
- if (loc) addMapping(loc.start, name)
-
- if (newlineIndex === NewlineType.Unknown) {
- // multiple newlines, full iteration
- advancePositionWithMutation(context, code)
- } else {
- // fast paths
- context.offset += code.length
- if (newlineIndex === NewlineType.None) {
- // no newlines; fast path to avoid newline detection
- if (__TEST__ && code.includes('\n')) {
- throw new Error(
- `CodegenContext.push() called newlineIndex: none, but contains` +
- `newlines: ${code.replace(/\n/g, '\\n')}`,
- )
- }
- context.column += code.length
- } else {
- // single newline at known index
- if (newlineIndex === NewlineType.End) {
- newlineIndex = code.length - 1
- }
- if (
- __TEST__ &&
- (code.charAt(newlineIndex) !== '\n' ||
- code.slice(0, newlineIndex).includes('\n') ||
- code.slice(newlineIndex + 1).includes('\n'))
- ) {
- throw new Error(
- `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
- `but does not conform: ${code.replace(/\n/g, '\\n')}`,
- )
- }
- context.line++
- context.column = code.length - newlineIndex
- }
- }
- if (loc && loc !== locStub) {
- addMapping(loc.end)
- }
- }
+ push,
+ newline() {
+ return [`\n${` `.repeat(context.indentLevel)}`, NewlineType.Start]
},
- newline(code, newlineIndex, node) {
- context.push(`\n${` `.repeat(context.indentLevel)}`, NewlineType.Start)
- code && context.push(code, newlineIndex, node)
- },
- pushMulti([left, right, seg], ...fns) {
+ multi([left, right, seg], ...fns) {
+ const frag: CodeFragment[] = []
fns = fns.filter(Boolean)
- context.push(left)
+ frag.push(left)
for (const [i, fn] of fns.entries()) {
- if (isString(fn)) context.push(fn)
- else (fn as () => void)()
- if (seg && i < fns.length - 1) context.push(seg)
+ if (fn) {
+ frag.push(...fn)
+ if (i < fns.length - 1) frag.push(seg)
+ }
}
- context.push(right)
+ frag.push(right)
+ return frag
},
- pushCall(name, ...args) {
- context.push(name)
- context.pushMulti(['(', ')', ', '], ...args)
+ call(name, ...args) {
+ return [name, ...context.multi(['(', ')', ', '], ...args)]
},
withIndent(fn) {
++context.indentLevel
- fn()
+ const ret = fn()
--context.indentLevel
+ return ret
},
}
const filename = context.options.filename
-
- function addMapping(loc: Position, name: string | null = null) {
- // we use the private property to directly add the mapping
- // because the addMapping() implementation in source-map-js has a bunch of
- // unnecessary arg and validation checks that are pure overhead in our case.
- const { _names, _mappings } = context.map!
- if (name !== null && !_names.has(name)) _names.add(name)
- _mappings.add({
- originalLine: loc.line,
- originalColumn: loc.column - 1, // source-map column is 0 based
- generatedLine: context.line,
- generatedColumn: context.column - 1,
- source: filename,
- // @ts-expect-error it is possible to be null
- name,
- })
- }
-
if (!__BROWSER__ && context.options.sourceMap) {
// lazy require source-map implementation, only in non-browser builds
context.map = new SourceMapGenerator()
options: CodegenOptions = {},
): VaporCodegenResult {
const ctx = createCodegenContext(ir, options)
- const { push, withIndent, newline, helpers, vaporHelper, vaporHelpers } = ctx
+ const { push, withIndent, newline, helpers, vaporHelpers } = ctx
const functionName = 'render'
const isSetupInlined = !!options.inline
if (isSetupInlined) {
push(`(() => {`)
} else {
- // placeholder for preamble
- newline()
- newline(`export function ${functionName}(_ctx) {`)
+ push(
+ // placeholder for preamble
+ newline(),
+ newline(),
+ `export function ${functionName}(_ctx) {`,
+ )
}
withIndent(() => {
- ir.template.forEach((template, i) => {
- if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
- // TODO source map?
- newline(
- `const t${i} = ${vaporHelper('template')}(${JSON.stringify(
- template.template,
- )})`,
- )
- } else {
- // fragment
- newline(`const t${i} = ${vaporHelper('fragment')}()`)
- }
- })
-
- genBlockFunctionContent(ir, ctx)
+ ir.template.forEach((template, i) => push(...genTemplate(template, i, ctx)))
+ push(...genBlockFunctionContent(ir, ctx))
})
- newline()
+ push(newline())
if (isSetupInlined) {
push('})()')
} else {
.map(h => `${h} as _${h}`)
.join(', ')} } from 'vue';`
+ let codegen = genCodeFragment(ctx)
if (!isSetupInlined) {
- ctx.code = preamble + ctx.code
+ codegen = preamble + codegen
}
return {
- code: ctx.code,
+ code: codegen,
ast: ir,
preamble,
map: ctx.map ? ctx.map.toJSON() : undefined,
}
}
+function genCodeFragment(context: CodegenContext) {
+ let codegen = ''
+ let line = 1
+ let column = 1
+ let offset = 0
+
+ for (let frag of context.code) {
+ if (!frag) continue
+ if (isString(frag)) frag = [frag]
+
+ let [code, newlineIndex = NewlineType.None, loc, name] = frag
+ codegen += code
+
+ if (!__BROWSER__ && context.map) {
+ if (loc) addMapping(loc.start, name)
+ if (newlineIndex === NewlineType.Unknown) {
+ // multiple newlines, full iteration
+ advancePositionWithMutation({ line, column, offset }, code)
+ } else {
+ // fast paths
+ offset += code.length
+ if (newlineIndex === NewlineType.None) {
+ // no newlines; fast path to avoid newline detection
+ if (__TEST__ && code.includes('\n')) {
+ throw new Error(
+ `CodegenContext.push() called newlineIndex: none, but contains` +
+ `newlines: ${code.replace(/\n/g, '\\n')}`,
+ )
+ }
+ column += code.length
+ } else {
+ // single newline at known index
+ if (newlineIndex === NewlineType.End) {
+ newlineIndex = code.length - 1
+ }
+ if (
+ __TEST__ &&
+ (code.charAt(newlineIndex) !== '\n' ||
+ code.slice(0, newlineIndex).includes('\n') ||
+ code.slice(newlineIndex + 1).includes('\n'))
+ ) {
+ throw new Error(
+ `CodegenContext.push() called with newlineIndex: ${newlineIndex} ` +
+ `but does not conform: ${code.replace(/\n/g, '\\n')}`,
+ )
+ }
+ line++
+ column = code.length - newlineIndex
+ }
+ }
+ if (loc && loc !== locStub) {
+ addMapping(loc.end)
+ }
+ }
+ }
+
+ return codegen
+
+ function addMapping(loc: Position, name: string | null = null) {
+ // we use the private property to directly add the mapping
+ // because the addMapping() implementation in source-map-js has a bunch of
+ // unnecessary arg and validation checks that are pure overhead in our case.
+ const { _names, _mappings } = context.map!
+ if (name !== null && !_names.has(name)) _names.add(name)
+ _mappings.add({
+ originalLine: loc.line,
+ originalColumn: loc.column - 1, // source-map column is 0 based
+ generatedLine: line,
+ generatedColumn: column - 1,
+ source: context.options.filename,
+ // @ts-expect-error it is possible to be null
+ name,
+ })
+ }
+}
+
function genChildren(children: IRDynamicInfo[]) {
let code = ''
let offset = 0
return `{${code}}`
}
-function genOperation(oper: OperationNode, context: CodegenContext) {
+function genOperation(
+ oper: OperationNode,
+ context: CodegenContext,
+): CodeFragment[] {
// TODO: cache old value
switch (oper.type) {
case IRNodeTypes.SET_PROP:
return genIf(oper, context)
case IRNodeTypes.WITH_DIRECTIVE:
// generated, skip
- return
+ break
default:
return checkNever(oper)
}
+
+ return []
+}
+
+export function buildCodeFragment() {
+ const frag: CodeFragment[] = []
+ const push = frag.push.bind(frag)
+ return [frag, push] as const
}
export function genBlockFunctionContent(
ir: BlockFunctionIRNode | RootIRNode,
ctx: CodegenContext,
-) {
+): CodeFragment[] {
const { newline, withIndent, vaporHelper } = ctx
- newline(`const n${ir.dynamic.id} = t${ir.templateIndex}()`)
+ const [frag, push] = buildCodeFragment()
+
+ push(newline(), `const n${ir.dynamic.id} = t${ir.templateIndex}()`)
const children = genChildren(ir.dynamic.children)
if (children) {
- newline(`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`)
+ push(
+ newline(),
+ `const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
+ )
}
const directiveOps = ir.operation.filter(
oper.type === IRNodeTypes.WITH_DIRECTIVE,
)
for (const directives of groupDirective(directiveOps)) {
- genWithDirective(directives, ctx)
+ push(...genWithDirective(directives, ctx))
}
for (const operation of ir.operation) {
- genOperation(operation, ctx)
+ push(...genOperation(operation, ctx))
}
for (const { operations } of ir.effect) {
- newline(`${vaporHelper('renderEffect')}(() => {`)
+ push(newline(), `${vaporHelper('renderEffect')}(() => {`)
withIndent(() => {
- for (const operation of operations) {
- genOperation(operation, ctx)
- }
+ operations.forEach(op => push(...genOperation(op, ctx)))
})
- newline('})')
+ push(newline(), '})')
}
- newline(`return n${ir.dynamic.id}`)
+ push(newline(), `return n${ir.dynamic.id}`)
+
+ return frag
}
function groupDirective(ops: WithDirectiveIRNode[]): WithDirectiveIRNode[][] {
import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom'
import { camelize } from '@vue/shared'
import { genExpression } from './expression'
-import type { CodegenContext } from '../generate'
+import type { CodeFragment, CodegenContext } from '../generate'
import type { WithDirectiveIRNode } from '../ir'
export function genWithDirective(
opers: WithDirectiveIRNode[],
context: CodegenContext,
) {
- const {
- push,
- newline,
- pushCall,
- pushMulti,
- vaporHelper,
- options: { bindingMetadata },
- } = context
+ const { newline, call, multi, vaporHelper } = context
- newline()
- pushCall(
- vaporHelper('withDirectives'),
- // 1st arg: node
- `n${opers[0].element}`,
- // 2nd arg: directives
- () => {
- // directive
- pushMulti(
- ['[', ']', ', '],
- ...opers.map(oper => () => {
- push('[')
+ const element = `n${opers[0].element}`
+ const directiveItems = opers.map(genDirective)
+ const directives = multi(['[', ']', ', '], ...directiveItems)
- const { dir, builtin } = oper
- if (dir.name === 'show') {
- push(vaporHelper('vShow'))
- } else if (builtin) {
- push(vaporHelper(builtin))
- } else {
- const directiveReference = camelize(`v-${dir.name}`)
- // TODO resolve directive
- if (bindingMetadata[directiveReference]) {
- const directiveExpression =
- createSimpleExpression(directiveReference)
- directiveExpression.ast = null
- genExpression(directiveExpression, context)
- } else {
- push(vaporHelper(`resolveDirective("${directiveReference}")`))
- }
- }
+ return [
+ newline(),
+ ...call(vaporHelper('withDirectives'), [element], directives),
+ ]
- if (dir.exp) {
- push(', () => ')
- genExpression(dir.exp, context)
- } else if (dir.arg || dir.modifiers.length) {
- push(', void 0')
- }
+ function genDirective({ dir, builtin }: WithDirectiveIRNode): CodeFragment[] {
+ const NULL = ['void 0']
- if (dir.arg) {
- push(', ')
- genExpression(dir.arg, context)
- } else if (dir.modifiers.length) {
- push(', void 0')
- }
+ const directive = genDirective()
+ const value = dir.exp
+ ? ['() => ', ...genExpression(dir.exp, context)]
+ : dir.arg || dir.modifiers.length
+ ? NULL
+ : false
+ const argument = dir.arg
+ ? genExpression(dir.arg, context)
+ : dir.modifiers.length
+ ? NULL
+ : false
+ const modifiers = dir.modifiers.length
+ ? ['{ ', genDirectiveModifiers(), ' }']
+ : false
- if (dir.modifiers.length) {
- push(', ')
- push('{ ')
- push(genDirectiveModifiers(dir.modifiers))
- push(' }')
- }
+ return multi(['[', ']', ', '], directive, value, argument, modifiers)
- push(']')
- }),
- )
- },
- )
-}
+ function genDirective(): CodeFragment[] {
+ const {
+ vaporHelper,
+ options: { bindingMetadata },
+ } = context
+ if (dir.name === 'show') {
+ return [vaporHelper('vShow')]
+ } else if (builtin) {
+ return [vaporHelper(builtin)]
+ } else {
+ const directiveReference = camelize(`v-${dir.name}`)
+ // TODO resolve directive
+ if (bindingMetadata[directiveReference]) {
+ const directiveExpression = createSimpleExpression(directiveReference)
+ directiveExpression.ast = null
+ return genExpression(directiveExpression, context)
+ } else {
+ return [vaporHelper(`resolveDirective("${directiveReference}")`)]
+ }
+ }
+ }
-function genDirectiveModifiers(modifiers: string[]) {
- return modifiers
- .map(
- value =>
- `${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
- )
- .join(', ')
+ function genDirectiveModifiers() {
+ return dir.modifiers
+ .map(
+ value =>
+ `${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
+ )
+ .join(', ')
+ }
+ }
}
-import type { CodegenContext } from '../generate'
+import type { CodeFragment, CodegenContext } from '../generate'
import type {
AppendNodeIRNode,
InsertNodeIRNode,
export function genInsertNode(
oper: InsertNodeIRNode,
- { newline, pushCall, vaporHelper }: CodegenContext,
-) {
+ { newline, call, vaporHelper }: CodegenContext,
+): CodeFragment[] {
const elements = ([] as number[]).concat(oper.element)
let element = elements.map(el => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
- newline()
- pushCall(vaporHelper('insert'), element, `n${oper.parent}`, `n${oper.anchor}`)
+ return [
+ newline(),
+ ...call(
+ vaporHelper('insert'),
+ [element],
+ [`n${oper.parent}`],
+ [`n${oper.anchor}`],
+ ),
+ ]
}
export function genPrependNode(
oper: PrependNodeIRNode,
- { newline, pushCall, vaporHelper }: CodegenContext,
-) {
- newline()
- pushCall(
- vaporHelper('prepend'),
- `n${oper.parent}`,
- oper.elements.map(el => `n${el}`).join(', '),
- )
+ { newline, call, vaporHelper }: CodegenContext,
+): CodeFragment[] {
+ return [
+ newline(),
+ ...call(
+ vaporHelper('prepend'),
+ [`n${oper.parent}`],
+ ...oper.elements.map(el => [`n${el}`]),
+ ),
+ ]
}
export function genAppendNode(
oper: AppendNodeIRNode,
- { newline, pushCall, vaporHelper }: CodegenContext,
-) {
+ { newline, call, vaporHelper }: CodegenContext,
+): CodeFragment[] {
newline()
- pushCall(
- vaporHelper('append'),
- `n${oper.parent}`,
- oper.elements.map(el => `n${el}`).join(', '),
- )
+ return [
+ newline(),
+ ...call(
+ vaporHelper('append'),
+ [`n${oper.parent}`],
+ ...oper.elements.map(el => [`n${el}`]),
+ ),
+ ]
}
import { isMemberExpression } from '@vue/compiler-dom'
-import type { CodegenContext } from '../generate'
+import type { CodeFragment, CodegenContext } from '../generate'
import type { SetEventIRNode } from '../ir'
import { genExpression } from './expression'
const fnExpRE =
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
-export function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
- const {
- vaporHelper,
- push,
- newline,
- pushMulti,
- pushCall,
- options: ctxOptions,
- } = context
+export function genSetEvent(
+ oper: SetEventIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { vaporHelper, newline, call, options: ctxOptions } = context
const { keys, nonKeys, options } = oper.modifiers
- newline()
- pushCall(
- vaporHelper('on'),
- // 1st arg: event name
- () => push(`n${oper.element}`),
- // 2nd arg: event name
- () => {
- if (oper.keyOverride) {
- const find = JSON.stringify(oper.keyOverride[0])
- const replacement = JSON.stringify(oper.keyOverride[1])
- pushMulti(['(', ')'], () => genExpression(oper.key, context))
- push(` === ${find} ? ${replacement} : `)
- pushMulti(['(', ')'], () => genExpression(oper.key, context))
- } else {
- genExpression(oper.key, context)
- }
- },
- // 3rd arg: event handler
- () => {
- const pushWithKeys = (fn: () => void) => {
- push(`${vaporHelper('withKeys')}(`)
- fn()
- push(`, ${genArrayExpression(keys)})`)
- }
- const pushWithModifiers = (fn: () => void) => {
- push(`${vaporHelper('withModifiers')}(`)
- fn()
- push(`, ${genArrayExpression(nonKeys)})`)
- }
- const pushNoop = (fn: () => void) => fn()
+ const name = genName()
+ const handler = genFinalizedHandler()
+ const opt = !!options.length && [
+ `{ ${options.map(v => `${v}: true`).join(', ')} }`,
+ ]
- ;(keys.length ? pushWithKeys : pushNoop)(() =>
- (nonKeys.length ? pushWithModifiers : pushNoop)(() => {
- genEventHandler()
- }),
- )
- },
- // 4th arg, gen options
- !!options.length &&
- (() => push(`{ ${options.map(v => `${v}: true`).join(', ')} }`)),
- )
+ return [
+ newline(),
+ ...call(vaporHelper('on'), [`n${oper.element}`], name, handler, opt),
+ ]
+
+ function genName(): CodeFragment[] {
+ const expr = genExpression(oper.key, context)
+ // TODO unit test
+ if (oper.keyOverride) {
+ const find = JSON.stringify(oper.keyOverride[0])
+ const replacement = JSON.stringify(oper.keyOverride[1])
+ const wrapped: CodeFragment[] = ['(', ...expr, ')']
+ return [...wrapped, ` === ${find} ? ${replacement} : `, ...wrapped]
+ } else {
+ return genExpression(oper.key, context)
+ }
+ }
- function genEventHandler() {
+ function genEventHandler(): CodeFragment[] {
const exp = oper.value
if (exp && exp.content.trim()) {
const isMemberExp = isMemberExpression(exp.content, ctxOptions)
const hasMultipleStatements = exp.content.includes(`;`)
if (isInlineStatement) {
- push('$event => ')
- push(hasMultipleStatements ? '{' : '(')
const knownIds = Object.create(null)
knownIds['$event'] = 1
- genExpression(exp, context, knownIds)
- push(hasMultipleStatements ? '}' : ')')
- } else if (isMemberExp) {
- push('(...args) => (')
- genExpression(exp, context)
- push(' && ')
- genExpression(exp, context)
- push('(...args))')
+
+ return [
+ '$event => ',
+ hasMultipleStatements ? '{' : '(',
+ ...genExpression(exp, context, knownIds),
+ hasMultipleStatements ? '}' : ')',
+ ]
} else {
- genExpression(exp, context)
+ const expr = genExpression(exp, context)
+ if (isMemberExp) {
+ return ['(...args) => (', ...expr, ' && ', ...expr, '(...args))']
+ } else {
+ return expr
+ }
}
- } else {
- push('() => {}')
}
+ return ['() => {}']
+ }
+
+ function genFinalizedHandler(): CodeFragment[] {
+ let expr = genEventHandler()
+
+ if (nonKeys.length) {
+ expr = [
+ vaporHelper('withModifiers'),
+ '(',
+ ...expr,
+ ', ',
+ genArrayExpression(nonKeys),
+ ')',
+ ]
+ }
+ if (keys.length) {
+ expr = [
+ vaporHelper('withKeys'),
+ '(',
+ ...expr,
+ ', ',
+ genArrayExpression(keys),
+ ')',
+ ]
+ }
+
+ return expr
}
}
import { isGloballyAllowed, isString, makeMap } from '@vue/shared'
import type { Identifier } from '@babel/types'
import type { IRExpression } from '../ir'
-import type { CodegenContext } from '../generate'
+import {
+ type CodeFragment,
+ type CodegenContext,
+ buildCodeFragment,
+} from '../generate'
export function genExpression(
node: IRExpression,
context: CodegenContext,
knownIds: Record<string, number> = Object.create(null),
-): void {
+): CodeFragment[] {
const {
- push,
options: { prefixIdentifiers },
} = context
- if (isString(node)) return push(node)
+ if (isString(node)) return [node]
const { content: rawExpr, ast, isStatic, loc } = node
if (isStatic) {
- return push(JSON.stringify(rawExpr), NewlineType.None, loc)
+ return [[JSON.stringify(rawExpr), NewlineType.None, loc]]
}
+
if (
__BROWSER__ ||
!prefixIdentifiers ||
isGloballyAllowed(rawExpr) ||
isLiteralWhitelisted(rawExpr)
) {
- return push(rawExpr, NewlineType.None, loc)
+ return [[rawExpr, NewlineType.None, loc]]
}
if (ast === null) {
// the expression is a simple identifier
- return genIdentifier(rawExpr, context, loc)
+ return [genIdentifier(rawExpr, context, loc)]
}
const ids: Identifier[] = []
)
if (ids.length) {
ids.sort((a, b) => a.start! - b.start!)
+ const [frag, push] = buildCodeFragment()
ids.forEach((id, i) => {
// range is offset by -1 due to the wrapping parens when parsed
const start = id.start! - 1
const last = ids[i - 1]
const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start)
- if (leadingText.length) push(leadingText, NewlineType.Unknown)
+ 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,
- })
+ push(
+ 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)
+ push([rawExpr.slice(end), NewlineType.Unknown])
}
})
+ return frag
} else {
- push(rawExpr, NewlineType.Unknown)
+ return [[rawExpr, NewlineType.Unknown]]
}
}
function genIdentifier(
id: string,
- { options, vaporHelper, push }: CodegenContext,
+ { options, vaporHelper }: CodegenContext,
loc?: SourceLocation,
-): void {
+): CodeFragment {
const { inline, bindingMetadata } = options
let name: string | undefined = id
if (inline) {
} else {
id = `_ctx.${id}`
}
- push(id, NewlineType.None, loc, name)
+ return [id, NewlineType.None, loc, name]
}
-import type { CodegenContext } from '../generate'
+import type { CodeFragment, CodegenContext } from '../generate'
import type { SetHtmlIRNode } from '../ir'
import { genExpression } from './expression'
-export function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
- const { newline, pushCall, vaporHelper } = context
- newline()
- pushCall(vaporHelper('setHtml'), `n${oper.element}`, () =>
- genExpression(oper.value, context),
- )
+export function genSetHtml(
+ oper: SetHtmlIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { newline, call, vaporHelper } = context
+ return [
+ newline(),
+ ...call(
+ vaporHelper('setHtml'),
+ [`n${oper.element}`],
+ genExpression(oper.value, context),
+ ),
+ ]
}
-import { type CodegenContext, genBlockFunctionContent } from '../generate'
+import {
+ type CodeFragment,
+ type CodegenContext,
+ buildCodeFragment,
+ genBlockFunctionContent,
+} from '../generate'
import { type BlockFunctionIRNode, IRNodeTypes, type IfIRNode } from '../ir'
import { genExpression } from './expression'
oper: IfIRNode,
context: CodegenContext,
isNested = false,
-) {
- const { pushCall, vaporHelper, newline, push } = context
+): CodeFragment[] {
+ const { call, vaporHelper, newline } = context
const { condition, positive, negative } = oper
+ const [frag, push] = buildCodeFragment()
- let positiveArg = () => genBlockFunction(positive, context)
- let negativeArg: false | (() => void) = false
+ const conditionExpr: CodeFragment[] = [
+ '() => (',
+ ...genExpression(condition, context),
+ ')',
+ ]
+
+ let positiveArg = genBlockFunction(positive, context)
+ let negativeArg: false | CodeFragment[] = false
if (negative) {
if (negative.type === IRNodeTypes.BLOCK_FUNCTION) {
- negativeArg = () => genBlockFunction(negative, context)
+ negativeArg = genBlockFunction(negative, context)
} else {
- negativeArg = () => {
- push('() => ')
- genIf(negative!, context, true)
- }
+ negativeArg = ['() => ', ...genIf(negative!, context, true)]
}
}
- if (!isNested) newline(`const n${oper.id} = `)
- pushCall(
- vaporHelper('createIf'),
- () => {
- push('() => (')
- genExpression(condition, context)
- push(')')
- },
- positiveArg,
- negativeArg,
+ if (!isNested) push(newline(), `const n${oper.id} = `)
+ push(
+ ...call(vaporHelper('createIf'), conditionExpr, positiveArg, negativeArg),
)
-}
-function genBlockFunction(oper: BlockFunctionIRNode, context: CodegenContext) {
- const { newline, push, withIndent } = context
+ return frag
+}
- push('() => {')
- withIndent(() => {
- genBlockFunctionContent(oper, context)
- })
- newline('}')
+function genBlockFunction(
+ oper: BlockFunctionIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { newline, withIndent } = context
+ return [
+ '() => {',
+ ...withIndent(() => genBlockFunctionContent(oper, context)),
+ newline(),
+ '}',
+ ]
}
import { camelize, isString } from '@vue/shared'
import { genExpression } from './expression'
import type { SetModelValueIRNode } from '../ir'
-import type { CodegenContext } from '../generate'
+import type { CodeFragment, CodegenContext } from '../generate'
export function genSetModelValue(
oper: SetModelValueIRNode,
context: CodegenContext,
-) {
+): CodeFragment[] {
const {
vaporHelper,
- push,
newline,
- pushCall,
+ call,
options: { isTS },
} = context
- newline()
- pushCall(
- vaporHelper('on'),
- // 1st arg: event name
- () => push(`n${oper.element}`),
- // 2nd arg: event name
- () => {
- if (isString(oper.key)) {
- push(JSON.stringify(`update:${camelize(oper.key)}`))
- } else {
- push('`update:${')
- genExpression(oper.key, context)
- push('}`')
- }
- },
- // 3rd arg: event handler
- () => {
- push((isTS ? `($event: any)` : `$event`) + ' => ((')
+ const name = genName()
+ const handler = genHandler()
+
+ return [
+ newline(),
+ ...call(vaporHelper('on'), [`n${oper.element}`], name, handler),
+ ]
+
+ function genName(): CodeFragment[] {
+ if (isString(oper.key)) {
+ return [JSON.stringify(`update:${camelize(oper.key)}`)]
+ } else {
+ return ['`update:${', ...genExpression(oper.key, context), '}`']
+ }
+ }
+
+ function genHandler(): CodeFragment[] {
+ return [
+ (isTS ? `($event: any)` : `$event`) + ' => ((',
// TODO handle not a ref
- genExpression(oper.value, context)
- push(') = $event)')
- },
- )
+ ...genExpression(oper.value, context),
+ ') = $event)',
+ ]
+ }
}
-import type { CodegenContext } from '../generate'
+import type { CodeFragment, CodegenContext } from '../generate'
import type { SetPropIRNode } from '../ir'
import { genExpression } from './expression'
import { isString } from '@vue/shared'
-export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
- const { pushCall, pushMulti, newline, vaporHelper, helper } = context
+export function genSetProp(
+ oper: SetPropIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { call, newline, vaporHelper, helper } = context
- newline()
-
- const element = `n${oper.element}`
+ const element = [`n${oper.element}`]
+ const expr = genExpression(oper.key, context)
// fast path for static props
if (isString(oper.key) || oper.key.isStatic) {
}
if (helperName) {
- pushCall(
- vaporHelper(helperName),
- element,
- omitKey
- ? false
- : () => {
- const expr = () => genExpression(oper.key, context)
- if (oper.runtimeCamelize) {
- pushCall(helper('camelize'), expr)
- } else {
- expr()
- }
- },
- () => genExpression(oper.value, context),
- )
- return
+ return [
+ newline(),
+ ...call(
+ vaporHelper(helperName),
+ element,
+ omitKey ? false : expr,
+ genExpression(oper.value, context),
+ ),
+ ]
}
}
- pushCall(
- vaporHelper('setDynamicProp'),
- element,
- // 2. key name
- () => {
- if (oper.runtimeCamelize) {
- pushCall(helper('camelize'), () => genExpression(oper.key, context))
- } else if (oper.modifier) {
- pushMulti([`\`${oper.modifier}\${`, `}\``], () =>
- genExpression(oper.key, context),
- )
- } else {
- genExpression(oper.key, context)
- }
- },
- () => genExpression(oper.value, context),
- )
+ return [
+ newline(),
+ ...call(
+ vaporHelper('setDynamicProp'),
+ element,
+ genDynamicKey(),
+ genExpression(oper.value, context),
+ ),
+ ]
+
+ function genDynamicKey(): CodeFragment[] {
+ if (oper.runtimeCamelize) {
+ return call(helper('camelize'), expr)
+ } else if (oper.modifier) {
+ return [`\`${oper.modifier}\${`, ...expr, `}\``]
+ } else {
+ return expr
+ }
+ }
}
-import type { CodegenContext } from '../generate'
-import type { SetRefIRNode } from '../ir'
import { genExpression } from './expression'
+import type { CodeFragment, CodegenContext } from '../generate'
+import type { SetRefIRNode } from '../ir'
-export function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
- const { newline, pushCall, vaporHelper } = context
- newline()
- pushCall(vaporHelper('setRef'), `n${oper.element}`, () =>
- genExpression(oper.value, context),
- )
+export function genSetRef(
+ oper: SetRefIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { newline, call, vaporHelper } = context
+ return [
+ newline(),
+ ...call(
+ vaporHelper('setRef'),
+ [`n${oper.element}`],
+ genExpression(oper.value, context),
+ ),
+ ]
}
--- /dev/null
+import type { CodeFragment, CodegenContext } from '../generate'
+import {
+ type FragmentFactoryIRNode,
+ IRNodeTypes,
+ type TemplateFactoryIRNode,
+} from '../ir'
+
+export function genTemplate(
+ node: TemplateFactoryIRNode | FragmentFactoryIRNode,
+ index: number,
+ { newline, vaporHelper }: CodegenContext,
+): CodeFragment[] {
+ if (node.type === IRNodeTypes.TEMPLATE_FACTORY) {
+ // TODO source map?
+ return [
+ newline(),
+ `const t${index} = ${vaporHelper('template')}(${JSON.stringify(
+ node.template,
+ )})`,
+ ]
+ } else {
+ // fragment
+ return [newline(), `const t${index} = ${vaporHelper('fragment')}()`]
+ }
+}
-import type { CodegenContext } from '../generate'
+import type { CodeFragment, CodegenContext } from '../generate'
import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir'
import { genExpression } from './expression'
-export function genSetText(oper: SetTextIRNode, context: CodegenContext) {
- const { pushCall, newline, vaporHelper } = context
- newline()
- pushCall(vaporHelper('setText'), `n${oper.element}`, () =>
- genExpression(oper.value, context),
- )
+export function genSetText(
+ oper: SetTextIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { call, newline, vaporHelper } = context
+ return [
+ newline(),
+ ...call(
+ vaporHelper('setText'),
+ [`n${oper.element}`],
+ genExpression(oper.value, context),
+ ),
+ ]
}
export function genCreateTextNode(
oper: CreateTextNodeIRNode,
context: CodegenContext,
-) {
- const { newline, pushCall, vaporHelper } = context
- newline(`const n${oper.id} = `)
- pushCall(vaporHelper('createTextNode'), () =>
- genExpression(oper.value, context),
- )
+): CodeFragment[] {
+ const { newline, call, vaporHelper } = context
+ return [
+ newline(),
+ `const n${oper.id} = `,
+ ...call(vaporHelper('createTextNode'), genExpression(oper.value, context)),
+ ]
}