diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts index ab74e5ffd..d271cc1bb 100644 --- a/packages/compiler-vapor/src/compile.ts +++ b/packages/compiler-vapor/src/compile.ts @@ -24,6 +24,7 @@ import { transformVShow } from './transforms/vShow' import { transformRef } from './transforms/transformRef' import { transformInterpolation } from './transforms/transformInterpolation' import type { HackOptions } from './ir' +import { transformVModel } from './transforms/vModel' export type CompilerOptions = HackOptions @@ -103,6 +104,7 @@ export function getBaseTransformPreset( html: transformVHtml, text: transformVText, show: transformVShow, + model: transformVModel, }, ] } diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts index b66a4629c..c6e5202de 100644 --- a/packages/compiler-vapor/src/generate.ts +++ b/packages/compiler-vapor/src/generate.ts @@ -25,6 +25,7 @@ import { type RootIRNode, type SetEventIRNode, type SetHtmlIRNode, + type SetModelValueIRNode, type SetPropIRNode, type SetRefIRNode, type SetTextIRNode, @@ -389,6 +390,8 @@ function genOperation(oper: OperationNode, context: CodegenContext) { return genSetHtml(oper, context) case IRNodeTypes.SET_REF: return genSetRef(oper, context) + case IRNodeTypes.SET_MODEL_VALUE: + return genSetModelValue(oper, context) case IRNodeTypes.CREATE_TEXT_NODE: return genCreateTextNode(oper, context) case IRNodeTypes.INSERT_NODE: @@ -453,6 +456,34 @@ function genSetRef(oper: SetRefIRNode, context: CodegenContext) { ) } +function genSetModelValue(oper: SetModelValueIRNode, context: CodegenContext) { + const { vaporHelper, push, newline, pushFnCall } = context + + newline() + pushFnCall( + vaporHelper('on'), + // 1st arg: event name + () => push(`n${oper.element}`), + // 2nd arg: event name + () => { + if (isString(oper.key)) { + push(JSON.stringify(`update:${camelize(oper.key)}`)) + } else { + push('`update:${') + genExpression(oper.key, context) + push('}`') + } + }, + // 3rd arg: event handler + () => { + push((context.isTS ? `($event: any)` : `$event`) + ' => ((') + // TODO handle not a ref + genExpression(oper.value, context) + push(') = $event)') + }, + ) +} + function genCreateTextNode( oper: CreateTextNodeIRNode, context: CodegenContext, @@ -576,7 +607,7 @@ function genSetEvent(oper: SetEventIRNode, context: CodegenContext) { function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) { const { push, newline, pushFnCall, pushMulti, vaporHelper, bindingMetadata } = context - const { dir } = oper + const { dir, builtin } = oper // TODO merge directive for the same node newline() @@ -591,6 +622,8 @@ function genWithDirective(oper: WithDirectiveIRNode, context: CodegenContext) { pushMulti(['[', ']', ', '], () => { if (dir.name === 'show') { push(vaporHelper('vShow')) + } else if (builtin) { + push(vaporHelper(builtin)) } else { const directiveReference = camelize(`v-${dir.name}`) // TODO resolve directive diff --git a/packages/compiler-vapor/src/ir.ts b/packages/compiler-vapor/src/ir.ts index 20e0d14ac..dbf381dcb 100644 --- a/packages/compiler-vapor/src/ir.ts +++ b/packages/compiler-vapor/src/ir.ts @@ -18,6 +18,7 @@ export enum IRNodeTypes { SET_EVENT, SET_HTML, SET_REF, + SET_MODEL_VALUE, INSERT_NODE, PREPEND_NODE, @@ -100,6 +101,14 @@ export interface SetRefIRNode extends BaseIRNode { value: IRExpression } +export interface SetModelValueIRNode extends BaseIRNode { + type: IRNodeTypes.SET_MODEL_VALUE + element: number + key: IRExpression + value: IRExpression + isComponent: boolean +} + export interface CreateTextNodeIRNode extends BaseIRNode { type: IRNodeTypes.CREATE_TEXT_NODE id: number @@ -129,6 +138,7 @@ export interface WithDirectiveIRNode extends BaseIRNode { type: IRNodeTypes.WITH_DIRECTIVE element: number dir: VaporDirectiveNode + builtin?: string } export type IRNode = @@ -142,6 +152,7 @@ export type OperationNode = | SetEventIRNode | SetHtmlIRNode | SetRefIRNode + | SetModelValueIRNode | CreateTextNodeIRNode | InsertNodeIRNode | PrependNodeIRNode diff --git a/packages/compiler-vapor/src/transforms/vBind.ts b/packages/compiler-vapor/src/transforms/vBind.ts index ff2799a15..1379db91c 100644 --- a/packages/compiler-vapor/src/transforms/vBind.ts +++ b/packages/compiler-vapor/src/transforms/vBind.ts @@ -3,7 +3,7 @@ import { type SimpleExpressionNode, createCompilerError, createSimpleExpression, -} from '@vue/compiler-core' +} from '@vue/compiler-dom' import { camelize, isReservedProp } from '@vue/shared' import { IRNodeTypes } from '../ir' import type { DirectiveTransform } from '../transform' diff --git a/packages/compiler-vapor/src/transforms/vModel.ts b/packages/compiler-vapor/src/transforms/vModel.ts new file mode 100644 index 000000000..adc344ff5 --- /dev/null +++ b/packages/compiler-vapor/src/transforms/vModel.ts @@ -0,0 +1,167 @@ +import { + BindingTypes, + DOMErrorCodes, + ElementTypes, + ErrorCodes, + NodeTypes, + createCompilerError, + createDOMCompilerError, + findDir, + findProp, + hasDynamicKeyVBind, + isMemberExpression, + isStaticArgOf, +} from '@vue/compiler-dom' +import type { DirectiveTransform } from '../transform' +import { IRNodeTypes } from '..' + +export const transformVModel: DirectiveTransform = (dir, node, context) => { + const { exp, arg, loc } = dir + if (!exp) { + context.options.onError( + createCompilerError(ErrorCodes.X_V_MODEL_NO_EXPRESSION, dir.loc), + ) + return + } + + // we assume v-model directives are always parsed + // (not artificially created by a transform) + const rawExp = exp.loc.source + const expString = exp.content + + // in SFC