A new way to generate React elements without JSX, inspired by pug syntax, compatible with Tailwind CSS, actually pure JavaScript.
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`,
),
);
}
- 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.
- 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.
_
and any variable you can get from theelementFactory
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.- Those attributes are modified before they are added to the list of CSS classes. As we can't use
-
,.
,:
or/
in JavaScript identifiers, we transfom e.g.textRed600
intotext-red-600
. This lets you use CSS classes from Tailwind, Bootstrap, or even your ownhyphen-separated
CSS classes as camel case, which is more consistent with JavaScript. - More info on className transformation is provided later.
- More setup is needed for Tailwind.
- Those attributes are modified before they are added to the list of CSS classes. As we can't use
- 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.
- From the documentation itself:
- See the How to use React Elements section for more info and the React Element or not section for examples.
- 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
- Available on npm as
xjsx-react
.
- You may still just copy
xjsx.js
into your React project.
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:
- Tailwind CSS uses PostCSS to curate classes, only including the used classes to reduce bundle size.
- When using xjsx you don't necessarily include the Tailwind classes that you will use in your source files.
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.
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.
Numbers will be preceded by a -
, unless the previous char is already a number, $
or _
.
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.
React Element or not
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.
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.
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.