@digitalbooting/jsx
v1.0.32
Published
Custom JSX runtime for Web components
Readme
DBJSX
DBJSX is a modern, lightweight micro-framework for building modular web components using JSX, hooks, and a React-like API. It provides a simple system for building custom web components, Shadow DOM, scoped styles, and advanced state management.
Table of Contents
- DBJSX
Goal
This documentation shows how to build custom web components using JSX, hooks, and a React-like API.
With this guide, you can build either individual components or your own installable library of custom web components via npm or yarn.
Installation
Install the package directly from npm or yarn:
yarn add @digitalbooting/jsx
# or
npm install @digitalbooting/jsxThis will add DBJSX as a dependency to your project. You do not need to clone the repository manually.
Your package.json should look something like this:
{
"name": "jsxui",
"version": "1.0.0",
"description": "",
"source": "./src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:prod": "webpack --mode production --config=webpack.config.js",
"start": "webpack-dev-server --mode development --open --port 8888",
"build": "webpack --config=webpack.config.js"
},
"author": "Your name",
"license": "MIT",
"devDependencies": {
"@digitalbooting/jsx": "^1.0.3",
"@babel/core": "^7.22.9",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.22.7",
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
"@babel/plugin-syntax-jsx": "^7.22.5",
"@babel/plugin-transform-react-jsx": "^7.22.5",
"@babel/preset-env": "^7.22.9",
"@babel/preset-react": "^7.22.5",
"@types/node": "20.4.6",
"babel-loader": "9.1.3",
"babel-plugin-module-alias": "^1.6.0",
"babel-plugin-syntax-decorators": "^6.13.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"html-webpack-plugin": "^5.5.3",
"prettier": "^3.0.0",
"svg-url-loader": "^8.0.0",
"transform-class-properties": "^1.0.0-beta",
"webpack": "5.88.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "^4.15.1",
"yarn": "^1.22.19"
}
}You need to add this to your babel.config.json:
{
"presets": ["@babel/preset-env", "@babel/preset-react"],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"throwIfNamespace": false,
"runtime": "automatic",
"allowSyntheticDefaultImports": true,
"importSource": "@digitalbooting/jsx"
}
],
[
"@babel/plugin-proposal-decorators",
{ "legacy": true }
],
"@babel/plugin-proposal-class-properties",
"@babel/proposal-object-rest-spread",
"babel-plugin-module-alias"
]
}Also, don't forget to configure your webpack.config.js:
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
mode: "development",
watchOptions: {
ignored: "**/node_modules",
aggregateTimeout: 600,
},
entry: path.resolve(__dirname, "src"),
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
},
resolve: {
alias: {
"@src/*": path.resolve(__dirname, "src/*.js"),
"@components": path.resolve(__dirname, "src/components/index.js"),
"@modules": path.resolve(__dirname, "src/modules/index.js"),
},
extensions: [".js", ".jsx", ".json"],
},
module: {
rules: [
{
test: /\.(js)x?$/,
loader: "babel-loader",
exclude: /node_modules/,
},
{
test: /\.svg$/,
use: [
{
loader: "svg-url-loader",
options: { limit: 10000 },
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: "webpack-dev-server",
template: path.resolve(__dirname, "index.html"),
}),
],
};I've also configured the jsconfig.json file so you can use aliases in your project:
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"rootDir": "./",
"moduleResolution": "NodeNext",
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"resolveJsonModule": true,
"importHelpers": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"baseUrl": "./",
"paths": {
"@src": ["src"],
"@components": ["src/components/index.js"],
"@modules": ["src/modules/index.js"]
}
},
"exclude": ["node_modules", "dist"]
}Suggested Project Structure
./
src/
components/ # Main components for registration
HelloWorld.js # Simple component
index.js # Entry point to export your components
index.js # Application entry point
index.html # Application entry point
babel.config.json # Babel configuration
webpack.config.js # Webpack configuration
jsconfig.json # jsconfig configurationOnce everything is configured, you can start building your components.
Getting Started
1. Register Your First Component
Create a simple component in src/components/HelloWorld.js:
import { connect } from "@digitalbooting/jsx";
function HelloWorld() {
return <div>Hello, World!</div>;
}
connect(HelloWorld, { component: "hello-world" });This allows you to create a custom element using JSX as the rendering engine.
No need for tagged template literals, as @digitalbooting/jsx uses JSX to render your components.
This is made possible by @babel/plugin-transform-react-jsx and other packages.
Register your component (usually in your app entry point):
import "./components/HelloWorld";Now you can use <hello-world></hello-world> in your HTML!
The base project configuration includes an index.html so you can test your component using webpack-dev-server.
Core Concepts
Component Registration
- Use
connectfrom@digitalbooting/jsxto register any function as a custom element. connectis a function that takes two arguments: the function you want to register and an object with the custom element configuration.
{
"component": string, // custom element name
"observerProps": string[], // list of observed attributes
"stylesheet": string // css for the custom element
}- Supports Shadow DOM, attribute-to-prop mapping, and hook context.
- Example:
import { connect } from "@digitalbooting/jsx";
const MyButton = (props) => {
return <button>{props.label}</button>;
};
export default connect(MyButton, { component: "my-button" });You can specify the custom element name in config.component or omit it to generate a default name based on the function name.
In config.observerProps, you can specify a list of attributes you want to observe, which will become props of the component.
In config.stylesheet, you can specify CSS for the custom element.
Runtime Rendering
Internally, connect uses createElement.
- The
createElementfunction in@digitalbooting/jsx/runtimehandles transpiling JSX and creating the DOM node with its attributes, props, and internal events. connectregisters the custom element and manages its lifecycle, returning a class that extendsHTMLElementand builds the native component.- Supports functional components, virtual components, and fragment syntax.
@digitalbooting/jsx provides a render method that renders JSX into the DOM without the need for AngularJS or React. It's mainly for use with Vanilla JS, and if you've used ReactJS, it will feel familiar to React DOM.
import { render, connect } from "@digitalbooting/jsx";
function MyButton(props) {
return <button>{props.label}</button>;
}
// Additional step to register the custom element *(V-DOM)*
const element = connect(MyButton, { component: "my-button" });
// Render the component into the real DOM
render(element, document.body.getElementById("root"));It's recommended to insert the custom element directly in the HTML:
<my-button label="Click me"></my-button>Hooks
DBJSX offers React-style hooks for state, effects, refs, context, and more. All hooks are in @digitalbooting/jsx and can be used directly in your components:
useStateuseEffectuseRefuseMemouseCallbackuseContextusePropusePropSyncusePropsusePropsSyncuseAttributeuseAttributeSyncuseAttributesuseAttributesSyncuseModeluseModelSyncuseStylesuseScriptuseClipboarduseEventuseExposeuseFetchuseLazyModuleuseLockScrolluseObserver
Examples
Basic Component
import { connect } from "@digitalbooting/jsx";
function BasicGreeting() {
return <span>Hello, World!</span>;
}
export default connect(BasicGreeting, { component: "basic-greeting" });Intermediate Component (Props & Styles)
import { connect } from "@digitalbooting/jsx";
import { useProp, useStyles } from "@digitalbooting/jsx";
function FancyButton(props) {
const [label, setLabel] = useProp("label", "Click me");
useStyles(
() =>
`button {
color: white;
background: #007bff;
padding: 8px 16px;
border: none;
border-radius: 4px;
}`,
[]
);
return <button onclick={() => setLabel("Clicked!")}>{label}</button>;
}
export default connect(FancyButton, { component: "fancy-button" });Advanced Component (Hooks & State)
import { connect } from "@digitalbooting/jsx";
import { useState, useEffect, useAttributes } from "@digitalbooting/jsx";
function Timer(props) {
const [attrs, setAttrs] = useAttributes({ start: 0 });
const [count, setCount] = useState(attrs.start);
useEffect(() => {
const interval = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(interval);
}, []);
return <div>Timer: {count}</div>;
}
export default connect(Timer, { component: "timer-widget" });Advanced Example: Modal <ui-modal>
Below is how to create a reusable modal component and how to integrate it into projects with different frameworks.
1. Building the Modal (with useModel and useExpose)
import {
connect,
useModel,
useExpose,
useEvent,
useEffect
} from "@digitalbooting/jsx";
function UIModal({ open, onClose, children }) {
// Sync the 'open' attribute with the modal state (two-way binding)
const [isOpen, setIsOpen] = useModel("open", false);
// Expose public methods to control the modal from outside
useExpose({
open: () => setIsOpen(true),
close: () => setIsOpen(false),
toggle: () => setIsOpen((prev) => !prev),
});
// Dispatch custom "close" event when closing
const dispatch = useEvent();
const handleClose = () => {
setIsOpen(false);
dispatch("close");
onClose?.();
};
useEffect(() => {
// Side effects when modal opens/closes
if (isOpen) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => (document.body.style.overflow = "");
}, [isOpen]);
return (
<dialog open={isOpen ? "open" : undefined} onclose={handleClose}>
<div style="background: white; margin: 10% auto; padding: 2rem; border-radius: 8px; min-width: 300px; max-width: 90vw; position: relative;">
<button
style="position: absolute; top: 10px; right: 10px;"
onclick={handleClose}
>
✕
</button>
{children}
</div>
</dialog>
);
}
export default connect(UIModal, {
component: "ui-modal",
observerProps: ["open"],
stylesheet: `
dialog {
background: white;
margin: 10% auto;
padding: 2rem;
border-radius: 8px;
min-width: 300px;
max-width: 90vw;
position: relative;
}
button {
position: absolute;
top: 10px;
right: 10px;
}
`,
});Advanced usage:
You can control the modal from outside using JavaScript:
const modal = document.querySelector("ui-modal");
modal.api.open(); // Open the modal
modal.api.close(); // Close the modal
modal.api.toggle(); // Toggle the stateDebugging and Debug Mode
DBJSX includes visual debugging tools and hooks/render tracking.
debugattribute: If you add thedebugattribute to any custom element generated with DBJSX, you'll see detailed information in the console about hooks, renders, and state changes.
<ui-modal debug></ui-modal>What does it show?
- Number of component renders
- State of each hook (
useState,useEffect, etc.) - Changes to props and attributes
- Render times
How does it work?
- Internally uses the
debugHookshook, which logs useful information to the console whenever the component renders or state changes.
- Internally uses the
Tip: Use it only in development, as it can generate a lot of console output.
Basic HTML usage:
<ui-modal open="true"></ui-modal>You can control visibility by changing the open attribute and react to the custom close event.
Integrating <ui-modal> with Popular Frameworks
React (SPA and SSR)
- Import and wrap the custom element in React:
import React, { useState, useEffect } from "react";
function ModalWrapper() {
const [open, setOpen] = useState(false);
const [mounted, setMounted] = useState(false);
const fetchModal = async () => {
try {
await import("https://unpkg.com/tulibreria/@1.0.0/dist/v1/ui-modal");
setMounted(true);
} catch (error) {
console.error("Error loading custom element:", error);
}
};
useEffect(() => {
fetchModal();
}, []);
return (
<>
<button onClick={() => setOpen(true)}>Open Modal</button>
{mounted && (
<ui-modal
open={open ? "true" : undefined}
onclose={() => setOpen(false)}
>
<h2>Modal Content</h2>
<p>Hello from the modal!</p>
</ui-modal>
)}
</>
);
}- SSR (Next.js, Remix): Make sure to import the custom element only on the client (use
useEffector lazy load)
Angular (Standalone Components, Angular 16+ / 19 recommended)
- Import the custom element once (in main.ts or the main entry):
import "tulibreria/dist/v1/ui-modal";- Declare your standalone component:
import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
@Component({
selector: "app-root",
standalone: true,
template: `
<button (click)="modalOpen = true">Open Modal</button>
<ui-modal
[attr.open]="modalOpen ? 'true' : null"
(close)="modalOpen = false"
>
<h2>Modal Content</h2>
</ui-modal>
`,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppComponent {
modalOpen = false;
}- No need for NgModule, just bootstrap your standalone component:
import { bootstrapApplication } from "@angular/platform-browser";
import { AppComponent } from "./app.component";
bootstrapApplication(AppComponent);Note: Custom elements work natively in Angular standalone. Just remember to import the web component script once at the start of the app (main.ts).
Vue
- Import the custom element in your entry (main.js):
import "tulibreria/dist/v1/ui-modal";- Use the tag in your components:
<ui-modal :open="modalOpen" @close="modalOpen = false">
<h2>Modal Content</h2>
</ui-modal>Astro
- Import the custom element globally in your layout or page:
import "tulibreria/dist/v1/ui-modal";- Use the tag in your
.astrofiles or components:
<ui-modal open="true">
<h2>Modal in Astro</h2>
</ui-modal>HTML5 (Vanilla JS)
- Include the custom element script:
<script
type="module"
src="https://unpkg.com/tulibreria/@1.0.0/dist/v1/ui-modal.js"
></script>- Use the tag directly:
<ui-modal open="true">
<h2>Vanilla Modal</h2>
</ui-modal>Notes:
- You can customize the modal with slots, styles, and custom events.
- The
<ui-modal>component is framework-agnostic and works the same in any modern environment. - For SSR, make sure to import the custom element only on the client (when the DOM is available).
Documentation
Author
Contributors
Contribution
The project is open for contributions. If you have any ideas or suggestions, please open an issue or pull request.
License
MIT
function FancyButton(props) {
const [label, setLabel] = useProp('label', 'Click Me');
useStyles(() => button { color: white; background: #007bff; padding: 8px 16px; border: none; border-radius: 4px; });
return <button onclick={() => setLabel('Clicked!')}>{label}; }
connect(FancyButton, { component: 'fancy-button' });
---
### Advanced Component (Hooks & State)
```js
import { connect } from '../core/component';
import { useState, useEffect } from '../hooks';
import { useAttributes } from '../hooks/useAttributes';
function Timer(props) {
const [attrs, setAttrs] = useAttributes({ start: 0 });
const [count, setCount] = useState(attrs.start);
useEffect(() => {
const interval = setInterval(() => setCount((c) => c + 1), 1000);
return () => clearInterval(interval);
}, []);
return <div>Timer: {count}</div>;
}
connect(Timer, { component: 'timer-widget' });License
MIT
DBJSX is not a React or Preact clone, it is a light -runtime JSX Runtime, based on Ecmascript 2023, the objective is to allow the construction of web -based modularized interfaces components.Taking advantage of the flexibility that JSX provides to build the templates of your web components.
Getting Started
Create a new project based with npm
npm initAdd index.js in your root project folder
--project_folder
-- components
index.js
-- modules
--index.js
-- index.js
-- index.html
-- babel.config.js
-- jsconfig.json
--webpack.config.js
-- package.json (this file generated with npm init command)Modify your package.json for install dependencies Babel, Webpack and others. Your Package.json file must be similar to this:
{
"name": "yourpackage",
"version": "1.0.0",
"description": "",
"source": "./src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build:prod": "webpack --mode production --config=webpack.config.js",
"start": "webpack-dev-server --mode development --open --port 8888",
"build": "webpack --config=webpack.config.js"
},
"author": "Your name",
"license": "MIT",
"devDependencies": {
"@babel/core": "^7.22.9",
"@babel/plugin-proposal-class-properties": "^7.18.6",
"@babel/plugin-proposal-decorators": "^7.22.7",
"@babel/plugin-proposal-object-rest-spread": "7.20.7",
"@babel/plugin-syntax-jsx": "^7.22.5",
"@babel/plugin-transform-react-jsx": "^7.22.5",
"@babel/preset-env": "^7.22.9",
"@babel/preset-react": "^7.22.5",
"@types/node": "20.4.6",
"babel-loader": "9.1.3",
"babel-plugin-module-alias": "^1.6.0",
"babel-plugin-syntax-decorators": "^6.13.0",
"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.5",
"html-webpack-plugin": "^5.5.3",
"prettier": "^3.0.0",
"svg-url-loader": "^8.0.0",
"transform-class-properties": "^1.0.0-beta",
"webpack": "5.88.2",
"webpack-cli": "5.1.4",
"webpack-dev-server": "^4.15.1",
"yarn": "^1.22.19"
}
}Add this module in your proyect with yarn or npm:
yarn add -D dbjsx
npm install --save-dev dbjsxYour Package.json file updated with added dbjsx:
{
"devDependencies": {
"dbjsx": "^1.0.0"
}
}Execute install command with yarn or npm:
yarn install
npm installNow we will configure Babel to be able to compile our JSX code
{
"presets": [
"@babel/preset-env",
""babel/preset-react"
],
"plugins": [
[
"@babel/plugin-transform-react-jsx",
{
"throwIfNamespace": false,
"runtime": "automatic", //set the automatic runtime
"allowSyntheticDefaultImports": true,
"importSource": "dbjsx" //set the dbjsx package
}
],
[
"@babel/plugin-proposal-decorators",
{
"legacy": true
}
],
"@babel/plugin-proposal-class-properties",
"@babel/proposal-object-rest-spread",
"babel-plugin-module-alias"
]
}Now add the jsconfig.json file into root project folder
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"rootDir": "./",
"moduleResolution": "NodeNext",
"resolvePackageJsonExports": true,
"resolvePackageJsonImports": true,
"resolveJsonModule": true,
"importHelpers": true,
"strict": true,
"allowSyntheticDefaultImports": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noImplicitAny": true,
"baseUrl": "./", // define base url for relative routes
"paths": { // add relative paths
"@src": [
"src"
],
"@components": [
"src/components/index.js"
],
"@modules": [
"src/modules/index.js"
]
}
},
"exclude": [
"node_modules",
"dist"
]
}Finally Rest Configure Webpack to compile our code
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'development',
watchOptions: {
ignored: '**/node_modules',
poll: 1000,
aggregateTimeout: 600,
},
entry: path.resolve(__dirname, 'src'),
output: {
filename: '[name].js',
path: path.resolve(__dirname, 'dist'),
},
resolve: {
// Here also configures the alias of relative routes
alias: {
'@src/*': path.resolve(__dirname, 'src/*.js'),
'@components': path.resolve(__dirname, 'src/components/index.js'),
'@modules': path.resolve(__dirname, 'src/modules/index.js'),
},
extensions: ['.js', '.jsx', '.json'],
},
module: {
rules: [
{
test: /\.(js)x?$/,
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.svg$/,
use: [
{
loader: 'svg-url-loader',
options: {
limit: 10000,
},
},
],
},
],
},
plugins: [
new HtmlWebpackPlugin({
title: 'webpack-dev-server',
template: path.resolve(__dirname, 'index.html'),
}),
],
}Lets create a index.html with this code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DBJSX Runtime developed by Digital Booting</title>
</head>
<body>
<div id="root"></div>
</body>
</html>Now edit your index.js and add this code example:
import { css, jsx, createElement, render } from 'dbjsx'
const styles = css`
font-size: 50px;
background-color: aliceblue;
color: blanchedalmond;
`
const App = () => {
return (
<div className="hola">
<h2>titulo generado mediante jsx</h2>
</div>
)
}
const $jsx = App()
const element = createElement($jsx)
render(element, document.getElementById('root')) Now init the project with yarn or npm
yarn start
npm startNOTE: This example only serves to show the compilation JSX rendering in the Dom a Div with an H2 element, in next version we will add examples of web rendering components.
It is important to understand the concept of JSX rendering before creating complex applications, this will allow you a greater learning curve
CONTRIBUTION
Feel free to contribute and contribute to this project, you can send an email to [email protected] and know the Road map we have in mind.
OBJETIVES
Our goal is to create a JSX rendering core that can be used both in large projects and small projects and personal modules, it has happened to me that I sometimes want to create some basic components that I use a lot and I do not want to be developing them in each novel JavaScript framework,And much less have a large alject with many units.
With DBJSX, the only thing you need is to create the main environment, develop your applications, modules or components once and use any project.The objective is to take advantage of all the benefits of the web components and of course as personnel developer personally there is nothing more great than using JSX to handle templates without depending on large compilations or React.
If you have experience in the management of JSX, all your contribution is welcome!
