diff --git a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap index 2d553fcea..dfdee6485 100644 --- a/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/__snapshots__/compile.spec.ts.snap @@ -12,20 +12,20 @@ export function render(_ctx, $props, $emit, $attrs, $slots) { `; 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("
", 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("") export function render(_ctx) { @@ -38,91 +38,91 @@ 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vShow.spec.ts.snap b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vShow.spec.ts.snap index 07b8d05b1..f595da5ef 100644 --- a/packages/compiler-vapor/__tests__/transforms/__snapshots__/vShow.spec.ts.snap +++ b/packages/compiler-vapor/__tests__/transforms/__snapshots__/vShow.spec.ts.snap @@ -1,12 +1,12 @@ // 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("", true) export function render(_ctx) { const n0 = t0() - _withDirectives(n0, [[_vShow, () => _ctx.foo]]) + _applyVShow(n0, () => (_ctx.foo)) return n0 }" `; diff --git a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts index 560bc37b9..1b3d47f3f 100644 --- a/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts +++ b/packages/compiler-vapor/__tests__/transforms/vModel.spec.ts @@ -14,7 +14,7 @@ const compileWithVModel = makeCompile({ }, }) -describe('compiler: vModel transform', () => { +describe.todo('compiler: vModel transform', () => { test('should support simple expression', () => { const { code, helpers } = compileWithVModel('') expect(code).toMatchSnapshot() diff --git a/packages/compiler-vapor/src/generators/directive.ts b/packages/compiler-vapor/src/generators/directive.ts index ae75a273e..6321ad216 100644 --- a/packages/compiler-vapor/src/generators/directive.ts +++ b/packages/compiler-vapor/src/generators/directive.ts @@ -14,39 +14,64 @@ import { 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 && [ @@ -57,24 +82,11 @@ export function genWithDirective( 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, - ) - } - } } } @@ -87,12 +99,14 @@ export function genDirectiveModifiers(modifiers: string[]): string { .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, ) } diff --git a/packages/compiler-vapor/src/generators/operation.ts b/packages/compiler-vapor/src/generators/operation.ts index d8a57edea..5159348fd 100644 --- a/packages/compiler-vapor/src/generators/operation.ts +++ b/packages/compiler-vapor/src/generators/operation.ts @@ -19,6 +19,7 @@ import { import { genCreateComponent } from './component' import { genSlotOutlet } from './slotOutlet' import { processExpressions } from './expression' +import { genBuiltinDirective } from './directive' export function genOperations( opers: OperationNode[], @@ -68,8 +69,8 @@ export function genOperation( 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( diff --git a/packages/compiler-vapor/src/generators/vModel.ts b/packages/compiler-vapor/src/generators/vModel.ts new file mode 100644 index 000000000..aab780488 --- /dev/null +++ b/packages/compiler-vapor/src/generators/vModel.ts @@ -0,0 +1,48 @@ +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'), + ')', + ] +} diff --git a/packages/compiler-vapor/src/generators/vShow.ts b/packages/compiler-vapor/src/generators/vShow.ts new file mode 100644 index 000000000..9a6ccefcd --- /dev/null +++ b/packages/compiler-vapor/src/generators/vShow.ts @@ -0,0 +1,18 @@ +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), + `)`, + ]), + ] +} diff --git a/packages/compiler-vapor/src/ir/index.ts b/packages/compiler-vapor/src/ir/index.ts index 3af8af961..f51591f37 100644 --- a/packages/compiler-vapor/src/ir/index.ts +++ b/packages/compiler-vapor/src/ir/index.ts @@ -31,7 +31,7 @@ export enum IRNodeTypes { CREATE_COMPONENT_NODE, SLOT_OUTLET_NODE, - WITH_DIRECTIVE, + DIRECTIVE, DECLARE_OLD_REF, // consider make it more general IF, @@ -183,13 +183,14 @@ export interface PrependNodeIRNode extends BaseIRNode { 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 { @@ -230,7 +231,7 @@ export type OperationNode = | CreateTextNodeIRNode | InsertNodeIRNode | PrependNodeIRNode - | WithDirectiveIRNode + | DirectiveIRNode | IfIRNode | ForIRNode | CreateComponentIRNode diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index 1a1242a7e..5963c14cc 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -381,7 +381,7 @@ function transformProp( } context.registerOperation({ - type: IRNodeTypes.WITH_DIRECTIVE, + type: IRNodeTypes.DIRECTIVE, element: context.reference(), dir: prop, name, diff --git a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts index 5db2e1fcb..a3652cbac 100644 --- a/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts +++ b/packages/compiler-vapor/src/transforms/transformSlotOutlet.ts @@ -13,11 +13,11 @@ import { 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' @@ -85,8 +85,8 @@ export const transformSlotOutlet: NodeTransform = (node, context) => { 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( diff --git a/packages/compiler-vapor/src/transforms/vModel.ts b/packages/compiler-vapor/src/transforms/vModel.ts index 11927da74..e616954eb 100644 --- a/packages/compiler-vapor/src/transforms/vModel.ts +++ b/packages/compiler-vapor/src/transforms/vModel.ts @@ -14,7 +14,7 @@ import { 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 @@ -79,7 +79,7 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => { ) 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' || @@ -92,17 +92,17 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => { 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, @@ -119,13 +119,13 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => { } 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() @@ -139,6 +139,7 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => { ) } + // TODO this should no longer be needed context.registerOperation({ type: IRNodeTypes.SET_MODEL_VALUE, element: context.reference(), @@ -147,12 +148,13 @@ export const transformVModel: DirectiveTransform = (dir, node, context) => { 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, }) diff --git a/packages/compiler-vapor/src/transforms/vShow.ts b/packages/compiler-vapor/src/transforms/vShow.ts index 0553a3de7..f1135d6b0 100644 --- a/packages/compiler-vapor/src/transforms/vShow.ts +++ b/packages/compiler-vapor/src/transforms/vShow.ts @@ -1,4 +1,10 @@ -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' @@ -8,13 +14,24 @@ export const transformVShow: DirectiveTransform = (dir, node, context) => { 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, }) } diff --git a/packages/runtime-dom/src/directives/vShow.ts b/packages/runtime-dom/src/directives/vShow.ts index f8f41bb04..2303d8197 100644 --- a/packages/runtime-dom/src/directives/vShow.ts +++ b/packages/runtime-dom/src/directives/vShow.ts @@ -1,8 +1,16 @@ 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 diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index 60c32ce5d..a23d0cb58 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -322,3 +322,11 @@ export { patchStyle } from './modules/style' * @internal */ export { shouldSetAsProp } from './patchProp' +/** + * @internal + */ +export { + vShowOriginalDisplay, + vShowHidden, + type VShowElement, +} from './directives/vShow' diff --git a/packages/runtime-vapor/src/directives/vShow.ts b/packages/runtime-vapor/src/directives/vShow.ts new file mode 100644 index 000000000..6a21e979c --- /dev/null +++ b/packages/runtime-vapor/src/directives/vShow.ts @@ -0,0 +1,16 @@ +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 +} diff --git a/packages/runtime-vapor/src/index.ts b/packages/runtime-vapor/src/index.ts index e774ffb0f..f69d61444 100644 --- a/packages/runtime-vapor/src/index.ts +++ b/packages/runtime-vapor/src/index.ts @@ -30,3 +30,4 @@ export { } from './apiCreateFor' export { createTemplateRefSetter } from './apiTemplateRef' export { createDynamicComponent } from './apiCreateDynamicComponent' +export { applyVShow } from './directives/vShow'