Build a micro-javascript frontend framework based on Web Components and no-build

VivekVivek
3 min read

Creating a basic JavaScript framework on Web Components with Shadow DOM and ES Modules, including client-side routing, requires a few steps. I shall outline how one can set up a framework.

Step 1: Setting Up Your Project

Create a brand new directory for your project and initialize it.

mkdir my-framework
cd my-framework
npm init -y

Step 2: Creating the Base Class for Components

Create a base class for your components. This one should extend HTMLElement and set up some basic functionality.

// src/MyComponent.js
export class MyComponent extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
  }

  connectedCallback() {
    this.render();
  }

  disconnectedCallback() {
    // Clean up if needed
  }

  render() {
    // This method should be overridden by subclasses
    this.shadowRoot.innerHTML = `<p>Override the render method in your component</p>`;
  }
}

Step 3: Creating a Simple Component

Create a simple component that extends our MyComponent base class.

// src/HelloWorld.js
import { MyComponent } from './MyComponent.js';

class HelloWorld extends MyComponent {
  render() {
    this.shadowRoot.innerHTML = `<p>Hello, World!</p>`;
  }
}

customElements.define('hello-world', HelloWorld);

Step 4: Creating the Router

Build a simple client-side router. This router will be bound to changes in the URL and load an appropriate component accordingly.

// src/Router.js
class Router {
  constructor(routes) {
    this.routes = routes;
    window.addEventListener('popstate', this.handleRoute.bind(this));
    this.handleRoute();
  }

  handleRoute() {
    const path = window.location.pathname;
    const route = this.routes[path] || this.routes['/404'];
    document.querySelector('router-view').innerHTML = `<${route}></${route}>`;
  }

  navigate(path) {
    window.history.pushState({}, path, path);
    this.handleRoute();
  }
}

export const createRouter = (routes) => new Router(routes);

Step 5: Create the Entry Point

Now it is time to create an entry point for your application. This file should import the components that will be used and configure the router.

// src/index.js
import './HelloWorld.js';
import { createRouter } from './Router.js';

const routes = {
  '/': 'hello-world',
  '/404': 'not-found' // Create this component or use a default one
};

const router = createRouter(routes);

document.addEventListener('DOMContentLoaded', () => {
  document.querySelectorAll('[route-link]').forEach(link => {
    link.addEventListener('click', (e) => {
      e.preventDefault();
      router.navigate(e.target.getAttribute('href'));
    });
  });
});

Step 6: Setting Up Your HTML

Create an HTML file to serve your application. This file will import the entry point module and set up the routing links.

<!-- public/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My Framework</title>
</head>
<body>
  <nav>
    <a href="/" route-link>Home</a>
    <a href="/404" route-link>Not Found</a>
  </nav>
  <router-view></router-view>
  <script type="module" src="./src/index.js"></script>
</body>
</html>

Step 7: Running the Application

Since we are using ES Modules, we can take advantage of modern browsers' native module support. Use a simple HTTP server to serve your application.

You can use a tool like http-server to serve the public directory.

npx http-server public

Outcome

Now you have a basic frontend javascript framework using Web Components with Shadow DOM, ES Modules, and client-side routing with no build step. Here is the summary of files that you should have:

  • my-framework/

    • src/

      • MyComponent.js

      • HelloWorld.js

      • Router.js

      • index.js

    • public/

      • index.html
    • package.json

Expanding the Framework

To expand this framework, consider adding:

  • State Management: Implement a simple state management solution.

  • Styling: Add scoped styles to components using the Shadow DOM.

  • HTTP Requests: Include utilities for making HTTP requests.

  • Advanced Routing: Implement more advanced routing features like nested routes and route guards.

This setup gives you a strong base for a modern, lightweight framework using Web Components, Shadow DOM, ES Modules, and client-side routing—with no build step.

10
Subscribe to my newsletter

Read articles from Vivek directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Vivek
Vivek

Curious Full Stack Developer wanting to try hands on ⌨️ new technologies and frameworks. More leaning towards React these days - Next, Blitz, Remix 👨🏻‍💻