diff --git a/packages-private/vapor-e2e-test/interop/VaporComp.vue b/packages-private/vapor-e2e-test/interop/VaporComp.vue
index 88a60c782..f01565449 100644
--- a/packages-private/vapor-e2e-test/interop/VaporComp.vue
+++ b/packages-private/vapor-e2e-test/interop/VaporComp.vue
@@ -27,7 +27,8 @@ const slotProp = ref('slot prop')
change slot prop
- #default:
+ #default:
+
#test:
fallback content
@@ -40,7 +41,7 @@ const slotProp = ref('slot prop')
>
Toggle default slot to vdom
-
+
slot prop: {{ foo }}
component prop: {{ msg }}
diff --git a/packages/compiler-core/__tests__/transforms/vModel.spec.ts b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
index 82dd4909f..d092e7201 100644
--- a/packages/compiler-core/__tests__/transforms/vModel.spec.ts
+++ b/packages/compiler-core/__tests__/transforms/vModel.spec.ts
@@ -449,7 +449,7 @@ describe('compiler: transform v-model', () => {
expect(codegen.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
})
- test('should generate modelModifiers for component v-model', () => {
+ test('should generate modelValueModifiers for component v-model', () => {
const root = parseWithVModel('', {
prefixIdentifiers: true,
})
@@ -461,7 +461,7 @@ describe('compiler: transform v-model', () => {
{ key: { content: `modelValue` } },
{ key: { content: `onUpdate:modelValue` } },
{
- key: { content: 'modelModifiers' },
+ key: { content: 'modelValueModifiers' },
value: {
content: `{ trim: true, "bar-baz": true }`,
isStatic: false,
@@ -469,7 +469,7 @@ describe('compiler: transform v-model', () => {
},
],
})
- // should NOT include modelModifiers in dynamicPropNames because it's never
+ // should NOT include modelValueModifiers in dynamicPropNames because it's never
// gonna change
expect(vnodeCall.dynamicProps).toBe(`["modelValue", "onUpdate:modelValue"]`)
})
diff --git a/packages/compiler-core/src/babelUtils.ts b/packages/compiler-core/src/babelUtils.ts
index 6ede6bd03..342050dc4 100644
--- a/packages/compiler-core/src/babelUtils.ts
+++ b/packages/compiler-core/src/babelUtils.ts
@@ -30,10 +30,6 @@ export function walkIdentifiers(
parentStack: Node[] = [],
knownIds: Record = Object.create(null),
): void {
- if (__BROWSER__) {
- return
- }
-
const rootExp =
root.type === 'Program'
? root.body[0].type === 'ExpressionStatement' && root.body[0].expression
@@ -110,10 +106,6 @@ export function isReferencedIdentifier(
parent: Node | null,
parentStack: Node[],
): boolean {
- if (__BROWSER__) {
- return false
- }
-
if (!parent) {
return true
}
diff --git a/packages/compiler-core/src/transforms/vModel.ts b/packages/compiler-core/src/transforms/vModel.ts
index 598c1ea43..c75ef1c3e 100644
--- a/packages/compiler-core/src/transforms/vModel.ts
+++ b/packages/compiler-core/src/transforms/vModel.ts
@@ -138,7 +138,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
? isStaticExp(arg)
? `${arg.content}Modifiers`
: createCompoundExpression([arg, ' + "Modifiers"'])
- : `modelModifiers`
+ : `modelValueModifiers`
props.push(
createObjectProperty(
modifiersKey,
diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap
index 12462dcf4..cdf710768 100644
--- a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/defineModel.spec.ts.snap
@@ -6,7 +6,7 @@ exports[`defineModel() > basic usage 1`] = `
export default {
props: {
"modelValue": { required: true },
- "modelModifiers": {},
+ "modelValueModifiers": {},
"count": {},
"countModifiers": {},
"toString": { type: Function },
@@ -34,7 +34,7 @@ export default /*@__PURE__*/_defineComponent({
"modelValue": {
required: true
},
- "modelModifiers": {},
+ "modelValueModifiers": {},
},
emits: ["update:modelValue"],
setup(__props, { expose: __expose }) {
@@ -60,7 +60,7 @@ export default /*@__PURE__*/_defineComponent({
default: 0,
required: true,
},
- "modelModifiers": {},
+ "modelValueModifiers": {},
},
emits: ["update:modelValue"],
setup(__props, { expose: __expose }) {
@@ -86,7 +86,7 @@ export default /*@__PURE__*/_defineComponent({
}, {
"modelValue": {
},
- "modelModifiers": {},
+ "modelValueModifiers": {},
}),
emits: ["update:modelValue"],
setup(__props: any, { expose: __expose }) {
@@ -109,7 +109,7 @@ exports[`defineModel() > w/ Boolean And Function types, production mode 1`] = `
export default /*@__PURE__*/_defineComponent({
props: {
"modelValue": { type: [Boolean, String] },
- "modelModifiers": {},
+ "modelValueModifiers": {},
},
emits: ["update:modelValue"],
setup(__props, { expose: __expose }) {
@@ -150,7 +150,7 @@ exports[`defineModel() > w/ defineProps and defineEmits 1`] = `
export default {
props: /*@__PURE__*/_mergeModels({ foo: String }, {
"modelValue": { default: 0 },
- "modelModifiers": {},
+ "modelValueModifiers": {},
}),
emits: /*@__PURE__*/_mergeModels(['change'], ["update:modelValue"]),
setup(__props, { expose: __expose }) {
@@ -172,7 +172,7 @@ exports[`defineModel() > w/ types, basic usage 1`] = `
export default /*@__PURE__*/_defineComponent({
props: {
"modelValue": { type: [Boolean, String] },
- "modelModifiers": {},
+ "modelValueModifiers": {},
"count": { type: Number },
"countModifiers": {},
"disabled": { type: Number, ...{ required: false } },
@@ -201,7 +201,7 @@ exports[`defineModel() > w/ types, production mode 1`] = `
export default /*@__PURE__*/_defineComponent({
props: {
"modelValue": { type: Boolean },
- "modelModifiers": {},
+ "modelValueModifiers": {},
"fn": {},
"fnModifiers": {},
"fnWithDefault": { type: Function, ...{ default: () => null } },
@@ -233,7 +233,7 @@ exports[`defineModel() > w/ types, production mode, boolean + multiple types 1`]
export default /*@__PURE__*/_defineComponent({
props: {
"modelValue": { type: [Boolean, String, Object] },
- "modelModifiers": {},
+ "modelValueModifiers": {},
},
emits: ["update:modelValue"],
setup(__props, { expose: __expose }) {
@@ -253,7 +253,7 @@ exports[`defineModel() > w/ types, production mode, function + runtime opts + mu
export default /*@__PURE__*/_defineComponent({
props: {
"modelValue": { type: [Number, Function], ...{ default: () => 1 } },
- "modelModifiers": {},
+ "modelValueModifiers": {},
},
emits: ["update:modelValue"],
setup(__props, { expose: __expose }) {
diff --git a/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
index 5d696a95d..210c18dda 100644
--- a/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript/defineModel.spec.ts
@@ -94,7 +94,7 @@ describe('defineModel()', () => {
)
assertCode(content)
expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
- expect(content).toMatch('"modelModifiers": {}')
+ expect(content).toMatch('"modelValueModifiers": {}')
expect(content).toMatch('"count": { type: Number }')
expect(content).toMatch(
'"disabled": { type: Number, ...{ required: false } }',
diff --git a/packages/compiler-sfc/src/script/defineModel.ts b/packages/compiler-sfc/src/script/defineModel.ts
index 050828002..9c8f254fe 100644
--- a/packages/compiler-sfc/src/script/defineModel.ts
+++ b/packages/compiler-sfc/src/script/defineModel.ts
@@ -167,9 +167,7 @@ export function genModelProps(ctx: ScriptCompileContext) {
modelPropsDecl += `\n ${JSON.stringify(name)}: ${decl},`
// also generate modifiers prop
- const modifierPropName = JSON.stringify(
- name === 'modelValue' ? `modelModifiers` : `${name}Modifiers`,
- )
+ const modifierPropName = JSON.stringify(`${name}Modifiers`)
modelPropsDecl += `\n ${modifierPropName}: {},`
}
return `{${modelPropsDecl}\n }`
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
index 7aa56aa9c..12eadee32 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/transformElement.spec.ts.snap
@@ -246,6 +246,36 @@ export function render(_ctx) {
}"
`;
+exports[`compiler: element transform > component event with keys modifier 1`] = `
+"import { resolveComponent as _resolveComponent, withKeys as _withKeys, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+ const _component_Foo = _resolveComponent("Foo")
+ const n0 = _createComponentWithFallback(_component_Foo, { onKeyup: () => _withKeys(_ctx.bar, ["enter"]) }, null, true)
+ return n0
+}"
+`;
+
+exports[`compiler: element transform > component event with multiple modifiers and event options 1`] = `
+"import { resolveComponent as _resolveComponent, withModifiers as _withModifiers, withKeys as _withKeys, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+ const _component_Foo = _resolveComponent("Foo")
+ const n0 = _createComponentWithFallback(_component_Foo, { onFooCaptureOnce: () => _withKeys(_withModifiers(_ctx.bar, ["stop","prevent"]), ["enter"]) }, null, true)
+ return n0
+}"
+`;
+
+exports[`compiler: element transform > component event with nonKeys modifier 1`] = `
+"import { resolveComponent as _resolveComponent, withModifiers as _withModifiers, createComponentWithFallback as _createComponentWithFallback } from 'vue';
+
+export function render(_ctx) {
+ const _component_Foo = _resolveComponent("Foo")
+ const n0 = _createComponentWithFallback(_component_Foo, { onFoo: () => _withModifiers(_ctx.bar, ["stop","prevent"]) }, null, true)
+ return n0
+}"
+`;
+
exports[`compiler: element transform > component event with once modifier 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
index 5ef064974..14330e578 100644
--- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
+++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vModel.spec.ts.snap
@@ -1,13 +1,13 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`compiler: vModel transform > component > v-model for component should generate modelModifiers 1`] = `
+exports[`compiler: vModel transform > component > v-model for component should generate modelValueModifiers 1`] = `
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
export function render(_ctx) {
const _component_Comp = _resolveComponent("Comp")
const n0 = _createComponentWithFallback(_component_Comp, { modelValue: () => (_ctx.foo),
"onUpdate:modelValue": () => _value => (_ctx.foo = _value),
- modelModifiers: () => ({ trim: true, "bar-baz": true }) }, null, true)
+ modelValueModifiers: () => ({ trim: true, "bar-baz": true }) }, null, true)
return n0
}"
`;
diff --git a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
index a693db4ad..297201159 100644
--- a/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/transformElement.spec.ts
@@ -896,6 +896,78 @@ describe('compiler: element transform', () => {
})
})
+ test('component event with keys modifier', () => {
+ const { code, ir } = compileWithElementTransform(
+ ``,
+ )
+ expect(code).toMatchSnapshot()
+ expect(ir.block.dynamic.children[0].operation).toMatchObject({
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ tag: 'Foo',
+ props: [
+ [
+ {
+ key: { content: 'keyup' },
+ handler: true,
+ handlerModifiers: {
+ keys: ['enter'],
+ nonKeys: [],
+ options: [],
+ },
+ },
+ ],
+ ],
+ })
+ })
+
+ test('component event with nonKeys modifier', () => {
+ const { code, ir } = compileWithElementTransform(
+ ``,
+ )
+ expect(code).toMatchSnapshot()
+ expect(ir.block.dynamic.children[0].operation).toMatchObject({
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ tag: 'Foo',
+ props: [
+ [
+ {
+ key: { content: 'foo' },
+ handler: true,
+ handlerModifiers: {
+ keys: [],
+ nonKeys: ['stop', 'prevent'],
+ options: [],
+ },
+ },
+ ],
+ ],
+ })
+ })
+
+ test('component event with multiple modifiers and event options', () => {
+ const { code, ir } = compileWithElementTransform(
+ ``,
+ )
+ expect(code).toMatchSnapshot()
+ expect(ir.block.dynamic.children[0].operation).toMatchObject({
+ type: IRNodeTypes.CREATE_COMPONENT_NODE,
+ tag: 'Foo',
+ props: [
+ [
+ {
+ key: { content: 'foo' },
+ handler: true,
+ handlerModifiers: {
+ keys: ['enter'],
+ nonKeys: ['stop', 'prevent'],
+ options: ['capture', 'once'],
+ },
+ },
+ ],
+ ],
+ })
+ })
+
test('component with dynamic event arguments', () => {
const { code, ir } = compileWithElementTransform(
``,
diff --git a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts
index 51eaa9e02..bed60ff63 100644
--- a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts
+++ b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts
@@ -266,13 +266,13 @@ describe('compiler: vModel transform', () => {
})
})
- test('v-model for component should generate modelModifiers', () => {
+ test('v-model for component should generate modelValueModifiers', () => {
const { code, ir } = compileWithVModel(
'',
)
expect(code).toMatchSnapshot()
expect(code).contain(
- `modelModifiers: () => ({ trim: true, "bar-baz": true })`,
+ `modelValueModifiers: () => ({ trim: true, "bar-baz": true })`,
)
expect(ir.block.dynamic.children[0].operation).toMatchObject({
type: IRNodeTypes.CREATE_COMPONENT_NODE,
diff --git a/packages/compiler-vapor/src/generate.ts b/packages/compiler-vapor/src/generate.ts
index 193a0f5da..469e55a70 100644
--- a/packages/compiler-vapor/src/generate.ts
+++ b/packages/compiler-vapor/src/generate.ts
@@ -19,7 +19,14 @@ import {
} from './generators/utils'
import { setTemplateRefIdent } from './generators/templateRef'
-export type CodegenOptions = Omit
+type CustomGenOperation = (
+ opers: any,
+ context: CodegenContext,
+) => CodeFragment[] | void
+
+export type CodegenOptions = Omit & {
+ customGenOperation?: CustomGenOperation | null
+}
export class CodegenContext {
options: Required
@@ -87,6 +94,7 @@ export class CodegenContext {
inline: false,
bindingMetadata: {},
expressionPlugins: [],
+ customGenOperation: null,
}
this.options = extend(defaultOptions, options)
this.block = ir.block
diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts
index 10705a2c7..318106acf 100644
--- a/packages/compiler-vapor/src/generators/component.ts
+++ b/packages/compiler-vapor/src/generators/component.ts
@@ -211,7 +211,7 @@ function genProp(prop: IRProp, context: CodegenContext, isStatic?: boolean) {
? genEventHandler(
context,
prop.values[0],
- undefined,
+ prop.handlerModifiers,
true /* wrap handlers passed to components */,
)
: isStatic
@@ -240,9 +240,7 @@ function genModelModifiers(
if (!modelModifiers || !modelModifiers.length) return []
const modifiersKey = key.isStatic
- ? key.content === 'modelValue'
- ? [`modelModifiers`]
- : [`${key.content}Modifiers`]
+ ? [`${key.content}Modifiers`]
: ['[', ...genExpression(key, context), ' + "Modifiers"]']
const modifiersVal = genDirectiveModifiers(modelModifiers)
diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts
index 563d72f1e..78df6c703 100644
--- a/packages/compiler-vapor/src/generators/operation.ts
+++ b/packages/compiler-vapor/src/generators/operation.ts
@@ -88,6 +88,11 @@ export function genOperation(
case IRNodeTypes.GET_TEXT_CHILD:
return genGetTextChild(oper, context)
default:
+ if (context.options.customGenOperation) {
+ const result = context.options.customGenOperation(oper, context)
+ if (result) return result
+ }
+
const exhaustiveCheck: never = oper
throw new Error(
`Unhandled operation type in genOperation: ${exhaustiveCheck}`,
diff --git a/packages/compiler-vapor/src/generators/prop.ts b/packages/compiler-vapor/src/generators/prop.ts
index 42f063331..e486cffeb 100644
--- a/packages/compiler-vapor/src/generators/prop.ts
+++ b/packages/compiler-vapor/src/generators/prop.ts
@@ -114,9 +114,10 @@ export function genPropKey(
): CodeFragment[] {
const { helper } = context
- const handlerModifierPostfix = handlerModifiers
- ? handlerModifiers.map(capitalize).join('')
- : ''
+ const handlerModifierPostfix =
+ handlerModifiers && handlerModifiers.options
+ ? handlerModifiers.options.map(capitalize).join('')
+ : ''
// static arg was transformed by v-bind transformer
if (node.isStatic) {
// only quote keys if necessary
diff --git a/packages/compiler-vapor/src/generators/text.ts b/packages/compiler-vapor/src/generators/text.ts
index 89e3167c6..700297b3b 100644
--- a/packages/compiler-vapor/src/generators/text.ts
+++ b/packages/compiler-vapor/src/generators/text.ts
@@ -10,8 +10,8 @@ export function genSetText(
context: CodegenContext,
): CodeFragment[] {
const { helper } = context
- const { element, values, generated, jsx } = oper
- const texts = combineValues(values, context, jsx)
+ const { element, values, generated } = oper
+ const texts = combineValues(values, context)
return [
NEWLINE,
...genCall(helper('setText'), `${generated ? 'x' : 'n'}${element}`, texts),
@@ -21,16 +21,15 @@ export function genSetText(
function combineValues(
values: SimpleExpressionNode[],
context: CodegenContext,
- jsx?: boolean,
): CodeFragment[] {
return values.flatMap((value, i) => {
let exp = genExpression(value, context)
- if (!jsx && getLiteralExpressionValue(value) == null) {
+ if (getLiteralExpressionValue(value) == null) {
// dynamic, wrap with toDisplayString
exp = genCall(context.helper('toDisplayString'), exp)
}
if (i > 0) {
- exp.unshift(jsx ? ', ' : ' + ')
+ exp.unshift(' + ')
}
return exp
})
diff --git a/packages/compiler-vapor/src/generators/utils.ts b/packages/compiler-vapor/src/generators/utils.ts
index 904b3dc87..46d8e5250 100644
--- a/packages/compiler-vapor/src/generators/utils.ts
+++ b/packages/compiler-vapor/src/generators/utils.ts
@@ -10,6 +10,8 @@ import {
import { isArray, isString } from '@vue/shared'
import type { CodegenContext } from '../generate'
+export { genExpression } from './expression'
+
export const NEWLINE: unique symbol = Symbol(__DEV__ ? `newline` : ``)
/** increase offset but don't push actual code */
export const LF: unique symbol = Symbol(__DEV__ ? `line feed` : ``)
diff --git a/packages/compiler-vapor/src/index.ts b/packages/compiler-vapor/src/index.ts
index 6eda10210..7594f8355 100644
--- a/packages/compiler-vapor/src/index.ts
+++ b/packages/compiler-vapor/src/index.ts
@@ -13,13 +13,7 @@ export {
type CodegenOptions,
type VaporCodegenResult,
} from './generate'
-export {
- genCall,
- genMulti,
- buildCodeFragment,
- codeFragmentToString,
- type CodeFragment,
-} from './generators/utils'
+export * from './generators/utils'
export {
wrapTemplate,
compile,
diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts
index 18f0139ab..fd85975cf 100644
--- a/packages/compiler-vapor/src/ir/index.ts
+++ b/packages/compiler-vapor/src/ir/index.ts
@@ -123,7 +123,6 @@ export interface SetTextIRNode extends BaseIRNode {
element: number
values: SimpleExpressionNode[]
generated?: boolean // whether this is a generated empty text node by `processTextLikeContainer`
- jsx?: boolean
}
export type KeyOverride = [find: string, replacement: string]
diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts
index 946c89b73..64e7fbcc4 100644
--- a/packages/compiler-vapor/src/transform.ts
+++ b/packages/compiler-vapor/src/transform.ts
@@ -24,6 +24,7 @@ import {
type IRSlots,
type OperationNode,
type RootIRNode,
+ type SetEventIRNode,
type VaporDirectiveNode,
} from './ir'
import { isConstantExpression, isStaticExpression } from './utils'
@@ -46,7 +47,7 @@ export interface DirectiveTransformResult {
modifier?: '.' | '^'
runtimeCamelize?: boolean
handler?: boolean
- handlerModifiers?: string[]
+ handlerModifiers?: SetEventIRNode['modifiers']
model?: boolean
modelModifiers?: string[]
}
diff --git a/packages/compiler-vapor/src/transforms/vOn.ts b/packages/compiler-vapor/src/transforms/vOn.ts
index fcbfc265d..fe63ece0a 100644
--- a/packages/compiler-vapor/src/transforms/vOn.ts
+++ b/packages/compiler-vapor/src/transforms/vOn.ts
@@ -65,7 +65,11 @@ export const transformVOn: DirectiveTransform = (dir, node, context) => {
key: arg,
value: handler,
handler: true,
- handlerModifiers: eventOptionModifiers,
+ handlerModifiers: {
+ keys: keyModifiers,
+ nonKeys: nonKeyModifiers,
+ options: eventOptionModifiers,
+ },
}
}
diff --git a/packages/runtime-core/__tests__/componentEmits.spec.ts b/packages/runtime-core/__tests__/componentEmits.spec.ts
index dc82c0491..c79343392 100644
--- a/packages/runtime-core/__tests__/componentEmits.spec.ts
+++ b/packages/runtime-core/__tests__/componentEmits.spec.ts
@@ -325,7 +325,7 @@ describe('component: emit', () => {
const Comp = () =>
h(Foo, {
modelValue: null,
- modelModifiers: { number: true },
+ modelValueModifiers: { number: true },
'onUpdate:modelValue': fn1,
foo: null,
@@ -356,7 +356,7 @@ describe('component: emit', () => {
const Comp = () =>
h(Foo, {
modelValue: null,
- modelModifiers: { trim: true },
+ modelValueModifiers: { trim: true },
'onUpdate:modelValue': fn1,
foo: null,
@@ -410,7 +410,7 @@ describe('component: emit', () => {
const Comp = () =>
h(Foo, {
modelValue: null,
- modelModifiers: { trim: true },
+ modelValueModifiers: { trim: true },
'onUpdate:modelValue': fn1,
firstName: null,
@@ -464,7 +464,7 @@ describe('component: emit', () => {
const Comp = () =>
h(Foo, {
modelValue: null,
- modelModifiers: { trim: true, number: true },
+ modelValueModifiers: { trim: true, number: true },
'onUpdate:modelValue': fn1,
foo: null,
@@ -492,7 +492,7 @@ describe('component: emit', () => {
const Comp = () =>
h(Foo, {
modelValue: null,
- modelModifiers: { trim: true },
+ modelValueModifiers: { trim: true },
'onUpdate:modelValue': fn,
})
diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts
index 5bdd204cf..205ac1d9a 100644
--- a/packages/runtime-core/src/apiCreateApp.ts
+++ b/packages/runtime-core/src/apiCreateApp.ts
@@ -35,6 +35,7 @@ import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import type { DefineComponent } from './apiDefineComponent'
export interface App {
+ vapor?: boolean
version: string
config: AppConfig
diff --git a/packages/runtime-core/src/componentCurrentInstance.ts b/packages/runtime-core/src/componentCurrentInstance.ts
index c091b9c69..22e55610b 100644
--- a/packages/runtime-core/src/componentCurrentInstance.ts
+++ b/packages/runtime-core/src/componentCurrentInstance.ts
@@ -16,10 +16,25 @@ export let currentInstance: GenericComponentInstance | null = null
export const getCurrentGenericInstance: () => GenericComponentInstance | null =
() => currentInstance || currentRenderingInstance
-export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
- currentInstance && !currentInstance.vapor
- ? (currentInstance as ComponentInternalInstance)
+/**
+ * Retrieves the current component instance.
+ *
+ * @param generic - A boolean flag indicating whether to return a generic component instance.
+ * If `true`, returns a `GenericComponentInstance` including vapor instance.
+ * If `false` or unset, returns a `ComponentInternalInstance` if available.
+ * @returns The current component instance, or `null` if no instance is active.
+ */
+export function getCurrentInstance(
+ generic: true,
+): GenericComponentInstance | null
+export function getCurrentInstance(
+ generic?: boolean,
+): ComponentInternalInstance | null
+export function getCurrentInstance(generic?: boolean) {
+ return currentInstance && (generic || !currentInstance.vapor)
+ ? currentInstance
: currentRenderingInstance
+}
export let isInSSRComponentSetup = false
diff --git a/packages/runtime-core/src/helpers/useModel.ts b/packages/runtime-core/src/helpers/useModel.ts
index e85edc6e9..603a7bf6e 100644
--- a/packages/runtime-core/src/helpers/useModel.ts
+++ b/packages/runtime-core/src/helpers/useModel.ts
@@ -145,9 +145,9 @@ export const getModelModifiers = (
modelName: string,
getter: (props: Record, key: string) => any,
): Record | undefined => {
- return modelName === 'modelValue' || modelName === 'model-value'
- ? getter(props, 'modelModifiers')
- : getter(props, `${modelName}Modifiers`) ||
- getter(props, `${camelize(modelName)}Modifiers`) ||
- getter(props, `${hyphenate(modelName)}Modifiers`)
+ return (
+ getter(props, `${modelName}Modifiers`) ||
+ getter(props, `${camelize(modelName)}Modifiers`) ||
+ getter(props, `${hyphenate(modelName)}Modifiers`)
+ )
}
diff --git a/packages/runtime-vapor/__tests__/component.spec.ts b/packages/runtime-vapor/__tests__/component.spec.ts
index 22294b1e7..67e3ac452 100644
--- a/packages/runtime-vapor/__tests__/component.spec.ts
+++ b/packages/runtime-vapor/__tests__/component.spec.ts
@@ -331,25 +331,29 @@ describe('component', () => {
__DEV__ = true
})
- it('warn if functional vapor component not return a block', () => {
- define(() => {
- return () => {}
+ it('functional vapor component return a object', () => {
+ const { host } = define(() => {
+ return {}
}).render()
- expect(
- 'Functional vapor component must return a block directly',
- ).toHaveBeenWarned()
+ expect(host.textContent).toBe(`[object Object]`)
})
- it('warn if setup return a function and no render function', () => {
- define({
+ it('functional vapor component return a function', () => {
+ const { host } = define(() => {
+ return () => ({})
+ }).render()
+
+ expect(host.textContent).toBe(`() => ({})`)
+ })
+
+ it('setup return a function and no render function', () => {
+ const { host } = define({
setup() {
return () => []
},
}).render()
- expect(
- 'Vapor component setup() returned non-block value, and has no render function',
- ).toHaveBeenWarned()
+ expect(host.textContent).toBe(`() => []`)
})
})
diff --git a/packages/runtime-vapor/__tests__/componentEmits.spec.ts b/packages/runtime-vapor/__tests__/componentEmits.spec.ts
index 8c8a56085..6b542bbf6 100644
--- a/packages/runtime-vapor/__tests__/componentEmits.spec.ts
+++ b/packages/runtime-vapor/__tests__/componentEmits.spec.ts
@@ -265,7 +265,7 @@ describe('component: emit', () => {
const fn2 = vi.fn()
render({
modelValue: () => null,
- modelModifiers: () => ({ number: true }),
+ modelValueModifiers: () => ({ number: true }),
['onUpdate:modelValue']: () => fn1,
foo: () => null,
fooModifiers: () => ({ number: true }),
@@ -291,7 +291,7 @@ describe('component: emit', () => {
modelValue() {
return null
},
- modelModifiers() {
+ modelValueModifiers() {
return { trim: true }
},
['onUpdate:modelValue']() {
@@ -327,7 +327,7 @@ describe('component: emit', () => {
modelValue() {
return null
},
- modelModifiers() {
+ modelValueModifiers() {
return { trim: true, number: true }
},
['onUpdate:modelValue']() {
@@ -361,7 +361,7 @@ describe('component: emit', () => {
modelValue() {
return null
},
- modelModifiers() {
+ modelValueModifiers() {
return { trim: true }
},
['onUpdate:modelValue']() {
diff --git a/packages/runtime-vapor/__tests__/dom/node.spec.ts b/packages/runtime-vapor/__tests__/dom/node.spec.ts
new file mode 100644
index 000000000..89995406d
--- /dev/null
+++ b/packages/runtime-vapor/__tests__/dom/node.spec.ts
@@ -0,0 +1,25 @@
+import { createTextNode, normalizeNode } from '../../src/dom/node'
+import { VaporFragment } from '../../src'
+
+describe('dom node', () => {
+ test('normalizeNode', () => {
+ // null / undefined -> Comment
+ expect(normalizeNode(null)).toBeInstanceOf(Comment)
+ expect(normalizeNode(undefined)).toBeInstanceOf(Comment)
+
+ // boolean -> Comment
+ expect(normalizeNode(true)).toBeInstanceOf(Comment)
+ expect(normalizeNode(false)).toBeInstanceOf(Comment)
+
+ // array -> Fragment
+ expect(normalizeNode(['foo'])).toBeInstanceOf(VaporFragment)
+
+ // VNode -> VNode
+ const vnode = createTextNode('div')
+ expect(normalizeNode(vnode)).toBe(vnode)
+
+ // primitive types
+ expect(normalizeNode('foo')).toMatchObject(createTextNode('foo'))
+ expect(normalizeNode(1)).toMatchObject(createTextNode('1'))
+ })
+})
diff --git a/packages/runtime-vapor/__tests__/errorHandling.spec.ts b/packages/runtime-vapor/__tests__/errorHandling.spec.ts
index 87a79614d..a8e55482a 100644
--- a/packages/runtime-vapor/__tests__/errorHandling.spec.ts
+++ b/packages/runtime-vapor/__tests__/errorHandling.spec.ts
@@ -182,7 +182,6 @@ describe('error handling', () => {
define(Comp).render()
expect(fn).toHaveBeenCalledWith(err, 'setup function')
- expect(`returned non-block value`).toHaveBeenWarned()
})
test('in render function', () => {
diff --git a/packages/runtime-vapor/src/apiCreateApp.ts b/packages/runtime-vapor/src/apiCreateApp.ts
index 834437ee3..ee4c00c88 100644
--- a/packages/runtime-vapor/src/apiCreateApp.ts
+++ b/packages/runtime-vapor/src/apiCreateApp.ts
@@ -100,6 +100,7 @@ function postPrepareApp(app: App) {
)
}
+ app.vapor = true
const mount = app.mount
app.mount = (container, ...args: any[]) => {
container = normalizeContainer(container) as ParentNode
diff --git a/packages/runtime-vapor/src/component.ts b/packages/runtime-vapor/src/component.ts
index af15133db..47148305d 100644
--- a/packages/runtime-vapor/src/component.ts
+++ b/packages/runtime-vapor/src/component.ts
@@ -23,9 +23,8 @@ import {
simpleSetCurrentInstance,
startMeasure,
unregisterHMR,
- warn,
} from '@vue/runtime-dom'
-import { type Block, DynamicFragment, insert, isBlock, remove } from './block'
+import { type Block, DynamicFragment, insert, remove } from './block'
import {
type ShallowRef,
markRaw,
@@ -59,6 +58,7 @@ import {
} from './componentSlots'
import { hmrReload, hmrRerender } from './hmr'
import { isHydrating, locateHydrationNode } from './dom/hydration'
+import { normalizeNode } from './dom/node'
import {
insertionAnchor,
insertionParent,
@@ -150,8 +150,9 @@ export function createComponent(
resetInsertionState()
}
+ const isFnComponent = isFunction(component)
// vdom interop enabled and component is not an explicit vapor component
- if (appContext.vapor && !component.__vapor) {
+ if (appContext.vapor && !isFnComponent && !component.__vapor) {
const frag = appContext.vapor.vdomMount(
component as any,
rawProps,
@@ -188,6 +189,14 @@ export function createComponent(
appContext,
)
+ // HMR
+ if (__DEV__ && component.__hmrId) {
+ registerHMR(instance)
+ instance.isSingleRoot = isSingleRoot
+ instance.hmrRerender = hmrRerender.bind(null, instance)
+ instance.hmrReload = hmrReload.bind(null, instance)
+ }
+
if (__DEV__) {
pushWarningContext(instance)
startMeasure(instance, `init`)
@@ -205,36 +214,22 @@ export function createComponent(
setupPropsValidation(instance)
}
- const setupFn = isFunction(component) ? component : component.setup
+ const setupFn = isFnComponent ? component : component.setup
const setupResult = setupFn
? callWithErrorHandling(setupFn, instance, ErrorCodes.SETUP_FUNCTION, [
instance.props,
instance,
- ]) || EMPTY_OBJ
- : EMPTY_OBJ
+ ]) || []
+ : []
- if (__DEV__ && !isBlock(setupResult)) {
- if (isFunction(component)) {
- warn(`Functional vapor component must return a block directly.`)
- instance.block = []
- } else if (!component.render) {
- warn(
- `Vapor component setup() returned non-block value, and has no render function.`,
- )
- instance.block = []
+ if (__DEV__) {
+ if (isFnComponent || !component.render) {
+ instance.block = normalizeNode(setupResult)
} else {
instance.devtoolsRawSetupState = setupResult
// TODO make the proxy warn non-existent property access during dev
instance.setupState = proxyRefs(setupResult)
devRender(instance)
-
- // HMR
- if (component.__hmrId) {
- registerHMR(instance)
- instance.isSingleRoot = isSingleRoot
- instance.hmrRerender = hmrRerender.bind(null, instance)
- instance.hmrReload = hmrReload.bind(null, instance)
- }
}
} else {
// component has a render function but no setup function
@@ -247,7 +242,7 @@ export function createComponent(
)
} else {
// in prod result can only be block
- instance.block = setupResult as Block
+ instance.block = normalizeNode(setupResult)
}
}
@@ -291,18 +286,33 @@ export let isApplyingFallthroughProps = false
*/
export function devRender(instance: VaporComponentInstance): void {
instance.block =
- callWithErrorHandling(
- instance.type.render!,
- instance,
- ErrorCodes.RENDER_FUNCTION,
- [
- instance.setupState,
- instance.props,
- instance.emit,
- instance.attrs,
- instance.slots,
- ],
- ) || []
+ (instance.type.render
+ ? callWithErrorHandling(
+ instance.type.render,
+ instance,
+ ErrorCodes.RENDER_FUNCTION,
+ [
+ instance.setupState,
+ instance.props,
+ instance.emit,
+ instance.attrs,
+ instance.slots,
+ ],
+ )
+ : callWithErrorHandling(
+ isFunction(instance.type) ? instance.type : instance.type.setup!,
+ instance,
+ ErrorCodes.SETUP_FUNCTION,
+ [
+ instance.props,
+ {
+ slots: instance.slots,
+ attrs: instance.attrs,
+ emit: instance.emit,
+ expose: instance.expose,
+ },
+ ],
+ )) || []
}
const emptyContext: GenericAppContext = {
diff --git a/packages/runtime-vapor/src/dom/node.ts b/packages/runtime-vapor/src/dom/node.ts
index 83bc32c57..608aba627 100644
--- a/packages/runtime-vapor/src/dom/node.ts
+++ b/packages/runtime-vapor/src/dom/node.ts
@@ -1,3 +1,6 @@
+import { type Block, VaporFragment, isBlock } from '../block'
+import { isArray } from '@vue/shared'
+
/*! #__NO_SIDE_EFFECTS__ */
export function createTextNode(value = ''): Text {
return document.createTextNode(value)
@@ -27,3 +30,24 @@ export function nthChild(node: Node, i: number): Node {
export function next(node: Node): Node {
return node.nextSibling!
}
+
+type NodeChildAtom = Node | string | number | boolean | null | undefined | void
+
+export type NodeArrayChildren = Array
+
+export type NodeChild = NodeChildAtom | NodeArrayChildren
+
+export function normalizeNode(node: NodeChild): Block {
+ if (node == null || typeof node === 'boolean') {
+ // empty placeholder
+ return createComment('')
+ } else if (isArray(node) && node.length) {
+ // fragment
+ return new VaporFragment(node.map(normalizeNode))
+ } else if (isBlock(node)) {
+ return node
+ } else {
+ // strings and numbers
+ return createTextNode(String(node))
+ }
+}
diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts
index 682532fa4..7a8aea5a0 100644
--- a/packages/runtime-vapor/src/index.ts
+++ b/packages/runtime-vapor/src/index.ts
@@ -7,7 +7,11 @@ export type { VaporDirective } from './directives/custom'
// compiler-use only
export { insert, prepend, remove, isFragment, VaporFragment } from './block'
export { setInsertionState } from './insertionState'
-export { createComponent, createComponentWithFallback } from './component'
+export {
+ createComponent,
+ createComponentWithFallback,
+ isVaporComponent,
+} from './component'
export { renderEffect } from './renderEffect'
export { createSlot } from './componentSlots'
export { template } from './dom/template'
diff --git a/packages/runtime-vapor/src/vdomInterop.ts b/packages/runtime-vapor/src/vdomInterop.ts
index e277024d7..f6a63306b 100644
--- a/packages/runtime-vapor/src/vdomInterop.ts
+++ b/packages/runtime-vapor/src/vdomInterop.ts
@@ -154,7 +154,7 @@ function createVDOMComponent(
const frag = new VaporFragment([])
const vnode = createVNode(
component,
- rawProps && new Proxy(rawProps, rawPropsProxyHandlers),
+ rawProps && extend({}, new Proxy(rawProps, rawPropsProxyHandlers)),
)
const wrapper = new VaporComponentInstance(
{ props: component.props },