Skip to content

Commit

Permalink
feat(svelte-template-compiler): first template ref implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
kazupon committed Sep 19, 2024
1 parent 0d302cf commit c482547
Show file tree
Hide file tree
Showing 10 changed files with 237 additions and 17 deletions.
3 changes: 2 additions & 1 deletion packages/svelte-template-compiler/src/compile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
transformChildren,
transformComment,
transformElement,
transformTemplateRef,
transformText,
transformVBind,
transformVFor,
Expand Down Expand Up @@ -155,7 +156,7 @@ export function getBaseTransformPreset(_prefixIdentifiers?: boolean): TransformP
transformVIf,
transformVFor,
// transformSlotOutlet,
// transformTemplateRef,
transformTemplateRef,
transformText,
transformElement,
// transformVSlot,
Expand Down
50 changes: 50 additions & 0 deletions packages/svelte-template-compiler/src/ir/converter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,56 @@ describe('convertProps', () => {
}
])
})

test('bind:this: native element', () => {
const el = getSvelteElement('<canvas bind:this={el} />')
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('<MyComp bind:this={el} />')
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', () => {
Expand Down
48 changes: 42 additions & 6 deletions packages/svelte-template-compiler/src/ir/converter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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))
}
}

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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("<canvas></canvas>")
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("<canvas></canvas>")
export function render(_ctx) {
const n0 = t0()
_setRef(n0, "el")
return n0
}"
`;
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export function buildProps(
context: TransformContext<SvelteElement>,
isComponent: boolean
): PropsResult {
// convert from svelte props to vapor props
const props = convertProps(node)
if (props.length === 0) {
return [false, []]
Expand Down
1 change: 1 addition & 0 deletions packages/svelte-template-compiler/src/transforms/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Original file line number Diff line number Diff line change
@@ -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 = '<div bind:this={foo} />'
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 = '<canvas bind:this={el} />'
const source2 = '<canvas ref="el" />'
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(['<canvas></canvas>'])
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 = '<MyComp bind:this={instance} />'
const source2 = '<MyComp ref="instance" />'

const { code, ir: _ } = compileWithTransformRef(source1)
const expectedResult = vaporCompile(source2)

expect(code).toMatchSnapshot('received')
expect(expectedResult.code).toMatchSnapshot('expected')
})
50 changes: 45 additions & 5 deletions packages/svelte-template-compiler/src/transforms/templateRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
}
}
4 changes: 3 additions & 1 deletion packages/svelte-template-compiler/src/transforms/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof makeMap> = /*#__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<typeof createSimpleExpression> = createSimpleExpression(
Expand Down
3 changes: 2 additions & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit c482547

Please sign in to comment.