UNPKG

@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
<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">@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 `@inkline/paper` to either `@inkline/paper/vue` or `@inkline/paper/react`. - `@inkline/paper` => `@inkline/paper/vue` - `@inkline/paper` => `@inkline/paper/react` 2. Import the common component definition interface from `@inkline/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 @change="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).