ライブラリやフレームワークなしで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>タグをホバーした時にページの読み込みが開始されるようになった
このドキュメントどう?