diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap index b626c6e9..07dadbcd 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/transformText.spec.ts.snap @@ -62,6 +62,24 @@ return function render(_ctx, _cache) { }" `; +exports[`compiler: transform text element with custom directives and only one text child node 1`] = ` +"const _Vue = Vue + +return function render(_ctx, _cache) { + with (_ctx) { + const { toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, resolveDirective: _resolveDirective, withDirectives: _withDirectives, openBlock: _openBlock, createBlock: _createBlock } = _Vue + + const _directive_foo = _resolveDirective(\\"foo\\") + + return _withDirectives((_openBlock(), _createBlock(\\"p\\", null, [ + _createTextVNode(_toDisplayString(foo), 1 /* TEXT */) + ], 512 /* NEED_PATCH */)), [ + [_directive_foo] + ]) + } +}" +`; + exports[`compiler: transform text no consecutive text 1`] = ` "const _Vue = Vue diff --git a/packages/compiler-core/__tests__/transforms/transformText.spec.ts b/packages/compiler-core/__tests__/transforms/transformText.spec.ts index ab5341c3..6e321d14 100644 --- a/packages/compiler-core/__tests__/transforms/transformText.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformText.spec.ts @@ -4,7 +4,8 @@ import { transform, NodeTypes, generate, - ForNode + ForNode, + ElementNode } from '../../src' import { transformFor } from '../../src/transforms/vFor' import { transformText } from '../../src/transforms/transformText' @@ -20,8 +21,8 @@ function transformWithTextOpt(template: string, options: CompilerOptions = {}) { nodeTransforms: [ transformFor, ...(options.prefixIdentifiers ? [transformExpression] : []), - transformText, - transformElement + transformElement, + transformText ], ...options }) @@ -193,4 +194,29 @@ describe('compiler: transform text', () => { }).code ).toMatchSnapshot() }) + + // #3756 + test('element with custom directives and only one text child node', () => { + const root = transformWithTextOpt(`
{{ foo }}
`) + expect(root.children.length).toBe(1) + expect(root.children[0].type).toBe(NodeTypes.ELEMENT) + expect((root.children[0] as ElementNode).children[0]).toMatchObject({ + type: NodeTypes.TEXT_CALL, + codegenNode: { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: CREATE_TEXT, + arguments: [ + { + type: NodeTypes.INTERPOLATION, + content: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'foo' + } + }, + genFlagText(PatchFlags.TEXT) + ] + } + }) + expect(generate(root).code).toMatchSnapshot() + }) }) diff --git a/packages/compiler-core/src/transforms/transformText.ts b/packages/compiler-core/src/transforms/transformText.ts index a8b65c96..2ab6805c 100644 --- a/packages/compiler-core/src/transforms/transformText.ts +++ b/packages/compiler-core/src/transforms/transformText.ts @@ -64,6 +64,16 @@ export const transformText: NodeTransform = (node, context) => { (node.type === NodeTypes.ROOT || (node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.ELEMENT && + // #3756 + // custom directives can potentially add DOM elements arbitrarily, + // we need to avoid setting textContent of the element at runtime + // to avoid accidentally overwriting the DOM elements added + // by the user through custom directives. + !node.props.find( + p => + p.type === NodeTypes.DIRECTIVE && + !context.directiveTransforms[p.name] + ) && // in compat mode, tags with no special directives // will be rendered as a fragment so its children must be // converted into vnodes.