`;
exports[`compile > custom directive > basic 1`] = `
-"import { resolveDirective as _resolveDirective, withDirectives as _withDirectives, template as _template } from 'vue';
+"import { resolveDirective as _resolveDirective, withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const _directive_test = _resolveDirective("test")
const _directive_hello = _resolveDirective("hello")
const n0 = t0()
- _withDirectives(n0, [[_directive_test], [_directive_hello, void 0, void 0, { world: true }]])
+ _withVaporDirectives(n0, [[_directive_test], [_directive_hello, void 0, void 0, { world: true }]])
return n0
}"
`;
exports[`compile > custom directive > component 1`] = `
-"import { resolveComponent as _resolveComponent, resolveDirective as _resolveDirective, createComponentWithFallback as _createComponentWithFallback, withDirectives as _withDirectives, insert as _insert, createIf as _createIf, template as _template } from 'vue';
+"import { resolveComponent as _resolveComponent, resolveDirective as _resolveDirective, createComponentWithFallback as _createComponentWithFallback, withVaporDirectives as _withVaporDirectives, insert as _insert, createIf as _createIf, template as _template } from 'vue';
const t0 = _template("<div></div>")
export function render(_ctx) {
const n0 = _createIf(() => (true), () => {
const n3 = t0()
const n2 = _createComponentWithFallback(_component_Bar)
- _withDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
+ _withVaporDirectives(n2, [[_directive_hello, void 0, void 0, { world: true }]])
_insert(n2, n3)
return n3
})
return n0
}
}, true)
- _withDirectives(n4, [[_directive_test]])
+ _withVaporDirectives(n4, [[_directive_test]])
return n4
}"
`;
exports[`compile > directives > custom directive > basic 1`] = `
-"import { withDirectives as _withDirectives, template as _template } from 'vue';
+"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
- _withDirectives(n0, [[_ctx.vExample]])
+ _withVaporDirectives(n0, [[_ctx.vExample]])
return n0
}"
`;
exports[`compile > directives > custom directive > binding value 1`] = `
-"import { withDirectives as _withDirectives, template as _template } from 'vue';
+"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
- _withDirectives(n0, [[_ctx.vExample, () => _ctx.msg]])
+ _withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg]])
return n0
}"
`;
exports[`compile > directives > custom directive > dynamic parameters 1`] = `
-"import { withDirectives as _withDirectives, template as _template } from 'vue';
+"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
- _withDirectives(n0, [[_ctx.vExample, () => _ctx.msg, _ctx.foo]])
+ _withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, _ctx.foo]])
return n0
}"
`;
exports[`compile > directives > custom directive > modifiers 1`] = `
-"import { withDirectives as _withDirectives, template as _template } from 'vue';
+"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
- _withDirectives(n0, [[_ctx.vExample, () => _ctx.msg, void 0, { bar: true }]])
+ _withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, void 0, { bar: true }]])
return n0
}"
`;
exports[`compile > directives > custom directive > modifiers w/o binding 1`] = `
-"import { withDirectives as _withDirectives, template as _template } from 'vue';
+"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
- _withDirectives(n0, [[_ctx.vExample, void 0, void 0, { "foo-bar": true }]])
+ _withVaporDirectives(n0, [[_ctx.vExample, void 0, void 0, { "foo-bar": true }]])
return n0
}"
`;
exports[`compile > directives > custom directive > static parameters 1`] = `
-"import { withDirectives as _withDirectives, template as _template } from 'vue';
+"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
- _withDirectives(n0, [[_ctx.vExample, () => _ctx.msg, "foo"]])
+ _withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, "foo"]])
return n0
}"
`;
exports[`compile > directives > custom directive > static parameters and modifiers 1`] = `
-"import { withDirectives as _withDirectives, template as _template } from 'vue';
+"import { withVaporDirectives as _withVaporDirectives, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx, $props, $emit, $attrs, $slots) {
const n0 = t0()
- _withDirectives(n0, [[_ctx.vExample, () => _ctx.msg, "foo", { bar: true }]])
+ _withVaporDirectives(n0, [[_ctx.vExample, () => _ctx.msg, "foo", { bar: true }]])
return n0
}"
`;
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
exports[`compiler: v-show transform > simple expression 1`] = `
-"import { vShow as _vShow, withDirectives as _withDirectives, template as _template } from 'vue';
+"import { applyVShow as _applyVShow, template as _template } from 'vue';
const t0 = _template("<div></div>", true)
export function render(_ctx) {
const n0 = t0()
- _withDirectives(n0, [[_vShow, () => _ctx.foo]])
+ _applyVShow(n0, () => (_ctx.foo))
return n0
}"
`;
},
})
-describe('compiler: vModel transform', () => {
+describe.todo('compiler: vModel transform', () => {
test('should support simple expression', () => {
const { code, helpers } = compileWithVModel('<input v-model="model" />')
expect(code).toMatchSnapshot()
genCall,
genMulti,
} from './utils'
-import {
- IRNodeTypes,
- type OperationNode,
- type WithDirectiveIRNode,
-} from '../ir'
+import { type DirectiveIRNode, IRNodeTypes, type OperationNode } from '../ir'
+import { genVShow } from './vShow'
+import { genVModel } from './vModel'
+export function genBuiltinDirective(
+ oper: DirectiveIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ switch (oper.name) {
+ case 'show':
+ return genVShow(oper, context)
+ case 'model':
+ return genVModel(oper, context)
+ default:
+ return []
+ }
+}
+
+/**
+ * user directives via `withVaporDirectives`
+ * TODO the compiler side is implemented but no runtime support yet
+ * it was removed due to perf issues
+ */
export function genDirectivesForElement(
id: number,
context: CodegenContext,
): CodeFragment[] {
- const dirs = filterDirectives(id, context.block.operation)
- return dirs.length ? genWithDirective(dirs, context) : []
+ const dirs = filterCustomDirectives(id, context.block.operation)
+ return dirs.length ? genCustomDirectives(dirs, context) : []
}
-export function genWithDirective(
- opers: WithDirectiveIRNode[],
+function genCustomDirectives(
+ opers: DirectiveIRNode[],
context: CodegenContext,
): CodeFragment[] {
const { helper } = context
const element = `n${opers[0].element}`
- const directiveItems = opers.map(genDirective)
+ const directiveItems = opers.map(genDirectiveItem)
const directives = genMulti(DELIMITERS_ARRAY, ...directiveItems)
- return [NEWLINE, ...genCall(helper('withDirectives'), element, directives)]
+ return [
+ NEWLINE,
+ // @ts-expect-error
+ ...genCall(helper('withVaporDirectives'), element, directives),
+ ]
- function genDirective({
+ function genDirectiveItem({
dir,
name,
- builtin,
asset,
- }: WithDirectiveIRNode): CodeFragment[] {
- const directive = genDirective()
+ }: DirectiveIRNode): CodeFragment[] {
+ const directiveVar = asset
+ ? toValidAssetId(name, 'directive')
+ : genExpression(
+ extend(createSimpleExpression(name, false), { ast: null }),
+ context,
+ )
const value = dir.exp && ['() => ', ...genExpression(dir.exp, context)]
const argument = dir.arg && genExpression(dir.arg, context)
const modifiers = !!dir.modifiers.length && [
return genMulti(
DELIMITERS_ARRAY.concat('void 0') as CodeFragmentDelimiters,
- directive,
+ directiveVar,
value,
argument,
modifiers,
)
-
- function genDirective() {
- if (builtin) {
- return helper(name as any)
- } else if (asset) {
- return toValidAssetId(name, 'directive')
- } else {
- return genExpression(
- extend(createSimpleExpression(name, false), { ast: null }),
- context,
- )
- }
- }
}
}
.join(', ')
}
-function filterDirectives(
+function filterCustomDirectives(
id: number,
operations: OperationNode[],
-): WithDirectiveIRNode[] {
+): DirectiveIRNode[] {
return operations.filter(
- (oper): oper is WithDirectiveIRNode =>
- oper.type === IRNodeTypes.WITH_DIRECTIVE && oper.element === id,
+ (oper): oper is DirectiveIRNode =>
+ oper.type === IRNodeTypes.DIRECTIVE &&
+ oper.element === id &&
+ !oper.builtin,
)
}
import { genCreateComponent } from './component'
import { genSlotOutlet } from './slotOutlet'
import { processExpressions } from './expression'
+import { genBuiltinDirective } from './directive'
export function genOperations(
opers: OperationNode[],
return genDeclareOldRef(oper)
case IRNodeTypes.SLOT_OUTLET_NODE:
return genSlotOutlet(oper, context)
- case IRNodeTypes.WITH_DIRECTIVE:
- return [] // TODO
+ case IRNodeTypes.DIRECTIVE:
+ return genBuiltinDirective(oper, context)
default:
const exhaustiveCheck: never = oper
throw new Error(
--- /dev/null
+import type { CodegenContext } from '../generate'
+import type { DirectiveIRNode } from '../ir'
+import type { CodeFragment } from './utils'
+
+export function genVModel(
+ oper: DirectiveIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ return []
+}
+
+import { camelize } from '@vue/shared'
+import { genExpression } from './expression'
+import type { SetModelValueIRNode } from '../ir'
+import { NEWLINE, genCall } from './utils'
+import type { SimpleExpressionNode } from '@vue/compiler-dom'
+
+export function genSetModelValue(
+ oper: SetModelValueIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const { helper } = context
+ const name = oper.key.isStatic
+ ? [JSON.stringify(`update:${camelize(oper.key.content)}`)]
+ : ['`update:${', ...genExpression(oper.key, context), '}`']
+
+ const handler = genModelHandler(oper.value, context)
+
+ return [
+ NEWLINE,
+ ...genCall(helper('delegate'), `n${oper.element}`, name, handler),
+ ]
+}
+
+export function genModelHandler(
+ value: SimpleExpressionNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ const {
+ options: { isTS },
+ } = context
+
+ return [
+ `() => ${isTS ? `($event: any)` : `$event`} => (`,
+ ...genExpression(value, context, '$event'),
+ ')',
+ ]
+}
--- /dev/null
+import type { CodegenContext } from '../generate'
+import type { DirectiveIRNode } from '../ir'
+import { genExpression } from './expression'
+import { type CodeFragment, NEWLINE, genCall } from './utils'
+
+export function genVShow(
+ oper: DirectiveIRNode,
+ context: CodegenContext,
+): CodeFragment[] {
+ return [
+ NEWLINE,
+ ...genCall(context.helper('applyVShow'), `n${oper.element}`, [
+ `() => (`,
+ ...genExpression(oper.dir.exp!, context),
+ `)`,
+ ]),
+ ]
+}
CREATE_COMPONENT_NODE,
SLOT_OUTLET_NODE,
- WITH_DIRECTIVE,
+ DIRECTIVE,
DECLARE_OLD_REF, // consider make it more general
IF,
parent: number
}
-export interface WithDirectiveIRNode extends BaseIRNode {
- type: IRNodeTypes.WITH_DIRECTIVE
+export interface DirectiveIRNode extends BaseIRNode {
+ type: IRNodeTypes.DIRECTIVE
element: number
dir: VaporDirectiveNode
name: string
builtin?: boolean
asset?: boolean
+ modelType?: 'text' | 'dynamic' | 'radio' | 'checkbox' | 'select'
}
export interface CreateComponentIRNode extends BaseIRNode {
| CreateTextNodeIRNode
| InsertNodeIRNode
| PrependNodeIRNode
- | WithDirectiveIRNode
+ | DirectiveIRNode
| IfIRNode
| ForIRNode
| CreateComponentIRNode
}
context.registerOperation({
- type: IRNodeTypes.WITH_DIRECTIVE,
+ type: IRNodeTypes.DIRECTIVE,
element: context.reference(),
dir: prop,
name,
import type { NodeTransform, TransformContext } from '../transform'
import {
type BlockIRNode,
+ type DirectiveIRNode,
DynamicFlag,
IRNodeTypes,
type IRProps,
type VaporDirectiveNode,
- type WithDirectiveIRNode,
} from '../ir'
import { camelize, extend } from '@vue/shared'
import { newBlock } from './utils'
irProps = isDynamic ? props : [props]
const runtimeDirective = context.block.operation.find(
- (oper): oper is WithDirectiveIRNode =>
- oper.type === IRNodeTypes.WITH_DIRECTIVE && oper.element === id,
+ (oper): oper is DirectiveIRNode =>
+ oper.type === IRNodeTypes.DIRECTIVE && oper.element === id,
)
if (runtimeDirective) {
context.options.onError(
isStaticArgOf,
} from '@vue/compiler-dom'
import type { DirectiveTransform } from '../transform'
-import { IRNodeTypes } from '../ir'
+import { type DirectiveIRNode, IRNodeTypes } from '../ir'
export const transformVModel: DirectiveTransform = (dir, node, context) => {
const { exp, arg } = dir
)
const { tag } = node
const isCustomElement = context.options.isCustomElement(tag)
- let runtimeDirective: string | undefined = 'vModelText'
+ let modelType: DirectiveIRNode['modelType'] | undefined = 'text'
// TODO let runtimeDirective: VaporHelper | undefined = 'vModelText'
if (
tag === 'input' ||
if (type) {
if (type.type === NodeTypes.DIRECTIVE) {
// :type="foo"
- runtimeDirective = 'vModelDynamic'
+ modelType = 'dynamic'
} else if (type.value) {
switch (type.value.content) {
case 'radio':
- runtimeDirective = 'vModelRadio'
+ modelType = 'radio'
break
case 'checkbox':
- runtimeDirective = 'vModelCheckbox'
+ modelType = 'checkbox'
break
case 'file':
- runtimeDirective = undefined
+ modelType = undefined
context.options.onError(
createDOMCompilerError(
DOMErrorCodes.X_V_MODEL_ON_FILE_INPUT_ELEMENT,
} else if (hasDynamicKeyVBind(node)) {
// element has bindings with dynamic keys, which can possibly contain
// "type".
- runtimeDirective = 'vModelDynamic'
+ modelType = 'dynamic'
} else {
// text type
__DEV__ && checkDuplicatedValue()
}
} else if (tag === 'select') {
- runtimeDirective = 'vModelSelect'
+ modelType = 'select'
} else {
// textarea
__DEV__ && checkDuplicatedValue()
)
}
+ // TODO this should no longer be needed
context.registerOperation({
type: IRNodeTypes.SET_MODEL_VALUE,
element: context.reference(),
isComponent,
})
- if (runtimeDirective)
+ if (modelType)
context.registerOperation({
- type: IRNodeTypes.WITH_DIRECTIVE,
+ type: IRNodeTypes.DIRECTIVE,
element: context.reference(),
dir,
- name: runtimeDirective,
+ name: 'model',
+ modelType,
builtin: true,
})
-import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
+import {
+ DOMErrorCodes,
+ ElementTypes,
+ ErrorCodes,
+ createCompilerError,
+ createDOMCompilerError,
+} from '@vue/compiler-dom'
import type { DirectiveTransform } from '../transform'
import { IRNodeTypes } from '../ir'
context.options.onError(
createDOMCompilerError(DOMErrorCodes.X_V_SHOW_NO_EXPRESSION, loc),
)
+ return
+ }
+
+ if (node.tagType === ElementTypes.SLOT) {
+ context.options.onError(
+ createCompilerError(
+ ErrorCodes.X_V_SLOT_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
+ loc,
+ ),
+ )
+ return
}
context.registerOperation({
- type: IRNodeTypes.WITH_DIRECTIVE,
+ type: IRNodeTypes.DIRECTIVE,
element: context.reference(),
dir,
- name: 'vShow',
+ name: 'show',
builtin: true,
})
}
import type { ObjectDirective } from '@vue/runtime-core'
+/**
+ * @internal
+ */
export const vShowOriginalDisplay: unique symbol = Symbol('_vod')
+/**
+ * @internal
+ */
export const vShowHidden: unique symbol = Symbol('_vsh')
-
+/**
+ * @internal
+ */
export interface VShowElement extends HTMLElement {
// _vod = vue original display
[vShowOriginalDisplay]: string
* @internal
*/
export { shouldSetAsProp } from './patchProp'
+/**
+ * @internal
+ */
+export {
+ vShowOriginalDisplay,
+ vShowHidden,
+ type VShowElement,
+} from './directives/vShow'
--- /dev/null
+import {
+ type VShowElement,
+ vShowHidden,
+ vShowOriginalDisplay,
+} from '@vue/runtime-dom'
+import { renderEffect } from '../renderEffect'
+
+export function applyVShow(el: VShowElement, source: () => any): void {
+ el[vShowOriginalDisplay] = el.style.display === 'none' ? '' : el.style.display
+ renderEffect(() => setDisplay(el, source()))
+}
+
+function setDisplay(el: VShowElement, value: unknown): void {
+ el.style.display = value ? el[vShowOriginalDisplay] : 'none'
+ el[vShowHidden] = !value
+}
} from './apiCreateFor'
export { createTemplateRefSetter } from './apiTemplateRef'
export { createDynamicComponent } from './apiCreateDynamicComponent'
+export { applyVShow } from './directives/vShow'