mirror of https://github.com/vuejs/core.git
refactor(compiler-vapor): CodeFragment for codegen
This commit is contained in:
parent
d942be14f2
commit
c0b7515369
|
@ -17,7 +17,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > custom directive > basic 1`] = `
|
exports[`compile > custom directive > basic 1`] = `
|
||||||
"import { template as _template, children as _children, withDirectives as _withDirectives, resolveDirective("vTest") as _resolveDirective("vTest"), resolveDirective("vHello") as _resolveDirective("vHello") } from 'vue/vapor';
|
"import { template as _template, children as _children, resolveDirective("vTest") as _resolveDirective("vTest"), resolveDirective("vHello") as _resolveDirective("vHello"), withDirectives as _withDirectives } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
exports[`compiler: v-if > basic v-if 1`] = `
|
exports[`compiler: v-if > basic v-if 1`] = `
|
||||||
"import { template as _template, fragment as _fragment, createIf as _createIf, children as _children, renderEffect as _renderEffect, setText as _setText, append as _append } from 'vue/vapor';
|
"import { template as _template, fragment as _fragment, children as _children, renderEffect as _renderEffect, setText as _setText, createIf as _createIf, append as _append } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -69,7 +69,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compiler: v-if > template v-if 1`] = `
|
exports[`compiler: v-if > template v-if 1`] = `
|
||||||
"import { template as _template, fragment as _fragment, createIf as _createIf, children as _children, renderEffect as _renderEffect, setText as _setText, append as _append } from 'vue/vapor';
|
"import { template as _template, fragment as _fragment, children as _children, renderEffect as _renderEffect, setText as _setText, createIf as _createIf, append as _append } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>hello<p></p>")
|
const t0 = _template("<div></div>hello<p></p>")
|
||||||
|
|
|
@ -67,7 +67,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > event modifier 1`] = `
|
exports[`v-on > event modifier 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers, withKeys as _withKeys } from 'vue/vapor';
|
"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on, withKeys as _withKeys } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<a></a><form></form><a></a><div></div><div></div><a></a><div></div><input><input><input><input><input><input><input><input><input><input><input><input><input><input><input>")
|
const t0 = _template("<a></a><form></form><a></a><div></div><div></div><a></a><div></div><input><input><input><input><input><input><input><input><input><input><input><input><input><input><input>")
|
||||||
|
@ -239,7 +239,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = `
|
exports[`v-on > should not wrap keys guard if no key modifier is present 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -251,7 +251,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = `
|
exports[`v-on > should support multiple events and modifiers options w/ prefixIdentifiers: true 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers, withKeys as _withKeys } from 'vue/vapor';
|
"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on, withKeys as _withKeys } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -264,7 +264,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should support multiple modifiers and event options w/ prefixIdentifiers: true 1`] = `
|
exports[`v-on > should support multiple modifiers and event options w/ prefixIdentifiers: true 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -276,7 +276,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should transform click.middle 1`] = `
|
exports[`v-on > should transform click.middle 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -288,7 +288,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should transform click.middle 2`] = `
|
exports[`v-on > should transform click.middle 2`] = `
|
||||||
"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, renderEffect as _renderEffect, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -302,7 +302,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should transform click.right 1`] = `
|
exports[`v-on > should transform click.right 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, withModifiers as _withModifiers, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -314,7 +314,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should transform click.right 2`] = `
|
exports[`v-on > should transform click.right 2`] = `
|
||||||
"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, renderEffect as _renderEffect, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -340,7 +340,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should wrap both for dynamic key event w/ left/right modifiers 1`] = `
|
exports[`v-on > should wrap both for dynamic key event w/ left/right modifiers 1`] = `
|
||||||
"import { template as _template, children as _children, renderEffect as _renderEffect, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, renderEffect as _renderEffect, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -354,7 +354,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
|
exports[`v-on > should wrap keys guard for keyboard events or dynamic events 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withKeys as _withKeys, withModifiers as _withModifiers } from 'vue/vapor';
|
"import { template as _template, children as _children, withModifiers as _withModifiers, withKeys as _withKeys, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
@ -366,7 +366,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = `
|
exports[`v-on > should wrap keys guard for static key event w/ left/right modifiers 1`] = `
|
||||||
"import { template as _template, children as _children, on as _on, withKeys as _withKeys } from 'vue/vapor';
|
"import { template as _template, children as _children, withKeys as _withKeys, on as _on } from 'vue/vapor';
|
||||||
|
|
||||||
export function render(_ctx) {
|
export function render(_ctx) {
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { genSetModelValue } from './generators/modelValue'
|
||||||
import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
|
import { genAppendNode, genInsertNode, genPrependNode } from './generators/dom'
|
||||||
import { genWithDirective } from './generators/directive'
|
import { genWithDirective } from './generators/directive'
|
||||||
import { genIf } from './generators/if'
|
import { genIf } from './generators/if'
|
||||||
|
import { genTemplate } from './generators/template'
|
||||||
|
|
||||||
interface CodegenOptions extends BaseCodegenOptions {
|
interface CodegenOptions extends BaseCodegenOptions {
|
||||||
expressionPlugins?: ParserPlugin[]
|
expressionPlugins?: ParserPlugin[]
|
||||||
|
@ -38,35 +39,27 @@ interface CodegenOptions extends BaseCodegenOptions {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
function checkNever(x: never): never {}
|
function checkNever(x: never): never {}
|
||||||
|
|
||||||
|
export type CodeFragment =
|
||||||
|
| string
|
||||||
|
| [code: string, newlineIndex?: number, loc?: SourceLocation, name?: string]
|
||||||
|
| undefined
|
||||||
|
|
||||||
export interface CodegenContext {
|
export interface CodegenContext {
|
||||||
options: Required<CodegenOptions>
|
options: Required<CodegenOptions>
|
||||||
|
|
||||||
source: string
|
source: string
|
||||||
code: string
|
code: CodeFragment[]
|
||||||
line: number
|
|
||||||
column: number
|
|
||||||
offset: number
|
|
||||||
indentLevel: number
|
indentLevel: number
|
||||||
map?: SourceMapGenerator
|
map?: SourceMapGenerator
|
||||||
|
|
||||||
push(
|
push(...args: CodeFragment[]): void
|
||||||
code: string,
|
newline(): CodeFragment
|
||||||
newlineIndex?: number,
|
multi(
|
||||||
loc?: SourceLocation,
|
codes: [left: string, right: string, segment: string],
|
||||||
name?: string,
|
...fn: Array<false | CodeFragment[]>
|
||||||
): void
|
): CodeFragment[]
|
||||||
newline(
|
call(name: string, ...args: Array<false | CodeFragment[]>): CodeFragment[]
|
||||||
code?: string,
|
withIndent<T>(fn: () => T): T
|
||||||
newlineIndex?: number,
|
|
||||||
loc?: SourceLocation,
|
|
||||||
name?: string,
|
|
||||||
): void
|
|
||||||
pushMulti(
|
|
||||||
codes: [left: string, right: string, segment?: string],
|
|
||||||
...fn: Array<false | string | (() => void)>
|
|
||||||
): void
|
|
||||||
pushCall(name: string, ...args: Array<false | string | (() => void)>): void
|
|
||||||
withIndent(fn: () => void): void
|
|
||||||
|
|
||||||
helpers: Set<string>
|
helpers: Set<string>
|
||||||
vaporHelpers: Set<string>
|
vaporHelpers: Set<string>
|
||||||
|
@ -77,6 +70,7 @@ export interface CodegenContext {
|
||||||
function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
|
function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
|
||||||
const helpers = new Set<string>([])
|
const helpers = new Set<string>([])
|
||||||
const vaporHelpers = new Set<string>([])
|
const vaporHelpers = new Set<string>([])
|
||||||
|
const [code, push] = buildCodeFragment()
|
||||||
const context: CodegenContext = {
|
const context: CodegenContext = {
|
||||||
options: extend(
|
options: extend(
|
||||||
{
|
{
|
||||||
|
@ -100,10 +94,7 @@ function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
|
||||||
),
|
),
|
||||||
|
|
||||||
source: ir.source,
|
source: ir.source,
|
||||||
code: '',
|
code,
|
||||||
column: 1,
|
|
||||||
line: 1,
|
|
||||||
offset: 0,
|
|
||||||
indentLevel: 0,
|
indentLevel: 0,
|
||||||
|
|
||||||
helpers,
|
helpers,
|
||||||
|
@ -117,95 +108,35 @@ function createCodegenContext(ir: RootIRNode, options: CodegenOptions) {
|
||||||
return `_${name}`
|
return `_${name}`
|
||||||
},
|
},
|
||||||
|
|
||||||
push(code, newlineIndex = NewlineType.None, loc, name) {
|
push,
|
||||||
context.code += code
|
newline() {
|
||||||
if (!__BROWSER__ && context.map) {
|
return [`\n${` `.repeat(context.indentLevel)}`, NewlineType.Start]
|
||||||
if (loc) addMapping(loc.start, name)
|
|
||||||
|
|
||||||
if (newlineIndex === NewlineType.Unknown) {
|
|
||||||
// multiple newlines, full iteration
|
|
||||||
advancePositionWithMutation(context, code)
|
|
||||||
} else {
|
|
||||||
// fast paths
|
|
||||||
context.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')}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
context.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')}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
context.line++
|
|
||||||
context.column = code.length - newlineIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (loc && loc !== locStub) {
|
|
||||||
addMapping(loc.end)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
newline(code, newlineIndex, node) {
|
multi([left, right, seg], ...fns) {
|
||||||
context.push(`\n${` `.repeat(context.indentLevel)}`, NewlineType.Start)
|
const frag: CodeFragment[] = []
|
||||||
code && context.push(code, newlineIndex, node)
|
|
||||||
},
|
|
||||||
pushMulti([left, right, seg], ...fns) {
|
|
||||||
fns = fns.filter(Boolean)
|
fns = fns.filter(Boolean)
|
||||||
context.push(left)
|
frag.push(left)
|
||||||
for (const [i, fn] of fns.entries()) {
|
for (const [i, fn] of fns.entries()) {
|
||||||
if (isString(fn)) context.push(fn)
|
if (fn) {
|
||||||
else (fn as () => void)()
|
frag.push(...fn)
|
||||||
if (seg && i < fns.length - 1) context.push(seg)
|
if (i < fns.length - 1) frag.push(seg)
|
||||||
}
|
}
|
||||||
context.push(right)
|
}
|
||||||
|
frag.push(right)
|
||||||
|
return frag
|
||||||
},
|
},
|
||||||
pushCall(name, ...args) {
|
call(name, ...args) {
|
||||||
context.push(name)
|
return [name, ...context.multi(['(', ')', ', '], ...args)]
|
||||||
context.pushMulti(['(', ')', ', '], ...args)
|
|
||||||
},
|
},
|
||||||
withIndent(fn) {
|
withIndent(fn) {
|
||||||
++context.indentLevel
|
++context.indentLevel
|
||||||
fn()
|
const ret = fn()
|
||||||
--context.indentLevel
|
--context.indentLevel
|
||||||
|
return ret
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename = context.options.filename
|
const filename = context.options.filename
|
||||||
|
|
||||||
function addMapping(loc: Position, name: string | null = null) {
|
|
||||||
// we use the private property to directly add the mapping
|
|
||||||
// because the addMapping() implementation in source-map-js has a bunch of
|
|
||||||
// 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: context.line,
|
|
||||||
generatedColumn: context.column - 1,
|
|
||||||
source: filename,
|
|
||||||
// @ts-expect-error it is possible to be null
|
|
||||||
name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!__BROWSER__ && context.options.sourceMap) {
|
if (!__BROWSER__ && context.options.sourceMap) {
|
||||||
// lazy require source-map implementation, only in non-browser builds
|
// lazy require source-map implementation, only in non-browser builds
|
||||||
context.map = new SourceMapGenerator()
|
context.map = new SourceMapGenerator()
|
||||||
|
@ -228,37 +159,27 @@ export function generate(
|
||||||
options: CodegenOptions = {},
|
options: CodegenOptions = {},
|
||||||
): VaporCodegenResult {
|
): VaporCodegenResult {
|
||||||
const ctx = createCodegenContext(ir, options)
|
const ctx = createCodegenContext(ir, options)
|
||||||
const { push, withIndent, newline, helpers, vaporHelper, vaporHelpers } = ctx
|
const { push, withIndent, newline, helpers, vaporHelpers } = ctx
|
||||||
|
|
||||||
const functionName = 'render'
|
const functionName = 'render'
|
||||||
const isSetupInlined = !!options.inline
|
const isSetupInlined = !!options.inline
|
||||||
if (isSetupInlined) {
|
if (isSetupInlined) {
|
||||||
push(`(() => {`)
|
push(`(() => {`)
|
||||||
} else {
|
} else {
|
||||||
|
push(
|
||||||
// placeholder for preamble
|
// placeholder for preamble
|
||||||
newline()
|
newline(),
|
||||||
newline(`export function ${functionName}(_ctx) {`)
|
newline(),
|
||||||
|
`export function ${functionName}(_ctx) {`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
withIndent(() => {
|
withIndent(() => {
|
||||||
ir.template.forEach((template, i) => {
|
ir.template.forEach((template, i) => push(...genTemplate(template, i, ctx)))
|
||||||
if (template.type === IRNodeTypes.TEMPLATE_FACTORY) {
|
push(...genBlockFunctionContent(ir, ctx))
|
||||||
// TODO source map?
|
|
||||||
newline(
|
|
||||||
`const t${i} = ${vaporHelper('template')}(${JSON.stringify(
|
|
||||||
template.template,
|
|
||||||
)})`,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// fragment
|
|
||||||
newline(`const t${i} = ${vaporHelper('fragment')}()`)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
genBlockFunctionContent(ir, ctx)
|
push(newline())
|
||||||
})
|
|
||||||
|
|
||||||
newline()
|
|
||||||
if (isSetupInlined) {
|
if (isSetupInlined) {
|
||||||
push('})()')
|
push('})()')
|
||||||
} else {
|
} else {
|
||||||
|
@ -276,12 +197,13 @@ export function generate(
|
||||||
.map(h => `${h} as _${h}`)
|
.map(h => `${h} as _${h}`)
|
||||||
.join(', ')} } from 'vue';`
|
.join(', ')} } from 'vue';`
|
||||||
|
|
||||||
|
let codegen = genCodeFragment(ctx)
|
||||||
if (!isSetupInlined) {
|
if (!isSetupInlined) {
|
||||||
ctx.code = preamble + ctx.code
|
codegen = preamble + codegen
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: ctx.code,
|
code: codegen,
|
||||||
ast: ir,
|
ast: ir,
|
||||||
preamble,
|
preamble,
|
||||||
map: ctx.map ? ctx.map.toJSON() : undefined,
|
map: ctx.map ? ctx.map.toJSON() : undefined,
|
||||||
|
@ -290,6 +212,82 @@ export function generate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function genCodeFragment(context: CodegenContext) {
|
||||||
|
let codegen = ''
|
||||||
|
let line = 1
|
||||||
|
let column = 1
|
||||||
|
let offset = 0
|
||||||
|
|
||||||
|
for (let frag of context.code) {
|
||||||
|
if (!frag) 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({ line, column, offset }, code)
|
||||||
|
} else {
|
||||||
|
// fast paths
|
||||||
|
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')}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
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')}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
line++
|
||||||
|
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: line,
|
||||||
|
generatedColumn: column - 1,
|
||||||
|
source: context.options.filename,
|
||||||
|
// @ts-expect-error it is possible to be null
|
||||||
|
name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function genChildren(children: IRDynamicInfo[]) {
|
function genChildren(children: IRDynamicInfo[]) {
|
||||||
let code = ''
|
let code = ''
|
||||||
let offset = 0
|
let offset = 0
|
||||||
|
@ -320,7 +318,10 @@ function genChildren(children: IRDynamicInfo[]) {
|
||||||
return `{${code}}`
|
return `{${code}}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function genOperation(oper: OperationNode, context: CodegenContext) {
|
function genOperation(
|
||||||
|
oper: OperationNode,
|
||||||
|
context: CodegenContext,
|
||||||
|
): CodeFragment[] {
|
||||||
// TODO: cache old value
|
// TODO: cache old value
|
||||||
switch (oper.type) {
|
switch (oper.type) {
|
||||||
case IRNodeTypes.SET_PROP:
|
case IRNodeTypes.SET_PROP:
|
||||||
|
@ -347,22 +348,35 @@ function genOperation(oper: OperationNode, context: CodegenContext) {
|
||||||
return genIf(oper, context)
|
return genIf(oper, context)
|
||||||
case IRNodeTypes.WITH_DIRECTIVE:
|
case IRNodeTypes.WITH_DIRECTIVE:
|
||||||
// generated, skip
|
// generated, skip
|
||||||
return
|
break
|
||||||
default:
|
default:
|
||||||
return checkNever(oper)
|
return checkNever(oper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildCodeFragment() {
|
||||||
|
const frag: CodeFragment[] = []
|
||||||
|
const push = frag.push.bind(frag)
|
||||||
|
return [frag, push] as const
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genBlockFunctionContent(
|
export function genBlockFunctionContent(
|
||||||
ir: BlockFunctionIRNode | RootIRNode,
|
ir: BlockFunctionIRNode | RootIRNode,
|
||||||
ctx: CodegenContext,
|
ctx: CodegenContext,
|
||||||
) {
|
): CodeFragment[] {
|
||||||
const { newline, withIndent, vaporHelper } = ctx
|
const { newline, withIndent, vaporHelper } = ctx
|
||||||
newline(`const n${ir.dynamic.id} = t${ir.templateIndex}()`)
|
const [frag, push] = buildCodeFragment()
|
||||||
|
|
||||||
|
push(newline(), `const n${ir.dynamic.id} = t${ir.templateIndex}()`)
|
||||||
|
|
||||||
const children = genChildren(ir.dynamic.children)
|
const children = genChildren(ir.dynamic.children)
|
||||||
if (children) {
|
if (children) {
|
||||||
newline(`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`)
|
push(
|
||||||
|
newline(),
|
||||||
|
`const ${children} = ${vaporHelper('children')}(n${ir.dynamic.id})`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const directiveOps = ir.operation.filter(
|
const directiveOps = ir.operation.filter(
|
||||||
|
@ -370,24 +384,24 @@ export function genBlockFunctionContent(
|
||||||
oper.type === IRNodeTypes.WITH_DIRECTIVE,
|
oper.type === IRNodeTypes.WITH_DIRECTIVE,
|
||||||
)
|
)
|
||||||
for (const directives of groupDirective(directiveOps)) {
|
for (const directives of groupDirective(directiveOps)) {
|
||||||
genWithDirective(directives, ctx)
|
push(...genWithDirective(directives, ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const operation of ir.operation) {
|
for (const operation of ir.operation) {
|
||||||
genOperation(operation, ctx)
|
push(...genOperation(operation, ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const { operations } of ir.effect) {
|
for (const { operations } of ir.effect) {
|
||||||
newline(`${vaporHelper('renderEffect')}(() => {`)
|
push(newline(), `${vaporHelper('renderEffect')}(() => {`)
|
||||||
withIndent(() => {
|
withIndent(() => {
|
||||||
for (const operation of operations) {
|
operations.forEach(op => push(...genOperation(op, ctx)))
|
||||||
genOperation(operation, ctx)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
newline('})')
|
push(newline(), '})')
|
||||||
}
|
}
|
||||||
|
|
||||||
newline(`return n${ir.dynamic.id}`)
|
push(newline(), `return n${ir.dynamic.id}`)
|
||||||
|
|
||||||
|
return frag
|
||||||
}
|
}
|
||||||
|
|
||||||
function groupDirective(ops: WithDirectiveIRNode[]): WithDirectiveIRNode[][] {
|
function groupDirective(ops: WithDirectiveIRNode[]): WithDirectiveIRNode[][] {
|
||||||
|
|
|
@ -1,86 +1,73 @@
|
||||||
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 { CodegenContext } from '../generate'
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
import type { WithDirectiveIRNode } from '../ir'
|
import type { WithDirectiveIRNode } from '../ir'
|
||||||
|
|
||||||
export function genWithDirective(
|
export function genWithDirective(
|
||||||
opers: WithDirectiveIRNode[],
|
opers: WithDirectiveIRNode[],
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
) {
|
) {
|
||||||
|
const { newline, call, multi, vaporHelper } = context
|
||||||
|
|
||||||
|
const element = `n${opers[0].element}`
|
||||||
|
const directiveItems = opers.map(genDirective)
|
||||||
|
const directives = multi(['[', ']', ', '], ...directiveItems)
|
||||||
|
|
||||||
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(vaporHelper('withDirectives'), [element], directives),
|
||||||
|
]
|
||||||
|
|
||||||
|
function genDirective({ dir, builtin }: WithDirectiveIRNode): CodeFragment[] {
|
||||||
|
const NULL = ['void 0']
|
||||||
|
|
||||||
|
const directive = genDirective()
|
||||||
|
const value = dir.exp
|
||||||
|
? ['() => ', ...genExpression(dir.exp, context)]
|
||||||
|
: dir.arg || dir.modifiers.length
|
||||||
|
? NULL
|
||||||
|
: false
|
||||||
|
const argument = dir.arg
|
||||||
|
? genExpression(dir.arg, context)
|
||||||
|
: dir.modifiers.length
|
||||||
|
? NULL
|
||||||
|
: false
|
||||||
|
const modifiers = dir.modifiers.length
|
||||||
|
? ['{ ', genDirectiveModifiers(), ' }']
|
||||||
|
: false
|
||||||
|
|
||||||
|
return multi(['[', ']', ', '], directive, value, argument, modifiers)
|
||||||
|
|
||||||
|
function genDirective(): CodeFragment[] {
|
||||||
const {
|
const {
|
||||||
push,
|
|
||||||
newline,
|
|
||||||
pushCall,
|
|
||||||
pushMulti,
|
|
||||||
vaporHelper,
|
vaporHelper,
|
||||||
options: { bindingMetadata },
|
options: { bindingMetadata },
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
newline()
|
|
||||||
pushCall(
|
|
||||||
vaporHelper('withDirectives'),
|
|
||||||
// 1st arg: node
|
|
||||||
`n${opers[0].element}`,
|
|
||||||
// 2nd arg: directives
|
|
||||||
() => {
|
|
||||||
// directive
|
|
||||||
pushMulti(
|
|
||||||
['[', ']', ', '],
|
|
||||||
...opers.map(oper => () => {
|
|
||||||
push('[')
|
|
||||||
|
|
||||||
const { dir, builtin } = oper
|
|
||||||
if (dir.name === 'show') {
|
if (dir.name === 'show') {
|
||||||
push(vaporHelper('vShow'))
|
return [vaporHelper('vShow')]
|
||||||
} else if (builtin) {
|
} else if (builtin) {
|
||||||
push(vaporHelper(builtin))
|
return [vaporHelper(builtin)]
|
||||||
} else {
|
} else {
|
||||||
const directiveReference = camelize(`v-${dir.name}`)
|
const directiveReference = camelize(`v-${dir.name}`)
|
||||||
// TODO resolve directive
|
// TODO resolve directive
|
||||||
if (bindingMetadata[directiveReference]) {
|
if (bindingMetadata[directiveReference]) {
|
||||||
const directiveExpression =
|
const directiveExpression = createSimpleExpression(directiveReference)
|
||||||
createSimpleExpression(directiveReference)
|
|
||||||
directiveExpression.ast = null
|
directiveExpression.ast = null
|
||||||
genExpression(directiveExpression, context)
|
return genExpression(directiveExpression, context)
|
||||||
} else {
|
} else {
|
||||||
push(vaporHelper(`resolveDirective("${directiveReference}")`))
|
return [vaporHelper(`resolveDirective("${directiveReference}")`)]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dir.exp) {
|
function genDirectiveModifiers() {
|
||||||
push(', () => ')
|
return dir.modifiers
|
||||||
genExpression(dir.exp, context)
|
|
||||||
} else if (dir.arg || dir.modifiers.length) {
|
|
||||||
push(', void 0')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir.arg) {
|
|
||||||
push(', ')
|
|
||||||
genExpression(dir.arg, context)
|
|
||||||
} else if (dir.modifiers.length) {
|
|
||||||
push(', void 0')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dir.modifiers.length) {
|
|
||||||
push(', ')
|
|
||||||
push('{ ')
|
|
||||||
push(genDirectiveModifiers(dir.modifiers))
|
|
||||||
push(' }')
|
|
||||||
}
|
|
||||||
|
|
||||||
push(']')
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function genDirectiveModifiers(modifiers: string[]) {
|
|
||||||
return modifiers
|
|
||||||
.map(
|
.map(
|
||||||
value =>
|
value =>
|
||||||
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
|
`${isSimpleIdentifier(value) ? value : JSON.stringify(value)}: true`,
|
||||||
)
|
)
|
||||||
.join(', ')
|
.join(', ')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
import type {
|
import type {
|
||||||
AppendNodeIRNode,
|
AppendNodeIRNode,
|
||||||
InsertNodeIRNode,
|
InsertNodeIRNode,
|
||||||
|
@ -7,35 +7,47 @@ import type {
|
||||||
|
|
||||||
export function genInsertNode(
|
export function genInsertNode(
|
||||||
oper: InsertNodeIRNode,
|
oper: InsertNodeIRNode,
|
||||||
{ newline, pushCall, vaporHelper }: CodegenContext,
|
{ newline, call, vaporHelper }: CodegenContext,
|
||||||
) {
|
): 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}]`
|
||||||
newline()
|
return [
|
||||||
pushCall(vaporHelper('insert'), element, `n${oper.parent}`, `n${oper.anchor}`)
|
newline(),
|
||||||
|
...call(
|
||||||
|
vaporHelper('insert'),
|
||||||
|
[element],
|
||||||
|
[`n${oper.parent}`],
|
||||||
|
[`n${oper.anchor}`],
|
||||||
|
),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genPrependNode(
|
export function genPrependNode(
|
||||||
oper: PrependNodeIRNode,
|
oper: PrependNodeIRNode,
|
||||||
{ newline, pushCall, vaporHelper }: CodegenContext,
|
{ newline, call, vaporHelper }: CodegenContext,
|
||||||
) {
|
): CodeFragment[] {
|
||||||
newline()
|
return [
|
||||||
pushCall(
|
newline(),
|
||||||
|
...call(
|
||||||
vaporHelper('prepend'),
|
vaporHelper('prepend'),
|
||||||
`n${oper.parent}`,
|
[`n${oper.parent}`],
|
||||||
oper.elements.map(el => `n${el}`).join(', '),
|
...oper.elements.map(el => [`n${el}`]),
|
||||||
)
|
),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genAppendNode(
|
export function genAppendNode(
|
||||||
oper: AppendNodeIRNode,
|
oper: AppendNodeIRNode,
|
||||||
{ newline, pushCall, vaporHelper }: CodegenContext,
|
{ newline, call, vaporHelper }: CodegenContext,
|
||||||
) {
|
): CodeFragment[] {
|
||||||
newline()
|
newline()
|
||||||
pushCall(
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(
|
||||||
vaporHelper('append'),
|
vaporHelper('append'),
|
||||||
`n${oper.parent}`,
|
[`n${oper.parent}`],
|
||||||
oper.elements.map(el => `n${el}`).join(', '),
|
...oper.elements.map(el => [`n${el}`]),
|
||||||
)
|
),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { isMemberExpression } from '@vue/compiler-dom'
|
import { isMemberExpression } from '@vue/compiler-dom'
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
import type { SetEventIRNode } from '../ir'
|
import type { SetEventIRNode } from '../ir'
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
|
|
||||||
|
@ -7,60 +7,38 @@ import { genExpression } from './expression'
|
||||||
const fnExpRE =
|
const fnExpRE =
|
||||||
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||||
|
|
||||||
export function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
|
export function genSetEvent(
|
||||||
const {
|
oper: SetEventIRNode,
|
||||||
vaporHelper,
|
context: CodegenContext,
|
||||||
push,
|
): CodeFragment[] {
|
||||||
newline,
|
const { vaporHelper, newline, call, options: ctxOptions } = context
|
||||||
pushMulti,
|
|
||||||
pushCall,
|
|
||||||
options: ctxOptions,
|
|
||||||
} = context
|
|
||||||
const { keys, nonKeys, options } = oper.modifiers
|
const { keys, nonKeys, options } = oper.modifiers
|
||||||
|
|
||||||
newline()
|
const name = genName()
|
||||||
pushCall(
|
const handler = genFinalizedHandler()
|
||||||
vaporHelper('on'),
|
const opt = !!options.length && [
|
||||||
// 1st arg: event name
|
`{ ${options.map(v => `${v}: true`).join(', ')} }`,
|
||||||
() => push(`n${oper.element}`),
|
]
|
||||||
// 2nd arg: event name
|
|
||||||
() => {
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(vaporHelper('on'), [`n${oper.element}`], name, handler, opt),
|
||||||
|
]
|
||||||
|
|
||||||
|
function genName(): CodeFragment[] {
|
||||||
|
const expr = genExpression(oper.key, context)
|
||||||
|
// TODO unit test
|
||||||
if (oper.keyOverride) {
|
if (oper.keyOverride) {
|
||||||
const find = JSON.stringify(oper.keyOverride[0])
|
const find = JSON.stringify(oper.keyOverride[0])
|
||||||
const replacement = JSON.stringify(oper.keyOverride[1])
|
const replacement = JSON.stringify(oper.keyOverride[1])
|
||||||
pushMulti(['(', ')'], () => genExpression(oper.key, context))
|
const wrapped: CodeFragment[] = ['(', ...expr, ')']
|
||||||
push(` === ${find} ? ${replacement} : `)
|
return [...wrapped, ` === ${find} ? ${replacement} : `, ...wrapped]
|
||||||
pushMulti(['(', ')'], () => genExpression(oper.key, context))
|
|
||||||
} else {
|
} else {
|
||||||
genExpression(oper.key, context)
|
return genExpression(oper.key, context)
|
||||||
}
|
}
|
||||||
},
|
|
||||||
// 3rd arg: event handler
|
|
||||||
() => {
|
|
||||||
const pushWithKeys = (fn: () => void) => {
|
|
||||||
push(`${vaporHelper('withKeys')}(`)
|
|
||||||
fn()
|
|
||||||
push(`, ${genArrayExpression(keys)})`)
|
|
||||||
}
|
}
|
||||||
const pushWithModifiers = (fn: () => void) => {
|
|
||||||
push(`${vaporHelper('withModifiers')}(`)
|
|
||||||
fn()
|
|
||||||
push(`, ${genArrayExpression(nonKeys)})`)
|
|
||||||
}
|
|
||||||
const pushNoop = (fn: () => void) => fn()
|
|
||||||
|
|
||||||
;(keys.length ? pushWithKeys : pushNoop)(() =>
|
function genEventHandler(): CodeFragment[] {
|
||||||
(nonKeys.length ? pushWithModifiers : pushNoop)(() => {
|
|
||||||
genEventHandler()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
// 4th arg, gen options
|
|
||||||
!!options.length &&
|
|
||||||
(() => push(`{ ${options.map(v => `${v}: true`).join(', ')} }`)),
|
|
||||||
)
|
|
||||||
|
|
||||||
function genEventHandler() {
|
|
||||||
const exp = oper.value
|
const exp = oper.value
|
||||||
if (exp && exp.content.trim()) {
|
if (exp && exp.content.trim()) {
|
||||||
const isMemberExp = isMemberExpression(exp.content, ctxOptions)
|
const isMemberExp = isMemberExpression(exp.content, ctxOptions)
|
||||||
|
@ -68,25 +46,53 @@ export function genSetEvent(oper: SetEventIRNode, context: CodegenContext) {
|
||||||
const hasMultipleStatements = exp.content.includes(`;`)
|
const hasMultipleStatements = exp.content.includes(`;`)
|
||||||
|
|
||||||
if (isInlineStatement) {
|
if (isInlineStatement) {
|
||||||
push('$event => ')
|
|
||||||
push(hasMultipleStatements ? '{' : '(')
|
|
||||||
const knownIds = Object.create(null)
|
const knownIds = Object.create(null)
|
||||||
knownIds['$event'] = 1
|
knownIds['$event'] = 1
|
||||||
genExpression(exp, context, knownIds)
|
|
||||||
push(hasMultipleStatements ? '}' : ')')
|
return [
|
||||||
} else if (isMemberExp) {
|
'$event => ',
|
||||||
push('(...args) => (')
|
hasMultipleStatements ? '{' : '(',
|
||||||
genExpression(exp, context)
|
...genExpression(exp, context, knownIds),
|
||||||
push(' && ')
|
hasMultipleStatements ? '}' : ')',
|
||||||
genExpression(exp, context)
|
]
|
||||||
push('(...args))')
|
|
||||||
} else {
|
} else {
|
||||||
genExpression(exp, context)
|
const expr = genExpression(exp, context)
|
||||||
}
|
if (isMemberExp) {
|
||||||
|
return ['(...args) => (', ...expr, ' && ', ...expr, '(...args))']
|
||||||
} else {
|
} else {
|
||||||
push('() => {}')
|
return expr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return ['() => {}']
|
||||||
|
}
|
||||||
|
|
||||||
|
function genFinalizedHandler(): CodeFragment[] {
|
||||||
|
let expr = genEventHandler()
|
||||||
|
|
||||||
|
if (nonKeys.length) {
|
||||||
|
expr = [
|
||||||
|
vaporHelper('withModifiers'),
|
||||||
|
'(',
|
||||||
|
...expr,
|
||||||
|
', ',
|
||||||
|
genArrayExpression(nonKeys),
|
||||||
|
')',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (keys.length) {
|
||||||
|
expr = [
|
||||||
|
vaporHelper('withKeys'),
|
||||||
|
'(',
|
||||||
|
...expr,
|
||||||
|
', ',
|
||||||
|
genArrayExpression(keys),
|
||||||
|
')',
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
return expr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function genArrayExpression(elements: string[]) {
|
function genArrayExpression(elements: string[]) {
|
||||||
|
|
|
@ -8,23 +8,27 @@ import {
|
||||||
import { isGloballyAllowed, isString, makeMap } from '@vue/shared'
|
import { isGloballyAllowed, isString, makeMap } from '@vue/shared'
|
||||||
import type { Identifier } from '@babel/types'
|
import type { Identifier } from '@babel/types'
|
||||||
import type { IRExpression } from '../ir'
|
import type { IRExpression } from '../ir'
|
||||||
import type { CodegenContext } from '../generate'
|
import {
|
||||||
|
type CodeFragment,
|
||||||
|
type CodegenContext,
|
||||||
|
buildCodeFragment,
|
||||||
|
} from '../generate'
|
||||||
|
|
||||||
export function genExpression(
|
export function genExpression(
|
||||||
node: IRExpression,
|
node: IRExpression,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
knownIds: Record<string, number> = Object.create(null),
|
knownIds: Record<string, number> = Object.create(null),
|
||||||
): void {
|
): CodeFragment[] {
|
||||||
const {
|
const {
|
||||||
push,
|
|
||||||
options: { prefixIdentifiers },
|
options: { prefixIdentifiers },
|
||||||
} = context
|
} = context
|
||||||
if (isString(node)) return push(node)
|
if (isString(node)) return [node]
|
||||||
|
|
||||||
const { content: rawExpr, ast, isStatic, loc } = node
|
const { content: rawExpr, ast, isStatic, loc } = node
|
||||||
if (isStatic) {
|
if (isStatic) {
|
||||||
return push(JSON.stringify(rawExpr), NewlineType.None, loc)
|
return [[JSON.stringify(rawExpr), NewlineType.None, loc]]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
__BROWSER__ ||
|
__BROWSER__ ||
|
||||||
!prefixIdentifiers ||
|
!prefixIdentifiers ||
|
||||||
|
@ -34,12 +38,12 @@ export function genExpression(
|
||||||
isGloballyAllowed(rawExpr) ||
|
isGloballyAllowed(rawExpr) ||
|
||||||
isLiteralWhitelisted(rawExpr)
|
isLiteralWhitelisted(rawExpr)
|
||||||
) {
|
) {
|
||||||
return push(rawExpr, NewlineType.None, loc)
|
return [[rawExpr, NewlineType.None, loc]]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ast === null) {
|
if (ast === null) {
|
||||||
// the expression is a simple identifier
|
// the expression is a simple identifier
|
||||||
return genIdentifier(rawExpr, context, loc)
|
return [genIdentifier(rawExpr, context, loc)]
|
||||||
}
|
}
|
||||||
|
|
||||||
const ids: Identifier[] = []
|
const ids: Identifier[] = []
|
||||||
|
@ -55,6 +59,7 @@ export function genExpression(
|
||||||
)
|
)
|
||||||
if (ids.length) {
|
if (ids.length) {
|
||||||
ids.sort((a, b) => a.start! - b.start!)
|
ids.sort((a, b) => a.start! - b.start!)
|
||||||
|
const [frag, push] = buildCodeFragment()
|
||||||
ids.forEach((id, i) => {
|
ids.forEach((id, i) => {
|
||||||
// range is offset by -1 due to the wrapping parens when parsed
|
// range is offset by -1 due to the wrapping parens when parsed
|
||||||
const start = id.start! - 1
|
const start = id.start! - 1
|
||||||
|
@ -62,21 +67,24 @@ export function genExpression(
|
||||||
const last = ids[i - 1]
|
const last = ids[i - 1]
|
||||||
|
|
||||||
const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start)
|
const leadingText = rawExpr.slice(last ? last.end! - 1 : 0, start)
|
||||||
if (leadingText.length) push(leadingText, NewlineType.Unknown)
|
if (leadingText.length) push([leadingText, NewlineType.Unknown])
|
||||||
|
|
||||||
const source = rawExpr.slice(start, end)
|
const source = rawExpr.slice(start, end)
|
||||||
|
push(
|
||||||
genIdentifier(source, context, {
|
genIdentifier(source, context, {
|
||||||
start: advancePositionWithClone(node.loc.start, source, start),
|
start: advancePositionWithClone(node.loc.start, source, start),
|
||||||
end: advancePositionWithClone(node.loc.start, source, end),
|
end: advancePositionWithClone(node.loc.start, source, end),
|
||||||
source,
|
source,
|
||||||
})
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
if (i === ids.length - 1 && end < rawExpr.length) {
|
if (i === ids.length - 1 && end < rawExpr.length) {
|
||||||
push(rawExpr.slice(end), NewlineType.Unknown)
|
push([rawExpr.slice(end), NewlineType.Unknown])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return frag
|
||||||
} else {
|
} else {
|
||||||
push(rawExpr, NewlineType.Unknown)
|
return [[rawExpr, NewlineType.Unknown]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,9 +92,9 @@ const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||||
|
|
||||||
function genIdentifier(
|
function genIdentifier(
|
||||||
id: string,
|
id: string,
|
||||||
{ options, vaporHelper, push }: CodegenContext,
|
{ options, vaporHelper }: CodegenContext,
|
||||||
loc?: SourceLocation,
|
loc?: SourceLocation,
|
||||||
): void {
|
): CodeFragment {
|
||||||
const { inline, bindingMetadata } = options
|
const { inline, bindingMetadata } = options
|
||||||
let name: string | undefined = id
|
let name: string | undefined = id
|
||||||
if (inline) {
|
if (inline) {
|
||||||
|
@ -102,5 +110,5 @@ function genIdentifier(
|
||||||
} else {
|
} else {
|
||||||
id = `_ctx.${id}`
|
id = `_ctx.${id}`
|
||||||
}
|
}
|
||||||
push(id, NewlineType.None, loc, name)
|
return [id, NewlineType.None, loc, name]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
import type { SetHtmlIRNode } from '../ir'
|
import type { SetHtmlIRNode } from '../ir'
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
|
|
||||||
export function genSetHtml(oper: SetHtmlIRNode, context: CodegenContext) {
|
export function genSetHtml(
|
||||||
const { newline, pushCall, vaporHelper } = context
|
oper: SetHtmlIRNode,
|
||||||
newline()
|
context: CodegenContext,
|
||||||
pushCall(vaporHelper('setHtml'), `n${oper.element}`, () =>
|
): CodeFragment[] {
|
||||||
|
const { newline, call, vaporHelper } = context
|
||||||
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(
|
||||||
|
vaporHelper('setHtml'),
|
||||||
|
[`n${oper.element}`],
|
||||||
genExpression(oper.value, context),
|
genExpression(oper.value, context),
|
||||||
)
|
),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
import { type CodegenContext, genBlockFunctionContent } from '../generate'
|
import {
|
||||||
|
type CodeFragment,
|
||||||
|
type CodegenContext,
|
||||||
|
buildCodeFragment,
|
||||||
|
genBlockFunctionContent,
|
||||||
|
} from '../generate'
|
||||||
import { type BlockFunctionIRNode, IRNodeTypes, type IfIRNode } from '../ir'
|
import { type BlockFunctionIRNode, IRNodeTypes, type IfIRNode } from '../ir'
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
|
|
||||||
|
@ -6,43 +11,45 @@ export function genIf(
|
||||||
oper: IfIRNode,
|
oper: IfIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
isNested = false,
|
isNested = false,
|
||||||
) {
|
): CodeFragment[] {
|
||||||
const { pushCall, vaporHelper, newline, push } = context
|
const { call, vaporHelper, newline } = context
|
||||||
const { condition, positive, negative } = oper
|
const { condition, positive, negative } = oper
|
||||||
|
const [frag, push] = buildCodeFragment()
|
||||||
|
|
||||||
let positiveArg = () => genBlockFunction(positive, context)
|
const conditionExpr: CodeFragment[] = [
|
||||||
let negativeArg: false | (() => void) = false
|
'() => (',
|
||||||
|
...genExpression(condition, context),
|
||||||
|
')',
|
||||||
|
]
|
||||||
|
|
||||||
|
let positiveArg = genBlockFunction(positive, context)
|
||||||
|
let negativeArg: false | CodeFragment[] = false
|
||||||
|
|
||||||
if (negative) {
|
if (negative) {
|
||||||
if (negative.type === IRNodeTypes.BLOCK_FUNCTION) {
|
if (negative.type === IRNodeTypes.BLOCK_FUNCTION) {
|
||||||
negativeArg = () => genBlockFunction(negative, context)
|
negativeArg = genBlockFunction(negative, context)
|
||||||
} else {
|
} else {
|
||||||
negativeArg = () => {
|
negativeArg = ['() => ', ...genIf(negative!, context, true)]
|
||||||
push('() => ')
|
|
||||||
genIf(negative!, context, true)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isNested) newline(`const n${oper.id} = `)
|
if (!isNested) push(newline(), `const n${oper.id} = `)
|
||||||
pushCall(
|
push(
|
||||||
vaporHelper('createIf'),
|
...call(vaporHelper('createIf'), conditionExpr, positiveArg, negativeArg),
|
||||||
() => {
|
|
||||||
push('() => (')
|
|
||||||
genExpression(condition, context)
|
|
||||||
push(')')
|
|
||||||
},
|
|
||||||
positiveArg,
|
|
||||||
negativeArg,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return frag
|
||||||
}
|
}
|
||||||
|
|
||||||
function genBlockFunction(oper: BlockFunctionIRNode, context: CodegenContext) {
|
function genBlockFunction(
|
||||||
const { newline, push, withIndent } = context
|
oper: BlockFunctionIRNode,
|
||||||
|
context: CodegenContext,
|
||||||
push('() => {')
|
): CodeFragment[] {
|
||||||
withIndent(() => {
|
const { newline, withIndent } = context
|
||||||
genBlockFunctionContent(oper, context)
|
return [
|
||||||
})
|
'() => {',
|
||||||
newline('}')
|
...withIndent(() => genBlockFunctionContent(oper, context)),
|
||||||
|
newline(),
|
||||||
|
'}',
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,41 @@
|
||||||
import { camelize, isString } from '@vue/shared'
|
import { camelize, isString } from '@vue/shared'
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
import type { SetModelValueIRNode } from '../ir'
|
import type { SetModelValueIRNode } from '../ir'
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
|
|
||||||
export function genSetModelValue(
|
export function genSetModelValue(
|
||||||
oper: SetModelValueIRNode,
|
oper: SetModelValueIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
) {
|
): CodeFragment[] {
|
||||||
const {
|
const {
|
||||||
vaporHelper,
|
vaporHelper,
|
||||||
push,
|
|
||||||
newline,
|
newline,
|
||||||
pushCall,
|
call,
|
||||||
options: { isTS },
|
options: { isTS },
|
||||||
} = context
|
} = context
|
||||||
|
|
||||||
newline()
|
const name = genName()
|
||||||
pushCall(
|
const handler = genHandler()
|
||||||
vaporHelper('on'),
|
|
||||||
// 1st arg: event name
|
return [
|
||||||
() => push(`n${oper.element}`),
|
newline(),
|
||||||
// 2nd arg: event name
|
...call(vaporHelper('on'), [`n${oper.element}`], name, handler),
|
||||||
() => {
|
]
|
||||||
|
|
||||||
|
function genName(): CodeFragment[] {
|
||||||
if (isString(oper.key)) {
|
if (isString(oper.key)) {
|
||||||
push(JSON.stringify(`update:${camelize(oper.key)}`))
|
return [JSON.stringify(`update:${camelize(oper.key)}`)]
|
||||||
} else {
|
} else {
|
||||||
push('`update:${')
|
return ['`update:${', ...genExpression(oper.key, context), '}`']
|
||||||
genExpression(oper.key, context)
|
|
||||||
push('}`')
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// 3rd arg: event handler
|
|
||||||
() => {
|
function genHandler(): CodeFragment[] {
|
||||||
push((isTS ? `($event: any)` : `$event`) + ' => ((')
|
return [
|
||||||
|
(isTS ? `($event: any)` : `$event`) + ' => ((',
|
||||||
// TODO handle not a ref
|
// TODO handle not a ref
|
||||||
genExpression(oper.value, context)
|
...genExpression(oper.value, context),
|
||||||
push(') = $event)')
|
') = $event)',
|
||||||
},
|
]
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
import type { SetPropIRNode } from '../ir'
|
import type { SetPropIRNode } from '../ir'
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
import { isString } from '@vue/shared'
|
import { isString } from '@vue/shared'
|
||||||
|
|
||||||
export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
|
export function genSetProp(
|
||||||
const { pushCall, pushMulti, newline, vaporHelper, helper } = context
|
oper: SetPropIRNode,
|
||||||
|
context: CodegenContext,
|
||||||
|
): CodeFragment[] {
|
||||||
|
const { call, newline, vaporHelper, helper } = context
|
||||||
|
|
||||||
newline()
|
const element = [`n${oper.element}`]
|
||||||
|
const expr = genExpression(oper.key, context)
|
||||||
const element = `n${oper.element}`
|
|
||||||
|
|
||||||
// fast path for static props
|
// fast path for static props
|
||||||
if (isString(oper.key) || oper.key.isStatic) {
|
if (isString(oper.key) || oper.key.isStatic) {
|
||||||
|
@ -27,40 +29,35 @@ export function genSetProp(oper: SetPropIRNode, context: CodegenContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (helperName) {
|
if (helperName) {
|
||||||
pushCall(
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(
|
||||||
vaporHelper(helperName),
|
vaporHelper(helperName),
|
||||||
element,
|
element,
|
||||||
omitKey
|
omitKey ? false : expr,
|
||||||
? false
|
genExpression(oper.value, context),
|
||||||
: () => {
|
),
|
||||||
const expr = () => genExpression(oper.key, context)
|
]
|
||||||
if (oper.runtimeCamelize) {
|
|
||||||
pushCall(helper('camelize'), expr)
|
|
||||||
} else {
|
|
||||||
expr()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
() => genExpression(oper.value, context),
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pushCall(
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(
|
||||||
vaporHelper('setDynamicProp'),
|
vaporHelper('setDynamicProp'),
|
||||||
element,
|
element,
|
||||||
// 2. key name
|
genDynamicKey(),
|
||||||
() => {
|
genExpression(oper.value, context),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
function genDynamicKey(): CodeFragment[] {
|
||||||
if (oper.runtimeCamelize) {
|
if (oper.runtimeCamelize) {
|
||||||
pushCall(helper('camelize'), () => genExpression(oper.key, context))
|
return call(helper('camelize'), expr)
|
||||||
} else if (oper.modifier) {
|
} else if (oper.modifier) {
|
||||||
pushMulti([`\`${oper.modifier}\${`, `}\``], () =>
|
return [`\`${oper.modifier}\${`, ...expr, `}\``]
|
||||||
genExpression(oper.key, context),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
genExpression(oper.key, context)
|
return expr
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
() => genExpression(oper.value, context),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
import type { CodegenContext } from '../generate'
|
|
||||||
import type { SetRefIRNode } from '../ir'
|
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
|
import type { SetRefIRNode } from '../ir'
|
||||||
|
|
||||||
export function genSetRef(oper: SetRefIRNode, context: CodegenContext) {
|
export function genSetRef(
|
||||||
const { newline, pushCall, vaporHelper } = context
|
oper: SetRefIRNode,
|
||||||
newline()
|
context: CodegenContext,
|
||||||
pushCall(vaporHelper('setRef'), `n${oper.element}`, () =>
|
): CodeFragment[] {
|
||||||
|
const { newline, call, vaporHelper } = context
|
||||||
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(
|
||||||
|
vaporHelper('setRef'),
|
||||||
|
[`n${oper.element}`],
|
||||||
genExpression(oper.value, context),
|
genExpression(oper.value, context),
|
||||||
)
|
),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
|
import {
|
||||||
|
type FragmentFactoryIRNode,
|
||||||
|
IRNodeTypes,
|
||||||
|
type TemplateFactoryIRNode,
|
||||||
|
} from '../ir'
|
||||||
|
|
||||||
|
export function genTemplate(
|
||||||
|
node: TemplateFactoryIRNode | FragmentFactoryIRNode,
|
||||||
|
index: number,
|
||||||
|
{ newline, vaporHelper }: CodegenContext,
|
||||||
|
): CodeFragment[] {
|
||||||
|
if (node.type === IRNodeTypes.TEMPLATE_FACTORY) {
|
||||||
|
// TODO source map?
|
||||||
|
return [
|
||||||
|
newline(),
|
||||||
|
`const t${index} = ${vaporHelper('template')}(${JSON.stringify(
|
||||||
|
node.template,
|
||||||
|
)})`,
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
// fragment
|
||||||
|
return [newline(), `const t${index} = ${vaporHelper('fragment')}()`]
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +1,30 @@
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodeFragment, CodegenContext } from '../generate'
|
||||||
import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir'
|
import type { CreateTextNodeIRNode, SetTextIRNode } from '../ir'
|
||||||
import { genExpression } from './expression'
|
import { genExpression } from './expression'
|
||||||
|
|
||||||
export function genSetText(oper: SetTextIRNode, context: CodegenContext) {
|
export function genSetText(
|
||||||
const { pushCall, newline, vaporHelper } = context
|
oper: SetTextIRNode,
|
||||||
newline()
|
context: CodegenContext,
|
||||||
pushCall(vaporHelper('setText'), `n${oper.element}`, () =>
|
): CodeFragment[] {
|
||||||
|
const { call, newline, vaporHelper } = context
|
||||||
|
return [
|
||||||
|
newline(),
|
||||||
|
...call(
|
||||||
|
vaporHelper('setText'),
|
||||||
|
[`n${oper.element}`],
|
||||||
genExpression(oper.value, context),
|
genExpression(oper.value, context),
|
||||||
)
|
),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genCreateTextNode(
|
export function genCreateTextNode(
|
||||||
oper: CreateTextNodeIRNode,
|
oper: CreateTextNodeIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
) {
|
): CodeFragment[] {
|
||||||
const { newline, pushCall, vaporHelper } = context
|
const { newline, call, vaporHelper } = context
|
||||||
newline(`const n${oper.id} = `)
|
return [
|
||||||
pushCall(vaporHelper('createTextNode'), () =>
|
newline(),
|
||||||
genExpression(oper.value, context),
|
`const n${oper.id} = `,
|
||||||
)
|
...call(vaporHelper('createTextNode'), genExpression(oper.value, context)),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue