]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(compiler-dom): transform for v-html
authorEvan You <yyx990803@gmail.com>
Tue, 8 Oct 2019 19:35:57 +0000 (15:35 -0400)
committerEvan You <yyx990803@gmail.com>
Tue, 8 Oct 2019 19:35:57 +0000 (15:35 -0400)
packages/compiler-core/src/errors.ts
packages/compiler-core/src/index.ts
packages/compiler-core/src/transform.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/compiler-core/src/transforms/transformSlotOutlet.ts
packages/compiler-core/src/transforms/vBind.ts
packages/compiler-core/src/transforms/vOn.ts
packages/compiler-dom/__tests__/transforms/vHtml.spec.ts [new file with mode: 0644]
packages/compiler-dom/src/index.ts
packages/compiler-dom/src/transforms/vHtml.ts

index 64716f7ed12de5edb3fcf953a0f5d478ad129c21..e9c16bfcf27e75ef40e2309c8065e1e3dc982b78 100644 (file)
@@ -68,6 +68,8 @@ export const enum ErrorCodes {
   X_FOR_MALFORMED_EXPRESSION,
   X_V_BIND_NO_EXPRESSION,
   X_V_ON_NO_EXPRESSION,
+  X_V_HTML_NO_EXPRESSION,
+  X_V_HTML_WITH_CHILDREN,
   X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET,
   X_NAMED_SLOT_ON_COMPONENT,
   X_MIXED_SLOT_USAGE,
@@ -144,6 +146,8 @@ 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 epxression.`,
+  [ErrorCodes.X_V_HTML_WITH_CHILDREN]: `v-html will override element children.`,
   [ErrorCodes.X_UNEXPECTED_DIRECTIVE_ON_SLOT_OUTLET]: `Unexpected custom directive on <slot> outlet.`,
   [ErrorCodes.X_NAMED_SLOT_ON_COMPONENT]:
     `Named v-slot on component. ` +
index 34998f73e8818520c739d6ce46005e5a11f085b9..35624b10608f15e6de7780b613fda3fb22db1f82 100644 (file)
@@ -78,7 +78,8 @@ export {
   TransformOptions,
   TransformContext,
   NodeTransform,
-  StructuralDirectiveTransform
+  StructuralDirectiveTransform,
+  DirectiveTransform
 } from './transform'
 export {
   generate,
index 4e8877f30e5aeb6389e32ccd1f798f68f17932ed..1fe68d5de58a37ba4f167cf00df60593ee8939c6 100644 (file)
@@ -45,6 +45,7 @@ export type NodeTransform = (
 //   It translates the raw directive into actual props for the VNode.
 export type DirectiveTransform = (
   dir: DirectiveNode,
+  node: ElementNode,
   context: TransformContext
 ) => {
   props: Property | Property[]
index 3a483adba178f3ae2d20d8dadc3ae8517f7ce863..33ddcb607f787cadf4c2b1b4dcb15ff7e1377129 100644 (file)
@@ -13,8 +13,7 @@ import {
   createObjectProperty,
   createSimpleExpression,
   createObjectExpression,
-  Property,
-  SourceLocation
+  Property
 } from '../ast'
 import { isArray, PatchFlags, PatchFlagNames } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
@@ -44,7 +43,6 @@ export const transformElement: NodeTransform = (node, context) => {
       return () => {
         const isComponent = node.tagType === ElementTypes.COMPONENT
         let hasProps = node.props.length > 0
-        const hasChildren = node.children.length > 0
         let patchFlag: number = 0
         let runtimeDirectives: DirectiveNode[] | undefined
         let dynamicPropNames: string[] | undefined
@@ -59,12 +57,7 @@ export const transformElement: NodeTransform = (node, context) => {
         ]
         // props
         if (hasProps) {
-          const propsBuildResult = buildProps(
-            node.props,
-            node.loc,
-            context,
-            isComponent
-          )
+          const propsBuildResult = buildProps(node, context)
           patchFlag = propsBuildResult.patchFlag
           dynamicPropNames = propsBuildResult.dynamicPropNames
           runtimeDirectives = propsBuildResult.directives
@@ -75,6 +68,7 @@ export const transformElement: NodeTransform = (node, context) => {
           }
         }
         // children
+        const hasChildren = node.children.length > 0
         if (hasChildren) {
           if (!hasProps) {
             args.push(`null`)
@@ -162,16 +156,17 @@ export const transformElement: NodeTransform = (node, context) => {
 export type PropsExpression = ObjectExpression | CallExpression | ExpressionNode
 
 export function buildProps(
-  props: ElementNode['props'],
-  elementLoc: SourceLocation,
+  node: ElementNode,
   context: TransformContext,
-  isComponent: boolean = false
+  props: ElementNode['props'] = node.props
 ): {
   props: PropsExpression | undefined
   directives: DirectiveNode[]
   patchFlag: number
   dynamicPropNames: string[]
 } {
+  const elementLoc = node.loc
+  const isComponent = node.tagType === ElementTypes.COMPONENT
   let properties: ObjectExpression['properties'] = []
   const mergeArgs: PropsExpression[] = []
   const runtimeDirectives: DirectiveNode[] = []
@@ -278,7 +273,7 @@ export function buildProps(
       const directiveTransform = context.directiveTransforms[name]
       if (directiveTransform) {
         // has built-in directive transform.
-        const { props, needRuntime } = directiveTransform(prop, context)
+        const { props, needRuntime } = directiveTransform(prop, node, context)
         if (isArray(props)) {
           properties.push(...props)
           properties.forEach(analyzePatchFlag)
index 70421c6fda4d37d8bc091ff92d75c2fffe29948e..1c65d31776ad3b84e20ab518caed4d8238ac323e 100644 (file)
@@ -52,9 +52,9 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
     let hasProps = propsWithoutName.length > 0
     if (hasProps) {
       const { props: propsExpression, directives } = buildProps(
-        propsWithoutName,
-        loc,
-        context
+        node,
+        context,
+        propsWithoutName
       )
       if (directives.length) {
         context.onError(
index 1d28e3546af2ecabe98f2e2387ea7dd4bb1efaf6..065889f072c069fca015403e9df0a917b5f03831 100644 (file)
@@ -7,7 +7,7 @@ import { CAMELIZE } from '../runtimeHelpers'
 // v-bind without arg is handled directly in ./element.ts due to it affecting
 // codegen for the entire props object. This transform here is only for v-bind
 // *with* args.
-export const transformBind: DirectiveTransform = (dir, context) => {
+export const transformBind: DirectiveTransform = (dir, node, context) => {
   const { exp, modifiers, loc } = dir
   const arg = dir.arg!
   if (!exp) {
index 68131746730f5a785c4a71e36fc867e476f8f63a..521d37dfc22d6881f09b4d429c57329ac49ec170 100644 (file)
@@ -17,7 +17,7 @@ const simplePathRE = /^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['[^']*?']|\["[^"]
 // v-on without arg is handled directly in ./element.ts due to it affecting
 // codegen for the entire props object. This transform here is only for v-on
 // *with* args.
-export const transformOn: DirectiveTransform = (dir, context) => {
+export const transformOn: DirectiveTransform = (dir, node, context) => {
   const { loc, modifiers } = dir
   const arg = dir.arg!
   if (!dir.exp && !modifiers.length) {
diff --git a/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts b/packages/compiler-dom/__tests__/transforms/vHtml.spec.ts
new file mode 100644 (file)
index 0000000..1e3648c
--- /dev/null
@@ -0,0 +1,74 @@
+import {
+  parse,
+  transform,
+  PlainElementNode,
+  CompilerOptions,
+  ErrorCodes
+} from '@vue/compiler-core'
+import { transformVHtml } from '../../src/transforms/vHtml'
+import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
+import {
+  createObjectMatcher,
+  genFlagText
+} from '../../../compiler-core/__tests__/testUtils'
+import { PatchFlags } from '@vue/shared'
+
+function transformWithVHtml(template: string, options: CompilerOptions = {}) {
+  const ast = parse(template)
+  transform(ast, {
+    nodeTransforms: [transformElement],
+    directiveTransforms: {
+      html: transformVHtml
+    },
+    ...options
+  })
+  return ast
+}
+
+describe('compiler: v-html transform', () => {
+  it('should convert v-html to innerHTML', () => {
+    const ast = transformWithVHtml(`<div v-html="test"/>`)
+    expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
+      arguments: [
+        `"div"`,
+        createObjectMatcher({
+          innerHTML: `[test]`
+        }),
+        `null`,
+        genFlagText(PatchFlags.PROPS),
+        `["innerHTML"]`
+      ]
+    })
+  })
+
+  it('should raise error and ignore children when v-html is present', () => {
+    const onError = jest.fn()
+    const ast = transformWithVHtml(`<div v-html="test">hello</div>`, {
+      onError
+    })
+    expect(onError.mock.calls).toMatchObject([
+      [{ code: ErrorCodes.X_V_HTML_WITH_CHILDREN }]
+    ])
+    expect((ast.children[0] as PlainElementNode).codegenNode).toMatchObject({
+      arguments: [
+        `"div"`,
+        createObjectMatcher({
+          innerHTML: `[test]`
+        }),
+        `null`, // <-- children should have been removed
+        genFlagText(PatchFlags.PROPS),
+        `["innerHTML"]`
+      ]
+    })
+  })
+
+  it('should raise error if has no expression', () => {
+    const onError = jest.fn()
+    transformWithVHtml(`<div v-html></div>`, {
+      onError
+    })
+    expect(onError.mock.calls).toMatchObject([
+      [{ code: ErrorCodes.X_V_HTML_NO_EXPRESSION }]
+    ])
+  })
+})
index 17a53a3f480d0985dee2aae3637aeacd3d14943c..fa7855fd50c2ccc9036bcd0ffaf90dcfcbfca957 100644 (file)
@@ -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 { transformVHtml } from './transforms/vHtml'
 
 export function compile(
   template: string,
@@ -12,7 +13,7 @@ export function compile(
     ...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
     nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
     directiveTransforms: {
-      // TODO include DOM-specific directiveTransforms
+      html: transformVHtml,
       ...(options.directiveTransforms || {})
     }
   })
index 70b786d12ed055a08b57f5cf47f717bf6a266301..abd2a23b185fc084ba13c0e12347486c1e812e11 100644 (file)
@@ -1 +1,25 @@
-// TODO
+import {
+  DirectiveTransform,
+  createCompilerError,
+  ErrorCodes,
+  createObjectProperty,
+  createSimpleExpression
+} from '@vue/compiler-core'
+
+export const transformVHtml: DirectiveTransform = (dir, node, context) => {
+  const { exp, loc } = dir
+  if (!exp) {
+    context.onError(createCompilerError(ErrorCodes.X_V_HTML_NO_EXPRESSION, loc))
+  }
+  if (node.children.length) {
+    context.onError(createCompilerError(ErrorCodes.X_V_HTML_WITH_CHILDREN, loc))
+    node.children.length = 0
+  }
+  return {
+    props: createObjectProperty(
+      createSimpleExpression(`innerHTML`, true, loc),
+      exp || createSimpleExpression('', true)
+    ),
+    needRuntime: false
+  }
+}