JavaScript Mock Single Page App Script

Just to explain this tutorial a bit more in detail, I'm writing it up. Today, I am sharing with you all a script that I use on a few of my projects. It basically makes a single page application out of a staticly generated website. Interesting fact, any projects this script is used on will keep the ability to be indexed by search engines. Also, resources that are used on every single page of the application will only be loaded ONCE...yeah, less bandwidth usage, smoother page transition, and an overall better experience. I see no loss here, but that's just my opinion. Time to move into the actual tutorial!


For those of you that had not watched the video, or cannot, I shall state that the finished product of this tutorial is ready for production-level projects. For instance, I use it on my portfolio! Yes, this one!

Dependencies:

The Script:

let dom = document.implementation.createHTMLDocument()

if('content' in dom.createElement('template')) {
    let startMockSPA = () => {
        let links = document.querySelectorAll('a')

        for(var i = links.length;i--;) {
            if(links[i].hostname === location.hostname) {
                page(links[i].pathname, (ctx) => {
                    axios.get(ctx.pathname)
                        .then((response) => {
                            let diffPage = dom.createElement('template')
                            diffPage.innerHTML = response.data

                            document.getElementById('app').innerHTML = diffPage.content.getElementById('app').innerHTML

                            startMockSPA()
                        })
                })

                links[i].addEventListener('click', (e) => {
                    e.preventDefault()

                    page(e.target.pathname)
                }, false)
            }
        }
    }

    startMockSPA()
    page()
}

This is written in ES6, but even an ES5 version is extremely short in length. Let me explain this script in chunks:

Our Virtual DOM:
let dom = document.implementation.createHTMLDocument()

Simply put, we're creating a new DOM to hold all requested documents.

Browser Test:
if('content' in dom.createElement('template')) {
    ...
}

Just seeing if the client's browser supports the template tag. If not, the fallback is to use the standard static navigation.

let startMockSPA = () => {
    let links = document.querySelectorAll('a')

    for(var i = links.length;i--;) {
        if(links[i].hostname === location.hostname) {
            ...
        }
    }
}

This chunk does the following:

  1. Create a function called startMockSPA, we'll need to call this every page transition.
  2. Grab all the links on the page, and cycle through them.
  3. Check if the current link is internal (our website), if not, skip it.
page(links[i].pathname, (ctx) => {
    ...
})

links[i].addEventListener('click', (e) => {
    e.preventDefault()

    page(e.target.pathname)
}, false)

Using PageJS, define what happens when a page is accessed using the pathname of the current link. After doing that, prevent the default action of the internal link, and access the page using PageJS.

Grab Requested Page:
axios.get(ctx.pathname)
    .then((response) => {
        let diffPage = dom.createElement('template')
        diffPage.innerHTML = response.data

        document.getElementById('app').innerHTML = diffPage.content.getElementById('app').innerHTML

        startMockSPA()
    })

This chunk does the following:

  1. Uses the accessed page's pathname to request the new page.
  2. Place the response's data into a template to prevent resources from loading.
  3. Set visual DOM's HTML to that of our virtual DOM's
  4. Finally, call our function again to restart the process.
Begin From the End:
startMockSPA()
page()

We need to initially call our created function, then call page() after. PageJS requires pages to be defined prior to starting. Make sure to start PageJS or some functionality will not work.

Conclusion:

To sum it all up, this script creates a lighter application for both the client and the server. I see no downside here, yet someone will probably find one. I have had no issues with this script yet, but if I do, I will update this information promptly.