[Sapper] export で 完全静的サイトを作る [Svelte]

<img width="606" alt="Sapper の Hello World のおっちゃん" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418007/6c50feaf-58f6-8988-b6f8-c5c1b0cdcd2a.png">
( Sapper のクセの強い Hello World 画面 )

<br>
これから来るとか来ないとか、海外ではもう来てるとか言われている web フロントエンド JS のフレームワーク（コンパイラ）の **Svelte**。
今回はその Svelte を Web アプリケーション制作用途に拡張するフレームワーク、**Sapper** のお話。

<br>
{参考}

-  [最近名前をよく見かけるsvelte/sapperを試してみた ~その1 導入編~](https://qiita.com/inagacky/items/bac20282d44737e9740f)
-  [Vue使いなら秒で覚えられるSvelte入門](https://qiita.com/POPOPON/items/3522c48765b6e9439bde)

#静的ジェネレート

SSR メインなフレームワークで作ったサイトを、 Netlify 等の静的ファイルホスティングサービスで運用する、すなわち **JAMstack** な構成をとりたいとき、全ページを事前に静的ジェネレート（プリレンダリング)する必要がある。
Nuxt.js で言えば `nuxt generate` 、Next.js で言えば `next export`が該当する。

Sapperでは `sapper export` がそれに該当する。

# 問題点 ： ページ遷移時にAPIリクエストが走る
※エラーハンドリングは省略

```vue:/src/routes/example.svelte

<script context="module">
	export async function preload() {

		const res = await this.fetch('https://your-api-endpoint-here.com/');
		const data = await res.json();

		if (res.status === 200) {
			return { post: data };
		}
	}
</script>

<script>
	export let post;
</script>

{@html post.content}
```

上記のような svelte を `sapper export` し、トップページにアクセス後にこの svelte に対応するページ( /example )に aタグ遷移してみると、**クライアントサイドから API サーバーへの fetch が発生**してしまう。

すなわち`preload`関数（ Nuxt.js でいう `asyncData` / Next.js でいう`getInitialProps` ）がそのままクライアントサイドで実行される。
※ Sapper では aタグはデフォルトで History API を使った遷移になる

APIサーバーがあまり強くないときや、リミットが設けられている等、この仕様はいただけない、**完全静的にしたい**と感じた時に次の方法で解決できる。

# 解決策 : API 通信にプロキシを用意する

`this.fetch` の通信先をいきなり API サーバーにするのではなく、一旦ローカルに向ける。

```vue:/src/routes/example.svelte
<script context="module">
	export async function preload() {
        //ここでいきなり https://~~ ではなく、ローカルを参照する
		const res = await this.fetch('api.json');
		const data = await res.json();

		if (res.status === 200) {
			return { post: data };
		}
	}
</script>
```

そしてこのローカルに向けた先に

```js:/src/routes/api.json.js

import fetch from 'node-fetch'

export async function get(req, res) {
    fetch(`https://your-api-endpoint-here.com/`)
        .then(response => response.json())
        .then(json => {
            res.writeHead(200, {
                'Content-Type': 'application/json'
            });
            res.end(JSON.stringify(json))
        })
}

```

と、APIサーバーと通信して内容を返してくれる js を設置する。
（通信ライブラリに node-fetch を使用しているが、似たようなものであれば何でも良いと思う）

この状態で `sapper export` すると、APIサーバーからのレスポンス内容が静的ファイル( json )としてしっかり書き出され、ページ遷移時もこのローカル json を参照するようになる。

<img width="293" alt="api.jsonが存在する" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/418007/a144b002-0772-33bd-5ee8-eee2ffd935db.png">


#なぜ

`sapper export` したときのメカニズム
[Docs • Sapper #How it works](https://sapper.svelte.dev/docs#How_it_works)

- `sapper build` して出来上がったプロダクションファイル（この段階ではまだ SSR ）を**クロール**し、ルートから全てのaタグを巡ってキャプチャーし、静的ファイルにする。
- `preload` 関数中の `this.fetch` の先すらもキャプチャーして静的ファイルにする。

###ただし
[Is it possible to run preload once for completely static html generate when "sapper export" was used? · Issue #972 · sveltejs/sapper](https://github.com/sveltejs/sapper/issues/972#issuecomment-548808240)
 >If preload is calling this.fetch and that's getting a file at a local path, that response will also be saved when export is crawling the site. If the data is coming from a remote server, you can make a local endpoint that proxies to the remote endpoint, and have this.fetch request that.

静的ファイルにするのはローカルパスのみ。
なので `this.fetch` にリモートパスを直接書くのではなく、一旦ローカルパスにして、プロキシすると、プロキシした内容をキャプチャーして静的ファイルが生成される、という仕組み。

#☕

この Svelte / Sapper は Vue / Nuxt をやった事がある人ならかなりとっつきやすいと感じました。
学習面で言えば[公式チュートリアル](https://svelte.dev/tutorial/basics)が充実しているのも魅力です。
個人的に Sapper の aタグを力技で全力キャプチャーする仕組みは気に入りました。
ルート指定しなくてもいいので楽ですね。

$Thank$ $You$.