Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/michijs/michijs

Web Components with TSX
https://github.com/michijs/michijs

autonomous-web-components built-in-components constructable-stylesheets css-modules shadow-dom tsx web-components

Last synced: about 1 month ago
JSON representation

Web Components with TSX

Awesome Lists containing this project

README

        



A Vainilla Library for Web Components


[![Open in Visual Studio Code][open-in-vscode]][open-in-vscode-url]
![npm][version]
[![license][github-license]][github-license-url]
![npm][npm-downloads]
![npm][repo-size]
![npm][minzipped-size]
[![CodeQL](https://github.com/michijs/michijs/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/michijs/michijs/actions/workflows/codeql-analysis.yml)
[![Tests](https://github.com/michijs/michijs/actions/workflows/tests.yml/badge.svg)](https://github.com/michijs/michijs/actions/workflows/tests.yml)

## Why "MichiJS?"

| | MichiJS | React | StencilJS | SvelteJS | VanillaJS |
|--|--|--|--|--|--|
| Prefer real DOM over virtual DOM | ✅ | ❌ | ❌ | ✅ | ✅ |
| Prefer Javascript templates over compiled plain text | ✅ | ✅ | ✅ | ❌ | ✅ |
| Templates with [JSX](https://es.reactjs.org/docs/introducing-jsx.html) | ✅ | ✅ | ✅ | ❌ | ❌ |
| [Element internals](https://developer.mozilla.org/en-US/docs/Web/API/ElementInternals) support | ✅ | ❌ | ❌ | ❌ | ✅ |
| Does not require extensions to be identified by the IDE | ✅ | ✅ | ✅ | ❌ | ✅ |
| [Differentiation between attributes and properties in jsx / templates](#attributes-vs-properties-in-jsx) | ✅ | ❌ | ❌ | ❌ | ❌ |
| Standard Web Components | ✅ | ⭕ | ✅ | ⭕ | ✅ |
| Observables / stores support | ✅ | ⭕ | ⭕ | ⭕ | ❌ |
| [Esbuild](https://esbuild.github.io/) as default bundler | ✅ | ❌ | ❌ | ❌ |❌ |
| [TypeScript](https://www.typescriptlang.org) support | ✅ | ✅ | ✅ | ✅ | ⭕ |
| Reactive | ✅ | ✅ | ✅ | ✅ | ❌ |
| Styling / Constructable Stylesheets support | ✅ | ❌ | ✅ | ❌ | ✅ |
| Automatic component type generation | ✅ | ❌ | ✅ | ❌ | ❌ |
| Without polyfills | ✅ | ✅ | ❌ | ❌ | ✅ |
| Attributes / Native events support | ✅ | ❌ | ⭕ | ✅ | ✅ |
| Supports [Shadow DOM](https://developers.google.com/web/fundamentals/web-components/shadowdom) | ✅ | ❌ | ✅ | ✅ | ✅ |
| Supports Custom Built-in elements | ✅ | ❌ | ❌ | ✅ | ✅ |
| Can be used with different frameworks right out of the box | ✅ | ❌ | ✅ | ⭕ | ✅ |
| ✅ = implemented
| ⭕ = partially implemented
| ❌ = not implemented

## Getting Started

You can use [this template](https://github.com/michijs/michijs-template) or you can see on [Code Sandbox](https://githubbox.com/michijs/michijs-storybook-template/tree/master).

## Creating components

MichiJS custom elements are plain objects.

New components can be created using the `jsx/tsx` extension, such as `MyCounter.tsx`.

```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()
},
adoptedStyleSheets: [counterStyle],
observe: {
count() {
this.countChanged(this.count)
}
},
render() {
return (
<>
-
{this.count}
+
>
)
}
})
```

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:

```tsx
import '../Counter';

console.log(`New count value: ${ev.detail}`)} />
```

Or if you are using jsx
```tsx
import Counter from '../Counter';

console.log(`New count value: ${ev.detail}`)} />
```

## Component structure
A component consists of the following properties:



Property
Description




attributes
Allows to define attributes.


nonObservedAttributes
Allows to define non observed attributes. This is useful for
complex objects that cannot be observed.


reflectedAttributes
Allows to define reflected attributes and follows the Kebab case. A reflected attribute cannot be initialized with a true value



transactions
Transactions are functions that notify changes at the end of the transaction.


methods
Methods are functions that notify changes at the time of making the change.


adoptedStyleSheets
Allows to use Constructable Stylesheets. Remember that you need to use Shadow DOM to be able to use Constructable Stylesheets. In case your component doesn't support this feature, it will return a style tag.


cssVariables
Allows to define CSS variables. CSS variables changes does not trigger a rerender.


reflectedCssVariables
Allows to define reflected CSS variables and follows the Kebab case. CSS variables changes does not trigger a rerender. A reflected CSS variable cannot be initialized with a true value


computedStyleSheet
Allows you to define a Constructable Stylesheet that depend on the state of the component. When there is no shadow root the style will be reflected in the style attribute.


render
Function that renders the component.


observe
Contains methods with a name of an attribute / reflected attribute / css variables / observable like. Those methods are executed when a change has been made to their corresponding property.


lifecycle

Custom Element related

willConstruct
This method is called at the start of constructor.


didConstruct
This method is called at the end of constructor.


connected
This method is called when a component is connected to the DOM.


willMount
This method is called right before a component mounts.


didMount
This method is called after the component has mounted.


willUpdate
This method is called before re-rendering occurs.


didUpdate
This method is called after re-rendering occurs.


willReceiveAttribute
This method is called before a component does anything with an attribute.


didUnmount
This method is called after a component is removed from the DOM.



Form-associated Custom Element related

formAssociatedCallback
Called when the browser associates the element with a form element, or disassociates the element from a form element.


formDisabledCallback
Called after the disabled state of the element changes, either because the disabled attribute of this element was added or removed;
or because the disabled state changed on a fieldset that's an ancestor of this element. The disabled parameter represents the new disabled state of the element. The element may, for example, disable elements in its shadow DOM when it is disabled.


formResetCallback
Called after the form is reset. The element should reset itself to some kind of default state. For input elements, this usually involves setting the value property to match the value attribute set in markup (or in the case of a checkbox, setting the checked property to match the checked attribute.


formStateRestoreCallback

Called in one of two circumstances:



  • When the browser restores the state of the element (for example,after a navigation, or when the browser restarts). The mode argument is "restore" in this case.


  • When the browser's input-assist features such as form autofilling sets a value. The mode argument is "autocomplete" in this case.


The type of the first argument depends on how the setFormValue() method was called.





events
Allows you to define an event to his parent and triggering it easily. It will be defined using Lower case. For example countChanged will be registered as countchanged.


subscribeTo
Allows you to subscribe to an observable like (like a store). When the store emit an event, the custom element will be re-rendered.


shadow
Allows you to add a Shadow DOM. By default, it uses open mode on Autonomous Custom elements and does not use Shadow DOM on Customized built-in elements. Only this elements are allowed to use Shadow DOM.


formAssociated
This tells the browser to treat the element like a form control.


fakeRoot
Allows to create a fake root on the element. This is especially useful if you don't have shadow root. Since it allows you to add children from a parent node.


extends
Allows to create a Customized built-in element

tag
The tag to extend


class
The class you want to extend


If the extends field is not provided an [Autonomous custom element](https://developers.google.com/web/fundamentals/web-components/customelements#shadowdom) will be created.

## store structure
A store consists of the following properties:



Property
Description




state
Allows to define the store state.


transactions
Transactions are functions that notify changes at the end of the transaction.

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.

## CSS
To use css we provide functions to create Constructable Stylesheets.
### createStyleSheet
Allows to create a Constructable Stylesheet with a CSSObject
```js
export const counterStyle = createStyleSheet({
':host': {
display: 'flex',
flexDirection: 'row'
},
span: {
minWidth: '60px',
textAlign: 'center'
}
});
```
### css
Allows to create a Constructable Stylesheet with a Template String.
[Recomended extension for VSCode](https://marketplace.visualstudio.com/items?itemName=paulmolluzzo.convert-css-in-js).

```js
export const counterStyle = css`
:host {
display: flex;
flex-direction: row;
}

span {
min-width: 60px;
text-align: center;
}
`
```

### CSS module scripts
We do not provide support for this functionality yet as ESBuild does not support it yet. You can read how it works [here](https://web.dev/css-module-scripts/)

## Components
### Host
Allows to set attributes and event listeners to the host element itself.

### List
Creates a container component without styles with the tag "michi-list"

### Fragment
Creates a container component without styles with the tag "michi-fragment"

### ElementInternals
*(Only available if formAssociated is true)*

It allows to:
- Make the element accessible to the browser
- Access element internals
- Validate and assign values to forms

### AsyncComponent
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.

### Link
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.

## Custom element methods
### child
Allows to get a child element from the host with the selector

### rerender
Forces the element to re-render

### idGen
Create unique IDs with a discernible key

## Attributes vs Properties in jsx
Usually, if you want to get an html like this:
```html


```
In React / Stencil / etc you should write a jsx like this:
```jsx
() =>

```
And eventually code like this would be executed:
```js
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:
```jsx
// Using properties
() =>

// Using attributes
() =>

```
And eventually code like this would be executed:
```js
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.

## Special attributes

### $staticChildren
Indicates that their children are created but not updated
### $doNotTouchChildren
Indicates that their Children are not created or updated. Element creation/update is delegated
### $oncreated
Callback that is called when the element is created
### $onupdate
Callback that is called when the element is updated

## Lists
There are 3 ways to create a list
### Using map
It's the usual way to create lists in jsx.
```jsx
const arrayTest = [0, 1, 2];

arrayTest.map(item =>

{item}
)
```
This will generate an element like:

```html

0

1

2

```
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.

### Using List component
It's similar to using maps. But it allows to use different container than michi-list.
```jsx
const arrayTest = [0, 1, 2];

{item}
}
/>
```
This will generate an element like:

```html

0

1

2


```
### Using ElementList
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.
```jsx
const arrayTest = new ElementList(0, 1, 2);

{item}
}
/>
```
This will generate an element like:

```html

0

1

2


```

### Comparison




Map
List component
ElementList




Performance
Diff algorithm order
Diff algorithm order
Close to vanilla


Container
michi-list
michi-list or any other element
michi-list or any other element


Keys
Required
Required
Not required


Index
Yes
Yes
No


Transactions allowed
Yes
Yes
No


Updates
The entire component
The entire component
Just the list itself

## Routing
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.

```js
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:

Hello World
,
title: 'Sync title'
},
//Redirect route
'/': {
component:
},
});

//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: Loading...
},
//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
```js
export const AsyncChildExample = pagesChild.asyncChildRoute(({ searchParams, hash }) => (
<>
{/* Will show the value of searchParam1 */}

{searchParams.searchParam1}

{/* Will show true if the hash is #hash1 */}
{hash['#hash1']}

>
);
);
```

## I18n
It is supported by using a custom store
```js
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 (
{t.hello}
);
}
});
```

## Limitations
### Observable objects
Because some objects are not proxy compatible we limit the observable objects to:
- Arrays
- Dates
- Maps
- Sets
- Any object whose prototype is Object

## Polyfills
If you REALLY need polyfills i recommend you to read this topics:

- https://www.webcomponents.org/polyfills
- https://ungap.github.io

## Browser Support

### Customized built-in elements
- https://www.chromestatus.com/feature/4670146924773376

### Autonomous custom elements
- https://www.chromestatus.com/feature/4696261944934400
- https://www.webcomponents.org/

### Compatibility with frameworks
- https://custom-elements-everywhere.com

### Element internals
- https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/attachInternals

## Supporting MichiJS
### Sponsors
Support us with a donation and help us continue our activities [here](https://www.paypal.com/paypalme/lsegurado2).

## License
- [MIT](https://github.com/michijs/michijs/blob/master/LICENSE.md)

[open-in-vscode]: https://img.shields.io/static/v1?label=Open%20in&logo=Visual%20Studio%20Code&message=Visual%20Studio%20Code&logoColor=007ACC&color=007ACC
[open-in-vscode-url]: https://vscode.dev/github/michijs/michijs-template

[minzipped-size]: https://img.shields.io/bundlephobia/minzip/@michijs/michijs
[repo-size]: https://img.shields.io/github/repo-size/michijs/michijs
[npm-downloads]: https://img.shields.io/npm/dt/@michijs/michijs
[version]: https://img.shields.io/npm/v/@michijs/michijs
[github-license]: https://img.shields.io/github/license/michijs/michijs
[github-license-url]: https://github.com/michijs/michijs/blob/master/LICENSE.md