}
export type CodeFragment =
+ | typeof NEWLINE
+ | typeof INDENT_START
+ | typeof INDENT_END
| string
| [code: string, newlineIndex?: number, loc?: SourceLocation, name?: string]
| undefined
source: string
code: CodeFragment[]
- indentLevel: number = 0
map?: SourceMapGenerator
push: (...args: CodeFragment[]) => void
- newline = (): CodeFragment => {
- return [`\n${` `.repeat(this.indentLevel)}`, NewlineType.Start]
- }
multi = (
[left, right, seg]: [left: string, right: string, segment: string],
...fns: Array<false | string | CodeFragment[]>
): CodeFragment[] => {
return [name, ...this.multi(['(', ')', ', '], ...args)]
}
- withIndent = <T>(fn: () => T): T => {
- ++this.indentLevel
- const ret = fn()
- --this.indentLevel
- return ret
- }
helpers = new Set<string>([])
vaporHelpers = new Set<string>([])
vaporHelpers: Set<string>
}
+export const NEWLINE = Symbol(__DEV__ ? `newline` : ``)
+export const INDENT_START = Symbol(__DEV__ ? `indent start` : ``)
+export const INDENT_END = Symbol(__DEV__ ? `indent end` : ``)
+
// IR -> JS codegen
export function generate(
ir: RootIRNode,
options: CodegenOptions = {},
): VaporCodegenResult {
const ctx = new CodegenContext(ir, options)
- const { push, withIndent, newline, helpers, vaporHelpers } = ctx
+ const { push, helpers, vaporHelpers } = ctx
const functionName = 'render'
const isSetupInlined = !!options.inline
} else {
push(
// placeholder for preamble
- newline(),
- newline(),
+ NEWLINE,
+ NEWLINE,
`export function ${functionName}(_ctx) {`,
)
}
- withIndent(() => {
- ir.template.forEach((template, i) => push(...genTemplate(template, i, ctx)))
- push(...genBlockFunctionContent(ir, ctx))
- })
+ push(INDENT_START)
+ ir.template.forEach((template, i) => push(...genTemplate(template, i, ctx)))
+ push(...genBlockFunctionContent(ir, ctx))
+ push(INDENT_END, NEWLINE)
- push(newline())
if (isSetupInlined) {
push('})()')
} else {
function genCodeFragment(context: CodegenContext) {
let codegen = ''
const pos = { line: 1, column: 1, offset: 0 }
+ let indentLevel = 0
for (let frag of context.code) {
if (!frag) continue
+
+ if (frag === NEWLINE) {
+ frag = [`\n${` `.repeat(indentLevel)}`, NewlineType.Start]
+ } else if (frag === INDENT_START) {
+ indentLevel++
+ continue
+ } else if (frag === INDENT_END) {
+ indentLevel--
+ continue
+ }
+
if (isString(frag)) frag = [frag]
let [code, newlineIndex = NewlineType.None, loc, name] = frag
}
}
-export function buildCodeFragment() {
- const frag: CodeFragment[] = []
+export function buildCodeFragment(...frag: CodeFragment[]) {
const push = frag.push.bind(frag)
return [frag, push] as const
}
import {
type CodeFragment,
type CodegenContext,
+ INDENT_END,
+ INDENT_START,
+ NEWLINE,
buildCodeFragment,
} from '../generate'
import { genWithDirective } from './directive'
args: CodeFragment[] = [],
returnValue?: () => CodeFragment[],
): CodeFragment[] {
- const { newline, withIndent } = context
return [
'(',
...args,
') => {',
- ...withIndent(() => genBlockFunctionContent(oper, context, returnValue)),
- newline(),
+ INDENT_START,
+ ...genBlockFunctionContent(oper, context, returnValue),
+ INDENT_END,
+ NEWLINE,
'}',
]
}
ctx: CodegenContext,
returnValue?: () => CodeFragment[],
): CodeFragment[] {
- const { newline, vaporHelper } = ctx
- const [frag, push] = buildCodeFragment()
-
- push(newline(), `const n${ir.dynamic.id} = t${ir.templateIndex}()`)
+ const { vaporHelper } = ctx
+ const [frag, push] = buildCodeFragment(
+ NEWLINE,
+ `const n${ir.dynamic.id} = t${ir.templateIndex}()`,
+ )
const children = genChildren(ir.dynamic.children)
if (children) {
push(
- newline(),
+ NEWLINE,
`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
)
}
push(...genEffects(ir.effect, ctx))
push(
- newline(),
+ NEWLINE,
'return ',
...(returnValue ? returnValue() : [`n${ir.dynamic.id}`]),
)
import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom'
import { camelize } from '@vue/shared'
import { genExpression } from './expression'
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import type { WithDirectiveIRNode } from '../ir'
export function genWithDirective(
opers: WithDirectiveIRNode[],
context: CodegenContext,
-) {
- const { newline, call, multi, vaporHelper } = context
+): CodeFragment[] {
+ const { call, multi, vaporHelper } = context
const element = `n${opers[0].element}`
const directiveItems = opers.map(genDirective)
const directives = multi(['[', ']', ', '], ...directiveItems)
- return [
- newline(),
- ...call(vaporHelper('withDirectives'), element, directives),
- ]
+ return [NEWLINE, ...call(vaporHelper('withDirectives'), element, directives)]
function genDirective({ dir, builtin }: WithDirectiveIRNode): CodeFragment[] {
const NULL = 'void 0'
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import type {
AppendNodeIRNode,
InsertNodeIRNode,
export function genInsertNode(
oper: InsertNodeIRNode,
- { newline, call, vaporHelper }: CodegenContext,
+ { 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}]`
return [
- newline(),
+ NEWLINE,
...call(
vaporHelper('insert'),
element,
export function genPrependNode(
oper: PrependNodeIRNode,
- { newline, call, vaporHelper }: CodegenContext,
+ { call, vaporHelper }: CodegenContext,
): CodeFragment[] {
return [
- newline(),
+ NEWLINE,
...call(
vaporHelper('prepend'),
`n${oper.parent}`,
export function genAppendNode(
oper: AppendNodeIRNode,
- { newline, call, vaporHelper }: CodegenContext,
+ { call, vaporHelper }: CodegenContext,
): CodeFragment[] {
- newline()
return [
- newline(),
+ NEWLINE,
...call(
vaporHelper('append'),
`n${oper.parent}`,
import { isMemberExpression } from '@vue/compiler-dom'
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import type { SetEventIRNode } from '../ir'
import { genExpression } from './expression'
oper: SetEventIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { vaporHelper, newline, call, options: ctxOptions } = context
+ const { vaporHelper, call, options: ctxOptions } = context
const { keys, nonKeys, options } = oper.modifiers
const name = genName()
!!options.length && `{ ${options.map(v => `${v}: true`).join(', ')} }`
return [
- newline(),
+ NEWLINE,
...call(vaporHelper('on'), `n${oper.element}`, name, handler, opt),
]
import {
type CodeFragment,
type CodegenContext,
+ INDENT_END,
+ INDENT_START,
+ NEWLINE,
buildCodeFragment,
} from '../generate'
import type { ForIRNode, IREffect } from '../ir'
oper: ForIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { newline, call, vaporHelper } = context
+ const { call, vaporHelper } = context
const { source, value, key, render } = oper
const rawValue = value && value.content
context.genEffect = undefined
return [
- newline(),
+ NEWLINE,
`const n${oper.id} = `,
...call(vaporHelper('createFor'), sourceExpr, blockFn),
]
- function genEffectInFor(effects: IREffect[]) {
+ function genEffectInFor(effects: IREffect[]): CodeFragment[] {
if (!effects.length) {
updateFn = '() => {}'
return []
}
- const [frag, push] = buildCodeFragment()
-
- context.withIndent(() => {
- if (rawValue || rawKey) {
- push(
- newline(),
- 'const ',
- '[',
- rawValue && [rawValue, NewlineType.None, value.loc],
- rawKey && ', ',
- rawKey && [rawKey, NewlineType.None, key.loc],
- '] = _block.s',
- )
- }
+ const [frag, push] = buildCodeFragment(INDENT_START)
+ // const [value, key] = _block.s
+ if (rawValue || rawKey) {
+ push(
+ NEWLINE,
+ 'const ',
+ '[',
+ rawValue && [rawValue, NewlineType.None, value.loc],
+ rawKey && ', ',
+ rawKey && [rawKey, NewlineType.None, key.loc],
+ '] = _block.s',
+ )
+ }
- const idMap: Record<string, string | null> = {}
- if (value) idMap[value.content] = null
- if (key) idMap[key.content] = null
+ const idMap: Record<string, string | null> = {}
+ if (value) idMap[value.content] = null
+ if (key) idMap[key.content] = null
+ context.withId(() => {
+ effects.forEach(effect =>
+ push(...genOperations(effect.operations, context)),
+ )
+ }, idMap)
- context.withId(() => {
- effects.forEach(effect =>
- push(...genOperations(effect.operations, context)),
- )
- }, idMap)
- })
+ push(INDENT_END)
return [
- newline(),
+ NEWLINE,
`const ${updateFn} = () => {`,
...frag,
- newline(),
+ NEWLINE,
'}',
- newline(),
+ NEWLINE,
`${vaporHelper('renderEffect')}(${updateFn})`,
]
}
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import type { SetHtmlIRNode } from '../ir'
import { genExpression } from './expression'
oper: SetHtmlIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { newline, call, vaporHelper } = context
+ const { call, vaporHelper } = context
return [
- newline(),
+ NEWLINE,
...call(
vaporHelper('setHtml'),
`n${oper.element}`,
import {
type CodeFragment,
type CodegenContext,
+ NEWLINE,
buildCodeFragment,
} from '../generate'
import { IRNodeTypes, type IfIRNode } from '../ir'
context: CodegenContext,
isNested = false,
): CodeFragment[] {
- const { call, vaporHelper, newline } = context
+ const { call, vaporHelper } = context
const { condition, positive, negative } = oper
const [frag, push] = buildCodeFragment()
}
}
- if (!isNested) push(newline(), `const n${oper.id} = `)
+ if (!isNested) push(NEWLINE, `const n${oper.id} = `)
push(
...call(vaporHelper('createIf'), conditionExpr, positiveArg, negativeArg),
)
import { camelize, isString } from '@vue/shared'
import { genExpression } from './expression'
import type { SetModelValueIRNode } from '../ir'
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
export function genSetModelValue(
oper: SetModelValueIRNode,
): CodeFragment[] {
const {
vaporHelper,
- newline,
call,
options: { isTS },
} = context
- const name = genName()
- const handler = genHandler()
+ const name = isString(oper.key)
+ ? [JSON.stringify(`update:${camelize(oper.key)}`)]
+ : ['`update:${', ...genExpression(oper.key, context), '}`']
+ const handler = [
+ (isTS ? `($event: any)` : `$event`) + ' => ((',
+ // TODO handle not a ref
+ ...genExpression(oper.value, context),
+ ') = $event)',
+ ]
return [
- newline(),
+ 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),
- ') = $event)',
- ]
- }
}
import {
type CodeFragment,
type CodegenContext,
+ INDENT_END,
+ INDENT_START,
+ NEWLINE,
buildCodeFragment,
} from '../generate'
import { genAppendNode, genInsertNode, genPrependNode } from './dom'
}
function genEffect({ operations }: IREffect, context: CodegenContext) {
- const { newline, withIndent, vaporHelper } = context
- const [frag, push] = buildCodeFragment()
- push(newline(), `${vaporHelper('renderEffect')}(() => {`)
- withIndent(() => {
- operations.forEach(op => push(...genOperation(op, context)))
- })
- push(newline(), '})')
+ const { vaporHelper } = context
+ const [frag, push] = buildCodeFragment(
+ NEWLINE,
+ `${vaporHelper('renderEffect')}(() => {`,
+ INDENT_START,
+ )
+ operations.forEach(op => push(...genOperation(op, context)))
+ push(INDENT_END, NEWLINE, '})')
return frag
}
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import type { SetPropIRNode, VaporHelper } from '../ir'
import { genExpression } from './expression'
import { isString } from '@vue/shared'
oper: SetPropIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { call, newline, vaporHelper, helper } = context
+ const { call, vaporHelper, helper } = context
const element = `n${oper.element}`
const key = genExpression(oper.key, context)
if (helperName) {
return [
- newline(),
+ NEWLINE,
...call(vaporHelper(helperName), element, omitKey ? false : key, value),
]
}
}
return [
- newline(),
+ NEWLINE,
...call(vaporHelper('setDynamicProp'), element, genDynamicKey(), value),
]
import { genExpression } from './expression'
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import type { SetRefIRNode } from '../ir'
export function genSetRef(
oper: SetRefIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { newline, call, vaporHelper } = context
+ const { call, vaporHelper } = context
return [
- newline(),
+ NEWLINE,
...call(
vaporHelper('setRef'),
[`n${oper.element}`],
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import {
type FragmentFactoryIRNode,
IRNodeTypes,
export function genTemplate(
node: TemplateFactoryIRNode | FragmentFactoryIRNode,
index: number,
- { newline, vaporHelper }: CodegenContext,
+ { vaporHelper }: CodegenContext,
): CodeFragment[] {
if (node.type === IRNodeTypes.TEMPLATE_FACTORY) {
// TODO source map?
return [
- newline(),
+ NEWLINE,
`const t${index} = ${vaporHelper('template')}(${JSON.stringify(
node.template,
)})`,
]
} else {
// fragment
- return [newline(), `const t${index} = ${vaporHelper('fragment')}()`]
+ return [NEWLINE, `const t${index} = ${vaporHelper('fragment')}()`]
}
}
-import type { CodeFragment, CodegenContext } from '../generate'
+import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate'
import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir'
import { genExpression } from './expression'
oper: SetTextIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { call, newline, vaporHelper } = context
+ const { call, vaporHelper } = context
return [
- newline(),
+ NEWLINE,
...call(
vaporHelper('setText'),
`n${oper.element}`,
oper: CreateTextNodeIRNode,
context: CodegenContext,
): CodeFragment[] {
- const { newline, call, vaporHelper } = context
+ const { call, vaporHelper } = context
return [
- newline(),
+ NEWLINE,
`const n${oper.id} = `,
...call(vaporHelper('createTextNode')),
]
loc: SourceLocation
}
-// TODO refactor
-export type VaporHelper = keyof typeof import('../../runtime-vapor/src')
+export type VaporHelper = keyof typeof import('@vue/runtime-vapor')
export interface BlockFunctionIRNode extends BaseIRNode {
type: IRNodeTypes.BLOCK_FUNCTION
import { insert, querySelector, remove } from './dom'
import { queuePostRenderEffect } from './scheduler'
-export const fragmentKey = Symbol('fragment')
+export const fragmentKey = Symbol(__DEV__ ? `fragmentKey` : ``)
export type Block = Node | Fragment | Block[]
export type ParentBlock = ParentNode | Block[]