https://github.com/padcom/vuelit
https://github.com/padcom/vuelit
Last synced: over 1 year ago
JSON representation
- Host: GitHub
- URL: https://github.com/padcom/vuelit
- Owner: padcom
- Created: 2023-11-28T14:50:41.000Z (over 2 years ago)
- Default Branch: master
- Last Pushed: 2023-11-30T15:09:10.000Z (over 2 years ago)
- Last Synced: 2025-01-04T13:18:22.255Z (over 1 year ago)
- Language: TypeScript
- Size: 312 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Vuelit - Lit with Vue's reactivity system
This project is a spin-off off a project initially created as a PoC by Evan You in 2020.
You can check the original project [here](https://www.npmjs.com/package/@vue/lit)
The point of this library is to be able to create Lit components using the reactivity system of Vue.js. Currently the industry trend is to add signals to every framework out there. Luckily, Vue.js has added it already years ago. What's even nicer is that the implementation is extracted to a separate package which makes it much easier to integrate in other tools.
## Differences from the original project
The main differences in this implementation are:
- full TypeScript support
- hidden state / clean(er) API
- automatic dereferencing of Vue.js' `ref`
- 2-way binding
- `provide`/`inject`
## Getting started
Vuelit uses the [from-github](https://npmjs.org/package/create-from-github) generator which just clones the given repository and clears it out to a point where it is usable as a new project:
```
npm create from-github padcom/vuelit-template hello-world
```
The project is very simple but has everything that is needed to get you started.
## Example component
Since the framework is designed to create [webcomponents](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) there is no application framework that you need to instantiate. All you need is your component and an `index.html` to put the tag in.
Here's how you define a component:
```typescript
import { defineComponent, html } from 'vuelit'
const styles = `
:host {
font-family: monospace
}
`
defineComponent(
// name of the tag that you will later on use in HTML
'hello-world',
{
// Styles are passed as plain text. You can get them from an external file
// or specify them in-line. Your choice
styles,
// Should the component have a `shadowRoot`? (default: false)
shadowRoot: true
},
// A list of reactive properties that will be added to the instance
{
counter: 0
},
// The `props` are the same as defined above
({ props }) => {
function increment() {
props.counter++
}
// From here on it's all Lit
return () => html`
Number of clicks: ${props.counter}
Increment
Now delete me and get to work!
`
}
)
```
## Using reactive `ref` and 2-way data binding
Vuelit understands that Vue's [`ref`](https://vuejs.org/api/reactivity-core.html#ref) has a `.value` field. You don't need to dereference it manually.
If you want to react to the `@input` event to implement 2-way binding there's a special `update()` function that you'd use as follows:
```typescript
import { defineComponent, html, ref, update } from 'vuelit'
defineComponent('hello-world', {}, {}, () => {
const message = ref('Hello, world!')
return () => html`
${message}
`
})
```
## Lifecycle hooks
Just like Vue.js, Vuelit exposes a number of lifecycle hooks:
### `onBeforeMount(({ component }) => void)`
This lifecycle hook is called after the setup function has completed but before the component is mounted to the DOM
### `onMounted(({ component }) => void)`
This lifecycle hook is called right after the component is mounted in the DOM
### `onBeforeUpdate(({ component }) => void)`
This lifecycle hook is called once a change in props is detected but before the internal state is updated
### `onUpdated(({ component }) => void)`
This lifecycle hook is called once a change in props is detected and after the internal state is updated
### `onUnmounted(({ component }) => void)`
This lifecycle hook is called after the component has been unmounted from the DOM
## `getComponentInstance(): VuelitComponent` - getting component instance
First things first: each Vuelit component is actually a valid DOM element. So if you will `console.log` it, it will be a DOM element.
In many different places getting access to that instance might be very useful, for example to examine some props and/or attributes or to call a method on that given object.
All lifecycle hooks are provided the `component` instance so you don't have to get it yourself. However, in composables that don't use the lifecycle hooks you still might want to access the `component` instance. For those rare cases there's the `getComponentInstance()` function exposed from Vuelit.
## Dependency injection
Vue.js provides a dependency injection mechanism that's really useful for mitigating prop drilling. [Vue.js](https://vuejs.org/guide/components/provide-inject.html) does it using [provide](https://vuejs.org/api/options-composition.html#provide)/[inject](https://vuejs.org/api/options-composition.html#inject) composition functions.
Vuelit is no different. It provides the same functionality using `provide()` and `inject()` composition functions:
### `[component.]provide(key: Symbol, value: any)`
Provides a value for injection using `inject()`. Please note `key` needs to be a `Symbol` because the underlying mechanism storing the provided values is a `WeakMap`.
Please note that both the component instance, that you can access by destructuring it from the first parameter of your composition function as well as a standalone function exported from the library expose this function. This means that in runtime, if you're using Vuelit, you can also dynamically provide values for later injections.
### `[component.]inject(key: Symbol, defaultValue: T): T | null`
Injects a value provided using `inject()`. Please note `key` needs to be a `Symbol` because the underlying mechanism storing the provided values is a `WeakMap`.
Please note that both the component instance, that you can access by destructuring it from the first parameter of your composition function as well as a standalone function exported from the library expose this function. This means that in runtime, if you're using Vuelit, you can also dynamically retrieve values from providers.
## Exposing additional properties and methods
The `expose(things)` function is the ultimate weapon when it comes to defining additional APIs for your component.
Assuming you'd like your `` to also have the `increment()` method:
```typescript
import { defineComponent, html, expose } from 'vuelit'
defineComponent('counter-component', { shadowRoot: true }, { value: 0 }, ({ props }) => {
function increment() {
props.value++
}
expose({ increment })
return () => html`
Current value: ${props.value}
`
})
```
Then later on in your code you can just access that method on the instance:
```typescript
document.querySelector('counter-component').increment()
```
### Example
Let's assume we have 2 components:
- `` that will provide a reactive counter
- `` that will display and allow for manipulation of the counter
This is how you would implement this:
```typescript
const counterSymbol = Symbol('counter')
defineComponent('counter-provider', {}, {}, () => {
const counter = ref(0)
provide(counterSymbol, counter)
return () => html``
})
defineComponent('counter-display', {}, {}, () => {
const counter = inject>(counterSymbol)
function increment() {
if (counter) counter.value++
}
return () => html`
Current counter: ${counter}
Increment
`
})
```
As you can see you can literally provide any value, including reactive refs. The `provide`/`inject` pair doesn't care.
Alternatively you can use the component instance instead of the imported methods:
```typescript
defineComponent('counter-provider', {}, {}, ({ component }) => {
component.provide(counterSymbol, ref(0))
...
defineComponent('counter-display', {}, {}, ({ component }) => {
const counter = component.inject>(counterSymbol)
```
## Credits
Big thank you to to Evan You and the entire Vue.js team.