Skip to content
/ xjsx Public

A new way to generate React elements without jsx, inspired by pug syntax, compatible with Tailwind CSS, actually pure JavaScript.

License

Notifications You must be signed in to change notification settings

c4ffein/xjsx

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

77 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

xjsx

A new way to generate React elements without JSX, inspired by pug syntax, compatible with Tailwind CSS, actually pure JavaScript.

xjsx demo screen xjsx demo screen

JSX syntax example

import { useState } from 'react';
import reactLogo from './assets/logo-react.svg';

export default function JSX() {
  const [count, setCount] = useState(0);

  return (
    <div className="p-8">
      <h1 className="text-3xl font-bold text-slate-600 mb-4">
        Hello world from <span className="text-react">JSX</span>
      </h1>
      <button className="mb-4 custom-button hover:border-react" onClick={() => setCount((count) => count + 1)}>
        Clicked : {count}
      </button>
      <div className="flex items-center gap-2.5">
        <a href="https://react.dev/learn/writing-markup-with-jsx" target="_blank" rel="noreferrer">
          <img className="h-8 w-8" src={reactLogo} alt="React logo"></img>
        </a>
        <p className="text-black dark:text-white">Click on the React logo to read the React JSX documentation</p>
      </div>
    </div>
  );
}

xjsx syntax example

import { useState } from 'react';
import xjsxLogo from './assets/logo-xjsx.svg';
import { _, elementFactory } from '../libs/xjsx';

const { a, img, h1, span, button, p } = elementFactory;

export default function XJSX() {
  const [count, setCount] = useState(0);

  return _.p8(
    h1.text3xl.fontBold.textSlate500.mb4('Hello world from ', span.textReact`xjsx`),
    button.mb4.customButton.hover$borderReact({ onClick: () => setCount((count) => count + 1) })`Clicked : ${count}`,
    _.flex.itemsCenter.gap2_5(
      a({ href: 'https://github.com/c4ffein/xjsx', target: '_blank', rel: 'noreferrer' })(
        img.h8.w8({ src: xjsxLogo, alt: 'xjsx logo' }),
      ),
      p.textBlack.dark$textWhite`Click on the xjsx logo to read the xjsx documentation`,
    ),
  );
}
  • See live at xjsx.dev
  • The whole demo code is available here

Opinionated

  • JSX is very nice to put logic in your React (conditionals, list rendering...) as you can directly use JavaScript for it.
  • Tailwind CSS doesn't work well with JSX as it is class-based - not only do you have to close any tag, but also use that repetitive className.
  • pug works very well with Tailwind CSS as it lets you easily chain CSS classes through its syntax, and even more with template-based frameworks that let you put logic in it.
  • We should have a way to benefit from a pug-like syntax to use Tailwind classes in React without compromising on the ability to use all JavaScript.

How does it actually work

  • Pure JavaScript, so it won't introduce weird complexity in your build system (as most things shouldn't).
  • Voodoo logic with Proxy objects, so that you can chain CSS classes that will automatically be added to the Proxy CSS classes one after another, and will generate a React element in the end.

In detail

  • _ and any variable you can get from the elementFactory are Proxies.
  • Getting any attribute (with the Javascript . syntax) will generate a new Proxy that will include this attribute in it's own list of CSS classes, in addition to all previously added CSS classes.
  • If you call that Proxy, the behaviour now depends on the arguments:
    • If the argument is an object that is neither an Array or a React Element, we consider that this object represents HTML attributes, and so you can use it to set href, onclick and so on, and a new Proxy will be returned.
    • Anything else will return the calling of React createElement with the adequate props (from the Proxy chain) and the arguments as ...children.
      • From the documentation itself: Zero or more child nodes. They can be any React nodes, including React elements, strings, numbers, portals, empty nodes (null, undefined, true, and false), and arrays of React nodes.
      • If ...children contains xjsx Proxies, those are automatically called to be converted to React Elements.
    • See the How to use React Elements section for more info and the React Element or not section for examples.

How to use

How to import

You may import elementFactory to get the xjsx builder for any HTML tag you want, or the short _ for div.

import { elementFactory, _ } from 'xjsx-react'
const { a, div, span } = elementFactory;

How to use React Elements

You may also use elementFactory to convert React Elements to xjsx Proxies.

import CodeRE from './Code';
const { a, button, Code } = elementFactory({ Code: CodeRE });

And use those as any other xjsx Proxies.

_.flex(
  Code({ jsCode: codeStringA }),
  Code({ jsCode: codeStringB }),
);

Make it work with Tailwind CSS

First, ensure that Tailwind CSS has been set up.
Then, in tailwind.config.js:

import { tailwindExtract } from './libs/xjsx'

export default {
  content: {
    files: [
      './index.html',
      './src/**/*.{js,ts,jsx,tsx}',
    ],
    extract: tailwindExtract,
  },
}

This has to be done as:

So, through tailwindExtract, we just use a regex to split what could be JavaScript identifiers, and apply the same transformation into what could be a Tailwind class to re-expose it to PostCSS, so it is included if it matches a Tailwind class.

Alternative: use Twind

You may directly use Twind instead as this is a Tailwind in JavaScript solution, and so no additional setup is required to make it work with xjsx.

Tricks

CSS class names

What about characters used in Tailwind classes that are not allowed in JavaScript identifiers?

  • - Just capitalize the next letter, or let the number as-is. Also works for classes starting with -.
  • . Just replace the char with a _, will be transformed to a ..
  • : Just replace that char with a $, will be transformed to : if the previous char is not a digit.
  • / Just replace that char with a $, will be transformed to a / if the previous char is a digit.

What about numbers?

Numbers will be preceded by a -, unless the previous char is already a number, $ or _.

What about anything else that can't be covered?

You may mix xjsx Proxy tricks and regular className usage, e.g.

const App = () => _.hFull.flex.itemsCenter({ className: 'w-screen' })(
  _.w1$2(JSX), _.w1$2(XJSX)
);

You may also use regular Tailwind classes in your own CSS, regular PostCSS treatment isn't broken.

Following How does it actually work, which of these are React Elements?
Otherwise they are xjsx Proxy objects
.

✖️ _.h4.w4.bgRed600
✔️ _.h4.w4.bgRed600()
✔️ _`Hello`
✔️ _.textRed600('In red and ', _textBlue600`in blue`)
✖️ _.h4.w4.bgRed600({})
✖️ _.h4.w4.bgRed600({whatever: 'whatever'})
✔️ _.h4.w4.bgRed600({whatever: 'whatever'})()
✔️ _.h4.w4.bgRed600({whatever: 'whatever'})`with some text`
✔️ _.flex([_({key: 1})`a`, _({key: 2})`b`])

This may seem uncomfortable at first, but the thing is that all this is pure JavaScript and feels kinda like pug. But where you would still be able to use map, IIFE with Arrow functions, and everything else (not that you should).

As xjsx Proxies are automatically called when passed as children of another xjsx Proxy, this usually doesn't make a difference, except when defining a React Element : in that case, be sure that you are not returning an xjsx Proxy instead.

Error diagnosis

  • Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.: You probably forgot to make a final call on the xjsx Proxy when trying to define a React Element.
  • TypeError: Component({ ...xxx }) is not a function. (In 'Component({ ...xxx })()', 'Component({ ...xxx })' is an instance of Object): This is probably the opposite, you tried to treat a React Element as a xjsx Proxy. See How to use React Elements.

Compatibility

The good thing is that there are no dependencies besides React.
The bad thing is that short lib relies on Proxy objects, and even though they are available on all updated major web browsers it may not suit your use case, your call.

About

A new way to generate React elements without jsx, inspired by pug syntax, compatible with Tailwind CSS, actually pure JavaScript.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published