mirror of https://github.com/vuejs/core.git
Merge 9e2eea9103
into bb4ae25793
This commit is contained in:
commit
7914078ba4
|
@ -27,7 +27,8 @@ const slotProp = ref('slot prop')
|
|||
change slot prop
|
||||
</button>
|
||||
<div class="vdom-slot-in-vapor-default">
|
||||
#default: <slot :foo="slotProp" />
|
||||
#default:
|
||||
<slot :foo="slotProp" />
|
||||
</div>
|
||||
<div class="vdom-slot-in-vapor-test">
|
||||
#test: <slot name="test">fallback content</slot>
|
||||
|
@ -40,7 +41,7 @@ const slotProp = ref('slot prop')
|
|||
>
|
||||
Toggle default slot to vdom
|
||||
</button>
|
||||
<VdomComp :msg="msg">
|
||||
<VdomComp :msg="msg" class="foo">
|
||||
<template #default="{ foo }" v-if="passSlot">
|
||||
<div>slot prop: {{ foo }}</div>
|
||||
<div>component prop: {{ msg }}</div>
|
||||
|
|
|
@ -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('<Comp v-model.trim.bar-baz="foo" />', {
|
||||
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"]`)
|
||||
})
|
||||
|
|
|
@ -30,10 +30,6 @@ export function walkIdentifiers(
|
|||
parentStack: Node[] = [],
|
||||
knownIds: Record<string, number> = 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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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 } }',
|
||||
|
|
|
@ -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 }`
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -896,6 +896,78 @@ describe('compiler: element transform', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('component event with keys modifier', () => {
|
||||
const { code, ir } = compileWithElementTransform(
|
||||
`<Foo @keyup.enter="bar" />`,
|
||||
)
|
||||
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(
|
||||
`<Foo @foo.stop.prevent="bar" />`,
|
||||
)
|
||||
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(
|
||||
`<Foo @foo.enter.stop.prevent.capture.once="bar" />`,
|
||||
)
|
||||
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(
|
||||
`<Foo @[foo-bar]="bar" @[baz]="qux" />`,
|
||||
|
|
|
@ -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(
|
||||
'<Comp v-model.trim.bar-baz="foo" />',
|
||||
)
|
||||
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,
|
||||
|
|
|
@ -19,7 +19,14 @@ import {
|
|||
} from './generators/utils'
|
||||
import { setTemplateRefIdent } from './generators/templateRef'
|
||||
|
||||
export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
|
||||
type CustomGenOperation = (
|
||||
opers: any,
|
||||
context: CodegenContext,
|
||||
) => CodeFragment[] | void
|
||||
|
||||
export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'> & {
|
||||
customGenOperation?: CustomGenOperation | null
|
||||
}
|
||||
|
||||
export class CodegenContext {
|
||||
options: Required<CodegenOptions>
|
||||
|
@ -87,6 +94,7 @@ export class CodegenContext {
|
|||
inline: false,
|
||||
bindingMetadata: {},
|
||||
expressionPlugins: [],
|
||||
customGenOperation: null,
|
||||
}
|
||||
this.options = extend(defaultOptions, options)
|
||||
this.block = ir.block
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}`,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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` : ``)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
})
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
|||
import type { DefineComponent } from './apiDefineComponent'
|
||||
|
||||
export interface App<HostElement = any> {
|
||||
vapor?: boolean
|
||||
version: string
|
||||
config: AppConfig
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -145,9 +145,9 @@ export const getModelModifiers = (
|
|||
modelName: string,
|
||||
getter: (props: Record<string, any>, key: string) => any,
|
||||
): Record<string, boolean> | 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`)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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(`() => []`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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']() {
|
||||
|
|
|
@ -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'))
|
||||
})
|
||||
})
|
|
@ -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', () => {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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<NodeArrayChildren | NodeChildAtom>
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 },
|
||||
|
|
Loading…
Reference in New Issue