mirror of https://github.com/vuejs/core.git
refactor(compiler-vapor): re-organize
This commit is contained in:
parent
1a8fb86c75
commit
edaa3a0649
|
@ -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>,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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}`)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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}`),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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[] {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)),
|
||||
)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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')),
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
|
@ -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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue