refactor(compiler-vapor): re-organize

This commit is contained in:
三咲智子 Kevin Deng 2024-02-09 02:40:01 +08:00
parent 1a8fb86c75
commit edaa3a0649
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
27 changed files with 329 additions and 282 deletions

View File

@ -27,7 +27,7 @@ import { transformVModel } from './transforms/vModel'
import { transformVIf } from './transforms/vIf' import { transformVIf } from './transforms/vIf'
import { transformVFor } from './transforms/vFor' import { transformVFor } from './transforms/vFor'
export type CompilerOptions = HackOptions<BaseCompilerOptions> export { wrapTemplate } from './transforms/utils'
// TODO: copied from @vue/compiler-core // TODO: copied from @vue/compiler-core
// code/AST -> IR -> JS codegen // code/AST -> IR -> JS codegen
@ -89,6 +89,7 @@ export function compile(
return generate(ir, resolvedOptions) return generate(ir, resolvedOptions)
} }
export type CompilerOptions = HackOptions<BaseCompilerOptions>
export type TransformPreset = [ export type TransformPreset = [
NodeTransform[], NodeTransform[],
Record<string, DirectiveTransform>, Record<string, DirectiveTransform>,

View File

@ -1,66 +1,35 @@
import { import type {
type CodegenOptions as BaseCodegenOptions, CodegenOptions as BaseCodegenOptions,
type BaseCodegenResult, BaseCodegenResult,
NewlineType,
type Position,
type SourceLocation,
advancePositionWithMutation,
locStub,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import type { IREffect, RootIRNode, VaporHelper } from './ir' import type { IREffect, RootIRNode, VaporHelper } from './ir'
import { SourceMapGenerator } from 'source-map-js' import { SourceMapGenerator } from 'source-map-js'
import { extend, isArray, isString, remove } from '@vue/shared' import { extend, remove } from '@vue/shared'
import type { ParserPlugin } from '@babel/parser' import type { ParserPlugin } from '@babel/parser'
import { genBlockFunctionContent } from './generators/block' import { genBlockFunctionContent } from './generators/block'
import { genTemplates } from './generators/template' import { genTemplates } from './generators/template'
import {
type CodeFragment,
INDENT_END,
INDENT_START,
LF,
NEWLINE,
buildCodeFragment,
genCodeFragment,
} from './generators/utils'
interface CodegenOptions extends BaseCodegenOptions { export * from './generators/utils'
export interface CodegenOptions extends BaseCodegenOptions {
expressionPlugins?: ParserPlugin[] expressionPlugins?: ParserPlugin[]
} }
type FalsyValue = false | null | undefined
export type CodeFragment =
| typeof NEWLINE
| typeof LF
| typeof INDENT_START
| typeof INDENT_END
| string
| [code: string, newlineIndex?: number, loc?: SourceLocation, name?: string]
| FalsyValue
type CodeFragments = Exclude<CodeFragment, any[]> | CodeFragment[]
export class CodegenContext { export class CodegenContext {
options: Required<CodegenOptions> options: Required<CodegenOptions>
code: CodeFragment[] code: CodeFragment[]
map?: SourceMapGenerator map?: SourceMapGenerator
push: (...args: CodeFragment[]) => void push: (...args: CodeFragment[]) => void
multi = (
[left, right, seg]: [
left: CodeFragment,
right: CodeFragment,
segment: CodeFragment,
],
...fns: CodeFragments[]
): CodeFragment[] => {
const frag: CodeFragment[] = []
fns = fns.filter(Boolean)
frag.push(left)
for (let [i, fn] of (
fns as Array<Exclude<CodeFragments, FalsyValue>>
).entries()) {
if (!isArray(fn)) fn = [fn]
frag.push(...fn)
if (i < fns.length - 1) frag.push(seg)
}
frag.push(right)
return frag
}
call = (name: string, ...args: CodeFragments[]): CodeFragment[] => {
return [name, ...this.multi(['(', ')', ', '], ...args)]
}
helpers = new Set<string>([]) helpers = new Set<string>([])
vaporHelpers = new Set<string>([]) vaporHelpers = new Set<string>([])
@ -135,12 +104,6 @@ export interface VaporCodegenResult extends BaseCodegenResult {
vaporHelpers: Set<string> vaporHelpers: Set<string>
} }
export const NEWLINE = Symbol(__DEV__ ? `newline` : ``)
/** increase offset but don't push actual code */
export const LF = Symbol(__DEV__ ? `line feed` : ``)
export const INDENT_START = Symbol(__DEV__ ? `indent start` : ``)
export const INDENT_END = Symbol(__DEV__ ? `indent end` : ``)
// IR -> JS codegen // IR -> JS codegen
export function generate( export function generate(
ir: RootIRNode, ir: RootIRNode,
@ -193,102 +156,6 @@ export function generate(
} }
} }
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
} else if (frag === LF) {
pos.line++
pos.column = 0
pos.offset++
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(pos, code)
} else {
// fast paths
pos.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')}`,
)
}
pos.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')}`,
)
}
pos.line++
pos.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: pos.line,
generatedColumn: pos.column - 1,
source: context.options.filename,
// @ts-expect-error it is possible to be null
name,
})
}
}
export function buildCodeFragment(...frag: CodeFragment[]) {
const push = frag.push.bind(frag)
return [frag, push] as const
}
function genHelperImports({ helpers, vaporHelpers, code }: CodegenContext) { function genHelperImports({ helpers, vaporHelpers, code }: CodegenContext) {
let imports = '' let imports = ''
if (helpers.size) { if (helpers.size) {

View File

@ -6,15 +6,16 @@ import {
} from '../ir' } from '../ir'
import { import {
type CodeFragment, type CodeFragment,
type CodegenContext,
INDENT_END, INDENT_END,
INDENT_START, INDENT_START,
NEWLINE, NEWLINE,
buildCodeFragment, buildCodeFragment,
} from '../generate' } from './utils'
import type { CodegenContext } from '../generate'
import { genWithDirective } from './directive' import { genWithDirective } from './directive'
import { genEffects, genOperations } from './operation' import { genEffects, genOperations } from './operation'
import { genChildren } from './template' import { genChildren } from './template'
import { genMulti } from './utils'
export function genBlockFunction( export function genBlockFunction(
oper: BlockFunctionIRNode, oper: BlockFunctionIRNode,
@ -37,7 +38,6 @@ export function genBlockFunctionContent(
ir: BlockFunctionIRNode | RootIRNode, ir: BlockFunctionIRNode | RootIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { multi } = context
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()
if (ir.templateIndex > -1) { if (ir.templateIndex > -1) {
@ -59,7 +59,7 @@ export function genBlockFunctionContent(
push( push(
NEWLINE, NEWLINE,
`return `, `return `,
...multi(['[', ']', ', '], ...ir.returns.map(n => `n${n}`)), ...genMulti(['[', ']', ', '], ...ir.returns.map(n => `n${n}`)),
) )
} else { } else {
push(NEWLINE, `return n${ir.dynamic.id}`) push(NEWLINE, `return n${ir.dynamic.id}`)

View File

@ -1,20 +1,24 @@
import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom' import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom'
import { camelize } from '@vue/shared' import { camelize } from '@vue/shared'
import { genExpression } from './expression' import { genExpression } from './expression'
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate' import type { CodegenContext } from '../generate'
import { type CodeFragment, NEWLINE, genCall, genMulti } from './utils'
import type { WithDirectiveIRNode } from '../ir' import type { WithDirectiveIRNode } from '../ir'
export function genWithDirective( export function genWithDirective(
opers: WithDirectiveIRNode[], opers: WithDirectiveIRNode[],
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, multi, vaporHelper } = context const { vaporHelper } = context
const element = `n${opers[0].element}` const element = `n${opers[0].element}`
const directiveItems = opers.map(genDirective) const directiveItems = opers.map(genDirective)
const directives = multi(['[', ']', ', '], ...directiveItems) const directives = genMulti(['[', ']', ', '], ...directiveItems)
return [NEWLINE, ...call(vaporHelper('withDirectives'), element, directives)] return [
NEWLINE,
...genCall(vaporHelper('withDirectives'), element, directives),
]
function genDirective({ dir, builtin }: WithDirectiveIRNode): CodeFragment[] { function genDirective({ dir, builtin }: WithDirectiveIRNode): CodeFragment[] {
const NULL = 'void 0' const NULL = 'void 0'
@ -34,7 +38,7 @@ export function genWithDirective(
? ['{ ', genDirectiveModifiers(), ' }'] ? ['{ ', genDirectiveModifiers(), ' }']
: false : false
return multi(['[', ']', ', '], directive, value, argument, modifiers) return genMulti(['[', ']', ', '], directive, value, argument, modifiers)
function genDirective() { function genDirective() {
const { const {

View File

@ -1,20 +1,21 @@
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate' import type { CodegenContext } from '../generate'
import type { import type {
AppendNodeIRNode, AppendNodeIRNode,
InsertNodeIRNode, InsertNodeIRNode,
PrependNodeIRNode, PrependNodeIRNode,
} from '../ir' } from '../ir'
import { type CodeFragment, NEWLINE, genCall } from './utils'
export function genInsertNode( export function genInsertNode(
oper: InsertNodeIRNode, oper: InsertNodeIRNode,
{ call, vaporHelper }: CodegenContext, { vaporHelper }: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const elements = ([] as number[]).concat(oper.element) const elements = ([] as number[]).concat(oper.element)
let element = elements.map(el => `n${el}`).join(', ') let element = elements.map(el => `n${el}`).join(', ')
if (elements.length > 1) element = `[${element}]` if (elements.length > 1) element = `[${element}]`
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('insert'), vaporHelper('insert'),
element, element,
`n${oper.parent}`, `n${oper.parent}`,
@ -25,11 +26,11 @@ export function genInsertNode(
export function genPrependNode( export function genPrependNode(
oper: PrependNodeIRNode, oper: PrependNodeIRNode,
{ call, vaporHelper }: CodegenContext, { vaporHelper }: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('prepend'), vaporHelper('prepend'),
`n${oper.parent}`, `n${oper.parent}`,
...oper.elements.map(el => `n${el}`), ...oper.elements.map(el => `n${el}`),
@ -39,11 +40,11 @@ export function genPrependNode(
export function genAppendNode( export function genAppendNode(
oper: AppendNodeIRNode, oper: AppendNodeIRNode,
{ call, vaporHelper }: CodegenContext, { vaporHelper }: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('append'), vaporHelper('append'),
`n${oper.parent}`, `n${oper.parent}`,
...oper.elements.map(el => `n${el}`), ...oper.elements.map(el => `n${el}`),

View File

@ -1,14 +1,15 @@
import { isMemberExpression } from '@vue/compiler-dom' import { isMemberExpression } from '@vue/compiler-dom'
import type { CodegenContext } from '../generate'
import type { SetEventIRNode } from '../ir'
import { genExpression } from './expression'
import { import {
type CodeFragment, type CodeFragment,
type CodegenContext,
INDENT_END, INDENT_END,
INDENT_START, INDENT_START,
NEWLINE, NEWLINE,
buildCodeFragment, buildCodeFragment,
} from '../generate' genCall,
import type { SetEventIRNode } from '../ir' } from './utils'
import { genExpression } from './expression'
// TODO: share this with compiler-core // TODO: share this with compiler-core
const fnExpRE = const fnExpRE =
@ -18,7 +19,7 @@ export function genSetEvent(
oper: SetEventIRNode, oper: SetEventIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { vaporHelper, call, options: ctxOptions } = context const { vaporHelper, options: ctxOptions } = context
const { keys, nonKeys, options } = oper.modifiers const { keys, nonKeys, options } = oper.modifiers
const name = genName() const name = genName()
@ -32,7 +33,7 @@ export function genSetEvent(
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('on'), vaporHelper('on'),
`n${oper.element}`, `n${oper.element}`,
name, name,

View File

@ -9,13 +9,10 @@ import {
walkIdentifiers, walkIdentifiers,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import type { Identifier } from '@babel/types' import type { Identifier } from '@babel/types'
import { import type { CodegenContext } from '../generate'
type CodeFragment,
type CodegenContext,
buildCodeFragment,
} from '../generate'
import type { Node } from '@babel/types' import type { Node } from '@babel/types'
import { isConstantExpression } from '../utils' import { isConstantExpression } from '../utils'
import { type CodeFragment, buildCodeFragment } from './utils'
export function genExpression( export function genExpression(
node: SimpleExpressionNode, node: SimpleExpressionNode,

View File

@ -1,22 +1,23 @@
import { NewlineType } from '@vue/compiler-dom'
import { genBlockFunction } from './block' import { genBlockFunction } from './block'
import { genExpression } from './expression' import { genExpression } from './expression'
import type { CodegenContext } from '../generate'
import type { ForIRNode, IREffect } from '../ir'
import { genOperation } from './operation'
import { import {
type CodeFragment, type CodeFragment,
type CodegenContext,
INDENT_END, INDENT_END,
INDENT_START, INDENT_START,
NEWLINE, NEWLINE,
buildCodeFragment, buildCodeFragment,
} from '../generate' genCall,
import type { ForIRNode, IREffect } from '../ir' } from './utils'
import { genOperation } from './operation'
import { NewlineType } from '@vue/compiler-dom'
export function genFor( export function genFor(
oper: ForIRNode, oper: ForIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
const { source, value, key, render, keyProperty } = oper const { source, value, key, render, keyProperty } = oper
const rawValue = value && value.content const rawValue = value && value.content
@ -58,7 +59,7 @@ export function genFor(
return [ return [
NEWLINE, NEWLINE,
`const n${oper.id} = `, `const n${oper.id} = `,
...call(vaporHelper('createFor'), sourceExpr, blockFn, getKeyFn), ...genCall(vaporHelper('createFor'), sourceExpr, blockFn, getKeyFn),
] ]
function genEffectInFor(effects: IREffect[]): CodeFragment[] { function genEffectInFor(effects: IREffect[]): CodeFragment[] {

View File

@ -1,15 +1,16 @@
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate' import type { CodegenContext } from '../generate'
import type { SetHtmlIRNode } from '../ir' import type { SetHtmlIRNode } from '../ir'
import { genExpression } from './expression' import { genExpression } from './expression'
import { type CodeFragment, NEWLINE, genCall } from './utils'
export function genSetHtml( export function genSetHtml(
oper: SetHtmlIRNode, oper: SetHtmlIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('setHtml'), vaporHelper('setHtml'),
`n${oper.element}`, `n${oper.element}`,
genExpression(oper.value, context), genExpression(oper.value, context),

View File

@ -1,19 +1,15 @@
import { import type { CodegenContext } from '../generate'
type CodeFragment,
type CodegenContext,
NEWLINE,
buildCodeFragment,
} from '../generate'
import { IRNodeTypes, type IfIRNode } from '../ir' import { IRNodeTypes, type IfIRNode } from '../ir'
import { genBlockFunction } from './block' import { genBlockFunction } from './block'
import { genExpression } from './expression' import { genExpression } from './expression'
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
export function genIf( export function genIf(
oper: IfIRNode, oper: IfIRNode,
context: CodegenContext, context: CodegenContext,
isNested = false, isNested = false,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
const { condition, positive, negative } = oper const { condition, positive, negative } = oper
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()
@ -36,7 +32,12 @@ export function genIf(
if (!isNested) push(NEWLINE, `const n${oper.id} = `) if (!isNested) push(NEWLINE, `const n${oper.id} = `)
push( push(
...call(vaporHelper('createIf'), conditionExpr, positiveArg, negativeArg), ...genCall(
vaporHelper('createIf'),
conditionExpr,
positiveArg,
negativeArg,
),
) )
return frag return frag

View File

@ -1,7 +1,8 @@
import { camelize } from '@vue/shared' import { camelize } from '@vue/shared'
import { genExpression } from './expression' import { genExpression } from './expression'
import type { SetModelValueIRNode } from '../ir' import type { SetModelValueIRNode } from '../ir'
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate' import type { CodegenContext } from '../generate'
import { type CodeFragment, NEWLINE, genCall } from './utils'
export function genSetModelValue( export function genSetModelValue(
oper: SetModelValueIRNode, oper: SetModelValueIRNode,
@ -9,7 +10,7 @@ export function genSetModelValue(
): CodeFragment[] { ): CodeFragment[] {
const { const {
vaporHelper, vaporHelper,
call,
options: { isTS }, options: { isTS },
} = context } = context
@ -24,6 +25,6 @@ export function genSetModelValue(
return [ return [
NEWLINE, NEWLINE,
...call(vaporHelper('on'), `n${oper.element}`, name, handler), ...genCall(vaporHelper('on'), `n${oper.element}`, name, handler),
] ]
} }

View File

@ -1,12 +1,5 @@
import { type IREffect, IRNodeTypes, type OperationNode } from '../ir' import { type IREffect, IRNodeTypes, type OperationNode } from '../ir'
import { import type { CodegenContext } from '../generate'
type CodeFragment,
type CodegenContext,
INDENT_END,
INDENT_START,
NEWLINE,
buildCodeFragment,
} from '../generate'
import { genAppendNode, genInsertNode, genPrependNode } from './dom' import { genAppendNode, genInsertNode, genPrependNode } from './dom'
import { genSetEvent } from './event' import { genSetEvent } from './event'
import { genFor } from './for' import { genFor } from './for'
@ -16,6 +9,13 @@ import { genSetModelValue } from './modelValue'
import { genDynamicProps, genSetProp } from './prop' import { genDynamicProps, genSetProp } from './prop'
import { genSetRef } from './ref' import { genSetRef } from './ref'
import { genCreateTextNode, genSetText } from './text' import { genCreateTextNode, genSetText } from './text'
import {
type CodeFragment,
INDENT_END,
INDENT_START,
NEWLINE,
buildCodeFragment,
} from './utils'
export function genOperations(opers: OperationNode[], context: CodegenContext) { export function genOperations(opers: OperationNode[], context: CodegenContext) {
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()

View File

@ -3,7 +3,7 @@ import {
type SimpleExpressionNode, type SimpleExpressionNode,
isSimpleIdentifier, isSimpleIdentifier,
} from '@vue/compiler-core' } from '@vue/compiler-core'
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate' import type { CodegenContext } from '../generate'
import type { import type {
IRProp, IRProp,
SetDynamicPropsIRNode, SetDynamicPropsIRNode,
@ -11,13 +11,14 @@ import type {
VaporHelper, VaporHelper,
} from '../ir' } from '../ir'
import { genExpression } from './expression' import { genExpression } from './expression'
import { type CodeFragment, NEWLINE, genCall, genMulti } from './utils'
// only the static key prop will reach here // only the static key prop will reach here
export function genSetProp( export function genSetProp(
oper: SetPropIRNode, oper: SetPropIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
const { const {
prop: { key, values, modifier }, prop: { key, values, modifier },
} = oper } = oper
@ -40,7 +41,7 @@ export function genSetProp(
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper(helperName), vaporHelper(helperName),
`n${oper.element}`, `n${oper.element}`,
omitKey ? false : genExpression(key, context), omitKey ? false : genExpression(key, context),
@ -54,10 +55,10 @@ export function genDynamicProps(
oper: SetDynamicPropsIRNode, oper: SetDynamicPropsIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('setDynamicProps'), vaporHelper('setDynamicProps'),
`n${oper.element}`, `n${oper.element}`,
...oper.props.map( ...oper.props.map(
@ -74,8 +75,7 @@ function genLiteralObjectProps(
props: IRProp[], props: IRProp[],
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { multi } = context return genMulti(
return multi(
['{ ', ' }', ', '], ['{ ', ' }', ', '],
...props.map(prop => [ ...props.map(prop => [
...genPropertyKey(prop, context), ...genPropertyKey(prop, context),
@ -89,7 +89,7 @@ function genPropertyKey(
{ key: node, runtimeCamelize, modifier }: IRProp, { key: node, runtimeCamelize, modifier }: IRProp,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, helper } = context const { helper } = context
// static arg was transformed by v-bind transformer // static arg was transformed by v-bind transformer
if (node.isStatic) { if (node.isStatic) {
@ -108,7 +108,7 @@ function genPropertyKey(
return [ return [
'[', '[',
modifier && `${JSON.stringify(modifier)} + `, modifier && `${JSON.stringify(modifier)} + `,
...(runtimeCamelize ? call(helper('camelize'), key) : key), ...(runtimeCamelize ? genCall(helper('camelize'), key) : key),
']', ']',
] ]
} }
@ -117,8 +117,7 @@ function genPropValue(values: SimpleExpressionNode[], context: CodegenContext) {
if (values.length === 1) { if (values.length === 1) {
return genExpression(values[0], context) return genExpression(values[0], context)
} }
const { multi } = context return genMulti(
return multi(
['[', ']', ', '], ['[', ']', ', '],
...values.map(expr => genExpression(expr, context)), ...values.map(expr => genExpression(expr, context)),
) )

View File

@ -1,15 +1,16 @@
import { genExpression } from './expression' import { genExpression } from './expression'
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate' import type { CodegenContext } from '../generate'
import type { SetRefIRNode } from '../ir' import type { SetRefIRNode } from '../ir'
import { type CodeFragment, NEWLINE, genCall } from './utils'
export function genSetRef( export function genSetRef(
oper: SetRefIRNode, oper: SetRefIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('setRef'), vaporHelper('setRef'),
[`n${oper.element}`], [`n${oper.element}`],
genExpression(oper.value, context), genExpression(oper.value, context),

View File

@ -1,9 +1,10 @@
import { type CodegenContext, NEWLINE, buildCodeFragment } from '../generate' import type { CodegenContext } from '../generate'
import { import {
DynamicFlag, DynamicFlag,
type IRDynamicInfo, type IRDynamicInfo,
type TemplateFactoryIRNode, type TemplateFactoryIRNode,
} from '../ir' } from '../ir'
import { NEWLINE, buildCodeFragment, genCall } from './utils'
export function genTemplates( export function genTemplates(
templates: TemplateFactoryIRNode[], templates: TemplateFactoryIRNode[],
@ -23,7 +24,7 @@ export function genChildren(
from: number, from: number,
paths: number[] = [], paths: number[] = [],
) { ) {
const { vaporHelper, call } = context const { vaporHelper } = context
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()
let offset = 0 let offset = 0
const { children } = dynamic const { children } = dynamic
@ -47,7 +48,11 @@ export function genChildren(
push( push(
NEWLINE, NEWLINE,
`const n${id} = `, `const n${id} = `,
...call(vaporHelper('children'), `n${from}`, ...newPaths.map(String)), ...genCall(
vaporHelper('children'),
`n${from}`,
...newPaths.map(String),
),
) )
push(...genChildren(child, context, id, [])) push(...genChildren(child, context, id, []))
} else { } else {

View File

@ -1,16 +1,17 @@
import { type CodeFragment, type CodegenContext, NEWLINE } from '../generate' import type { CodegenContext } from '../generate'
import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir' import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir'
import { genExpression } from './expression' import { genExpression } from './expression'
import { type CodeFragment, NEWLINE, genCall } from './utils'
export function genSetText( export function genSetText(
oper: SetTextIRNode, oper: SetTextIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
const { values } = oper const { values } = oper
return [ return [
NEWLINE, NEWLINE,
...call( ...genCall(
vaporHelper('setText'), vaporHelper('setText'),
`n${oper.element}`, `n${oper.element}`,
...values.map(value => genExpression(value, context)), ...values.map(value => genExpression(value, context)),
@ -22,10 +23,10 @@ export function genCreateTextNode(
oper: CreateTextNodeIRNode, oper: CreateTextNodeIRNode,
context: CodegenContext, context: CodegenContext,
): CodeFragment[] { ): CodeFragment[] {
const { call, vaporHelper } = context const { vaporHelper } = context
return [ return [
NEWLINE, NEWLINE,
`const n${oper.id} = `, `const n${oper.id} = `,
...call(vaporHelper('createTextNode')), ...genCall(vaporHelper('createTextNode')),
] ]
} }

View File

@ -0,0 +1,151 @@
import {
NewlineType,
type Position,
type SourceLocation,
advancePositionWithMutation,
locStub,
} from '@vue/compiler-dom'
import { isArray, isString } from '@vue/shared'
import type { CodegenContext } from '../generate'
export const NEWLINE = Symbol(__DEV__ ? `newline` : ``)
/** increase offset but don't push actual code */
export const LF = Symbol(__DEV__ ? `line feed` : ``)
export const INDENT_START = Symbol(__DEV__ ? `indent start` : ``)
export const INDENT_END = Symbol(__DEV__ ? `indent end` : ``)
type FalsyValue = false | null | undefined
export type CodeFragment =
| typeof NEWLINE
| typeof LF
| typeof INDENT_START
| typeof INDENT_END
| string
| [code: string, newlineIndex?: number, loc?: SourceLocation, name?: string]
| FalsyValue
export type CodeFragments = Exclude<CodeFragment, any[]> | CodeFragment[]
export function buildCodeFragment(...frag: CodeFragment[]) {
const push = frag.push.bind(frag)
return [frag, push] as const
}
export function genMulti(
[left, right, seg]: [
left: CodeFragment,
right: CodeFragment,
segment: CodeFragment,
],
...fns: CodeFragments[]
): CodeFragment[] {
const frag: CodeFragment[] = []
fns = fns.filter(Boolean)
frag.push(left)
for (let [i, fn] of (
fns as Array<Exclude<CodeFragments, FalsyValue>>
).entries()) {
if (!isArray(fn)) fn = [fn]
frag.push(...fn)
if (i < fns.length - 1) frag.push(seg)
}
frag.push(right)
return frag
}
export function genCall(
name: string,
...args: CodeFragments[]
): CodeFragment[] {
return [name, ...genMulti(['(', ')', ', '], ...args)]
}
export 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
} else if (frag === LF) {
pos.line++
pos.column = 0
pos.offset++
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(pos, code)
} else {
// fast paths
pos.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')}`,
)
}
pos.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')}`,
)
}
pos.line++
pos.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: pos.line,
generatedColumn: pos.column - 1,
source: context.options.filename,
// @ts-expect-error it is possible to be null
name,
})
}
}

View File

@ -1,7 +1,12 @@
export { parse } from '@vue/compiler-dom' export { parse } from '@vue/compiler-dom'
export { transform } from './transform' export * from './transform'
export { generate } from './generate' export * from './generate'
export { compile, type CompilerOptions } from './compile' export {
wrapTemplate,
compile,
type CompilerOptions,
type TransformPreset,
} from './compile'
export * from './ir' export * from './ir'
export * from './errors' export * from './errors'
export { transformElement } from './transforms/transformElement' export { transformElement } from './transforms/transformElement'

View File

@ -1,17 +1,13 @@
import { import {
type AllNode, type AllNode,
type AttributeNode,
type TransformOptions as BaseTransformOptions, type TransformOptions as BaseTransformOptions,
type CompilerCompatOptions, type CompilerCompatOptions,
type DirectiveNode,
type ElementNode, type ElementNode,
ElementTypes, ElementTypes,
NodeTypes, NodeTypes,
type RootNode, type RootNode,
type SimpleExpressionNode, type SimpleExpressionNode,
type TemplateChildNode, type TemplateChildNode,
type TemplateNode,
createSimpleExpression,
defaultOnError, defaultOnError,
defaultOnWarn, defaultOnWarn,
isVSlot, isVSlot,
@ -28,6 +24,7 @@ import {
type VaporDirectiveNode, type VaporDirectiveNode,
} from './ir' } from './ir'
import { isConstantExpression } from './utils' import { isConstantExpression } from './utils'
import { genDefaultDynamic } from './transforms/utils'
export type NodeTransform = ( export type NodeTransform = (
node: RootNode | TemplateChildNode, node: RootNode | TemplateChildNode,
@ -108,13 +105,6 @@ const defaultOptions = {
onWarn: defaultOnWarn, onWarn: defaultOnWarn,
} }
export const genDefaultDynamic = (): IRDynamicInfo => ({
id: null,
flags: DynamicFlag.NONE,
anchor: null,
children: [],
})
// TODO use class for better perf // TODO use class for better perf
function createRootContext( function createRootContext(
root: RootIRNode, root: RootIRNode,
@ -411,29 +401,3 @@ export function createStructuralDirectiveTransform(
} }
} }
} }
export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode {
if (node.tagType === ElementTypes.TEMPLATE) {
return node
}
const reserved: Array<AttributeNode | DirectiveNode> = []
const pass: Array<AttributeNode | DirectiveNode> = []
node.props.forEach(prop => {
if (prop.type === NodeTypes.DIRECTIVE && dirs.includes(prop.name)) {
reserved.push(prop)
} else {
pass.push(prop)
}
})
return extend({}, node, {
type: NodeTypes.ELEMENT,
tag: 'template',
props: reserved,
tagType: ElementTypes.TEMPLATE,
children: [extend({}, node, { props: pass } as TemplateChildNode)],
} as Partial<TemplateNode>)
}
export const EMPTY_EXPRESSION = createSimpleExpression('', true)

View File

@ -14,11 +14,10 @@ import {
isReservedProp, isReservedProp,
isVoidTag, isVoidTag,
} from '@vue/shared' } from '@vue/shared'
import { import type {
type DirectiveTransformResult, DirectiveTransformResult,
EMPTY_EXPRESSION, NodeTransform,
type NodeTransform, TransformContext,
type TransformContext,
} from '../transform' } from '../transform'
import { import {
IRNodeTypes, IRNodeTypes,
@ -26,6 +25,7 @@ import {
type IRProps, type IRProps,
type VaporDirectiveNode, type VaporDirectiveNode,
} from '../ir' } from '../ir'
import { EMPTY_EXPRESSION } from './utils'
export const transformElement: NodeTransform = (node, context) => { export const transformElement: NodeTransform = (node, context) => {
return function postTransformElement() { return function postTransformElement() {

View File

@ -3,10 +3,11 @@ import {
type SimpleExpressionNode, type SimpleExpressionNode,
createSimpleExpression, createSimpleExpression,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { EMPTY_EXPRESSION, type NodeTransform } from '../transform' import type { NodeTransform } from '../transform'
import { IRNodeTypes } from '../ir' import { IRNodeTypes } from '../ir'
import { normalizeBindShorthand } from './vBind' import { normalizeBindShorthand } from './vBind'
import { findProp } from '../utils' import { findProp } from '../utils'
import { EMPTY_EXPRESSION } from './utils'
export const transformRef: NodeTransform = (node, context) => { export const transformRef: NodeTransform = (node, context) => {
if (node.type !== NodeTypes.ELEMENT) return if (node.type !== NodeTypes.ELEMENT) return

View File

@ -0,0 +1,45 @@
import {
type AttributeNode,
type DirectiveNode,
type ElementNode,
ElementTypes,
NodeTypes,
type TemplateChildNode,
type TemplateNode,
createSimpleExpression,
} from '@vue/compiler-dom'
import { extend } from '@vue/shared'
import { DynamicFlag, type IRDynamicInfo } from '../ir'
export const genDefaultDynamic = (): IRDynamicInfo => ({
id: null,
flags: DynamicFlag.NONE,
anchor: null,
children: [],
})
export function wrapTemplate(node: ElementNode, dirs: string[]): TemplateNode {
if (node.tagType === ElementTypes.TEMPLATE) {
return node
}
const reserved: Array<AttributeNode | DirectiveNode> = []
const pass: Array<AttributeNode | DirectiveNode> = []
node.props.forEach(prop => {
if (prop.type === NodeTypes.DIRECTIVE && dirs.includes(prop.name)) {
reserved.push(prop)
} else {
pass.push(prop)
}
})
return extend({}, node, {
type: NodeTypes.ELEMENT,
tag: 'template',
props: reserved,
tagType: ElementTypes.TEMPLATE,
children: [extend({}, node, { props: pass } as TemplateChildNode)],
} as Partial<TemplateNode>)
}
export const EMPTY_EXPRESSION = createSimpleExpression('', true)

View File

@ -7,8 +7,6 @@ import {
import { import {
type TransformContext, type TransformContext,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
genDefaultDynamic,
wrapTemplate,
} from '../transform' } from '../transform'
import { import {
type BlockFunctionIRNode, type BlockFunctionIRNode,
@ -19,6 +17,7 @@ import {
} from '../ir' } from '../ir'
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
import { findProp, propToExpression } from '../utils' import { findProp, propToExpression } from '../utils'
import { genDefaultDynamic, wrapTemplate } from './utils'
export const transformVFor = createStructuralDirectiveTransform( export const transformVFor = createStructuralDirectiveTransform(
'for', 'for',

View File

@ -1,6 +1,7 @@
import { IRNodeTypes } from '../ir' import { IRNodeTypes } from '../ir'
import { type DirectiveTransform, EMPTY_EXPRESSION } from '../transform' import type { DirectiveTransform } from '../transform'
import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom' import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
import { EMPTY_EXPRESSION } from './utils'
export const transformVHtml: DirectiveTransform = (dir, node, context) => { export const transformVHtml: DirectiveTransform = (dir, node, context) => {
let { exp, loc } = dir let { exp, loc } = dir

View File

@ -9,8 +9,6 @@ import {
import { import {
type TransformContext, type TransformContext,
createStructuralDirectiveTransform, createStructuralDirectiveTransform,
genDefaultDynamic,
wrapTemplate,
} from '../transform' } from '../transform'
import { import {
type BlockFunctionIRNode, type BlockFunctionIRNode,
@ -21,6 +19,7 @@ import {
type VaporDirectiveNode, type VaporDirectiveNode,
} from '../ir' } from '../ir'
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
import { genDefaultDynamic, wrapTemplate } from './utils'
export const transformVIf = createStructuralDirectiveTransform( export const transformVIf = createStructuralDirectiveTransform(
['if', 'else', 'else-if'], ['if', 'else', 'else-if'],

View File

@ -1,6 +1,7 @@
import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom' import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
import { type DirectiveTransform, EMPTY_EXPRESSION } from '../transform'
import { IRNodeTypes } from '../ir' import { IRNodeTypes } from '../ir'
import { EMPTY_EXPRESSION } from './utils'
import type { DirectiveTransform } from '../transform'
export const transformVText: DirectiveTransform = (dir, node, context) => { export const transformVText: DirectiveTransform = (dir, node, context) => {
let { exp, loc } = dir let { exp, loc } = dir

View File

@ -1,3 +1,4 @@
import type { NumericLiteral, StringLiteral } from '@babel/types'
import { isGloballyAllowed } from '@vue/shared' import { isGloballyAllowed } from '@vue/shared'
import { import {
type AttributeNode, type AttributeNode,
@ -9,8 +10,7 @@ import {
isLiteralWhitelisted, isLiteralWhitelisted,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import type { VaporDirectiveNode } from './ir' import type { VaporDirectiveNode } from './ir'
import { EMPTY_EXPRESSION } from './transform' import { EMPTY_EXPRESSION } from './transforms/utils'
import type { NumericLiteral, StringLiteral } from '@babel/types'
export const findProp = _findProp as ( export const findProp = _findProp as (
node: ElementNode, node: ElementNode,