From c482547259e771f24596bcf3e4e8c585ab062bbc Mon Sep 17 00:00:00 2001 From: kazuya kawaguchi Date: Thu, 19 Sep 2024 16:51:30 +0900 Subject: [PATCH] feat(svelte-template-compiler): first template ref implementation --- .../svelte-template-compiler/src/compile.ts | 3 +- .../src/ir/converter.test.ts | 50 +++++++++++++ .../src/ir/converter.ts | 48 +++++++++++-- .../__snapshots__/templateRef.test.ts.snap | 23 ++++++ .../src/transforms/element.ts | 1 + .../src/transforms/index.ts | 1 + .../src/transforms/templateRef.test.ts | 71 ++++++++++++++++++- .../src/transforms/templateRef.ts | 50 +++++++++++-- .../src/transforms/utils.ts | 4 +- vitest.config.ts | 3 +- 10 files changed, 237 insertions(+), 17 deletions(-) create mode 100644 packages/svelte-template-compiler/src/transforms/__snapshots__/templateRef.test.ts.snap diff --git a/packages/svelte-template-compiler/src/compile.ts b/packages/svelte-template-compiler/src/compile.ts index 307bda2..aa7a4be 100644 --- a/packages/svelte-template-compiler/src/compile.ts +++ b/packages/svelte-template-compiler/src/compile.ts @@ -14,6 +14,7 @@ import { transformChildren, transformComment, transformElement, + transformTemplateRef, transformText, transformVBind, transformVFor, @@ -155,7 +156,7 @@ export function getBaseTransformPreset(_prefixIdentifiers?: boolean): TransformP transformVIf, transformVFor, // transformSlotOutlet, - // transformTemplateRef, + transformTemplateRef, transformText, transformElement, // transformVSlot, diff --git a/packages/svelte-template-compiler/src/ir/converter.test.ts b/packages/svelte-template-compiler/src/ir/converter.test.ts index ea62249..3760987 100644 --- a/packages/svelte-template-compiler/src/ir/converter.test.ts +++ b/packages/svelte-template-compiler/src/ir/converter.test.ts @@ -639,6 +639,56 @@ describe('convertProps', () => { } ]) }) + + test('bind:this: native element', () => { + const el = getSvelteElement('') + expect(convertProps(el!)).toMatchObject([ + { + type: NodeTypes.ATTRIBUTE, + name: 'ref', + loc: { + // TODO: we want to map for svelte code correctly... + source: `bind:this={el}` + }, + nameLoc: { + source: `bind:this` + }, + value: { + type: NodeTypes.TEXT, + content: 'el', + loc: { + // TODO: we want to map for svelte code correctly... + source: 'el' + } + } + } + ]) + }) + + test('bind:this: component', () => { + const el = getSvelteElement('') + expect(convertProps(el!)).toMatchObject([ + { + type: NodeTypes.ATTRIBUTE, + name: 'ref', + loc: { + // TODO: we want to map for svelte code correctly... + source: `bind:this={el}` + }, + nameLoc: { + source: `bind:this` + }, + value: { + type: NodeTypes.TEXT, + content: 'el', + loc: { + // TODO: we want to map for svelte code correctly... + source: 'el' + } + } + } + ]) + }) }) test('no attribute', () => { diff --git a/packages/svelte-template-compiler/src/ir/converter.ts b/packages/svelte-template-compiler/src/ir/converter.ts index b42ffbd..9923129 100644 --- a/packages/svelte-template-compiler/src/ir/converter.ts +++ b/packages/svelte-template-compiler/src/ir/converter.ts @@ -12,7 +12,12 @@ import { isSvelteText } from './svelte.ts' -import type { AttributeNode, SimpleExpressionNode, SourceLocation } from '@vue-vapor/compiler-dom' +import type { + AttributeNode, + SimpleExpressionNode, + SourceLocation, + TextNode +} from '@vue-vapor/compiler-dom' import type { VaporDirectiveNode } from './nodes' import type { SvelteAttribute, @@ -33,12 +38,12 @@ export function convertProps(node: SvelteElement): (VaporDirectiveNode | Attribu } else { props.push(convertSvelteAttribute(attr)) } - } else if ( - isSvelteSpreadAttribute(attr) || - isSvelteEventHandler(attr) || - isSvelteBindingDirective(attr) - ) { + } else if (isSvelteSpreadAttribute(attr) || isSvelteEventHandler(attr)) { + props.push(convertVaporDirective(attr, node)) + } else if (isSvelteBindingDirective(attr) && attr.name !== 'this') { props.push(convertVaporDirective(attr, node)) + // ignore `this:bind` converting on `convertProps` + // props.push(convertVaporAttribute(attr)) } } @@ -285,6 +290,37 @@ function convertVaporDirectiveExpression( } } +export function convertVaporAttribute(node: SvelteBaseDirective): AttributeNode { + if (isSvelteBindingDirective(node) && node.name === 'this') { + if (__DEV__ && !node.expression) { + throw new Error('no expression in binding node') + } + + const content = generate(node.expression!) + const value: TextNode = { + type: NodeTypes.TEXT, + content, + // TODO: align loc for svlete compiler + loc: convertSvelteLocation( + node.expression as unknown as { start: number; end: number }, + content + ) + } + + return { + type: NodeTypes.ATTRIBUTE, + name: 'ref', + value, + // TODO: align loc for svlete compiler + loc: convertSvelteLocation(node as { start: number; end: number }, `bind:this={${content}}`), + // TODO: align loc for svlete compiler + nameLoc: convertSvelteLocation(node, `bind:this`) + } + } + + throw new Error('unexpected node type') +} + export function convertSvelteLocation( node: { start: number; end: number }, source: string diff --git a/packages/svelte-template-compiler/src/transforms/__snapshots__/templateRef.test.ts.snap b/packages/svelte-template-compiler/src/transforms/__snapshots__/templateRef.test.ts.snap new file mode 100644 index 0000000..d9162e6 --- /dev/null +++ b/packages/svelte-template-compiler/src/transforms/__snapshots__/templateRef.test.ts.snap @@ -0,0 +1,23 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`native element > expected 1`] = ` +"import { setRef as _setRef, template as _template } from 'vue/vapor'; +const t0 = _template("") + +export function render(_ctx) { + const n0 = t0() + _setRef(n0, "el") + return n0 +}" +`; + +exports[`native element > received 1`] = ` +"import { setRef as _setRef, template as _template } from 'vue/vapor'; +const t0 = _template("") + +export function render(_ctx) { + const n0 = t0() + _setRef(n0, "el") + return n0 +}" +`; diff --git a/packages/svelte-template-compiler/src/transforms/element.ts b/packages/svelte-template-compiler/src/transforms/element.ts index 570c50c..586f81f 100644 --- a/packages/svelte-template-compiler/src/transforms/element.ts +++ b/packages/svelte-template-compiler/src/transforms/element.ts @@ -177,6 +177,7 @@ export function buildProps( context: TransformContext, isComponent: boolean ): PropsResult { + // convert from svelte props to vapor props const props = convertProps(node) if (props.length === 0) { return [false, []] diff --git a/packages/svelte-template-compiler/src/transforms/index.ts b/packages/svelte-template-compiler/src/transforms/index.ts index 4ba5065..4ed09b1 100644 --- a/packages/svelte-template-compiler/src/transforms/index.ts +++ b/packages/svelte-template-compiler/src/transforms/index.ts @@ -11,6 +11,7 @@ export * from './html.ts' export * from './if.ts' export * from './model.ts' export * from './on.ts' +export * from './templateRef.ts' export * from './text.ts' export * from './types.ts' export * from './utils.ts' diff --git a/packages/svelte-template-compiler/src/transforms/templateRef.test.ts b/packages/svelte-template-compiler/src/transforms/templateRef.test.ts index 2b100c6..4e3fda9 100644 --- a/packages/svelte-template-compiler/src/transforms/templateRef.test.ts +++ b/packages/svelte-template-compiler/src/transforms/templateRef.test.ts @@ -1,6 +1,71 @@ +import { NodeTypes } from '@vue-vapor/compiler-dom' +import { compile as vaporCompile } from '@vue-vapor/compiler-vapor' import { expect, test } from 'vitest' +import { IRNodeTypes } from '../ir/index.ts' +import { makeCompile } from './_utils.ts' +import { transformVBind } from './bind.ts' +import { transformChildren } from './children.ts' +import { transformComment } from './comment.ts' +import { transformElement } from './element.ts' +import { transformVModel } from './model.ts' +import { transformVOn } from './on.ts' +import { transformTemplateRef } from './templateRef.ts' +import { transformText } from './text.ts' -test.todo('basic', () => { - const source = '
' - expect(source).toBe('todo') +const compileWithTransformRef = makeCompile({ + prefixIdentifiers: false, + nodeTransforms: [ + transformElement, + transformChildren, + transformText, + transformComment, + transformTemplateRef + ], + directiveTransforms: { + bind: transformVBind, + on: transformVOn, + model: transformVModel + } +}) + +test('native element', () => { + const source1 = '' + const source2 = '' + const { code, ir } = compileWithTransformRef(source1) + const expectedResult = vaporCompile(source2) + + expect(code).toMatchSnapshot('received') + expect(expectedResult.code).toMatchSnapshot('expected') + + expect(code).contains(`_setRef(n0, "el")`) + + expect(ir.template).toEqual(['']) + expect(ir.block.operation).toMatchObject([ + { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: 0, + effect: false, + refFor: false, + value: { + type: NodeTypes.SIMPLE_EXPRESSION, + content: 'el', + isStatic: true, + loc: { + // TODO: we need to align svelte AST source location + source: 'el' + } + } + } + ]) +}) + +test.todo('component', () => { + const source1 = '' + const source2 = '' + + const { code, ir: _ } = compileWithTransformRef(source1) + const expectedResult = vaporCompile(source2) + + expect(code).toMatchSnapshot('received') + expect(expectedResult.code).toMatchSnapshot('expected') }) diff --git a/packages/svelte-template-compiler/src/transforms/templateRef.ts b/packages/svelte-template-compiler/src/transforms/templateRef.ts index 1a32a07..a59e899 100644 --- a/packages/svelte-template-compiler/src/transforms/templateRef.ts +++ b/packages/svelte-template-compiler/src/transforms/templateRef.ts @@ -5,14 +5,54 @@ // Repository url: https://github.com/vuejs/core-vapor // Code url: https://github.com/vuejs/core-vapor/blob/6608bb31973d35973428cae4fbd62026db068365/packages/compiler-vapor/src/transforms/transformTemplateRef.ts +import { createSimpleExpression } from '@vue-vapor/compiler-dom' +import { + convertVaporAttribute, + IRNodeTypes, + isSvelteBindingDirective, + isSvelteElement +} from '../ir/index.ts' +import { EMPTY_EXPRESSION, isConstantExpression } from './utils.ts' + import type { NodeTransform } from './types.ts' -export const transformTemplateRef: NodeTransform = (_node, _context) => { - // TODO: transform vapor template ref for svelte `bind:this` - // https://svelte.dev/docs/element-directives#bind-this - // https://svelte.dev/docs/component-directives#bind-this +// NOTE: transform vapor template ref for svelte `bind:this` +// https://svelte.dev/docs/element-directives#bind-this +// https://svelte.dev/docs/component-directives#bind-this +export const transformTemplateRef: NodeTransform = (node, context) => { + if (!isSvelteElement(node)) { + return + } + + const attr = node.attributes.find(attr => isSvelteBindingDirective(attr)) + if (!attr) { + return + } + + const vaporAttr = convertVaporAttribute(attr) + if (vaporAttr.name !== 'ref') { + return + } + + const value = vaporAttr.value + ? createSimpleExpression(vaporAttr.value.content, true, vaporAttr.value.loc) + : EMPTY_EXPRESSION return () => { - // TODO: + const id = context.reference() + const effect = !isConstantExpression(value) + if (effect) { + context.registerOperation({ + type: IRNodeTypes.DECLARE_OLD_REF, + id + }) + } + context.registerEffect([value], { + type: IRNodeTypes.SET_TEMPLATE_REF, + element: id, + value, + refFor: !!context.inVFor, + effect + }) } } diff --git a/packages/svelte-template-compiler/src/transforms/utils.ts b/packages/svelte-template-compiler/src/transforms/utils.ts index a5d2d19..3b4edda 100644 --- a/packages/svelte-template-compiler/src/transforms/utils.ts +++ b/packages/svelte-template-compiler/src/transforms/utils.ts @@ -29,9 +29,11 @@ import type { import type { TransformContext } from './context.ts' import type { NodeTransform, StructuralDirectiveTransform } from './types.ts' +// NOTE: we might not need this for svelte-vapor export const isReservedProp: ReturnType = /*#__PURE__*/ makeMap( // the leading comma is intentional so empty string "" is also included - ',key,ref,ref_for,ref_key,' + // ',key,ref,ref_for,ref_key,' + ',key,' ) export const EMPTY_EXPRESSION: ReturnType = createSimpleExpression( diff --git a/vitest.config.ts b/vitest.config.ts index 485c3f3..5c235ec 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -4,7 +4,8 @@ import { entries } from './scripts/aliases.ts' export default defineConfig({ define: { __BROWSER__: false, - __TEST__: true + __TEST__: true, + __DEV__: true }, resolve: { alias: entries