This library demonstrates how to use JSX without React. It can be used as a learning tool to understand the internals of front-end frameworks using functional components/closures instead of classes. Many of the constructs included are very similiar/identical to Mithril (routing, requests), React (useState
and useEffect
), and I'm sure other modern libraries.
You can use either via a CDN or via a node module. Both JavaScript and TypeScript are supported. The types are included in the node module.
<script src="https://unpkg.com/mantium/dist/index.umd.min.js"></script>
<script>
const m = mantium.m;
m.render(document.body, "hello world");
</script>
If you need a webpack setup for TypeScript with ESLint that supports JSX, you can use this template.
npm install mantium -S
import { m } from 'mantium';
m.render(document.body, 'hello world');
This library supports these features:
- Render function for JSX
- HyperScript support
- JSX using TypeScript (.tsx)
- JSX using Babel (.jsx)
- JSX fragments
- JSX declarations/interfaces for: IntrinsicElements, Element, ElementChildrenAttribute
- JSX children access via attributes (typing available using interfaces)
- JSX attribute access (using interfaces)
- JSX functional components
- JSX class components
- JSX as children in JSX components
- JSX keys for loops
- JSX attributes for strings
- JSX attributes for booleans (like 'required') - this needs testing
- Sort out class vs className
- Test forceUpdate for event handlers
- JSX event handling for 'on' functions
- Virtual DOM
- Reactivity
- Redrawing on click events
- Local variable state using 'useState'
- Router
- 404 page
- Hash URL prefix handling
- Router and virtual DOM handling
- Virtual DOM handling of fragments at top level
- Add Link to handle changing pages for URLs that don't include the hash
- Support history handling on page URLs
- Support regex on routes for authentication
- Request Handling for JSON
- Request handling for non-JSON
- Handle redraws on requests to ensure loop don't occur
- Remove all circular dependencies
- Add useEffect which triggers after a redraw to support lifecycle methods of creation and destruction
- On useEffect, allow specifying when to update (onLoad, on variable change, etc)
- Add redraw after request (doesn't alway work, especially with nested requested, but if useing useState then it will)
- Add redraw on setter from useState
- Ability to batch setState commands to prevent rendering after each update
- Allow useState to pass in function to get previous value
- Add simplified useContext without provider
- Easy way to view output of generated code (
npm run build-clean
) - Performance testing
- Add Jest
- Generate test coverage
- Unit tests
- Clean up the types
- Launch on NPM to see how the process works
- Publish on NPM in standalone JavaScript file format (Rollup)
- Publish on NPM in CommonJS format (Rollup)
Sample code is here. npm package is here.
const m = mantium.m;
m.render(document.body, 'hello world');
m.render(document.body, true);
import { m } from 'mantium';
m.state.routerPrefix = '#';
m.route(rootElem, '/', MainPage);
m.route(rootElem, '/app', UITestPage);
m.route(rootElem, '/404', ErrorPage);
import { m } from 'mantium';
export const BooleanFlip = (): JSX.Element => {
const [isBool, setBool] = m.useState(false);
return (
<>
<button
onclick={() => {
setBool((prev) => !prev);
}}
>
Change Boolean Value
</button>
<div>Current value: {isBool}</div>
</>
);
};
m.render(document.body, BooleanFlip);
const m = mantium.m;
const h = mantium.m.createElement;
function MainPage() {
return h('div', {}, 'hello world');
}
m.render(document.body, MainPage);
import { m } from 'mantium';
export const FragLevel1 = (): JSX.Element => {
return (
<>
<div>Fragment level 1.</div>
<FragLevel2 />
</>
);
};
export const FragLevel2 = (): JSX.Element => {
return (
<>
<div>Fragment level 2.</div>
</>
);
};
m.render(document.body, FragLevel1);
const m = mantium.m;
const h = mantium.m.createElement;
function FragLevel1() {
return h('FRAGMENT', {},
h('div', {}, 'Fragment level 1.'),
h(FragLevel2));
}
function FragLevel2() {
return h('FRAGMENT', {},
h('div', {}, 'Fragment level 2.'));
}
m.render(document.body, FragLevel1);
import { m } from 'mantium';
export const App = (): JSX.Element => {
return (
<div class='app'>
<FragmentChild num1='10A' num2='10B'>
<div>Text should be in a div.</div>
</FragmentChild>
</div>
);
};
interface FragmentsAttrs {
num1: string;
num2: string;
children: JSX.Element | string;
}
const FragmentChild = (attrs: FragmentsAttrs): JSX.Element => {
return (
<>
<div name={attrs.num1}>div {attrs.num1}</div>
{attrs.children}
<div name={attrs.num2}>div {attrs.num2}</div>
</>
);
};
const rootElem = document.createElement('div');
rootElem.setAttribute('id', 'root');
document.body.appendChild(rootElem);
m.render(rootElem, App);
import { m } from 'mantium';
export const RedrawButtons = (): JSX.Element => {
const [count, setCount] = m.useState(0);
return (
<>
<button
onclick={() => {
setTimeout(() => {
setCount((prev) => prev + 1);
}, 1000);
}}
>
1 Second Timer with setState [auto redraw] ({count} clicks)
</button>
<button
onclick={() => {
m.redraw();
}}
>
Manual Redraw
</button>
<button
onclick={() => {
setTimeout(() => {
globalCounter++;
}, 1000);
}}
>
1 Second Timer on Global Variable [requires manual redraw] (
{globalCounter} clicks)
</button>
</>
);
};
m.render(document.body, RedrawButtons);
import { m } from 'mantium';
interface PostResponse {
userId: number;
id: number;
title: string;
body: string;
}
interface UserResponse {
id: number;
name: string;
username: string;
email: string;
address: {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
};
};
phone: string;
website: string;
company: {
name: string;
catchPhrase: string;
bs: string;
};
}
export const JSONRequest = (): JSX.Element => {
const [getPost, setPost] = m.useState({} as PostResponse);
const [getUser, setUser] = m.useState({} as UserResponse);
m.useEffect(() => {
m.request<PostResponse>({
url: 'https://jsonplaceholder.typicode.com/posts/5',
})
.then((data: PostResponse) => {
setPost(data);
return m.request<UserResponse>({
url: `https://jsonplaceholder.typicode.com/users/${data.userId}`,
});
})
.then((udata: UserResponse) => {
setUser(udata);
})
.catch((error: Response) => {
console.warn(error);
});
}, []);
return (
<>
<a title='home' href='#/'>
Back
</a>
<h1>Title: {getPost.title}</h1>
<h2>By: {getUser.name}</h2>
<i>Post ID: {getPost.id}</i>
<p>{getPost.body}</p>
</>
);
};
m.render(document.body, JSONRequest);
You can read about it here.
import { m } from 'mantium';
interface StateAttrs {
count: number;
sqr: number;
}
const State = (): StateAttrs => ({
count: 0,
sqr: 0,
});
const Actions = (
S: StateAttrs,
A = {
sqr: () => (S.sqr = S.count ** 2),
inc: () => {
S.count++;
A.sqr();
},
dec: () => {
S.count--;
A.sqr();
},
},
) => A;
export const Meiosis = (): JSX.Element => {
const [state] = m.useState(State());
const [actions] = m.useState(Actions(state));
return (
<>
<button
onclick={() => {
actions.inc();
// Requires redraw if not interacting with useState setter directly.
m.redraw();
}}
>
Add
</button>
<button
onclick={() => {
actions.dec();
// Requires redraw if not interacting with useState setter directly.
m.redraw();
}}
>
Subtract
</button>
<div>
Current value: {state.count} | Squared: {state.sqr}
</div>
</>
);
};
m.render(document.body, Meiosis);
import { m } from 'mantium';
const UserContext = m.createContext('monkey');
const ContextChild = (): JSX.Element => {
const [value, setValue] = m.useContext(UserContext);
return (
<>
<div>Child value: {value}</div>
<button
onclick={() => {
setValue('duck');
}}
>
Change 2
</button>
</>
);
};
m.render(document.body, ContextChild);