DEV Community

Rajasegar Chandran
Rajasegar Chandran

Posted on

Micro-Frontends with Glimmer.js and single-spa

In this post, I am going to walk you through on how to create Micro-Frontends with Glimmer.js using the single-spa library.

We are going to create a simple application like a Starwars wiki with just only two pages for the list of planets and people references in Star Wars movie. The planets page will list the planets from the films and their details. Similarly the people page will list the characters from the movies and their details.

You can view the Glimmer Micro-frontends in action here and the source code is hosted in this github org.

Before diving into the tutorial, let's make ourselves clear about the tools and technologies we are going to use.

Micro-Frontends

Micro-Frontends is about extending the micro-service idea to frontend development. It is a collection of techniques, strategies and recipes for building a modern web app with multiple teams that can ship features independently.

Glimmer.js

Glimmer is one of the fastest DOM rendering engines, delivering exceptional performance for initial renders as well as updates. Architected like a virtual machine (VM), Glimmer compiles your templates into low-level code so it can run as fast as possible—without sacrificing ease of use. Glimmer VM powers the components in Ember.js

single-spa

single-spa is a JavaScript router for front-end microservices. With single-spa you can use multiple frameworks in a single-page application, allowing you to split code by functionality and have Angular, React, Vue.js, etc. apps all living in harmony. single-spa makes them work together and won't load them until they're needed.

single-spa-glimmer

single-spa-glimmer is a helper library that helps implement single-spa registered application lifecycle functions (bootstrap, mount and unmount) for for use with Glimmer. For more information, check out the github repository.

One important thing we are doing inside this library, especially inside the mount lifecycle hook is the below:

function mount(opts) {
  return Promise
    .resolve()
    .then(() => {
      // Turning off validator registration for allowing multiple glimmer apps in same page
      globalThis[Symbol.for('GLIMMER_VALIDATOR_REGISTRATION')] = false;
      const { renderComponent, App, root, owner } = opts;
      const element = document.getElementById(root);
      renderComponent(App, { 
        element,
        owner,
      });
    });
}
Enter fullscreen mode Exit fullscreen mode

We are deliberately turning off the @glimmer/validator registration, to allow multiple Glimmer apps co-exist within a single HTML page. Currently, this is not possible, but thanks to the above workaround proposed by John Griffin in this issue he had created in the glimmer-vm repository.

Recommended Setup by single-spa core team

The single-spa core team recommends a default setup to create micro-frontends with any framework based on a set of conventions which will help us to scale our micro frontends in the long run.

We are going to follow the below setup:

glimmer-micro-frontends
|- root-config
|- navbar
|- people
|- planets

Enter fullscreen mode Exit fullscreen mode

root-config

This is the host app that manages all the other micro frontends using single-spa.

Let's see how to create the root-config app using the CLI:
Invoke the create-single-spa command at the terminal

create-single-spa
Enter fullscreen mode Exit fullscreen mode

It will present you with a set of options like directory, type of application. With create-single-spa you can create different type of applications like single-spa-application, in-browser utility and root-config. You can read more about the different type of modules here.

For this project you have to choose the single-spa root config option and follow the other successive prompts.

? Directory for new project 
? Select type to generate (Use arrow keys)
  single-spa application / parcel
  in-browser utility module (styleguide, api cache, etc)
❯ single-spa root config
Enter fullscreen mode Exit fullscreen mode

Once the generators are done, you will have a src folder with two files. index.ejs is the html template for our host app where we will have the placeholders for the other micro frontends.

 <template id="single-spa-layout">
    <single-spa-router>
        <application name="@glimmer-mf/navbar"></application>
        <div id="navbar"></div>
      <main>
        <route default>
        <p class="mfe-info">
        This example project shows independently built and deployed microfrontends that use <a href="https://glimmerjs.com">Glimmer.js</a> and <a href="https://single-spa.js.org">single-spa</a>. Each nav link above takes you to a different microfrontend.
        </p>
        </route>
        <route path="people">
        <application name="@glimmer-mf/people"></application>
        </route>
        <route path="planets">
        <application name="@glimmer-mf/planets"></application>
        </route>
      </main>
      <footer>
        <p><a href="https://github.com/glimmer-mfe">Github</a></p>
      </footer>
    </single-spa-router>
  </template>

Enter fullscreen mode Exit fullscreen mode

And the next file will be root-config.js. This is where we will register all our Glimmer micro frontends using the single-spa registerApplication

We are making use of the single-spa-layout engine here. The layout engine provides a routing API that controls your top level routes, applications, and DOM elements. Using single-spa-layout makes it easier to accomplish the following:

  • DOM placement and ordering of applications.
  • Loading UIs when applications are downloaded.
  • Default routes for Not Found / 404 pages.
  • Server side rendering of single-spa applications
  • Error pages

To know more about the layout engine you can take a look at the official documentation.

import { registerApplication, start } from "single-spa";

import {
  constructApplications,
  constructRoutes,
  constructLayoutEngine,
} from "single-spa-layout";
const routes = constructRoutes(document.querySelector("#single-spa-layout"));
const applications = constructApplications({
  routes,
  loadApp({ name }) {
    return System.import(name);
  },
});
const layoutEngine = constructLayoutEngine({ routes, applications });
applications.forEach(registerApplication);
start();

Enter fullscreen mode Exit fullscreen mode

navbar

This app contains a navigation bar to route to the different micro-frontends based on the url.

Create the navigation markup in src/App.js component.

 <nav id="global-nav" {{startRouting}}>
    <ul>
    <li><a href="/">Home</a></li>
    <li><a href="/people">People</a></li>
    <li><a href="/planets">Planets</a></li>
    </ul>
 </nav>

Enter fullscreen mode Exit fullscreen mode

And finally we need to hook up the lifecycles for the Glimmer app with the single-spa library in src/index.js. We need to give the single-spa-glimmer function the parent Component, the application owner object with the list of services that needs to be injected, the renderComponent function from @glimmer/core and the DOM element id for mounting the parent component. The id will be the same as the application name we gave in the root-config markup earlier.

For example, if the application name is @glimmer-mf/people the DOM element id will be single-spa-application:@glimmer-mf/people which will be automatically created and added to the DOM by the single-spa layout engine, whenever the /people url is visited in the app.

import { renderComponent } from '@glimmer/core';
import App from './App.js';
import LocaleService from './services/LocaleService.js';
import RouterService from './services/RouterService.js';
import singleSpaGlimmer from './single-spa-glimmer.js';

const root = 'single-spa-application:@glimmer-mf/people';
const owner = {
  services: {
    locale: new LocaleService('en_US'),
    router: new RouterService()
  }
};
const glimmerLifecycles = singleSpaGlimmer({
  App,
  renderComponent,
  root,
  owner
});

export const bootstrap = glimmerLifecycles.bootstrap;
export const mount = glimmerLifecycles.mount;
export const unmount = glimmerLifecycles.unmount;
Enter fullscreen mode Exit fullscreen mode

We have to do the above step for each Glimmer app we create.

people

This micro frontend is for the people page and list the people and their details using the Starwars API SWAPI.

I am not going to discuss in depth about the functional code to fetch the api and show the list of people in this app. That has already been discussed in my previous blog post here.

Instead, we will take a look at the webpack.config.js for the Glimmer apps, because we need to tweak it a little bit, to build for single-spa compatibility.

const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const path = require('path');

module.exports = (env) => {
  return {
  mode: env.production ? 'production' : 'development',
  entry: './src/index.js',
  output: {
    filename: 'people.js',
    path: path.resolve(__dirname, 'dist'),
    libraryTarget: 'system',
    publicPath: env.production ? 'https://glimmer-mf-people.surge.sh/' : '//localhost:8081/'
  },
  devtool:'inline-source-map',
  plugins: [
    new CleanWebpackPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        use: {
          loader: 'babel-loader',
          options: {
            plugins:[ 
              ['@glimmer/babel-plugin-glimmer-env', { DEBUG: true}],
              '@glimmer/babel-plugin-strict-template-precompile',
              ['@babel/plugin-proposal-decorators', { legacy: true }],
              '@babel/plugin-proposal-class-properties',
            ],
            presets: [['@babel/preset-env', { targets: { esmodules: true } } ]],
          },
        },
      },
    ],
  },
  devServer: {
    headers: {
      "Access-Control-Allow-Origin": "*",
    }
  }
};
}
Enter fullscreen mode Exit fullscreen mode

The significant thing to notice in the config is the libraryTarget property which is assigned the value system to make the build compatible with single-spa, since it uses System.js under the hood. You can read more about the recommended build tools config for different bundlers like Webpack and Rollup.

I have made a sample repo to re-use for single-spa in Glimmer with webpack, so that you don't have to start from scratch for creating Glimmer Micro-Frontends with single-spa.

GitHub logo rajasegar / glimmer-sspa

single-spa Micro-Frontend app template for Glimmer.js

glimmer-sspa

This is a single-spa application template for glimmer.

Usage

Clone it via git

git clone https://github.com/rajasegar/glimmer-sspa

or use something like degit

degit rajasegar/glimmer-sspa

Available Scripts

npm start

Runs the app in the development mode. Open http://localhost:8080 to view it in the browser.

The page will reload if you make edits. You will also see any lint errors in the console.

npm run build

Builds a static copy of your site to the build/ folder. Your app is ready to be deployed!

This is how the people app will look like in action.

Alt Text

planets

The planets micro frontend is almost similar to the people micro frontend except it uses the planets api from SWAPI.

Alt Text

Running the app

Once you have done all the above things properly, you can start the Glimmer microfrontends and see them in action in the local environment by firing up yarn start from the root-config app.

yarn start
Enter fullscreen mode Exit fullscreen mode

This will start the webpack-dev-server in the port 9000 by booting up the app and you can view the app in localhost:9000

Alt Text

What's next?

We still have a long way to go in this approach of using Glimmer to create micro front-ends. There are some challenges with respect to routing within the apps which we need to address.

This is an entirely new approach of creating Micro-Frontends using Glimmer.js for all the frontends. Since Glimmer is lightweight and fast, the performance benefits supersede all the other boilerplate code we need to have for routing and rendering components.

You are not tied with a particular framework implementation, you are free to choose any framework to add to the micro frontends here with other Glimmer apps.

You don't have to redeploy other Glimmer apps if you change your dependencies with respect to the other micro frontends, because single-spa registers your dependencies from where you have hosted the other micro-frontends.

You can dynamically mount/unmount you apps with the single-spa-inspector browser addon provided by the single-spa team in your browser. It supports Chrome and Firefox browsers.

Alt Text

You can also override the apps at runtime inside the browser with the addon for testing any micro-frontend in your local development environments.

Please let me know your thoughts in the comments section, about building Glimmer micro-frontends using single-spa and also check out single-spa for building micro-frontends in other frameworks like React, Vue, Svelte, Preact, etc.,

You can find the list of examples in this page

References:

Top comments (0)