Pacharapol Withayasakpunt Pacharapol Withayasakpunt
Mon 2 March 2020

SPA with history mode router in vanilla JS (with potential for SSG)

I don't know non-JavaScript-based Static Site Generators do it, but JS-based, like Gatsby, Nuxt, or Gridsome can prevent reloading and show transition between pages on location.pathname changes.

True SPA like Vue can do it too, but on first load, you can hasten JavaScript by using prerender-spa-plugin.

I have just create an SPA with history mode router in vanilla JS, but I do use a bundler (Rollup).

patarapolw/minimal-rollup-ts-pug-sass-template

patarapolw/minimal-rollup-ts-pug-sass-template

Rollup + TypeScript + Pug + SASS template with no plan for JavaScript frameworks, whatsoever - patarapolw/minimal-rollup-ts-pug-sass-template

BTW, I cannot just use Parcel, because I want to enable it for Electron as well. (But Electron shouldn't use history mode.)

Why templating engine like Pug

Because I feel like it will minify HTML by default. Also, Pug is defaulted to be very tidy, with no burden of the closing tag.

Of course, you can use other templating engines, like EJS as well.

Meta tags

Meta tags for Google, Facebook, and Twitter are as follow.

    meta(http-equiv="Content-Type", content="text/html;charset=UTF-8")
    meta(name="viewport", content="width=device-width, initial-scale=1.0")

    meta(name="description", content=description data-meta="description")
    meta(name="keywords", content=keywords data-meta="keywords")

    meta(property="og:title" content=title data-meta="title")
    meta(property="og:description" content=description data-meta="description")
    meta(property="og:image" content=image data-meta="image")
    meta(property="og:url" content=url data-meta="url")

    meta(property="twitter:title" content=title data-meta="title")
    meta(property="twitter:description" content=description data-meta="description")
    meta(property="twitter:image" content=image data-meta="image")
    meta(property="twitter:card" content="summary_large_image")

    link(rel="shortcut icon", href=`${favicon || 'favicon.ico'}`, type="image/x-icon")

    title(data-title=title data-meta="title")= title

I put data-meta and data-title, in case I need to edit it from JavaScript, so it is as simple as document.querySelectorAll('[data-meta=...]').

Module and nomodule

By default, Rollup (and Snowpack) specializes in the newer ES module, but nomodule fallbacks to SystemJS for lazy-loading.

    script(src="module/index.js" type="module")
    script(src="nomodule/index.js" nomodule)

Forgot to mention that SPA router uses lazy loading for faster loading time, and load only what is needed.

Custom elements: <app-router> and <a is="router-link">

<a is="router-link"> is to provide convenience for creating a href that also have base URL and hash sign.

<app-router> might not need to be made custom element, because it is always singleton anyway; but I use it to make the element class-based.

popstate event

It is the event for page navigate that may have "state" in case HTML5 History is manipulated.

It can be superficially triggered by window.dispatchEvent(new PopStateEvent('popstate')), which is the basis of navigateTo function.

export function navigateTo (to: string) {
  if (ROUTER_MODE === 'history') {
    history.pushState({ to }, '', to)
    window.dispatchEvent(new PopStateEvent('popstate', { state: { to } }))
  } else {
    location.replace(to)
  }
}

spa-rendered event and data-spa-rendered attribute

This is to indicate that the SPA has finished loading and is now ready to be scraped by Puppeteer to create a multiple-page website.

I will try using it extensively, to see if it is any good.

patarapolw/minimal-rollup-ts-pug-sass-template

patarapolw/minimal-rollup-ts-pug-sass-template

Rollup + TypeScript + Pug + SASS template with no plan for JavaScript frameworks, whatsoever - patarapolw/minimal-rollup-ts-pug-sass-template