- [x] simple bindings
- [x] simple events
- [ ] TODO-MVC App
-- [ ] transform
+- [x] transform
- [x] NodeTransform
- - [ ] DirectiveTransform
+ - [x] DirectiveTransform
- [ ] directives
- [x] `v-once`
- [x] `v-html`
})
test('no expression', async () => {
- const code = await compile(`<div v-text></div>`)
+ const onError = vi.fn()
+ const code = await compile(`<div v-text></div>`, { onError })
expect(code).matchSnapshot()
+ expect(onError.mock.calls).toMatchObject([
+ [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }],
+ ])
})
})
type CodegenResult,
type CompilerOptions as BaseCompilerOptions,
type RootNode,
- type DirectiveTransform,
parse,
defaultOnError,
createCompilerError,
ErrorCodes,
} from '@vue/compiler-dom'
import { extend, isString } from '@vue/shared'
-import { NodeTransform, transform } from './transform'
+import { DirectiveTransform, NodeTransform, transform } from './transform'
import { generate } from './generate'
import { HackOptions } from './hack'
import { transformOnce } from './transforms/vOnce'
import { transformElement } from './transforms/transformElement'
+import { transformVHtml } from './transforms/vHtml'
+import { transformVText } from './transforms/vText'
export type CompilerOptions = HackOptions<BaseCompilerOptions>
export function getBaseTransformPreset(
prefixIdentifiers?: boolean,
): TransformPreset {
- return [[transformOnce, transformElement], {}]
+ return [
+ [transformOnce, transformElement],
+ {
+ html: transformVHtml,
+ text: transformVText,
+ },
+ ]
}
import type { Prettify } from '@vue/shared'
-import type { NodeTransform } from './transform'
+import type { DirectiveTransform, NodeTransform } from './transform'
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> &
Pick<U, Extract<keyof U, keyof T>>
export type HackOptions<T> = Prettify<
- Overwrite<T, { nodeTransforms?: NodeTransform[] }>
+ Overwrite<
+ T,
+ {
+ nodeTransforms?: NodeTransform[]
+ directiveTransforms?: Record<string, DirectiveTransform | undefined>
+ }
+ >
>
NodeTypes,
defaultOnError,
defaultOnWarn,
+ DirectiveNode,
} from '@vue/compiler-dom'
import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared'
import {
context: TransformContext<RootNode | TemplateChildNode>,
) => void | (() => void) | (() => void)[]
+export type DirectiveTransform = (
+ dir: DirectiveNode,
+ node: ElementNode,
+ context: TransformContext,
+ // a platform specific compiler can import the base transform and augment
+ // it by passing in this optional argument.
+ // augmentor?: (ret: DirectiveTransformResult) => DirectiveTransformResult,
+) => void
+
export type TransformOptions = HackOptions<BaseTransformOptions>
export interface TransformContext<T extends AllNode = AllNode> {
NodeTypes,
ErrorCodes,
createCompilerError,
- DOMErrorCodes,
- createDOMCompilerError,
ElementTypes,
} from '@vue/compiler-dom'
import { isVoidTag } from '@vue/shared'
}
const { tag, props } = node
+ const isComponent = node.tagType === ElementTypes.COMPONENT
ctx.template += `<${tag}`
- props.forEach((prop) =>
- transformProp(prop, ctx as TransformContext<ElementNode>),
- )
- ctx.template += `>`
- ctx.template += ctx.childrenTemplate.join('')
+ if (props.length) {
+ buildProps(
+ node,
+ ctx as TransformContext<ElementNode>,
+ undefined,
+ isComponent,
+ )
+ }
+ ctx.template += `>` + ctx.childrenTemplate.join('')
// TODO remove unnecessary close tag, e.g. if it's the last element of the template
if (!isVoidTag(tag)) {
}
}
+function buildProps(
+ node: ElementNode,
+ context: TransformContext<ElementNode>,
+ props: ElementNode['props'] = node.props,
+ isComponent: boolean,
+) {
+ for (const prop of props) {
+ transformProp(prop, node, context)
+ }
+}
+
function transformProp(
- node: DirectiveNode | AttributeNode,
- ctx: TransformContext<ElementNode>,
+ prop: DirectiveNode | AttributeNode,
+ node: ElementNode,
+ context: TransformContext<ElementNode>,
): void {
- const { name } = node
+ const { name } = prop
- if (node.type === NodeTypes.ATTRIBUTE) {
- if (node.value) {
- ctx.template += ` ${name}="${node.value.content}"`
- } else {
- ctx.template += ` ${name}`
- }
+ if (prop.type === NodeTypes.ATTRIBUTE) {
+ context.template += ` ${name}`
+ if (prop.value) context.template += `="${prop.value.content}"`
return
}
- const { arg, exp, loc, modifiers } = node
+ const { arg, exp, loc, modifiers } = prop
+ const directiveTransform = context.options.directiveTransforms[name]
+ if (directiveTransform) {
+ directiveTransform(prop, node, context)
+ }
switch (name) {
case 'bind': {
!exp ||
(exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim())
) {
- ctx.options.onError(
+ context.options.onError(
createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc),
)
return
return
}
- ctx.registerEffect(
+ context.registerEffect(
[exp],
[
{
type: IRNodeTypes.SET_PROP,
- loc: node.loc,
- element: ctx.reference(),
+ loc: prop.loc,
+ element: context.reference(),
name: arg,
value: exp,
},
}
case 'on': {
if (!exp && !modifiers.length) {
- ctx.options.onError(
+ context.options.onError(
createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc),
)
return
return
}
- ctx.registerEffect(
+ context.registerEffect(
[exp],
[
{
type: IRNodeTypes.SET_EVENT,
- loc: node.loc,
- element: ctx.reference(),
+ loc: prop.loc,
+ element: context.reference(),
name: arg,
value: exp,
modifiers,
)
break
}
- case 'html': {
- if (!exp) {
- ctx.options.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc),
- )
- }
- if (ctx.node.children.length) {
- ctx.options.onError(
- createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc),
- )
- ctx.childrenTemplate.length = 0
- }
-
- ctx.registerEffect(
- [exp],
- [
- {
- type: IRNodeTypes.SET_HTML,
- loc: node.loc,
- element: ctx.reference(),
- value: exp || '""',
- },
- ],
- )
- break
- }
- case 'text': {
- ctx.registerEffect(
- [exp],
- [
- {
- type: IRNodeTypes.SET_TEXT,
- loc: node.loc,
- element: ctx.reference(),
- value: exp || '""',
- },
- ],
- )
- break
- }
- case 'cloak': {
- // do nothing
- break
- }
}
}
--- /dev/null
+import { IRNodeTypes } from '../ir'
+import { DirectiveTransform } from '../transform'
+import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
+
+export const transformVHtml: DirectiveTransform = (dir, node, context) => {
+ const { exp, loc } = dir
+ if (!exp) {
+ context.options.onError(
+ createDOMCompilerError(DOMErrorCodes.X_V_HTML_NO_EXPRESSION, loc),
+ )
+ }
+ if (node.children.length) {
+ context.options.onError(
+ createDOMCompilerError(DOMErrorCodes.X_V_HTML_WITH_CHILDREN, loc),
+ )
+ context.childrenTemplate.length = 0
+ }
+
+ context.registerEffect(
+ [exp],
+ [
+ {
+ type: IRNodeTypes.SET_HTML,
+ loc: dir.loc,
+ element: context.reference(),
+ value: exp || '""',
+ },
+ ],
+ )
+}
--- /dev/null
+import { DOMErrorCodes, createDOMCompilerError } from '@vue/compiler-dom'
+import { DirectiveTransform } from '../transform'
+import { IRNodeTypes } from '../ir'
+
+export const transformVText: DirectiveTransform = (dir, node, context) => {
+ const { exp, loc } = dir
+ if (!exp) {
+ context.options.onError(
+ createDOMCompilerError(DOMErrorCodes.X_V_TEXT_NO_EXPRESSION, loc),
+ )
+ }
+ if (node.children.length) {
+ context.options.onError(
+ createDOMCompilerError(DOMErrorCodes.X_V_TEXT_WITH_CHILDREN, loc),
+ )
+ node.children.length = 0
+ }
+
+ context.registerEffect(
+ [exp],
+ [
+ {
+ type: IRNodeTypes.SET_TEXT,
+ loc: dir.loc,
+ element: context.reference(),
+ value: exp || '""',
+ },
+ ],
+ )
+}