Typed OOP in the browser!
<!doctype html>
<html lang="en">
<head>(...)</head>
<body>
<div id="root">
<app-component>
<slot>"Your App Goes Here!"</slot>
</app-component>
</div>
<script type="module"></script>
</body>
</html>- No config
- No framework
- No abstractions
Just raw Web API.
Powered by Typescript, TailwindCSS, ESBuild, and fast-refreshing development server!
Step by step!
startclass AppComponent {}AppComponent.constructor()AppComponent extends HTMLElementHTMLElement.super()thisAppComponent.innerHtmlCustomElementRegistry'app-component': AppComponentAppComponent.render()AppComponent.render(innerHTML)AppComponent.setup()createElement('app-component')App()render(App)<app-component>- Tips
- Further Reading
$ git clone git@github.com:nathanjhood/ts-web-components.git$ cd ts-web-components$ npm install# For Windows...
$env:NODE_ENV="development"
# For Linux/Mac...
export NODE_ENV="development"$ npm run start# ...
Rebuilding...
Done in 1623ms.
Server running at http://127.0.0.1:3000/
To exit: Ctrl + c
Open in your browser and edit src/App.ts - the page will automatically refresh itself after every save.
// src/App.ts
class AppComponent {}class AppComponent {
constructor() {/** setup goes here... */}
}// "I am a HTMLElement"
class AppComponent extends HTMLElement {
constructor() {}
}
// "...plus more ;) "class AppComponent extends HTMLElement {
constructor() {
super(); // MUST do this first...
}
}class AppComponent extends HTMLElement {
constructor() {
// inside here, "this" means "this 'AppComponent'"...
super();
this. // <-- '.' should produce a long list of props and methods...
}
}// 'innerHTML' === <app-component>innerHTML</app-component>
class AppComponent extends HTMLElement {
constructor() {
super();
this.innerHTML = `<slot>Your app goes here</slot>`;
}
}// IMPORTANT
window.customElements.define('app-component', AppComponent);window.customElements.define('app-component', // <-- wrap the class!
class AppComponent extends HTMLElement {
constructor() {
super();
this.innerHTML = `<slot>Your app goes here</slot>`;
}
}
); // <-- '.define()' ends here!window.customElements.define('app-component',
class AppComponent extends HTMLElement {
constructor() {
super();
this.innerHTML = this.render();
}
render() {
return `<slot>Your app goes here</slot>`;
}
}
);window.customElements.define('app-component',
class AppComponent extends HTMLElement {
constructor() {
super();
this.innerHTML = this.render('Your app goes here');
}
render(innerHTML: string): string {
return `<slot>${innerHTML}</slot>`;
}
}
);window.customElements.define('app-component',
class AppComponent extends HTMLElement {
constructor() {
super();
this.setup();
}
setup(): void {
this.innerHTML = this.render('Your app goes here');
}
render(innerHTML: string): string {
return `<slot>${innerHTML}</slot>`;
}
}
);window.customElements.define('app-component',
class AppComponent extends HTMLElement {
constructor() {
super();
this.setup();
}
setup(): void {
this.innerHTML = this.render('Your app goes here');
}
render(innerHTML: string): string {
return `<slot>${innerHTML}</slot>`;
}
}
);
const app = document.createElement('app-component');const App = () => {
// define the component
window.customElements.define('app-component',
class AppComponent extends HTMLElement {
constructor() {
super();
this.setup();
}
setup(): void {
this.innerHTML = this.render('Your app goes here');
}
render(innerHTML: string): string {
return `<slot>${innerHTML}</slot>`;
}
}
);
// then return it
return document.createElement('app-component');
}
// Now we can assign it :)
const app = App();// src/index.ts
import App = require('./App');
const render = (element: () => HTMLElement) = {
// ...attaches passed-in element to document
}
// so, pass it our App :)
render(App)<!-- This is what you see in your IDE... -->
<!doctype html>
<html lang="en">
<head></head>
<body>
<div id="root"></div>
<script type="module" src="./static/js/index.js"></script>
</body>
</html><!-- ...this is what you see in your web browser! -->
<!doctype html>
<html lang="en">
<head></head>
<body>
<div id="root">
<app-component>
#shadowRoot (open)
<slot>"Your App Goes Here!"</slot>
</app-component>
</div>
<script type="module"></script>
</body>
</html>// example:
const Button = (): HTMLButtonElement => {
return document.createElement('button')
}
// HTMLButtonElement
const button = Button();// example
const CustomButton = () => {
class CustomButtonElement extends HTMLButtonElement {
constructor() {
super();
}
}
customElements.define('custom-button', CustomButtonElement)
return document.createElement('custom-button') as CustomButtonElement;
};
// CustomButtom
const customButton = CustomButton();type CustomButtonProps = {
type: 'submit' | 'reset' | 'button';
};
const CustomButton = (props: CustomButtonProps) => {
class CustomButtonElement extends HTMLButtonElement {
constructor() {
super();
this.type = props.type;
}
}
customElements.define('custom-button', CustomButtonElement);
return document.createElement('custom-button') as CustomButtonElement;
};
const customButton = CustomButton({ type: 'submit' });type CustomButtonProps = {
type: 'submit' | 'reset' | 'button';
children?: Node;
};
const CustomButton = (props: CustomButtonProps) => {
class CustomButtonElement extends HTMLButtonElement {
constructor() {
super();
this.type = props.type;
if (props.children) this.appendChild(props.children);
}
}
customElements.define('custom-button', CustomButtonElement);
return document.createElement('custom-button') as CustomButtonElement;
};
const customButtonA = CustomButton({ type: 'submit' });
const customButtonB = CustomButton({ type: 'submit', children: customButtonA });type CustomButtonProps = {
type: 'submit' | 'reset' | 'button';
children?: Node;
className?: string;
};
const CustomButton = (props: CustomButtonProps) => {
class CustomButtonElement extends HTMLButtonElement {
constructor() {
super();
this.type = props.type;
if (props.children) this.appendChild(props.children);
if (props.className) this.className = props.className;
}
}
customElements.define('custom-button', CustomButtonElement);
return document.createElement('custom-button') as CustomButtonElement;
};
const tailwindButton = CustomButton({
type: 'submit',
className: 'flex align-left text-white bg-red-500',
});