あらすじ
とあるプロジェクトが忙しい峠を越えて運営が少し落ち着いてきたころに、サービスの読み込み速度が遅いな~~~って思っていたことが多々ありました。
CSS、その他編も別で書いていきたいと思うけど、まずはJS編ということでwebサービスの読み込み改善を行ったときの備忘録書きます。
この時は開発環境での実験になるんで、本番にも反映したいなーと思っている次第です。
※備忘録なのでかなり読みにくいと思います。ごめんなさい。
環境
webpack 4.29.6
webpack-cli 3.3.0
react 16.8.6
目標
jsファイルの読み込みで、読み込み速度の遅いファイルは圧倒的に bundle.js の読み込みが遅いため、ファイルを軽くしたい
(現状はentryしているものをビルドするとbundle.jsを自動生成しているだけ)
また、他にもcdnから読み込んでいるjsファイルだったり、他にも読み込み速度を阻害しているファイルがあるかチェック⇒あれば対応
※いずれも開発環境で実験
※ある程度webpackが理解できた前提で記載します
現状はどうなっているか
開発環境での検証のため、minifyなし、logも出てます。
スーパーリロードしてbundle.jsのみ確認してみますと
bundle.js
size 12.6MB time2.73s
3秒かかるんですね。。調子悪いともっとかかります。
全ての読み込みが終了するまで約20秒。ユーザーからするとかなり不快。
通常リロードでも16秒。
cdnで読み込んでいるjsファイルはそこまで重くなかったですが、一応チェックします。
解決策の模索
いらないモジュールは削除
特にいうことは無いですがbundle.jsで使用してないモジュールは削除。
開発当初使う予定だった、使っていたが使わなくなったものは削除していきました。
言わずもがなですが一応。
(このときはjqueryやTwitter関連のjsファイルやモジュール)
scriptタグにdeferを追加
bundle.jsとは分離しているjsファイルの読み込みはindex.htmlにて読み込んでます。
このscriptタグで呼ばれているjsファイルにdeferを追記します。
<script src="https://cdnjs.cloudflare.com/ajax/libs/test/test.js" defer></script>
scriptタグにasyncやdeferを付けない場合、
HTMLパースを不必要に止めてしまうためデフォルトではない属性(HTMLパースを阻害しない属性)を追加する必要がありました。
今回の場合、主にbundle.jsが原因で読み込みが遅くなっていることはあるものの、少しでも読み込みを高速化するべく対応します。
今回deferで対応したのはasyncの場合、読み込み実行順序が不定のためです。(deferは順番に読む)
詳しくは参考文献に記載しておきます。
webpackのsplitChunksを使う
以前CommonsChunkPluginだったものです。CommonsChunkPluginがwebpack4で廃止され、splitChunksが登場したということですね。
splitChunksはoptimizationプロパティにつけれられるもので、
複数のエントリーポイントで共有のモジュールを使っている場合、その共通モジュールだけ分離して(chunkに分離)設定するものです。
また、共通ファイルは共通なのでキャッシュからも読み込んでくれます。
このプロジェクトではReactを使っていますが、いたるところで呼び出していたjsファイルでReactがバンドルされていた、つまり重複してたところを共通化してくれるというわけですね。
webpackにはこのように記載しました。
/*一部省略*/
// モジュールの分割
const SPLIT_CHUNKS = {
// 分離されて生成される chunk の名前(任意の名前)
name: 'vendor',
// 対象とするチャンク(chunk)に含めるモジュールの種類
chunks: 'initial', // または 'all'
}
module.exports = {
// 環境によってentryを変える
entry: IS_DEVSERVER
? [
'react-hot-loader/patch',
`webpack-dev-server/client?http://localhost:${ PORT }`,
'webpack/hot/only-dev-server',
`${ SRC }/index.js`,
]
:
{
'bundle': `${ SRC }/index.js`,
},
output: {
path: DIST,
publicPath: '/',
filename: IS_DEVSERVER ? `bundle.js` : `[name].js`,
},
resolve: {
alias: ALIAS,
extensions: ['.js'],
},
stats: {
colors: true
},
module: {
rules: [
{
test: /\.js$/,
exclude: [/node_modules/],
use: {
loader: 'babel-loader'
}
},
{
// test: /\.scss$/,
test: /\.(sa|sc|c)ss$/,
loader: !IS_DEVSERVER ?
[
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {
url: false
}
},
'postcss-loader',
'sass-loader'
]
:
[
MiniCssExtractPlugin.loader,
'css-loader?url=false',
'postcss-loader?sourceMap=inline',
'sass-loader'
]
},
]
},
plugins,
devServer: {
contentBase: DIST,
historyApiFallback: true,
host: 'localhost',
port: PORT,
inline: true,
hot: true,
disableHostCheck: true,
},
},
optimization: {
minimizer: MINIMIZER,
splitChunks: SPLIT_CHUNKS // 追記
},
performance: {
hints: false
}
}
これでvendor.jsとして共通モジュールを分割してくれます。
SPLIT_CHUNKSに記載しているchunksには3種類あり、
initial⇒初期リロードに必要な場合
all⇒testに含まれる(ここでいうnode_modules)全てを分離
async⇒import()を使ってダイナミックに使用する場合に分離
初期リロードだけでいいので今回はinitialを指定しています。
※allも試してみましたが、ビルドにめちゃくちゃ時間かかったのでやめました。allでもやりようによってはいいやり方があるかもしれませんが、今回はinitial。
オプションもたくさんあります。詳しくは公式をご覧ください。
結果
最初はスーパーリロードして読み込みます。
bundle.js
size 4.1MB time 826ms
vendor.js
size 8.5MB time 1.51s
bundle.jsもだいぶ読み込みがはやくなりました。
スーパーリロードで10秒
通常リロードで6秒で全読み込み環境してます。
(6秒でも遅いので、今度はcss、その他編でもっと軽くできると思う。カスタムフォント削るとか。)
cdnのほうはあんまり変わらなかったかも。。
bundle.jsがかなり読み込みの阻害をしていた感じでした。
これは本番環境でも取り入れていきたいです。