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,
[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. ` +
TransformOptions,
TransformContext,
NodeTransform,
- StructuralDirectiveTransform
+ StructuralDirectiveTransform,
+ DirectiveTransform
} from './transform'
export {
generate,
// It translates the raw directive into actual props for the VNode.
export type DirectiveTransform = (
dir: DirectiveNode,
+ node: ElementNode,
context: TransformContext
) => {
props: Property | Property[]
createObjectProperty,
createSimpleExpression,
createObjectExpression,
- Property,
- SourceLocation
+ Property
} from '../ast'
import { isArray, PatchFlags, PatchFlagNames } from '@vue/shared'
import { createCompilerError, ErrorCodes } from '../errors'
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
]
// 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
}
}
// children
+ const hasChildren = node.children.length > 0
if (hasChildren) {
if (!hasProps) {
args.push(`null`)
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[] = []
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)
let hasProps = propsWithoutName.length > 0
if (hasProps) {
const { props: propsExpression, directives } = buildProps(
- propsWithoutName,
- loc,
- context
+ node,
+ context,
+ propsWithoutName
)
if (directives.length) {
context.onError(
// 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) {
// 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) {
--- /dev/null
+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 }]
+ ])
+ })
+})
import { parserOptionsMinimal } from './parserOptionsMinimal'
import { parserOptionsStandard } from './parserOptionsStandard'
import { transformStyle } from './transforms/transformStyle'
+import { transformVHtml } from './transforms/vHtml'
export function compile(
template: string,
...(__BROWSER__ ? parserOptionsMinimal : parserOptionsStandard),
nodeTransforms: [transformStyle, ...(options.nodeTransforms || [])],
directiveTransforms: {
- // TODO include DOM-specific directiveTransforms
+ html: transformVHtml,
...(options.directiveTransforms || {})
}
})
-// 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
+ }
+}