/simple-pjax

Zero-configuration PJAX for typical websites

Primary LanguageJavaScript

js-standard-style

TOC

Description

Lean library that improves page loading times for classic multi-page websites. Gives them some of the advantages enjoyed by SPA (single-page apps). Configuration is optional.

See a simple demo at https://mitranim.com/simple-pjax/.

Read an explanatory post at https://mitranim.com/posts/cheating-for-performance-pjax.

Pjax is a combo of pushState and Ajax. There are other pjax implementations floating around, but most of them are jQuery-based or overengineered. Hence simple-pjax.

To explain what pjax is about, first let's get a rough idea of how page transitions work on most sites:

  • Load and parse the new HTML document. Create a new JavaScript runtime.
  • Redownload all stylesheets, scripts, fonts, images, etc. (Connections take time even if the resources are cached.)
  • Parse and execute the downloaded scripts.
  • Parse styles and apply them to the document.
  • Throw away the current document and JavaScript runtime, switch to the new document and runtime.
  • Download and execute asynchronous scripts, if any.

Here's how page transitions work with simple-pjax:

  • Load and parse the new HTML document.
  • Replace the contents of the current document.
  • Let it execute the new scripts, if any.

Benefits:

  • Don't redownload stylesheets, scripts, fonts, images.
  • Don't execute scripts that have already been executed.
  • Keep the JavaScript runtime and WebSocket connections intact.

The benefits are especially dramatic on mobile devies and slow connections.

Works on IE10+. Has no effect in browsers that don't support history.pushState.

Installation

Grab through your favourite package manager:

npm i --save-dev simple-pjax
jspm install npm:simple-pjax

Import in your code:

import pjax from 'simple-pjax'

Or include as a script tag:

<script src="simple-pjax.js" async></script>

Usage

Works automatically. When navigating between internal pages, the library prevents a full page reload. Instead, it fetches the new document by ajax and replaces the contents of the current document.

After replacing the document, it executes any inline scripts found in it. Ignores scripts with an src under the assumption that all pages have the same set of scripts, and they have already been downloaded.

Affects both <a> clicks and popstate history events, such as when the back button is clicked.

Visibly indicates loading when it takes a while (by default after 250 ms). You can customise the timeout and the functions called to add and remove the indicator.

Configuration

simple-pjax works with zero configuration, but it also exports an object with configurable properties and useful methods. In the presense of a CommonJS-compliant module system, it does a proper export; otherwise the object is assigned to window.simplePjax.

Example config (see defaults in source).

import pjax from 'simple-pjax'

// Timeout before calling the loading indicator function. Set to 0 to disable.
pjax.indicateLoadAfter = 100

// Called when loading takes a while. Use it to display a custom loading indicator.
pjax.onIndicateLoadStart = function() {
  document.documentElement.style.opacity = 0.5
}

// Called when loading ends. Use it to hide a custom loading indicator.
pjax.onIndicateLoadEnd = function() {
  document.documentElement.style.opacity = null
}

// If a selector string is provided, it's checked every time when scrolling
// to an element (e.g. via data-scroll-to-id). If an element with the
// {position: 'fixed', top: '0px'} computed style properties is found, the
// scroll position will be offset by that element's height.
pjax.scrollOffsetSelector = '.navbar-fixed'

// If a string is provided, it will be used as the default value (default
// element `id`) for the `[data-scroll-to-id]` attribute.
pjax.defaultMainId = 'mainView'

You can prevent page scroll by adding the data-noscroll attribute to a link:

<a href="/other-page" data-noscroll>clicking me doesn't scroll the page!</a>

If you want an individual link without pjax behaviour, add the data-no-pjax attribute:

<a href="/other-page" data-no-pjax>I have native behaviour!</a>

By default, links to the same page are ignored. If you want to force a page refresh, add the data-force-reload attribute. This reload doesn't affect scroll position:

<a href="/current-page" data-force-reload>I sneakily refresh the page when clicked!</a>

Methods

simple-pjax exports one simple method that sneakily refreshes the current page, as if you clicked a <a> leading to this page. The refresh is done through pjax and doesn't destroy the JS runtime and other assets. It also doesn't affect scroll position.

import pjax from 'simple-pjax'

pjax.reload()

Gotchas

You need to watch out for code that modifies the DOM on page load. Most websites have this in the form of analytics and UI widgets. When transitioning to a new page, that code must be re-executed to modify the new document body.

simple-pjax mitigates this in two ways.

First, it automatically executes any inline scripts found in the new document. If you embed analytics and DOM bootstrap scripts inline, they should work out-of-the-box.

Second, it emits two document-level DOM events, before and after the transition. Use them to perform any necessary DOM mutations or cleanup. Example:

document.addEventListener('simple-pjax-after-transition', () => {
  // perform DOM mutations
})

document.addEventListener('simple-pjax-before-transition', () => {
  // perform cleanup
})

ToDo

Investigate if it's possible to get the final URL of an XHR after a server redirect without using responseURL, which is still not supported in Safari 8.0 and IE 11.