This is the second post in the series of building a Router component in Glimmer.js. In the first post we built our Router component from scratch for handling anchor tag clicks, history state changes and so on. In this post we are going to make use of a client-side routing library called page.js
for all those boilerplate code.
Building a Router component for Glimmer.js
Rajasegar Chandran ・ Dec 23 ・ 9 min read
About page.js
page is a tiny client-side router inspired by the Express router. It has got a lot of features like 404 support, plugin integrations, using history state to cache data and more.
App.js
This is how our template markup is going to look like for the main App
component in our project. As you can see, we have omitted all the custom componets like <Link/>
and <Route/>
. Because all of them seems to be irrelevant here since we are going to use the page
library, it can make use of the standard anchor tags in the browser and the route mapping will be done in pure javascript with the page()
functions.
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/contact">Contact</a></li>
</ul>
</nav>
<Router/>
Router Component
Our Router component will become slim here. Because we are removing the Link
and the Route
components since their functionality is taken care by page.js. Since page.js is going to take care of handling all the anchor click events we don't need a custom component to render our anchor tags. And the router registry which we created in our first post is also not required becuase page.js has its own way of keeping the route mappings.
Here we are using a modifier called routes
which is a function from a file called routes.js
where we will delegate all the routing logic to the page.js.
import routes from './routes.js';
import {
createTemplate,
setComponentTemplate,
templateOnlyComponent,
} from '@glimmer/core';
const Router = setComponentTemplate(createTemplate({ routes },`
<div id="glimmer-router-outlet" {{ routes }}></div>
`), templateOnlyComponent())
export { Router };
Let's take a look at our routes.js
file. First we are importing renderComponent
function from @glimmer/core
library. Then we are importing the page
library here with the default import. The main function takes an element
parameter, which is nothing but a reference to the actual DOM element sent by our Glimmer engine when the element is inserted in to the DOM. This acts as our mount point for rendering the various page components into our DOM when a particular route is visited.
routes.js
import { renderComponent } from '@glimmer/core';
import page from 'page';
export default function(element) {
}
This is how we can make use of page
library to implement our routing. These are some example patterns from their project readme page.
page('/', index)
page('/user/:user', show)
page('/user/:user/edit', edit)
page('/user/:user/album', album)
page('/user/:user/album/sort', sort)
page('*', notfound)
page()
As you can see the page()
function take two parameters, one the URL pattern with all the parent and child routes, and the dynamic segment placeholders. The second parameter is a callback function which handles the routing logic when the particular URL is visited.
Next we are going to use the page
function to render our components based on the route. Let's focus on the home page route which takes an URL like /
. For this route we are going to render a component called Home
from a folder called pages
. This is just for convention, to keep things organized like all the page level components are created in the pages
folder.
...
export default function(element) {
page('/', () => {
import('./pages/Home.js').then(component => {
element.innerHTML = '';
renderComponent(component.default, element);
});
}
}
Injecting services
We can also inject services into our components while rendering them. The renderComponent
function takes an options object as the second parameter in which you can inject services and set the arguments for the component. Let's inject a service called LocaleService
and initialize it with the language code en_US
into our Home
page component.
...
export default function(element) {
page('/', showPageLoad, () => {
import('./pages/Home.js').then(component => {
element.innerHTML = '';
renderComponent(component.default, {
element: element,
owner: {
services: {
locale: new LocaleService('en_US'),
},
}
});
});
});
}
Page loading indicators
It might take some time to fetch our component code and render them on to the DOM. So we want to show some loading status to our users while the components are being fetched.
With the current setup how we are going to achieve that. The page
library takes any number of callbacks for route handling and it will execute them one by one in the order
we are specifying. So we will add one more callback function called pageLoad
before our anonymous rendering callback in the routing logic.
function showPageLoad(ctx, next) {
element.innerHTML = '<h1>Loading page...</h1>';
next();
}
In this showPageLoad
callback we are doing two things. First, we will set our loading indicator html markup in our mount node in the DOM called element
. Second we
are using the the next
callback function given by page.js to move to the next callback in our routing logic sequence. This will allow us to do different things
even before mounting our page level components on to the DOM.
page('/', showPageLoad, () => {
import('./pages/Home.js').then(component => {
element.innerHTML = '';
renderComponent(component.default, {
element: element,
owner: {
services: {
locale: new LocaleService('en_US'),
},
}
});
});
});
Finally we have to call the page()
method to start routing and listening for anchor clicks and navigation events like history state changes in the browser.
export default function(element) {
...
page();
}
This is how our final routes.js
file is going to look like for handling various routes like /about
, /contact
, etc., You can see we are using the showPageLoad
callback prior to rendering the respective page components.
import { renderComponent } from '@glimmer/core';
import page from 'page';
export default function(element) {
function showPageLoad(ctx, next) {
element.innerHTML = 'Loading page...';
next();
}
page('/', showPageLoad, () => {
import('./pages/Home.js').then(component => {
element.innerHTML = '';
renderComponent(component.default, {
element: element,
owner: {
services: {
locale: new LocaleService('en_US'),
},
}
});
});
});
page('/about',showPageLoad, () => {
import('./pages/About.js').then(component => {
element.innerHTML = '';
renderComponent(component.default, element);
});
});
page('/contact', showPageLoad, () => {
import('./pages/Contact.js').then(component => {
element.innerHTML = '';
renderComponent(component.default, element);
});
});
page();
}
Source code
You can see the full source code for this post in the glimmer-snowpack repository, which is actually a create-snowpack-app template to build Glimmer apps with Snowpack. You can use this template like below:
npx create-snowpack-app my-glimmer-app --template glimmer-snowpack
Please let me know any feedback or queries in the comments section. In the next post in this series, we are going to make use of another client side routing library to build our Router
component.
Top comments (0)