diff --git a/packages/compiler-core/src/errors.ts b/packages/compiler-core/src/errors.ts index 64716f7ed12..fba4770d61e 100644 --- a/packages/compiler-core/src/errors.ts +++ b/packages/compiler-core/src/errors.ts @@ -68,6 +68,7 @@ export const enum ErrorCodes { X_FOR_MALFORMED_EXPRESSION, X_V_BIND_NO_EXPRESSION, X_V_ON_NO_EXPRESSION, + X_V_HTML_NO_EXPRESSION, X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET, X_NAMED_SLOT_ON_COMPONENT, X_MIXED_SLOT_USAGE, @@ -144,6 +145,7 @@ export const errorMessages: { [code: number]: string } = { [ErrorCodes.X_FOR_MALFORMED_EXPRESSION]: `v-for has invalid expression.`, [ErrorCodes.X_V_BIND_NO_EXPRESSION]: `v-bind is missing expression.`, [ErrorCodes.X_V_ON_NO_EXPRESSION]: `v-on is missing expression.`, + [ErrorCodes.X_V_HTML_NO_EXPRESSION]: `v-html is missing expression.`, [ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on outlet.`, [ErrorCodes.X_NAMED_SLOT_ON_COMPONENT]: `Named v-slot on component. ` + diff --git a/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts b/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts new file mode 100644 index 00000000000..104e1b5a2ba --- /dev/null +++ b/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts @@ -0,0 +1,67 @@ +import { + parse, + transform, + CompilerOptions, + ElementNode, + NodeTypes +} from '@vue/compiler-core' +import { mockWarn } from '@vue/runtime-test' +import { transformHtml } from '../../src/transforms/vHtml' + +function transformWithHtmlTransform( + template: string, + options: CompilerOptions = {} +) { + const ast = parse(template) + transform(ast, { + nodeTransforms: [transformHtml], + ...options + }) + return { + root: ast, + node: ast.children[0] as ElementNode + } +} + +describe('compiler: html transform', () => { + mockWarn() + it('should add `innerHtml` prop', () => { + const { node } = transformWithHtmlTransform(`
`) + expect(node.props[0]).toMatchObject({ + type: NodeTypes.DIRECTIVE, + name: `bind`, + arg: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `innerHTML`, + isStatic: true + }, + exp: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `test`, + isStatic: false + } + }) + }) + + it('should remove all children', () => { + const { node } = transformWithHtmlTransform( + `

foo

bar

` + ) + expect(node.children).toHaveLength(0) + expect(node.props[0]).toMatchObject({ + type: NodeTypes.DIRECTIVE, + name: `bind`, + arg: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `innerHTML`, + isStatic: true + }, + exp: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `test`, + isStatic: false + } + }) + expect(`"v-html" replaced children on "div" element`).toHaveBeenWarned() + }) +}) diff --git a/packages/compiler-dom/src/index.ts b/packages/compiler-dom/src/index.ts index 17a53a3f480..b5e7fef62fa 100644 --- a/packages/compiler-dom/src/index.ts +++ b/packages/compiler-dom/src/index.ts @@ -2,6 +2,7 @@ import { baseCompile, CompilerOptions, CodegenResult } from '@vue/compiler-core' import { parserOptionsMinimal } from './parserOptionsMinimal' import { parserOptionsStandard } from './parserOptionsStandard' import { transformStyle } from './transforms/transformStyle' +import { transformHtml } from './transforms/vHtml' export function compile( template: string, @@ -10,7 +11,11 @@ export function compile( return baseCompile(template, { ...options, ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard), - nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])], + nodeTransforms: [ + transformStyle, + transformHtml, + ...(options.nodeTransforms || []) + ], directiveTransforms: { // TODO include DOM-specific directiveTransforms ...(options.directiveTransforms || {}) diff --git a/packages/compiler-dom/src/transforms/vHtml.ts b/packages/compiler-dom/src/transforms/vHtml.ts index 70b786d12ed..07f90da7ceb 100644 --- a/packages/compiler-dom/src/transforms/vHtml.ts +++ b/packages/compiler-dom/src/transforms/vHtml.ts @@ -1 +1,33 @@ -// TODO +import { + createCompilerError, + ErrorCodes, + createSimpleExpression, + NodeTypes, + NodeTransform, + DirectiveNode +} from '@vue/compiler-core' + +export const transformHtml: NodeTransform = (node, context) => { + if (node.type === NodeTypes.ELEMENT) { + const prop = node.props.find( + x => x.type === NodeTypes.DIRECTIVE && x.name === 'html' + ) as DirectiveNode | undefined + if (prop) { + prop.name = `bind` + prop.arg = createSimpleExpression(`innerHTML`, true, prop.loc) + + if (!prop.exp || !prop.exp.loc.source.trim()) { + context.onError( + createCompilerError(ErrorCodes.X_V_HTML_NO_EXPRESSION, prop.loc) + ) + } + if (node.children.length > 0) { + // remove all the children, since they will be overridden by the `innerHTML` + node.children = [] + if (__DEV__) { + console.warn(`"v-html" replaced children on "${node.tag}" element`) + } + } + } + } +}