ライブラリやフレームワークなしでMPAをSPA化する

はじめに

普段、Web Appを作成する時FrontendにはNext.jsを用いて作っているのだが

Next.jsのようなフレームワークやライブラリを使うほどでもない、小さなサイトを作ることになった

普通に作ってもいいのだがやはりMPAなのでページのロードが気になる、そこで今回はフレームワークやライブラリなしで

SPAを作ろうと思う

info

SPAと書いているが、今回はページのロードを無くしたいだけなので厳密にはSPAではないかもしれない


どのように作るか

通常ページ間を移動する場合<a></a>タグを使うはずである

その場合

ロードが入り

ページが代わり

アドレスバーに表示されているURIも変わる

つまりそれをJSで再現すれば良いわけだ


具体的には

fetchして

DOMを入れ替えて

アドレスバーのアドレスを変える

以上のことをすればSPAができる



作ってみる

まずはベースとなるClassを作る

export class Router {
    constructor() {
        this.pages = {};
    }
}

次にページをロードする関数を書いて

/**
 * @param {string} href
 * @returns string
 */
async loadPage(href) {
    if(this.pages[href]) {
        return this.pages[href]
    }
    const res = await fetch(href);
    const body = await res.text();
    this.pages[href] = body
    return body
}

info

一度読み込んだページはthis.pagesにキャッシュして、再度読み込む、なんて無駄を無くしている

Domを入れ替えるコードと

/**
 * @param {string} html
 */
const WriteDoc = (html) => {
    const doc = (new DOMParser).parseFromString(`${html}`, 'text/html');
    document.querySelector("body").innerHTML = doc.documentElement.innerHTML;
}

アドレスバーを変えるコード

/**
 * @param {string} href
 */
async historyPush(href) {
    history.pushState(href, href, href)
}

最後にそれを合わせた関数を書いてほぼ完成である

/**
 * @param {string} href
 */
async replacePage(href) {
    const html = await this.loadPage(href)
    WriteDoc(html)
    this.historyPush(href)
}

あとは一連の動作を<a></a>タグを押した時に実行されるようにして、完成である

/**
 * @param {string} a
 * @param {string} b
 * @returns boolean
 */
const SomeOrigin = (a, b) => (new URL(a)).origin == (new URL(b)).origin
/**
 * @param {Event} e
 * @param {(href: string)=> void} handler
 */
async onEvent(e, handler) {
    const eName = e.srcElement.nodeName
    if (eName === "A") {
        const targetHref = e.srcElement.href
        if (SomeOrigin(targetHref, window.location.href)) {
            handler(targetHref)
        }
    }
}

async init() {
    window.addEventListener("click", (e) => {
        this.onEvent(e, (href)=> {
            e.preventDefault();
            this.replacePage(href)
        })
    })
    window.addEventListener("popstate", (event) => {
        this.replacePage(document.location.href)
    });
}

これで以下のようなjsを書けばMPAがSPAふうになる

const router = new Router()
router.init()

事前読み込みとか

上の実装では<a></a>タグを押した時に読み込みが始まるが、やはりSPAの醍醐味と言えば事前読み込みなどがあるだろう、ここまでできれば、それも数行で実装できる

window.addEventListener("mousemove", (e)=> {
    this.onEvent(e, (href)=> {
        this.loadPage(href)
    })
})

上のコードをinit関数に追加するだけで<a></a>タグをホバーした時にページの読み込みが開始されるようになった

このドキュメントどう?

emoji
emoji
emoji
emoji