@inkline/paper
Version:
Paper is a unified interface for defining components for Vue and React using a single code base.
499 lines (392 loc) • 10.2 kB
Markdown
<p align="center">
<a href="http://inkline.io/">
<img src="https://raw.githubusercontent.com/inkline/inkline.io/main/src/assets/images/logo/logo-black.svg" alt="Inkline logo" width=72 height=72>
</a>
</p>
<h1 align="center">Inkline - Paper</h1>
<p align="center">
Unified interface for defining components for Vue and React using a single code base.<br/><br/> Inkline is written and maintained by <a href="https://github.com/alexgrozav"></a>.
<br/>
<br/>
<a href="https://inkline.io">Homepage</a>
·
<a href="https://inkline.io/docs/introduction">Documentation</a>
·
<a href="https://storybook.inkline.io/">Storybook</a>
·
<a href="https://stackblitz.com/edit/inkline?file=src/App.vue">Playground</a>
·
<a href="https://github.com/inkline/inkline/issues">Issue Tracker</a>
</p>
<br/>
<p align="center">
<a href="https://www.npmjs.com/package/@inkline/paper">
<img src="https://img.shields.io/npm/v/@inkline/paper.svg" alt="npm version">
</a>
<a href="https://github.com/inkline/inkline/actions">
<img src="https://github.com/inkline/paper/workflows/Build/badge.svg" alt="Build">
</a>
<a href="https://www.npmjs.com/package/@inkline/inkline">
<img src="https://img.shields.io/npm/dm/@inkline/inkline.svg" alt="Downloads">
</a>
<a href="https://chat.inkline.io">
<img src="https://img.shields.io/discord/550436704482492429.svg" alt="Discord">
</a>
</p>
<br/>
<br/>
## Running locally
1. First, install dependencies using `npm install`.
2. From the root directory, run `npm run test` in the command line.
## Setup
1. Set an alias in your build system to resolve `/paper` to either `/paper/vue` or `/paper/react`.
- `/paper` => `/paper/vue`
- `/paper` => `/paper/react`
2. Import the common component definition interface from `/paper` and decide whether you're creating a library for `vue` or `react` at build time.
3. Configure `tsconfig.json` to use the custom `h` and `Fragment` JSX Factory functions.
~~~
{
"compilerOptions": {
"jsx": "preserve",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
~~~
## Usage
### Create element
```ts
h(type: string, props: Record<string, any>, children: (VNode | string)[]): VNode
```
The hoist function `h()` is used to create elements.
~~~ts
import { h } from '@inkline/paper';
const type = 'button';
const props = { id: 'button' };
const children = ['Hello world'];
const node = h(type, props, children);
~~~
It also serves as a JSX Factory.
~~~tsx
import { h } from '@inkline/paper';
const node = <div id="myid">Hello world!</div>
~~~
--------
### Define component
```ts
defineComponent<Props, State>(definition: ComponentDefinition<Props, State>)
```
The `defineComponent()` function is used to set up framework-specific internals and get type annotations.
~~~ts
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent({
render () {
return h('div');
}
});
~~~
~~~tsx
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent({
render () {
return <div />;
}
});
~~~
**Vue.js**
~~~html
<component />
~~~
**React.js**
~~~tsx
<Component />
~~~
--------
### Render function
```ts
defineComponent({ render(state: Props & State, ctx: RenderContext): VNode })
```
The `render()` function is mandatory and is used to return the component markup using hoisting.
~~~ts
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent({
render () {
return h('button', {}, 'Hello world');
}
});
~~~
~~~tsx
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent({
render () {
return <button>Hello world</button>;
}
});
~~~
**Vue.js**
~~~html
<component />
~~~
**React.js**
~~~tsx
<Component />
~~~
--------
### Setup function
```ts
defineComponent({ setup(props: Props, ctx: SetupContext) })
```
The `setup()` function is used to prepare functions.
~~~ts
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent<{}, { text: string }>({
setup () {
return {
text: "Hello world"
};
},
render (state) {
return h('button', {}, [
state.text
]);
}
});
~~~
~~~tsx
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent<{}, { text: string }>({
setup () {
return {
text: "Hello world"
};
},
render (state) {
return <button>{state.text}</button>;
}
});
~~~
**Vue.js**
~~~html
<component />
~~~
**React.js**
~~~tsx
<Component />
~~~
--------
### Reference variables
```ts
ref<Type>(defaultValue: Type)
```
The `ref` variable works similar to the Vue.js `ref`. To access or set the value of a reference variable, access or manipulate its `value` field directly.
~~~ts
import { defineComponent, ref, h, Ref } from '@inkline/paper';
const Component = defineComponent<{}, { text: Ref<string>, onClick: () => void }>({
setup () {
const text = ref('Hello world');
const onClick = () => {
text.value = 'Bye world';
}
return {
text,
onClick
};
},
render (state) {
return h('button', { onClick: state.onClick }, [
state.text.value
]);
}
});
~~~
**Vue.js**
~~~html
<component />
~~~
**React.js**
~~~tsx
<Component />
~~~
--------
### Computed variables
```ts
computed<Type>(() => Type)
```
~~~ts
import { defineComponent, ref, h, Ref } from '@inkline/paper';
const Component = defineComponent<{ value: number; }, { double: Ref<number> }>({
setup (props) {
const double = computed(() => props.value * 2);
return {
double
};
},
render (state) {
return h('button', {}, [
state.double.value
]);
}
});
~~~
**Vue.js**
~~~html
<component />
~~~
**React.js**
~~~tsx
<Component />
~~~
--------
### Provide and Inject
```ts
provide<Type>(identifier: string, value: Type)
inject<Type>(identifier: string, defaultValue?: Type): Type
```
~~~ts
import { defineComponent, ref, h, Ref } from '@inkline/paper';
const identifier = Symbol('identifier');
const Provider = defineComponent<{}, {}>({
setup (props, ctx) {
ctx.provide(identifier, 'value');
return {};
},
render (state, ctx) {
return h('div', {}, [
ctx.slot()
]);
}
});
const Consumer = defineComponent<{}, { value?: string; }>({
setup (props, ctx) {
const value = inject(identifier, 'defaultValue');
return { value };
},
render (state, ctx) {
return h('div', {}, [
`${state.value}`
]);
}
});
~~~
**Vue.js**
~~~html
<provider>
<consumer />
</provider>
~~~
**React.js**
~~~tsx
<Provider>
<Consumer />
</Provider>
~~~
--------
### Props
```ts
defineComponent({ props: ComponentProps<Props> })
```
Define the props using the `props` field, using the same format used in Vue.js.
The `setup()` function receives the defined prop values with default as fallback.
~~~ts
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent<{ text: string }, {}>({
props: {
text: {
type: String,
default: () => 'Hello world'
}
},
render (state) {
return h('button', {}, [
state.text
]);
}
});
~~~
**Vue.js**
~~~html
<component text="Button" />
~~~
**React.js**
~~~tsx
<Component text={"Button"} />
~~~
--------
### Slots
```
defineComponent({ slots: string[] })` and `renderContext.slot(slotName)
```
The `slots` array allows you to define multiple slot names for the component. Out of the box, the `default` slot is pre-defined.
The `slot()` function is available in the render function execution context.
~~~ts
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent({
slots: ['header', 'footer'],
render (state, ctx) {
return h('div', { class: 'card' }, [
ctx.slot('header'),
ctx.slot(), // Default slot
ctx.slot('footer'),
]);
}
});
~~~
**Vue.js**
~~~html
<component>
<template #header>Header</template>
Body
<template #footer>Header</template>
</component>
~~~
**React.js**
~~~tsx
<Component>
<Component.Header>Header</Component.Header>
Body
<Component.Footer>Header</Component.Footer>
</Component>
~~~
--------
### Events
```
defineComponent({ emits: string[] })` and `setupContext.emit(eventName, ...args)
```
The `emits` array allows you to define event emitters.
- for Vue.js, this uses the native `emit()` function
- for React.js, this creates an `on[EventName]` callback
~~~ts
import { defineComponent, h } from '@inkline/paper';
const Component = defineComponent<{}, { emitChange: () => void }>({
emits: ['change'],
setup (props, ctx) {
const emitChange = () => {
ctx.emit('change');
}
return { emitChange };
},
render (state, ctx) {
return h('button', { onClick: state.emitChange }, [ctx.slot()]);
}
});
~~~
**Vue.js**
~~~html
<component ="doSomething" />
~~~
**React.js**
~~~tsx
<Component onChange={() => doSomething()} />
~~~
<br/>
<br/>
## Creator
**Alex Grozav**
- <https://grozav.com>
- <https://twitter.com/alexgrozav>
- <https://facebook.com/alexgrozav>
- <https://github.com/alexgrozav>
If you use Inkline in your daily work and feel that it has made your life easier, please consider sponsoring me on [Github Sponsors](https://github.com/sponsors/alexgrozav). 💖
## Copyright and license
Homepage and documentation copyright 2017-2022 [Inkline Authors](https://github.com/inkline/inkline.io/graphs/contributors).
Docs released under [ISC License](https://github.com/inkline/inkline/blob/master/packages/docs/LICENSE).