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

View File

@ -1,66 +1,35 @@
import {
type CodegenOptions as BaseCodegenOptions,
type BaseCodegenResult,
NewlineType,
type Position,
type SourceLocation,
advancePositionWithMutation,
locStub,
import type {
CodegenOptions as BaseCodegenOptions,
BaseCodegenResult,
} from '@vue/compiler-dom'
import type { IREffect, RootIRNode, VaporHelper } from './ir'
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 { genBlockFunctionContent } from './generators/block'
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[]
}
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 {
options: Required<CodegenOptions>
code: CodeFragment[]
map?: SourceMapGenerator
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>([])
vaporHelpers = new Set<string>([])
@ -135,12 +104,6 @@ export interface VaporCodegenResult extends BaseCodegenResult {
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
export function generate(
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) {
let imports = ''
if (helpers.size) {

View File

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

View File

@ -1,20 +1,24 @@
import { createSimpleExpression, isSimpleIdentifier } from '@vue/compiler-dom'
import { camelize } from '@vue/shared'
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'
export function genWithDirective(
opers: WithDirectiveIRNode[],
context: CodegenContext,
): CodeFragment[] {
const { call, multi, vaporHelper } = context
const { vaporHelper } = context
const element = `n${opers[0].element}`
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[] {
const NULL = 'void 0'
@ -34,7 +38,7 @@ export function genWithDirective(
? ['{ ', genDirectiveModifiers(), ' }']
: false
return multi(['[', ']', ', '], directive, value, argument, modifiers)
return genMulti(['[', ']', ', '], directive, value, argument, modifiers)
function genDirective() {
const {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
import { camelize } from '@vue/shared'
import { genExpression } from './expression'
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(
oper: SetModelValueIRNode,
@ -9,7 +10,7 @@ export function genSetModelValue(
): CodeFragment[] {
const {
vaporHelper,
call,
options: { isTS },
} = context
@ -24,6 +25,6 @@ export function genSetModelValue(
return [
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 CodeFragment,
type CodegenContext,
INDENT_END,
INDENT_START,
NEWLINE,
buildCodeFragment,
} from '../generate'
import type { CodegenContext } from '../generate'
import { genAppendNode, genInsertNode, genPrependNode } from './dom'
import { genSetEvent } from './event'
import { genFor } from './for'
@ -16,6 +9,13 @@ import { genSetModelValue } from './modelValue'
import { genDynamicProps, genSetProp } from './prop'
import { genSetRef } from './ref'
import { genCreateTextNode, genSetText } from './text'
import {
type CodeFragment,
INDENT_END,
INDENT_START,
NEWLINE,
buildCodeFragment,
} from './utils'
export function genOperations(opers: OperationNode[], context: CodegenContext) {
const [frag, push] = buildCodeFragment()

View File

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

View File

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

View File

@ -1,9 +1,10 @@
import { type CodegenContext, NEWLINE, buildCodeFragment } from '../generate'
import type { CodegenContext } from '../generate'
import {
DynamicFlag,
type IRDynamicInfo,
type TemplateFactoryIRNode,
} from '../ir'
import { NEWLINE, buildCodeFragment, genCall } from './utils'
export function genTemplates(
templates: TemplateFactoryIRNode[],
@ -23,7 +24,7 @@ export function genChildren(
from: number,
paths: number[] = [],
) {
const { vaporHelper, call } = context
const { vaporHelper } = context
const [frag, push] = buildCodeFragment()
let offset = 0
const { children } = dynamic
@ -47,7 +48,11 @@ export function genChildren(
push(
NEWLINE,
`const n${id} = `,
...call(vaporHelper('children'), `n${from}`, ...newPaths.map(String)),
...genCall(
vaporHelper('children'),
`n${from}`,
...newPaths.map(String),
),
)
push(...genChildren(child, context, id, []))
} 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 { genExpression } from './expression'
import { type CodeFragment, NEWLINE, genCall } from './utils'
export function genSetText(
oper: SetTextIRNode,
context: CodegenContext,
): CodeFragment[] {
const { call, vaporHelper } = context
const { vaporHelper } = context
const { values } = oper
return [
NEWLINE,
...call(
...genCall(
vaporHelper('setText'),
`n${oper.element}`,
...values.map(value => genExpression(value, context)),
@ -22,10 +23,10 @@ export function genCreateTextNode(
oper: CreateTextNodeIRNode,
context: CodegenContext,
): CodeFragment[] {
const { call, vaporHelper } = context
const { vaporHelper } = context
return [
NEWLINE,
`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 { transform } from './transform'
export { generate } from './generate'
export { compile, type CompilerOptions } from './compile'
export * from './transform'
export * from './generate'
export {
wrapTemplate,
compile,
type CompilerOptions,
type TransformPreset,
} from './compile'
export * from './ir'
export * from './errors'
export { transformElement } from './transforms/transformElement'

View File

@ -1,17 +1,13 @@
import {
type AllNode,
type AttributeNode,
type TransformOptions as BaseTransformOptions,
type CompilerCompatOptions,
type DirectiveNode,
type ElementNode,
ElementTypes,
NodeTypes,
type RootNode,
type SimpleExpressionNode,
type TemplateChildNode,
type TemplateNode,
createSimpleExpression,
defaultOnError,
defaultOnWarn,
isVSlot,
@ -28,6 +24,7 @@ import {
type VaporDirectiveNode,
} from './ir'
import { isConstantExpression } from './utils'
import { genDefaultDynamic } from './transforms/utils'
export type NodeTransform = (
node: RootNode | TemplateChildNode,
@ -108,13 +105,6 @@ const defaultOptions = {
onWarn: defaultOnWarn,
}
export const genDefaultDynamic = (): IRDynamicInfo => ({
id: null,
flags: DynamicFlag.NONE,
anchor: null,
children: [],
})
// TODO use class for better perf
function createRootContext(
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,
isVoidTag,
} from '@vue/shared'
import {
type DirectiveTransformResult,
EMPTY_EXPRESSION,
type NodeTransform,
type TransformContext,
import type {
DirectiveTransformResult,
NodeTransform,
TransformContext,
} from '../transform'
import {
IRNodeTypes,
@ -26,6 +25,7 @@ import {
type IRProps,
type VaporDirectiveNode,
} from '../ir'
import { EMPTY_EXPRESSION } from './utils'
export const transformElement: NodeTransform = (node, context) => {
return function postTransformElement() {

View File

@ -3,10 +3,11 @@ import {
type SimpleExpressionNode,
createSimpleExpression,
} from '@vue/compiler-dom'
import { EMPTY_EXPRESSION, type NodeTransform } from '../transform'
import type { NodeTransform } from '../transform'
import { IRNodeTypes } from '../ir'
import { normalizeBindShorthand } from './vBind'
import { findProp } from '../utils'
import { EMPTY_EXPRESSION } from './utils'
export const transformRef: NodeTransform = (node, context) => {
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 {
type TransformContext,
createStructuralDirectiveTransform,
genDefaultDynamic,
wrapTemplate,
} from '../transform'
import {
type BlockFunctionIRNode,
@ -19,6 +17,7 @@ import {
} from '../ir'
import { extend } from '@vue/shared'
import { findProp, propToExpression } from '../utils'
import { genDefaultDynamic, wrapTemplate } from './utils'
export const transformVFor = createStructuralDirectiveTransform(
'for',

View File

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

View File

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

View File

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

View File

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