2019.6.28
静的サイトなんですが、一度アクセスした後はSPAのような感覚でページ遷移するようなサイトを作りたくてCode Splittingを実装したのでその作業ログです。
具体的には、S3などの静的サイトホスティング上で配信するサイト上で、hoge.com/page1
のようなサブページにアクセスした後もSPAのような感覚でページ遷移できるようにしたいです。
各ページで必要なjsファイルだけとってきてページ遷移するときに次のページで必要なjsファイルだけとってくるような構成を作ってみたかったので、やってみました。最初のアクセスでページのロード時間を短くしたいのです。スマホの回線環境とかだとSPAを開こうとしたときにたくさんページがある場合に初期表示に不要なファイルのロードも走るので時間がかかってしまいます。それを解消するための手法としてCode Splittingがあるので試してみました。
成果物のソースコードはこちらに置きました。
フローとしては以下のイメージ。
↓この画像がよくわかります。(ちゃんと理解するCode Splitting より抜粋)
ベストプラクティスがわからなかったんで、Reactの公式サイトを真似てみました。
https://reactjs.org/docs/code-splitting.html
React.lazyよりもLoadable Componentsを使ったほうがサーバサイドレンダリングができて良いのでそっちを選びました。
↓こんな感じでコンポーネントを読み込めば別々のjsファイルとしてバンドルしてくれます。
import loadable from '@loadable/component';
const Home = loadable(() => import('./pages/Home'));
const Page1 = loadable(() => import('./pages/Page1'));
const Page2 = loadable(() => import('./pages/Page2'));
Routingにはreact-router-domを選びました。3年前にreact-routerを使ってたので浦島太郎状態でした。quick startを見ながら実装しました。
↓こんな感じにしました。実際のコードはこちらです。
ReactDOM.render(
<>
<h1>hello</h1>,
<Router>
<nav>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/page1">Page1</Link>
</li>
<li>
<Link to="/page2">Page2</Link>
</li>
</ul>
</nav>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/page1" component={Page1} />
<Route path="/page2" component={Page2} />
</Switch>
</Suspense>
</Router>
</>,
document.querySelector("#app")
);
/page2
)に直接アクセスできない問題localhost:8888/page2
とかにアクセスすると404が返ってきてしまう問題に当たりました。
色々調べて、このサンプルにいきつきました。
https://github.com/jeantimex/react-webpack-code-splitting
webpackの設定を自分のプロジェクトと比べてみると一つそれっぽいところをみつけました。devServer.historyApiFallback
のdisableDotRule
をtrueにしたらいけました。
https://webpack.js.org/configuration/dev-server/#devserverhistoryapifallback
When using dots in your path (common with Angular), you may need to use the disableDotRule
パスでドット?どういうことだろう…?
ただ、この方法だとawsのS3や静的サイトのホスティングサービスなんかでは使えません。なのでページごとのhtmlを用意してやる必要があります。
devServer.historyApiFallback
を使わない方法を探してみるとHtmlWebpackPluginを使ってページごとのhtmlを生成する方法を見つけました。
HtmlWebpackPluginはWebpackのビルド時にhtmlも生成するプラグインです。Webpackでバンドルしたjsファイルを読み込むコードを注入してくれます。
これを使えば以下のような感じでページごとにディレクトリを切ってhtmlを出力できます。
index.html
page1/index.html
page2/index.html
インストール
npm i -D html-webpack-plugin
webpack.config.js
には下記のようにします。
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: `${__dirname}/src/index.html`, // テンプレートとなるhtml
filename: "index.html", // 出力ファイル名
inject: "body" // bodyの末尾にscriptタグを挿入
}),
new HtmlWebpackPlugin({
template: `${__dirname}/src/index.html`,
filename: "page1/index.html",
inject: "body"
}),
new HtmlWebpackPlugin({
template: `${__dirname}/src/index.html`,
filename: "page2/index.html",
inject: "body"
})
]
}
テンプレートとなるhtmlを用意します。今回は./src/index.html
をテンプレートとして各ページのhtmlを出力します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
これが出来上がったhtmlです。body
の末尾にjsファイルを読み込むコードが注入されています。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Hello</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="index.js"></script></body>
</html>
Code SplittingができたSPAができました。