@redneckz/uni-jsx
TypeScript icon, indicating that this package has built-in type declarations

2.6.4 • Public • Published

@redneckz/uni-jsx

Unification layer between React, Preact and Vue3. Write React-like unified components and use everywhere without recompilation.

NPM Version Build Status Bundle size

React as well as Vue3 comes with Virtual DOM and JSX approaches. There are a few differences at the low level.

This nano-library negates the differences between them on JSX level. Making it possible to implement universal components that work without recompiling in both React and Vue3.

Also the following React-like core hooks are available for Vue3:

  • useState
  • useEffect
  • useLayoutEffect
  • useCallback
  • useMemo
  • useRef

Install

$ npm i --save @redneckz/uni-jsx

or:

$ yarn add @redneckz/uni-jsx

How to declare universal components

React 17 introduced new JSX transform with separate runtime react/jsx-runtime which can be replaced with the custom one. For details please check this out https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html

To transpile universal components configure TypeScript as follows:

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@redneckz/uni-jsx"
    // ...
  }
  // ...
}

Universal component:

import { JSX } from '@redneckz/uni-jsx';
import { useState, useCallback, useRef, useEffect } from '@redneckz/uni-jsx/lib/hooks';

export interface TextBlockProps {
  html?: string;
  onClick?: (event: Event) => void;
}

export const TextBlock = JSX<TextBlockProps>(props => {
  const { html, onClick, children } = props;

  const [isDark, setDark] = useState(false);
  const handleClick = useCallback(
    (event: Event) => {
      setDark(_ => !_);
      onClick && onClick(event);
    },
    [onClick]
  );

  const ref = useRef<HTMLParagraphElement | null>(null);

  useEffect(() => {
    console.log('ref', ref.current);
  }, []);

  const style = isDark ? { color: '#CCC', backgroundColor: '#777' } : {};

  return (
    <section className="text-block" style={style} onClick={handleClick}>
      {html && <p ref={ref} dangerouslySetInnerHTML={{ __html: html }}></p>}
      {children && <p>{children}</p>}
    </section>
  );
});

[Vue3] How to use universal components

import '@redneckz/uni-jsx/lib/setup.vue';

import { createApp } from 'vue';
import App from './App.vue';

createApp(App).mount('#app');
<template>
  <h1>Vue with unified components</h1>
  <text-block html="<em>Primary text...</em>" @click="debugEvent">
    <cite>http://www.asimovonline.com</cite>
  </text-block>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { TextBlock } from '@demo/ui-kit';

export default defineComponent({
  name: 'App',
  components: {
    TextBlock
  },
  methods: {
    debugEvent(event: Event) {
      console.log(event);
    }
  }
});
</script>

[React] How to use universal components

import '@redneckz/uni-jsx/lib/setup.react';

import { createRoot } from 'react-dom/client';
import { App } from './App';

const root = createRoot(document.getElementById('root')!);
root.render(<App />);
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <div id="root"></div>
  </body>
  <script>
    globalThis.__UNI_REACT__ = true; // Temporal requirement
  </script>
</html>
import { useCallback } from 'react';
import { TextBlock } from '@demo/ui-kit';

export function App() {
  const debugEvent = useCallback((_: unknown) => {
    console.log(_);
  }, []);

  return (
    <>
      <h1>React with unified components</h1>
      <TextBlock html="<em>Primary text...</em>" onClick={debugEvent}>
        <cite>http://www.asimovonline.com</cite>
      </TextBlock>
    </>
  );
}

[Next.js] How to use universal components

Please take a look at /demo/next-demo

[Nuxt3] How to use universal components

Please take a look at /demo/nuxt-demo

[Preact] How to use universal components

Please take a look at /demo/preact-demo

[Next.js + Preact]

package.json:

{
  // ...
  "dependencies": {
    "next": "^12.3.3", // Not works with Next.js 13
    "preact": "^10.11.3",
    "preact-render-to-string": "^5.2.6",
    "react": "npm:@preact/compat@^17.1.2",
    "react-dom": "npm:@preact/compat@^17.1.2",
    "react-ssr-prepass": "npm:preact-ssr-prepass@^1.2.0"
  }
  // ...
}

next.config.js

const withPreact = require('next-plugin-preact');

module.exports = withPreact({});

setup.ts

import { setupHooks } from '@redneckz/uni-jsx/lib/hooks/index';
import { setup } from '@redneckz/uni-jsx/lib/setup';
import { h } from 'preact';
import * as React from 'react';

globalThis.__UNI_REACT__ = true;

setup((type, rawProps) => {
  const { children, ...props } = rawProps;

  return h(type, props, children);
});

setupHooks(React);

Utility Hooks

Structural Hooks

  • useBool - boolean state with operations
const [isEnabled, { setTrue: enable, setFalse: disable, toggle }] = useBool();
  • useList - list with operations
const [items, { push: addItem, pop: removeLastItem, remove: removeItem, clear: removeAllItems }] = useList<BuzItem>();
  • useDict - dictionary with operations
const [
  form,
  {
    setDict: updateForm,
    setItem: updateField,
    removeItem: removeField,
    assign: updateFields,
    unassign: removeFields,
    clear: resetForm
  }
] = useDict<BuzForm>({});

API-centric Hooks:

  • useAsyncData - useSWR-like hook
const fetcher = useCallback(
  async (url: string) => {
    await delay(timeout);
    return fetchJSON<ChuckJoke>(url);
  },
  [timeout]
);

const { data: chuckJoke } = useAsyncData<ChuckJoke>(
  `https://api.chucknorris.io/jokes/random?category=${CATEGORIES[rnd % CATEGORIES.length]}`,
  fetcher
);
  • useEventListener - apply event handler to native DOM node
useEventListener(globalThis.document, 'click', handleDocumentClick);
  • useOutsideClick - registers callback to handle click outside of some node (menu, dropdown, dialog and so on)
const menuRef = useOutsideClick<HTMLDivElement>(closeDropdownMenu);
...
<div role="menu" ref={menuRef}>...</div>
  • useDebouncedEffect - debounced effect with default timeout - 300ms
useDebouncedEffect(
  () => {
    doSomethingWith(some, dependencies);
  },
  [some, dependencies],
  300
);
  • useScript - TODO
// TODO

Limitations

Common limitations:

  • Events are streamed up as-is (avoid usage of normalized Event fields specific to React or Vue)

React specific limitations:

  • NO refs forwarding

License

MIT

Package Sidebar

Install

npm i @redneckz/uni-jsx

Weekly Downloads

89

Version

2.6.4

License

MIT

Unpacked Size

139 kB

Total Files

268

Last publish

Collaborators

  • redneckz