A light weight library ( 3k gzipped ) that gives you the speed and control of an SPA while keeping your site static through progressive enhancement and modern techniques.
Demo: https://bolt.oddcommon.dev
The primary goal of Bolt is to speed up page load times and give users the feel of an SPA while remaining static HTML. It's not quite a router and not quite a transition library, it's main objective is speed and progressive enhancement, it's up to you to decide how to implement.
You can use Bolt in a few different ways, out of the box it will improve load times with a few modifications to your code.
Heavily inspired by Quicklink, Turbo & Highway the goal of Bolt is to be an easy to integrate script that gives speed + flexibility to build around.
To Install: npm i @oddcommon/bolt
Bolt is a long lived script and needs to run on each page and between renders. If you initialize Bolt from a script that script will be considered protected, that means it will never be disposed of.
ex: App.js
import Router from "@oddcommon/bolt";
class App {
constructor(){
this.BoltRouter = new Router();
this.observe();
}
observe(){
this.BoltRouter.on('navigate-before', () => { .... })
}
}
new App();
npm run dev
- http://localhost:8000/examples
In order for Bolt to know where updates need to be rendered you need to specify a dom element to use. This is done by adding data-bolt
to any top level DOM element that will be changing from page to page. Typically this would be your main content.
Anything outside of data-bolt
will be left intact between page renders. You can have multiple wrappers by specifying an ID
example:
<div data-bolt="some-id">...</div>
To get Bolt running on your links just add data-bolt-link
as an attribute to any links. Bolt will handle the rest.
You can optionally set data-bolt-link="static"
and bolt will retain the current scroll position on the page between renders. This is helpful if you're paginating content and don't want to reset the user on every click. The default behavior is to scroll the user to the top of the page on every render.
Adding data-bolt-static='static-id'
to an element will freeze that element and Bolt will not touch it between renders. These elements will never be removed or changed, you can treat them as independent sandboxes for long lived content.
Bolt has a predefined lifecycle and emits events throughout this lifecycle. you can tap into any of them at any time.
- prefetch-before
- prefetch-complete
- navigate-before
- params:
{to: string, from: string}
- params:
- navigate-complete
- params:
{to: string, from: string}
- params:
- loading
- load-event
- params:
{total: integer, complete: integer}
- params:
- load-complete
- render-before
- render-complete
- bolt-complete
Bolt has a concept of merging static elements that are outside the main data-bolt
wrapper. By adding data-bolt-merge
to an element bolt will merge attributes of those elements. This is handy if you have different classes or require a specific layout between pages on elements that are static.
example:
<div data-bolt-merge="div-id">...</div>
If you want to really go after speed and have specific assets that you want to preload before a navigation event even happens you can use data-bolt-prefetch
. This will add preload
tags and the browser will begin preloading those assets in the background.
🚨 - This is very agressive and must be used with caution, but it can result in dramatically faster pages loads.
Bolt has a very simple API that allows you the ability to control lifecycle steps.
BoltRouter.pause()
- Pauses execution before the next step in lifecycleBoltRouter.resume()
- Resumes executionBoltRouter.initialize()
- Refreshes bindingsBoltRouter.disable()
- Disable Bolt
BoltRouter.lock()
- Override the lock that Bolt creates during routing ( similar to pause but blocks any new routing from happening )BoltRouter.unlock()
- Unlock the override
When you initialize Bolt you can supply a transitions object where you can define transition functions and then specify them using data-bolt-transition="transitionName"
Your transition function will be called on navigate-before
- it us up to you to hook into the Bolt lifecycle. In the below example we are simply adding an await to delay, it's up to you to decide what happens and when to resume Bolt lifecycle. You can also hook into any step of the lifecycle for your transition.
Example:
import Router from '@oddcommon/bolt';
class App {
constructor() {
const exampleTransition = this.exampleTransition;
this.BoltRouter = new Router({
transitions: [
{
name: 'exampleTransition',
transition: exampleTransition,
},
],
});
}
// Bolt provides the to and from in the callback
async exampleTransition({ to, from }) {
// Pause Bolt lifecycle
this.BoltRouter.pause();
// ... Fancy transition animation and logic
await setTimeout(() => {
new Promise(resolve => resolve());
}, 500);
// Resume Bolt Lifecycle
this.BoltRouter.resume();
}
}
new App();
You can register new transitions at any time by calling BoltRouter.registerTransition({ ...transition-object })
;
If you want to specify specific transitions based on pages you can define to
and from
in your transition object, Bolt will call the transiton whenever it's navigating to and from those specific pages.
const transitionObject = {
name: 'homeToAbout',
to: '/about/',
from: '/home/',
transition: exampleTransition,
};
You can also use regex to apply transitions to groups of pages.
BoltRouter = new Router({
transitions: [
{
name: 'exampleTransition',
from: /features/,
to: /link-example/,
transition: exampleTransition,
},
],
});
You can see a demo of Bolt in example
. Just run npm run dev
from the root of the Bolt project and the demo will start up.