feat(compiler-dom): transform for v-html
This commit is contained in:
parent
5c4478b00b
commit
eadcaead37
@ -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. ` +
|
||||
|
@ -78,7 +78,8 @@ export {
|
||||
TransformOptions,
|
||||
TransformContext,
|
||||
NodeTransform,
|
||||
StructuralDirectiveTransform
|
||||
StructuralDirectiveTransform,
|
||||
DirectiveTransform
|
||||
} from './transform'
|
||||
export {
|
||||
generate,
|
||||
|
@ -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[]
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
74
packages/compiler-dom/__tests__/transforms/vHtml.spec.ts
Normal file
74
packages/compiler-dom/__tests__/transforms/vHtml.spec.ts
Normal file
@ -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 }]
|
||||
])
|
||||
})
|
||||
})
|
@ -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 || {})
|
||||
}
|
||||
})
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user