type WithDirectiveIRNode,
} from './ir'
import { SourceMapGenerator } from 'source-map-js'
-import { isString } from '@vue/shared'
+import { extend, isString } from '@vue/shared'
import type { ParserPlugin } from '@babel/parser'
import { genSetProp } from './generators/prop'
import { genCreateTextNode, genSetText } from './generators/text'
// @ts-expect-error
function checkNever(x: never): never {}
-export interface CodegenContext extends Required<CodegenOptions> {
+export interface CodegenContext {
+ options: Required<CodegenOptions>
+
source: string
code: string
line: number
loc?: SourceLocation,
name?: string,
): void
- pushNewline(
- code: string,
+ newline(
+ code?: string,
newlineIndex?: number,
loc?: SourceLocation,
name?: string,
codes: [left: string, right: string, segment?: string],
...fn: Array<false | string | (() => void)>
): void
- pushFnCall(name: string, ...args: Array<false | string | (() => void)>): void
+ pushCall(name: string, ...args: Array<false | string | (() => void)>): void
withIndent(fn: () => void): void
- newline(): void
helpers: Set<string>
vaporHelpers: Set<string>
vaporHelper(name: string): string
}
-function createCodegenContext(
- ir: RootIRNode,
- {
- mode = 'function',
- prefixIdentifiers = mode === 'module',
- sourceMap = false,
- filename = `template.vue.html`,
- scopeId = null,
- optimizeImports = false,
- runtimeGlobalName = `Vue`,
- runtimeModuleName = `vue`,
- ssrRuntimeModuleName = 'vue/server-renderer',
- ssr = false,
- isTS = false,
- inSSR = false,
- inline = false,
- bindingMetadata = {},
- expressionPlugins = [],
- }: CodegenOptions,
-) {
+function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
const helpers = new Set<string>([])
const vaporHelpers = new Set<string>([])
const context: CodegenContext = {
- mode,
- prefixIdentifiers,
- sourceMap,
- filename,
- scopeId,
- optimizeImports,
- runtimeGlobalName,
- runtimeModuleName,
- ssrRuntimeModuleName,
- ssr,
- isTS,
- inSSR,
- bindingMetadata,
- expressionPlugins,
- inline,
+ options: extend(
+ {
+ mode: 'function',
+ prefixIdentifiers: options.mode === 'module',
+ sourceMap: false,
+ filename: `template.vue.html`,
+ scopeId: null,
+ optimizeImports: false,
+ runtimeGlobalName: `Vue`,
+ runtimeModuleName: `vue`,
+ ssrRuntimeModuleName: 'vue/server-renderer',
+ ssr: false,
+ isTS: false,
+ inSSR: false,
+ inline: false,
+ bindingMetadata: {},
+ expressionPlugins: [],
+ },
+ options,
+ ),
source: ir.source,
- code: ``,
+ code: '',
column: 1,
line: 1,
offset: 0,
vaporHelpers.add(name)
return `_${name}`
},
+
push(code, newlineIndex = NewlineType.None, loc, name) {
context.code += code
if (!__BROWSER__ && context.map) {
}
}
},
- pushNewline(code, newlineIndex, node) {
- context.newline()
- context.push(code, newlineIndex, node)
+ newline(code, newlineIndex, node) {
+ context.push(`\n${` `.repeat(context.indentLevel)}`, NewlineType.Start)
+ code && context.push(code, newlineIndex, node)
},
pushMulti([left, right, seg], ...fns) {
fns = fns.filter(Boolean)
context.push(left)
- for (let i = 0; i < fns.length; i++) {
- const fn = fns[i] as string | (() => void)
-
+ for (const [i, fn] of fns.entries()) {
if (isString(fn)) context.push(fn)
- else fn()
+ else (fn as () => void)()
if (seg && i < fns.length - 1) context.push(seg)
}
context.push(right)
},
- pushFnCall(name, ...args) {
+ pushCall(name, ...args) {
context.push(name)
context.pushMulti(['(', ')', ', '], ...args)
},
fn()
--context.indentLevel
},
- newline() {
- context.push(`\n${` `.repeat(context.indentLevel)}`, NewlineType.Start)
- },
}
+ 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
})
}
- if (!__BROWSER__ && sourceMap) {
+ if (!__BROWSER__ && context.options.sourceMap) {
// lazy require source-map implementation, only in non-browser builds
context.map = new SourceMapGenerator()
context.map.setSourceContent(filename, context.source)
options: CodegenOptions = {},
): VaporCodegenResult {
const ctx = createCodegenContext(ir, options)
- const {
- push,
- pushNewline,
- withIndent,
- newline,
- helpers,
- vaporHelper,
- vaporHelpers,
- } = ctx
+ const { push, withIndent, newline, helpers, vaporHelper, vaporHelpers } = ctx
const functionName = 'render'
const isSetupInlined = !!options.inline
} else {
// placeholder for preamble
newline()
- pushNewline(`export function ${functionName}(_ctx) {`)
+ newline(`export function ${functionName}(_ctx) {`)
}
withIndent(() => {
ir.template.forEach((template, i) => {
if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
// TODO source map?
- pushNewline(
+ newline(
`const t${i} = ${vaporHelper('template')}(${JSON.stringify(
template.template,
)})`,
)
} else {
// fragment
- pushNewline(`const t${i} = ${vaporHelper('fragment')}()`)
+ newline(`const t${i} = ${vaporHelper('fragment')}()`)
}
})
ir: BlockFunctionIRNode | RootIRNode,
ctx: CodegenContext,
) {
- const { pushNewline, withIndent, vaporHelper } = ctx
- pushNewline(`const n${ir.dynamic.id} = t${ir.templateIndex}()`)
+ const { newline, withIndent, vaporHelper } = ctx
+ newline(`const n${ir.dynamic.id} = t${ir.templateIndex}()`)
const children = genChildren(ir.dynamic.children)
if (children) {
- pushNewline(
- `const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
- )
+ newline(`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`)
}
const directiveOps = ir.operation.filter(
}
for (const { operations } of ir.effect) {
- pushNewline(`${vaporHelper('renderEffect')}(() => {`)
+ newline(`${vaporHelper('renderEffect')}(() => {`)
withIndent(() => {
for (const operation of operations) {
genOperation(operation, ctx)
}
})
- pushNewline('})')
+ newline('})')
}
- pushNewline(`return n${ir.dynamic.id}`)
+ newline(`return n${ir.dynamic.id}`)
}
function groupDirective(ops: WithDirectiveIRNode[]): WithDirectiveIRNode[][] {
opers: WithDirectiveIRNode[],
context: CodegenContext,
) {
- const { push, newline, pushFnCall, pushMulti, vaporHelper, bindingMetadata } =
- context
+ const {
+ push,
+ newline,
+ pushCall,
+ pushMulti,
+ vaporHelper,
+ options: { bindingMetadata },
+ } = context
newline()
- pushFnCall(
+ pushCall(
vaporHelper('withDirectives'),
// 1st arg: node
`n${opers[0].element}`,
PrependNodeIRNode,
} from '../ir'
-export function genInsertNode(oper: InsertNodeIRNode, context: CodegenContext) {
- const { newline, pushFnCall, vaporHelper } = context
+export function genInsertNode(
+ oper: InsertNodeIRNode,
+ { newline, pushCall, vaporHelper }: CodegenContext,
+) {
const elements = ([] as number[]).concat(oper.element)
let element = elements.map(el => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]`
newline()
- pushFnCall(
- vaporHelper('insert'),
- element,
- `n${oper.parent}`,
- `n${oper.anchor}`,
- )
+ pushCall(vaporHelper('insert'), element, `n${oper.parent}`, `n${oper.anchor}`)
}
export function genPrependNode(
oper: PrependNodeIRNode,
- context: CodegenContext,
+ { newline, pushCall, vaporHelper }: CodegenContext,
) {
- const { newline, pushFnCall, vaporHelper } = context
newline()
- pushFnCall(
+ pushCall(
vaporHelper('prepend'),
`n${oper.parent}`,
oper.elements.map(el => `n${el}`).join(', '),
)
}
-export function genAppendNode(oper: AppendNodeIRNode, context: CodegenContext) {
- const { newline, pushFnCall, vaporHelper } = context
+export function genAppendNode(
+ oper: AppendNodeIRNode,
+ { newline, pushCall, vaporHelper }: CodegenContext,
+) {
newline()
- pushFnCall(
+ pushCall(
vaporHelper('append'),
`n${oper.parent}`,
oper.elements.map(el => `n${el}`).join(', '),
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
export function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
- const { vaporHelper, push, newline, pushMulti, pushFnCall } = context
+ const {
+ vaporHelper,
+ push,
+ newline,
+ pushMulti,
+ pushCall,
+ options: ctxOptions,
+ } = context
const { keys, nonKeys, options } = oper.modifiers
newline()
- pushFnCall(
+ pushCall(
vaporHelper('on'),
// 1st arg: event name
() => push(`n${oper.element}`),
;(keys.length ? pushWithKeys : pushNoop)(() =>
(nonKeys.length ? pushWithModifiers : pushNoop)(() => {
- genEventHandler(context)
+ genEventHandler()
}),
)
},
(() => push(`{ ${options.map(v => `${v}: true`).join(', ')} }`)),
)
- function genEventHandler(context: CodegenContext) {
+ function genEventHandler() {
const exp = oper.value
if (exp && exp.content.trim()) {
- const isMemberExp = isMemberExpression(exp.content, context)
+ const isMemberExp = isMemberExpression(exp.content, ctxOptions)
const isInlineStatement = !(isMemberExp || fnExpRE.test(exp.content))
const hasMultipleStatements = exp.content.includes(`;`)
context: CodegenContext,
knownIds: Record<string, number> = Object.create(null),
): void {
- const { push } = context
+ const {
+ push,
+ options: { prefixIdentifiers },
+ } = context
if (isString(node)) return push(node)
const { content: rawExpr, ast, isStatic, loc } = node
}
if (
__BROWSER__ ||
- !context.prefixIdentifiers ||
+ !prefixIdentifiers ||
!node.content.trim() ||
// there was a parsing error
ast === false ||
function genIdentifier(
id: string,
- { inline, bindingMetadata, vaporHelper, push }: CodegenContext,
+ { options, vaporHelper, push }: CodegenContext,
loc?: SourceLocation,
): void {
+ const { inline, bindingMetadata } = options
let name: string | undefined = id
if (inline) {
switch (bindingMetadata[id]) {
import { genExpression } from './expression'
export function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
- const { newline, pushFnCall, vaporHelper } = context
+ const { newline, pushCall, vaporHelper } = context
newline()
- pushFnCall(vaporHelper('setHtml'), `n${oper.element}`, () =>
+ pushCall(vaporHelper('setHtml'), `n${oper.element}`, () =>
genExpression(oper.value, context),
)
}
context: CodegenContext,
isNested = false,
) {
- const { pushFnCall, vaporHelper, pushNewline, push } = context
+ const { pushCall, vaporHelper, newline, push } = context
const { condition, positive, negative } = oper
let positiveArg = () => genBlockFunction(positive, context)
}
}
- if (!isNested) pushNewline(`const n${oper.id} = `)
- pushFnCall(
+ if (!isNested) newline(`const n${oper.id} = `)
+ pushCall(
vaporHelper('createIf'),
() => {
push('() => (')
}
function genBlockFunction(oper: BlockFunctionIRNode, context: CodegenContext) {
- const { pushNewline, push, withIndent } = context
+ const { newline, push, withIndent } = context
push('() => {')
withIndent(() => {
genBlockFunctionContent(oper, context)
})
- pushNewline('}')
+ newline('}')
}
oper: SetModelValueIRNode,
context: CodegenContext,
) {
- const { vaporHelper, push, newline, pushFnCall } = context
+ const {
+ vaporHelper,
+ push,
+ newline,
+ pushCall,
+ options: { isTS },
+ } = context
newline()
- pushFnCall(
+ pushCall(
vaporHelper('on'),
// 1st arg: event name
() => push(`n${oper.element}`),
},
// 3rd arg: event handler
() => {
- push((context.isTS ? `($event: any)` : `$event`) + ' => ((')
+ push((isTS ? `($event: any)` : `$event`) + ' => ((')
// TODO handle not a ref
genExpression(oper.value, context)
push(') = $event)')
import { isString } from '@vue/shared'
export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
- const { pushFnCall, pushMulti, newline, vaporHelper, helper } = context
+ const { pushCall, pushMulti, newline, vaporHelper, helper } = context
newline()
}
if (helperName) {
- pushFnCall(
+ pushCall(
vaporHelper(helperName),
element,
omitKey
: () => {
const expr = () => genExpression(oper.key, context)
if (oper.runtimeCamelize) {
- pushFnCall(helper('camelize'), expr)
+ pushCall(helper('camelize'), expr)
} else {
expr()
}
}
}
- pushFnCall(
+ pushCall(
vaporHelper('setDynamicProp'),
element,
// 2. key name
() => {
if (oper.runtimeCamelize) {
- pushFnCall(helper('camelize'), () => genExpression(oper.key, context))
+ pushCall(helper('camelize'), () => genExpression(oper.key, context))
} else if (oper.modifier) {
pushMulti([`\`${oper.modifier}\${`, `}\``], () =>
genExpression(oper.key, context),
import { genExpression } from './expression'
export function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
- const { newline, pushFnCall, vaporHelper } = context
+ const { newline, pushCall, vaporHelper } = context
newline()
- pushFnCall(vaporHelper('setRef'), `n${oper.element}`, () =>
+ pushCall(vaporHelper('setRef'), `n${oper.element}`, () =>
genExpression(oper.value, context),
)
}
import { genExpression } from './expression'
export function genSetText(oper: SetTextIRNode, context: CodegenContext) {
- const { pushFnCall, newline, vaporHelper } = context
+ const { pushCall, newline, vaporHelper } = context
newline()
- pushFnCall(vaporHelper('setText'), `n${oper.element}`, () =>
+ pushCall(vaporHelper('setText'), `n${oper.element}`, () =>
genExpression(oper.value, context),
)
}
oper: CreateTextNodeIRNode,
context: CodegenContext,
) {
- const { pushNewline, pushFnCall, vaporHelper } = context
- pushNewline(`const n${oper.id} = `)
- pushFnCall(vaporHelper('createTextNode'), () =>
+ const { newline, pushCall, vaporHelper } = context
+ newline(`const n${oper.id} = `)
+ pushCall(vaporHelper('createTextNode'), () =>
genExpression(oper.value, context),
)
}