From edaa3a0649dc2368d6b50b9dbfc44f23268806ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Fri, 9 Feb 2024 02:40:01 +0800 Subject: [PATCH] refactor(compiler-vapor): re-organize --- packages/compiler-vapor/src/compile.ts | 3 +- packages/compiler-vapor/src/generate.ts | 165 ++---------------- .../compiler-vapor/src/generators/block.ts | 8 +- .../src/generators/directive.ts | 14 +- packages/compiler-vapor/src/generators/dom.ts | 15 +- .../compiler-vapor/src/generators/event.ts | 13 +- .../src/generators/expression.ts | 7 +- packages/compiler-vapor/src/generators/for.ts | 15 +- .../compiler-vapor/src/generators/html.ts | 7 +- packages/compiler-vapor/src/generators/if.ts | 17 +- .../src/generators/modelValue.ts | 7 +- .../src/generators/operation.ts | 16 +- .../compiler-vapor/src/generators/prop.ts | 21 ++- packages/compiler-vapor/src/generators/ref.ts | 7 +- .../compiler-vapor/src/generators/template.ts | 11 +- .../compiler-vapor/src/generators/text.ts | 11 +- .../compiler-vapor/src/generators/utils.ts | 151 ++++++++++++++++ packages/compiler-vapor/src/index.ts | 11 +- packages/compiler-vapor/src/transform.ts | 38 +--- .../src/transforms/transformElement.ts | 10 +- .../src/transforms/transformRef.ts | 3 +- .../compiler-vapor/src/transforms/utils.ts | 45 +++++ .../compiler-vapor/src/transforms/vFor.ts | 3 +- .../compiler-vapor/src/transforms/vHtml.ts | 3 +- packages/compiler-vapor/src/transforms/vIf.ts | 3 +- .../compiler-vapor/src/transforms/vText.ts | 3 +- packages/compiler-vapor/src/utils.ts | 4 +- 27 files changed, 329 insertions(+), 282 deletions(-) create mode 100644 packages/compiler-vapor/src/generators/utils.ts create mode 100644 packages/compiler-vapor/src/transforms/utils.ts diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts index cd0608359..ce5e5abb1 100644 --- a/packages/compiler-vapor/src/compile.ts +++ b/packages/compiler-vapor/src/compile.ts @@ -27,7 +27,7 @@ import { transformVModel } from './transforms/vModel' import { transformVIf } from './transforms/vIf' import { transformVFor } from './transforms/vFor' -export type CompilerOptions = HackOptions +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 export type TransformPreset = [ NodeTransform[], Record, diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index 88c25a029..2055fb8b5 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -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[] - export class CodegenContext { options: Required 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> - ).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([]) vaporHelpers = new Set([]) @@ -135,12 +104,6 @@ export interface VaporCodegenResult extends BaseCodegenResult { vaporHelpers: Set } -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) { diff --git a/packages/compiler-vapor/src/generators/block.ts b/packages/compiler-vapor/src/generators/block.ts index fbce09a21..10e17523c 100644 --- a/packages/compiler-vapor/src/generators/block.ts +++ b/packages/compiler-vapor/src/generators/block.ts @@ -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}`) diff --git a/packages/compiler-vapor/src/generators/directive.ts b/packages/compiler-vapor/src/generators/directive.ts index e5d4211aa..6dcbef50c 100644 --- a/packages/compiler-vapor/src/generators/directive.ts +++ b/packages/compiler-vapor/src/generators/directive.ts @@ -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 { diff --git a/packages/compiler-vapor/src/generators/dom.ts b/packages/compiler-vapor/src/generators/dom.ts index a8f755c56..869ade112 100644 --- a/packages/compiler-vapor/src/generators/dom.ts +++ b/packages/compiler-vapor/src/generators/dom.ts @@ -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}`), diff --git a/packages/compiler-vapor/src/generators/event.ts b/packages/compiler-vapor/src/generators/event.ts index 1033397e1..240e42783 100644 --- a/packages/compiler-vapor/src/generators/event.ts +++ b/packages/compiler-vapor/src/generators/event.ts @@ -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, diff --git a/packages/compiler-vapor/src/generators/expression.ts b/packages/compiler-vapor/src/generators/expression.ts index d691e9fe3..a86448635 100644 --- a/packages/compiler-vapor/src/generators/expression.ts +++ b/packages/compiler-vapor/src/generators/expression.ts @@ -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, diff --git a/packages/compiler-vapor/src/generators/for.ts b/packages/compiler-vapor/src/generators/for.ts index 8b9bf85da..b95a4eb5a 100644 --- a/packages/compiler-vapor/src/generators/for.ts +++ b/packages/compiler-vapor/src/generators/for.ts @@ -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[] { diff --git a/packages/compiler-vapor/src/generators/html.ts b/packages/compiler-vapor/src/generators/html.ts index 00cecc909..1b129375a 100644 --- a/packages/compiler-vapor/src/generators/html.ts +++ b/packages/compiler-vapor/src/generators/html.ts @@ -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), diff --git a/packages/compiler-vapor/src/generators/if.ts b/packages/compiler-vapor/src/generators/if.ts index df585b449..7f960f145 100644 --- a/packages/compiler-vapor/src/generators/if.ts +++ b/packages/compiler-vapor/src/generators/if.ts @@ -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 diff --git a/packages/compiler-vapor/src/generators/modelValue.ts b/packages/compiler-vapor/src/generators/modelValue.ts index c4808812f..6c7bceefb 100644 --- a/packages/compiler-vapor/src/generators/modelValue.ts +++ b/packages/compiler-vapor/src/generators/modelValue.ts @@ -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), ] } diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index 3dbb75f94..960bc6e56 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -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() diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts index 4422a49b6..7ef11811e 100644 --- a/packages/compiler-vapor/src/generators/prop.ts +++ b/packages/compiler-vapor/src/generators/prop.ts @@ -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)), ) diff --git a/packages/compiler-vapor/src/generators/ref.ts b/packages/compiler-vapor/src/generators/ref.ts index 6037133b0..2e8ffd03d 100644 --- a/packages/compiler-vapor/src/generators/ref.ts +++ b/packages/compiler-vapor/src/generators/ref.ts @@ -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), diff --git a/packages/compiler-vapor/src/generators/template.ts b/packages/compiler-vapor/src/generators/template.ts index a45228e48..9bf247a57 100644 --- a/packages/compiler-vapor/src/generators/template.ts +++ b/packages/compiler-vapor/src/generators/template.ts @@ -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 { diff --git a/packages/compiler-vapor/src/generators/text.ts b/packages/compiler-vapor/src/generators/text.ts index 6995b2b9e..5d032f74f 100644 --- a/packages/compiler-vapor/src/generators/text.ts +++ b/packages/compiler-vapor/src/generators/text.ts @@ -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')), ] } diff --git a/packages/compiler-vapor/src/generators/utils.ts b/packages/compiler-vapor/src/generators/utils.ts new file mode 100644 index 000000000..7b21f9145 --- /dev/null +++ b/packages/compiler-vapor/src/generators/utils.ts @@ -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[] + +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> + ).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, + }) + } +} diff --git a/packages/compiler-vapor/src/index.ts b/packages/compiler-vapor/src/index.ts index e2101ad3d..c2e599a8f 100644 --- a/packages/compiler-vapor/src/index.ts +++ b/packages/compiler-vapor/src/index.ts @@ -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' diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index bb692b049..1eb0b2488 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -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 = [] - const pass: Array = [] - 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) -} - -export const EMPTY_EXPRESSION = createSimpleExpression('', true) diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 6c65488fb..ff543d0fb 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -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() { diff --git a/packages/compiler-vapor/src/transforms/transformRef.ts b/packages/compiler-vapor/src/transforms/transformRef.ts index f09c2951d..ced579651 100644 --- a/packages/compiler-vapor/src/transforms/transformRef.ts +++ b/packages/compiler-vapor/src/transforms/transformRef.ts @@ -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 diff --git a/packages/compiler-vapor/src/transforms/utils.ts b/packages/compiler-vapor/src/transforms/utils.ts new file mode 100644 index 000000000..d80dd65fd --- /dev/null +++ b/packages/compiler-vapor/src/transforms/utils.ts @@ -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 = [] + const pass: Array = [] + 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) +} + +export const EMPTY_EXPRESSION = createSimpleExpression('', true) diff --git a/packages/compiler-vapor/src/transforms/vFor.ts b/packages/compiler-vapor/src/transforms/vFor.ts index 6980cd78f..9401494c6 100644 --- a/packages/compiler-vapor/src/transforms/vFor.ts +++ b/packages/compiler-vapor/src/transforms/vFor.ts @@ -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', diff --git a/packages/compiler-vapor/src/transforms/vHtml.ts b/packages/compiler-vapor/src/transforms/vHtml.ts index eef9bf2a0..40f029629 100644 --- a/packages/compiler-vapor/src/transforms/vHtml.ts +++ b/packages/compiler-vapor/src/transforms/vHtml.ts @@ -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 diff --git a/packages/compiler-vapor/src/transforms/vIf.ts b/packages/compiler-vapor/src/transforms/vIf.ts index e611f6d79..8f05dad34 100644 --- a/packages/compiler-vapor/src/transforms/vIf.ts +++ b/packages/compiler-vapor/src/transforms/vIf.ts @@ -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'], diff --git a/packages/compiler-vapor/src/transforms/vText.ts b/packages/compiler-vapor/src/transforms/vText.ts index 295632ae3..6b55d6334 100644 --- a/packages/compiler-vapor/src/transforms/vText.ts +++ b/packages/compiler-vapor/src/transforms/vText.ts @@ -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 diff --git a/packages/compiler-vapor/src/utils.ts b/packages/compiler-vapor/src/utils.ts index d0b211eba..627978538 100644 --- a/packages/compiler-vapor/src/utils.ts +++ b/packages/compiler-vapor/src/utils.ts @@ -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,