From 5a424218f11aa96d3cdbfe15a10270b341d85167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Sun, 3 Dec 2023 01:40:26 +0800 Subject: [PATCH] feat: DirectiveTransform --- README.md | 4 +- .../compiler-vapor/__tests__/compile.test.ts | 6 +- packages/compiler-vapor/src/compile.ts | 13 +- packages/compiler-vapor/src/hack.ts | 10 +- packages/compiler-vapor/src/transform.ts | 10 ++ .../src/transforms/transformElement.ts | 114 +++++++----------- .../compiler-vapor/src/transforms/vHtml.ts | 30 +++++ .../compiler-vapor/src/transforms/vText.ts | 30 +++++ 8 files changed, 138 insertions(+), 79 deletions(-) create mode 100644 packages/compiler-vapor/src/transforms/vHtml.ts create mode 100644 packages/compiler-vapor/src/transforms/vText.ts diff --git a/README.md b/README.md index b7100cc01..3b721f155 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,9 @@ PR are welcome! However, please create an issue before you start to work on it, - [x] simple bindings - [x] simple events - [ ] TODO-MVC App -- [ ] transform +- [x] transform - [x] NodeTransform - - [ ] DirectiveTransform + - [x] DirectiveTransform - [ ] directives - [x] `v-once` - [x] `v-html` diff --git a/packages/compiler-vapor/__tests__/compile.test.ts b/packages/compiler-vapor/__tests__/compile.test.ts index 197a1ecd7..417418726 100644 --- a/packages/compiler-vapor/__tests__/compile.test.ts +++ b/packages/compiler-vapor/__tests__/compile.test.ts @@ -172,8 +172,12 @@ describe('compile', () => { }) test('no expression', async () => { - const code = await compile(`
`) + const onError = vi.fn() + const code = await compile(`
`, { onError }) expect(code).matchSnapshot() + expect(onError.mock.calls).toMatchObject([ + [{ code: DOMErrorCodes.X_V_TEXT_NO_EXPRESSION }], + ]) }) }) diff --git a/packages/compiler-vapor/src/compile.ts b/packages/compiler-vapor/src/compile.ts index f9a922b33..4e40a206d 100644 --- a/packages/compiler-vapor/src/compile.ts +++ b/packages/compiler-vapor/src/compile.ts @@ -2,18 +2,19 @@ import { 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 @@ -85,5 +86,11 @@ export type TransformPreset = [ export function getBaseTransformPreset( prefixIdentifiers?: boolean, ): TransformPreset { - return [[transformOnce, transformElement], {}] + return [ + [transformOnce, transformElement], + { + html: transformVHtml, + text: transformVText, + }, + ] } diff --git a/packages/compiler-vapor/src/hack.ts b/packages/compiler-vapor/src/hack.ts index 6b74dceb6..a2fb6cb5a 100644 --- a/packages/compiler-vapor/src/hack.ts +++ b/packages/compiler-vapor/src/hack.ts @@ -1,9 +1,15 @@ import type { Prettify } from '@vue/shared' -import type { NodeTransform } from './transform' +import type { DirectiveTransform, NodeTransform } from './transform' type Overwrite = Pick> & Pick> export type HackOptions = Prettify< - Overwrite + Overwrite< + T, + { + nodeTransforms?: NodeTransform[] + directiveTransforms?: Record + } + > > diff --git a/packages/compiler-vapor/src/transform.ts b/packages/compiler-vapor/src/transform.ts index bfb3d732f..78f83f949 100644 --- a/packages/compiler-vapor/src/transform.ts +++ b/packages/compiler-vapor/src/transform.ts @@ -10,6 +10,7 @@ import { NodeTypes, defaultOnError, defaultOnWarn, + DirectiveNode, } from '@vue/compiler-dom' import { EMPTY_OBJ, NOOP, isArray } from '@vue/shared' import { @@ -26,6 +27,15 @@ export type NodeTransform = ( context: TransformContext, ) => 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 export interface TransformContext { diff --git a/packages/compiler-vapor/src/transforms/transformElement.ts b/packages/compiler-vapor/src/transforms/transformElement.ts index b205bf177..00537baa5 100644 --- a/packages/compiler-vapor/src/transforms/transformElement.ts +++ b/packages/compiler-vapor/src/transforms/transformElement.ts @@ -5,8 +5,6 @@ import { NodeTypes, ErrorCodes, createCompilerError, - DOMErrorCodes, - createDOMCompilerError, ElementTypes, } from '@vue/compiler-dom' import { isVoidTag } from '@vue/shared' @@ -28,13 +26,18 @@ export const transformElement: NodeTransform = (node, ctx) => { } const { tag, props } = node + const isComponent = node.tagType === ElementTypes.COMPONENT ctx.template += `<${tag}` - props.forEach((prop) => - transformProp(prop, ctx as TransformContext), - ) - ctx.template += `>` - ctx.template += ctx.childrenTemplate.join('') + if (props.length) { + buildProps( + node, + ctx as TransformContext, + 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)) { @@ -43,22 +46,35 @@ export const transformElement: NodeTransform = (node, ctx) => { } } -function transformProp( - node: DirectiveNode | AttributeNode, - ctx: TransformContext, -): void { - const { name } = node +function buildProps( + node: ElementNode, + context: TransformContext, + props: ElementNode['props'] = node.props, + isComponent: boolean, +) { + for (const prop of props) { + transformProp(prop, node, context) + } +} - if (node.type === NodeTypes.ATTRIBUTE) { - if (node.value) { - ctx.template += ` ${name}="${node.value.content}"` - } else { - ctx.template += ` ${name}` - } +function transformProp( + prop: DirectiveNode | AttributeNode, + node: ElementNode, + context: TransformContext, +): void { + const { name } = prop + + 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': { @@ -66,7 +82,7 @@ function transformProp( !exp || (exp.type === NodeTypes.SIMPLE_EXPRESSION && !exp.content.trim()) ) { - ctx.options.onError( + context.options.onError( createCompilerError(ErrorCodes.X_V_BIND_NO_EXPRESSION, loc), ) return @@ -81,13 +97,13 @@ function transformProp( 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, }, @@ -97,7 +113,7 @@ function transformProp( } case 'on': { if (!exp && !modifiers.length) { - ctx.options.onError( + context.options.onError( createCompilerError(ErrorCodes.X_V_ON_NO_EXPRESSION, loc), ) return @@ -112,13 +128,13 @@ function transformProp( 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, @@ -127,49 +143,5 @@ function transformProp( ) 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 - } } } diff --git a/packages/compiler-vapor/src/transforms/vHtml.ts b/packages/compiler-vapor/src/transforms/vHtml.ts new file mode 100644 index 000000000..5321f8992 --- /dev/null +++ b/packages/compiler-vapor/src/transforms/vHtml.ts @@ -0,0 +1,30 @@ +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 || '""', + }, + ], + ) +} diff --git a/packages/compiler-vapor/src/transforms/vText.ts b/packages/compiler-vapor/src/transforms/vText.ts new file mode 100644 index 000000000..7a235a793 --- /dev/null +++ b/packages/compiler-vapor/src/transforms/vText.ts @@ -0,0 +1,30 @@ +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 || '""', + }, + ], + ) +}