Skip to content
This repository has been archived by the owner on Apr 14, 2023. It is now read-only.

Commit

Permalink
fix!: remove default components, prefer createReusableTemplate, close
Browse files Browse the repository at this point in the history
  • Loading branch information
antfu committed Apr 13, 2023
1 parent c37b2e1 commit e8b4a08
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 175 deletions.
49 changes: 14 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ In the previous example, we could refactor it to:

```html
<script setup>
import { DefineTemplate, ReuseTemplate } from 'vue-reuse-template'
import { createReusableTemplate } from 'vue-reuse-template'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>

<template>
Expand All @@ -58,33 +60,6 @@ import { DefineTemplate, ReuseTemplate } from 'vue-reuse-template'

> **Note**: It's recommanded to extract as separate components whenever possible. Abusing this library might lead to bad practices for your codebase.
### Multiple Templates

You can assign a `name` prop to the `<DefineTemplate>` and `<ReuseTemplate>` to have multiple templates, and reuse them by name:

```html
<script setup>
import { DefineTemplate, ReuseTemplate } from 'vue-reuse-template'
</script>

<template>
<DefineTemplate name="foo">
Foo
</DefineTemplate>
<DefineTemplate name="bar">
Bar
</DefineTemplate>

<!-- Bar -->
<ReuseTemplate name="bar" />

<!-- Foo -->
<ReuseTemplate name="foo" />
</template>
```

By default, the `name` is set to `default`.

### Passing Data

You can also pass data to the template using slots:
Expand All @@ -94,23 +69,25 @@ You can also pass data to the template using slots:

```html
<script setup>
import { DefineTemplate, ReuseTemplate } from 'vue-reuse-template'
import { createReusableTemplate} from 'vue-reuse-template'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>

<template>
<DefineTemplate name="foo" v-slot="{ data, msg, anything }">
<DefineTemplate v-slot="{ data, msg, anything }">
<div>{{ data }} passed from usage</div>
</DefineTemplate>

<ReuseTemplate name="foo" :data="data" msg="The first usage" />
<ReuseTemplate name="foo" :data="anotherData" msg="The second usage" />
<ReuseTemplate name="foo" v-bind="{ data: something, msg: 'The third' }" />
<ReuseTemplate :data="data" msg="The first usage" />
<ReuseTemplate :data="anotherData" msg="The second usage" />
<ReuseTemplate v-bind="{ data: something, msg: 'The third' }" />
</template>
```

### TypeScript Support

You can use `createReusableTemplate` to create a reusable template with type support:
`createReusableTemplate` accepts a generic type to provide type support for the data passed to the template:

```html
<script setup lang="ts">
Expand Down Expand Up @@ -163,7 +140,9 @@ It's also possible to pass slots back from `<ReuseTemplate>`. You can access the

```html
<script setup>
import { DefineTemplate, ReuseTemplate } from 'vue-reuse-template'
import { createReusableTemplate } from 'vue-reuse-template'
const [DefineTemplate, ReuseTemplate] = createReusableTemplate()
</script>

<template>
Expand Down
17 changes: 2 additions & 15 deletions playground/src/App.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { ref } from 'vue'
import { DefineTemplate, ReuseTemplate, createReusableTemplate } from '../../src'
import { createReusableTemplate } from '../../src'
const greeting = ref('Hello')
Expand All @@ -10,22 +10,9 @@ const [DefineBiz, ReuseBiz] = createReusableTemplate<{ msg: string }>()
</script>

<template>
<!-- basic 1 -->
<DefineTemplate v-slot="{ data }" name="test">
<div>Test1: {{ greeting }} {{ data.toString().toUpperCase(0) }}</div>
</DefineTemplate>
<ReuseTemplate name="test" data="world" />
<ReuseTemplate name="test" :data="1 + 1" />

<!-- no name -->
<DefineTemplate v-slot="{ data }">
<div>Test2: {{ greeting }} {{ data.toString().toUpperCase(0) }}</div>
</DefineTemplate>
<ReuseTemplate data="Vue!" />

<!-- createReusableTemplate with array -->
<DefineFoo v-slot="{ msg }">
<div>Foo: {{ msg }}</div>
<div>Foo: {{ greeting }} {{ msg }}</div>
</DefineFoo>
<ReuseFoo msg="world" />

Expand Down
79 changes: 6 additions & 73 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ComponentInternalInstance, DefineComponent, Slot } from 'vue'
import { defineComponent, getCurrentInstance, renderSlot } from 'vue'
import type { DefineComponent, Slot } from 'vue'
import { defineComponent, renderSlot } from 'vue'

export type DefineTemplateComponent<
Bindings extends object,
Expand All @@ -16,69 +16,6 @@ export type ReuseTemplateComponent<
new(): { $slots: Slots }
}

const _map = /* @__PURE__ */ new WeakMap<ComponentInternalInstance, Map<string | symbol, Slot | undefined>>()
function getTemplateRegistry() {
// store the render registry with a WeakMap
// bound to the parent component instance (the one that uses the template)
const instance = getCurrentInstance()!.parent!
if (!_map.has(instance))
_map.set(instance, new Map())
return _map.get(instance)!
}

/**
* Define a reusable template, renders nothing
*/
export const DefineTemplate = /* @__PURE__ */ defineComponent({
props: {
name: {
type: String,
default: 'default',
},
},
setup(props, { slots }) {
const reg = getTemplateRegistry()
return () => {
// register the render function
reg.set(props.name, slots.default)
}
},
}) as DefineTemplateComponent<any, Record<string, Slot | undefined>, { name?: string }>

/**
* Reuse a template defined by `DefineTemplate`
*/
export const ReuseTemplate = /* @__PURE__ */ defineComponent({
inheritAttrs: false,
props: {
name: {
type: String,
default: 'default',
},
},
setup(props, { attrs, slots }) {
const reg = getTemplateRegistry()
return () => {
// get the render function from DefineTemplate
const render = reg.get(props.name)
if (!render && process.env.NODE_ENV !== 'production')
throw new Error(`[vue-reuse-template] Reusable template "${props.name}" is not defined, have you used <DefineTemplate name="${props.name}">?`)
return renderSlot({ default: render }, 'default', { ...attrs, $slots: slots })
}
},
}) as ReuseTemplateComponent<{ name?: string }, Record<string, Slot | undefined>>

export {
/**
* Alias for `DefineTemplate`
*/
DefineTemplate as TemplateDefine,
/**
* Alias for `ReuseTemplate`
*/
ReuseTemplate as TemplateReuse,
}

/**
* This function creates `define` and `reuse` components in pair,
* that you don't need to specify the name for each.
Expand All @@ -88,25 +25,21 @@ export {
export function createReusableTemplate<
Bindings extends object,
Slots extends Record<string, Slot | undefined> = Record<string, Slot | undefined>,
>() {
// eslint-disable-next-line symbol-description
const key = Symbol()
>(name?: string) {
let render: Slot | undefined

const define = defineComponent((_, { slots }) => {
const reg = getTemplateRegistry()
return () => {
reg.set(key, slots.default)
render = slots.default
}
}) as DefineTemplateComponent<Bindings, Slots>

const reuse = defineComponent({
inheritAttrs: false,
setup(_, { attrs, slots }) {
const reg = getTemplateRegistry()
return () => {
const render = reg.get(key)
if (!render && process.env.NODE_ENV !== 'production')
throw new Error('[vue-reuse-template] Reusable template is not defined')
throw new Error(`[vue-reuse-template] Failed to find the definition of template${name ? ` "${name}"` : ''}`)
return renderSlot({ default: render }, 'default', { ...attrs, $slots: slots })
}
},
Expand Down
52 changes: 0 additions & 52 deletions test/components.test.ts

This file was deleted.

25 changes: 25 additions & 0 deletions test/nested.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest'
import { mount } from '@vue/test-utils'
import { Fragment, defineComponent, h, renderSlot } from 'vue'
import { createReusableTemplate } from '../src'

describe('nested', () => {
it('should work', () => {
const CompA = defineComponent((_, { slots }) => {
return () => renderSlot(slots, 'default')
})

const [DefineFoo, ReuseFoo] = createReusableTemplate()

const wrapper = mount({
render() {
return h(Fragment, null, [
h(DefineFoo, () => ['Foo']),
h(CompA, () => h(ReuseFoo)),
])
},
})

expect(wrapper.text()).toBe('Foo')
})
})

0 comments on commit e8b4a08

Please sign in to comment.