MichiJS | React | StencilJS | SvelteJS | VanillaJS | |
---|---|---|---|---|---|
Prefer real DOM over virtual DOM | ✅ | ❌ | ❌ | ✅ | ✅ |
Prefer Javascript templates over compiled plain text | ✅ | ✅ | ✅ | ❌ | ✅ |
Templates with JSX | ✅ | ✅ | ✅ | ❌ | ❌ |
Element internals support | ✅ | ❌ | ❌ | ❌ | ✅ |
Does not require extensions to be identified by the IDE | ✅ | ✅ | ✅ | ❌ | ✅ |
Differentiation between attributes and properties in jsx / templates | ✅ | ❌ | ❌ | ❌ | ❌ |
Standard Web Components | ✅ | ⭕ | ✅ | ⭕ | ✅ |
Observables / stores support | ✅ | ⭕ | ⭕ | ⭕ | ❌ |
Esbuild as default bundler | ✅ | ❌ | ❌ | ❌ | ❌ |
TypeScript support | ✅ | ✅ | ✅ | ✅ | ⭕ |
Reactive | ✅ | ✅ | ✅ | ✅ | ❌ |
Styling / Constructable Stylesheets support | ✅ | ❌ | ✅ | ❌ | ✅ |
Automatic component type generation | ✅ | ❌ | ✅ | ❌ | ❌ |
Without polyfills | ✅ | ✅ | ❌ | ❌ | ✅ |
Attributes / Native events support | ✅ | ❌ | ⭕ | ✅ | ✅ |
Supports Shadow DOM | ✅ | ❌ | ✅ | ✅ | ✅ |
Supports Custom Built-in elements | ✅ | ❌ | ❌ | ✅ | ✅ |
Can be used with different frameworks right out of the box | ✅ | ❌ | ✅ | ⭕ | ✅ |
✅ = implemented | |||||
⭕ = partially implemented | |||||
❌ = not implemented |
You can use this template or you can see on Code Sandbox.
MichiJS custom elements are plain objects.
New components can be created using the jsx/tsx
extension, such as MyCounter.tsx
.
import { createCustomElement, EventDispatcher } from "@michijs/michijs";
import { counterStyle } from "./counterStyle";
export const MyCounter = createCustomElement('my-counter', {
reflectedAttributes: {
count: 0
},
methods: {
decrementCount() { this.count-- },
incrementCount() { this.count++ },
},
events: {
countChanged: new EventDispatcher<number>()
},
adoptedStyleSheets: [counterStyle],
observe: {
count() {
this.countChanged(this.count)
}
},
render() {
return (
<>
<button onpointerup={this.decrementCount}>-</button>
<span>{this.count}</span>
<button onpointerup={this.incrementCount}>+</button>
</>
)
}
})
Note: the .tsx
extension is required, as this is the standard for TypeScript classes that use JSX.
To use this component, just use it like any other HTML element:
import '../Counter';
<my-counter oncountchanged={(ev) => console.log(`New count value: ${ev.detail}`)} />
Or if you are using jsx
import Counter from '../Counter';
<Counter oncountchanged={(ev) => console.log(`New count value: ${ev.detail}`)} />
A component consists of the following properties:
If the extends field is not provided an Autonomous custom element will be created.
A store consists of the following properties:
stores use proxies to listen for changes in their state, in addition, they are observable. Each component has an store to listen for changes in its state.
To use css we provide functions to create Constructable Stylesheets.
Allows to create a Constructable Stylesheet with a CSSObject
export const counterStyle = createStyleSheet({
':host': {
display: 'flex',
flexDirection: 'row'
},
span: {
minWidth: '60px',
textAlign: 'center'
}
});
Allows to create a Constructable Stylesheet with a Template String. Recomended extension for VSCode.
export const counterStyle = css`
:host {
display: flex;
flex-direction: row;
}
span {
min-width: 60px;
text-align: center;
}
`
We do not provide support for this functionality yet as ESBuild does not support it yet. You can read how it works here
Allows to set attributes and event listeners to the host element itself.
Creates a container component without styles with the tag "michi-list"
Creates a container component without styles with the tag "michi-fragment"
(Only available if formAssociated is true)
It allows to:
Create a component whose content will load after the promise ends. In the meantime you can choose to show a load component or not show anything.
Provides the ability to move around the web page without reloading the page. It uses the same attributes as an anchor tag but also allows the use of URL objects. Uses the goTo method.
Allows to get a child element from the host with the selector
Forces the element to re-render
Create unique IDs with a discernible key
Usually, if you want to get an html like this:
<div class='test'></div>
In React / Stencil / etc you should write a jsx like this:
() => <div className='test'></div>
And eventually code like this would be executed:
const el = document.createElement('div');
el.className = 'test';
In MichiJS you have the freedom to use both attributes and properties and the result will be the same:
// Using properties
() => <div _={{className: 'test'}}></div>
// Using attributes
() => <div class='test'></div>
And eventually code like this would be executed:
const el = document.createElement('div');
// Using properties
el.className = 'test';
// Using attributes
el.setAttribute('class', 'test')
In this way the jsx syntax of MichiJS is more similar to html.
Indicates that their children are created but not updated
Indicates that their Children are not created or updated. Element creation/update is delegated
Callback that is called when the element is created
Callback that is called when the element is updated
There are 3 ways to create a list
It's the usual way to create lists in jsx.
const arrayTest = [0, 1, 2];
arrayTest.map(item => <div key={item}>{item}</div>)
This will generate an element like:
<michi-list>
<div>0</div>
<div>1</div>
<div>2</div>
</michi-list>
Why create the michi-list element? This is the way to avoid using Virtual DOM. Because the algorithm is dumb, it needs a way to remember that element is a list.
It's similar to using maps. But it allows to use different container than michi-list.
const arrayTest = [0, 1, 2];
<List
as="span"
data={arrayTest}
renderItem={item => <div key={item}>{item}</div>}
/>
This will generate an element like:
<span>
<div>0</div>
<div>1</div>
<div>2</div>
</span>
Is a proxy that allows you to avoid using dom diff algorithms to render lists. This allows it to have a performance close to vanilla js. An operation on the data implies an operation on the associated elements.
const arrayTest = new ElementList(0, 1, 2);
<arrayTest.List
as="span"
renderItem={item => <div>{item}</div>}
/>
This will generate an element like:
<span>
<div>0</div>
<div>1</div>
<div>2</div>
</span>
The intention of using a custom routing tool is to avoid the use of strings to represent the urls and to use modern apis that allow the use of the URL object itself. It also allows to separate the components of the routes which allows a cleaner code.
const Redirect = () => {
goTo(urls.syncRoute())
// Will generate and go to this url: /sync-route
return <></>
}
//Parent routes
export const { urls, Router, pages } = registerRoutes({
syncRoute: {
/**The component to display */
component: <div>Hello World</div>,
title: 'Sync title'
},
//Redirect route
'/': {
component: <Redirect />
},
});
//Child routes
export const { urls: urlsChild, Router: RouterChild, pages: pagesChild } = registerRoutes({
// Async route
asyncChildRoute: {
searchParams: {
searchParam1: String,
searchParam2: Number
},
hash: ['#hash1', '#hash2']
/** The promise to wait */
promise: async () => (await import('./AsyncChildExample')).AsyncChildExample,
/**The title of the page */
title: 'Async Page title'
/**The component to display while the promise is loading */
loadingComponent: <span>Loading...</span>
},
//The parent route
}, urls.syncRoute);
urlsChild.asyncChildRoute({ searchParams: { searchParam1: 'param 1', searchParam2: 2}, hash: '#hash1' })
// Will generate this url: /sync-route/async-child-route?searchParam1=param+1&searchParam2=2#hash1
Router and RouterChild are components that represent the mount points of each registered route.
The "pages" function is a utility to create asynchronous components that includes the search params and component hashes with the types that were defined when the route was registered
export const AsyncChildExample = pagesChild.asyncChildRoute(({ searchParams, hash }) => (
<>
{/* Will show the value of searchParam1 */}
<div>{searchParams.searchParam1}</div>
{/* Will show true if the hash is #hash1 */}
<div>{hash['#hash1']}</div>
</>
);
);
It is supported by using a custom store
const translator = new I18n<'es' | 'en'>(localStorage.getItem('lang'));
const store = translator.createTranslation({
es: () => import('./translations/es.json'),
en
});
const t = store.state.t;
export const MyComponent = createCustomElement('my-component', {
subscribeTo: {
store
},
render() {
return (
<span>{t.hello}</span>
);
}
});
Because some objects are not proxy compatible we limit the observable objects to:
If you REALLY need polyfills i recommend you to read this topics:
Support us with a donation and help us continue our activities here.