Extremely lightweight JSX runtime (~ 2 KiB when minified) compatible with TypeScript and JavaScript, to be used together with Bable automatic JSX runtime.
Compared to other solutions like Preact
and Inferno
, which are an alternative to all React features, this project just focuses on minimalism: a simplistic JSX runtime with basic features to build things like standalone widgets that require some JavaScript like: form controls, context menus, modal forms, pop ups, color pickers, etc.
The idea is that the final build of your widget will include the jsx-runtime, so it will be standalone and others won't need React or this runtime
in order to use it. microbundle
is a nice zero-configuration bundler for projects like that.
- JSX Runtime written in TypeScript to ensure 100% compatibility
- Minimal size (~2KiB minified), perfect for standalone UI packages
- Can be used in the browser, node or electron (multi target builds)
- Class components and props support
- Function components support (experimental)
- props.children support (experimental)
- Simple
onWillMount
andonDidMount
component life-cycle methods (experimental) - Auto-join (with space) of array of strings in the
className
attribute - Auto-bind of functions to the component's "
this
", e.g. on<div onClick={this.doSomething} />
npm i -D @itsjavi/jsx-runtime
or
yarn add -D @itsjavi/jsx-runtime
The most important part is to configure @babel/plugin-transform-react-jsx
correctly,
which will be the one detecting this library and using it to transform JSX / TSX to JS.
These example configurations showcase a setup with Babel, Webpack, TypeScript and CSS loaders.
Example babel.config.js
:
{
"presets": [
"@babel/preset-env",
[
"@babel/preset-typescript",
{
"isJSX": true,
"allExtensions": true,
"jsxPragma": "jsx",
"jsxPragmaFrag": "'jsx.Fragment'"
}
]
],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"throwIfNamespace": false,
"runtime": "automatic",
"importSource": "@itsjavi"
}
]
],
"comments": false
}
Example tsconfig.json
:
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"module": "ESNext",
"target": "ESNext",
"lib": [
"ESNext",
"DOM",
"DOM.Iterable"
],
"allowJs": true,
"jsx": "preserve",
"esModuleInterop": true,
"strict": true,
"sourceMap": true,
"moduleResolution": "Node" // important to find the proper JSX types on type check when writing TSX
},
"files": [
"src/index.ts"
],
"include": [
"src/**/*.ts",
"src/**/*.tsx"
]
}
Example webpack.config.js
(you can use other bundlers too):
// Generated using webpack-cli http://github.com/webpack-cli
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const baseConfig = {
mode: 'development',
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: ['/node_modules/'],
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.(ts|tsx)$/,
exclude: ['/node_modules/'],
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-typescript']
}
}
},
{
test: /\.(js|css)$/,
enforce: 'pre',
use: ['source-map-loader']
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/,
type: 'asset'
}
// Add your rules for custom modules here
// Learn more about loaders from https://webpack.js.org/loaders/
]
},
resolve: {
extensions: ['.tsx', '.ts', '.js', '.jsx']
}
}
module.exports = [
Object.assign({}, baseConfig, {
name: 'my-component',
devtool: 'inline-source-map',
entry: ['./src/index.ts'],
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'my-component.js'
}
})
]
Example package.json
:
{
"name": "my-component",
"amdName": "myComponent",
"private": true,
"version": "0.0.1",
"description": "My Component",
"author": "",
"license": "ISC",
"main": "./dist/my-component.js",
"module": "./dist/my-component.module.js",
"esmodule": "./dist/my-component.modern.js",
"exports": "./dist/my-component.modern.js",
"umd:main": "./dist/my-component.umd.js",
"source": "src/index.ts",
"types": "src/index.d.ts",
"scripts": {
"test": "run-s test:*",
"test:typecheck": "tsc --noEmit",
"build": "run-s clean build:*",
"build:webpack": "webpack --mode=development && webpack --mode=production",
"build:tsc": "tsc --declaration --emitDeclarationOnly",
"watch": "webpack --watch",
"serve": "npm run clean && webpack serve",
"clean": "rm -rf ./dist/*"
},
"devDependencies": {
"@babel/cli": "^7.13.16",
"@babel/core": "^7.13.16",
"@babel/plugin-transform-react-jsx": "^7.13.12",
"@babel/preset-env": "^7.13.15",
"@babel/preset-typescript": "^7.13.0",
"@itsjavi/jsx-runtime": "github:itsjavi/jsx-runtime",
"autoprefixer": "^10.2.5",
"babel-loader": "^8.2.2",
"css-loader": "^5.2.4",
"html-webpack-plugin": "^5.3.1",
"npm-run-all": "^4.1.5",
"postcss": "^8.2.12",
"postcss-loader": "^5.2.0",
"source-map-loader": "^2.0.1",
"style-loader": "^2.0.0",
"typescript": "^4.2.4",
"webpack": "^5.35.1",
"webpack-cli": "^4.6.0",
"webpack-dev-server": "^3.11.2"
},
"browserslist": [
"defaults",
"not IE 11"
]
}
Examples:
import { Component } from '@itsjavi/jsx-runtime'
function fnComp ({ a, b, children }: { a: number, b: number, children: any }): JSX.Element {
return <div>hey{a}, {b} <br/>{children}</div>
}
class PointInfo extends Component {
constructor (public props: { x: number, y: number }) {
super(props);
}
onClickFn (e: Event) {
console.log(this, e, "I am a sub button")
}
render () {
const { x, y } = this.props
return (<div>{x} + {y}
<button onClick={this.onClickFn}>Sub button</button>
</div>)
}
}
export default class Point extends Component {
private ratio: number
constructor (public props: { x: number, y: number }) {
super(props)
this.ratio = this.props.y / this.props.y
this.onClickFn.bind(this)
}
onClickFn (e: Event) {
console.log(this, e, e.target)
}
render () {
const { x, y } = this.props
return <div id="demo" className={['xx', 'yx']}>
<p>
Lorem
<b>
ipsum
<i>dolor</i>
</b>
</p>
<div>sit</div>
<hr/>
<>
amet
</>
<fnComp className="test-class">hello world</fnComp>
<PointInfo x={x} y={y}/>
<button onClick={this.onClickFn}>Click me</button>
</div>
}
}