3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Cloud Run上でLaravel×React(Mix使用)のサイトを爆速で動かす - ReactをCDN(Cloud Storage)にデプロイする

Last updated at Posted at 2020-09-07

前回は、Laravelを最適化した上でCloud Runにデプロイする方法について説明しました。
これにより、バックエンドは高速に動作するようになったハズです。
次はフロント側(React)について考えていきます。

バンドルしたjsファイルをCloud Storageにアップしてパフォーマンスを改善する

'Laravel-mix'はとても優れたライブラリだと思っています。通常であれば煩わしいTypescriptやReactのコンパイルなどの設定をラップして、最適化までしてくれるからです。しかし、デフォルトでは、Laravel-mix でコンパイルしたassetファイルはプロジェクト内に保管されます。
minifyされているとはいえ、Webサイトにアクセスすする度にバンドルされたapp.jsapp.cssCloud Runからダウンロードすることになり、最終的にはフロントの規模が大きくなった時に思ったようなパフォーマンスを出すことはできなくなります。
この部分を解消するため、Laravel-mixで生成したassetファイルは是非とも、CDNにおきたいところです。

ではこのために、どのようにすれば良いか説明していきます。

laravel-mixを使ってReactを有効にする

Cloud Runに関する説明とは逸れてしまいますが、一応、説明しておきます。

laravel/uiをインストールし、必要な設定ファイルを追加・更新する

まずはローカルで動作確認をしていきましょう。必要に応じて、ボリュームをマウントしたり、VSCodeのリモートコンテナを使ったりすると、どのようにファイルが更新されるかわかりやすいと思います。

(例)ホストのディレクトリをマウントして実行する

docker run --volume $PWD/app:/var/www/html  -p 8080:80 -d cloud-run-demo

では、前回の記事で作成したDockerコンテナ内で下記のコマンドを実行していきます。

/var/www/html# composer require laravel/ui
/var/www/html# php artisan ui react
/var/www/html# npm install

これで基本的にReactはインストールされるのですが、せっかくなのでTypescriptも導入します!
型の定義をいちいちしなければならないなど、ちょっとしんどいですが変数に何が入っているか、何を入れれば良いかが明確にわかるので弊社では重宝しています!

まずは同じくDockerコンテナ内で、下記のコマンドを実行します。

/var/www/html# npm add -D typescript @types/node @types/react @types/react-dom

appディレクトリの配下に、tsconfig.jsonを作成します。弊社の場合は下記のように書いています。

{
    "compilerOptions": {
        "jsx": "react",
        "outDir": "./built/",
        "sourceMap": true,
        "strict": true,
        "noImplicitReturns": true,
        "noImplicitAny": true,
        "module": "es2015",
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true,
        "moduleResolution": "node",
        "target": "es5",
        "allowSyntheticDefaultImports": true,
        "lib": [
            "es2016",
            "dom"
        ]
    },
    "include": [
        "resources/assets/ts/**/*"
    ]
}

仕上げにwebpack.mix.jsを下記の通りに書き換えてあげましょう。

const mix = require("laravel-mix");

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.ts("resources/assets/ts/app.tsx", "public/js")
    .sass("resources/assets/sass/app.scss", "public/css")
    .options({
        processCssUrls: false
    })
    .webpackConfig({
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    loader: "ts-loader",
                    exclude: /node_modules/
                },
                {
                    test: /\.js?$/,
                    exclude: /node_modules/,
                    use: {
                        loader: "babel-loader",
                        options: {
                            presets: ["@babel/preset-env", "@babel/react"]
                        }
                    }
                },
                {
                    test: /\.s[ac]ss$/i,
                    use: [
                        {
                            loader: "resolve-url-loader",
                            options: {}
                        },
                        {
                            loader: "sass-loader",
                            options: {
                                sourceMap: true
                            }
                        }
                    ]
                }
            ]
        },
        resolve: {
            extensions: ["*", ".js", ".jsx", ".ts", ".tsx"],
            alias: {
                react: path.resolve("./node_modules/react")
            }
        }
    });

ここまでで設定ファイルの変更は完了です。次からは実際に表示をさせてみましょう!

サンプル画面を表示する。

弊社の場合、ディレクトリ構造が複雑になりそうだったので、resources配下にassetsというディレクトリを作ってその下にファイルを作成しています。手順は以下の通りです。

  1. デフォルトではresources/js配下にあったファイルをresources/assets/tsに移動する。
  2. 同じくデフォルトではresources/sass配下にあったファイルをresources/assets/sassに移動する。
  3. Example.jsの名前をExample.tsxに変更する
  4. app.jsの名前をapp.tsxに変更する

対応はこの程度です。今回、ファイル内容の変更は行っていません。ディレクトリ の見た目は下記のようになるはずです。

image.png

最後に、bladeの記述を変更します。

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Laravel</title>
        <link rel="stylesheet" href="{{mix('css/app.css') }}">
    </head>
    <body>
        <div class="flex-center position-ref full-height">
            <div id="example"></div>
        </div>
        <script src="{{ mix('js/app.js')}} "></script>
    </body>
</html>

ポイントは

<link rel="stylesheet" href="{{mix('css/app.css') }}">

であったり、

<script src="{{ mix('js/app.js')}} "></script>

でバンドルしたcssjsを読み込むようにしていることです。

では、早速コンパイルして表示してみましょう!!コマンドは下記の通りです。

npm install && npm run dev

:bangbang:うまくいかない場合は、下記のコマンドでキャッシュしたviewファイルをクリアしてください。

/var/www/html# php artisan view:clear

エラーなくコンパイされていればapp/public/jsapp/public/css配下にファイルが作られているはずです。
image.png

そしてローカルで下記の画面が表示されればOKです!
image.png

長くなってしまいましたが、Laravel-mixを使ってReactをインストールすることができました!

Cloud Strageにアップするためのwebpackの書き方

次はいよいよ本題、生成したjscssをCloud Strageにアップする手順を紹介します。

webpack-google-cloud-storage-pluginをインストールする

Dockerコンテナ内で下記のコマンドを実行すればOKです。

npm install --save webpack-google-cloud-storage-plugin

webpack.mix.jsを修正する

webpack-google-cloud-storage-pluginに関する処理を追加します。mix.inProduction()の判定がありますが、これは

npm run prod

の時だけ、このプラグインを使いたいための判定です。(ローカルでのテストの時にはもちろん実行したくないですからね。)

const mix = require("laravel-mix");

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

 //ここを追加!
const WebpackGoogleCloudStoragePlugin = require("webpack-google-cloud-storage-plugin");

const plugins = [];
if (mix.inProduction()) {
    mix.version();
    plugins.push(
        new WebpackGoogleCloudStoragePlugin({
            directory: "public",
            include: ["app.js","app.css"],
            exclude: ["images"],
            storageOptions: {
                keyFilename: process.env.MIX_CLOUD_STORAGE_CREDENTIAL_FILE
            },
            uploadOptions: {
                bucketName: process.env.MIX_PUBLIC_BUCKET_NAME,
                destinationNameFn: file => path.join("", file.path),
                gzip: true,
                makePublic: true,
                resumable: true,
                concurrency: 5
            }
        })
    );
}
//追加ここまで

mix.ts("resources/assets/ts/app.tsx", "public/js")
    .sass("resources/assets/sass/app.scss", "public/css")
    .options({
        processCssUrls: false
    })
    .webpackConfig({
        module: {
            rules: [
                {
                    test: /\.tsx?$/,
                    loader: "ts-loader",
                    exclude: /node_modules/
                },
                {
                    test: /\.js?$/,
                    exclude: /node_modules/,
                    use: {
                        loader: "babel-loader",
                        options: {
                            presets: ["@babel/preset-env", "@babel/react"]
                        }
                    }
                },
                {
                    test: /\.s[ac]ss$/i,
                    use: [
                        {
                            loader: "resolve-url-loader",
                            options: {}
                        },
                        {
                            loader: "sass-loader",
                            options: {
                                sourceMap: true
                            }
                        }
                    ]
                }
            ]
        },
        resolve: {
            extensions: ["*", ".js", ".jsx", ".ts", ".tsx"],
            alias: {
                react: path.resolve("./node_modules/react")
            }
        },
        plugins: plugins//ここも追加しています。
    });

Cloud Storageにバケットを作る

もちろん、これがないと始まりませんね。GCPでバケットを作ったら、その名前を控えておきましょう。
また、サービスアカウントキーも必要となりますので、こちらのページを参考にJSONファイルをダウンロードしてapp直下に保存してください。

アップするための情報を.envに追加する

.envファイルに下記の記述を追記します。

# Bundleされたファイルの配置バケット
MIX_PUBLIC_BUCKET_NAME="joolen-cloud-run-example"
# デプロイする時に利用するCloud StorageのCredentialファイル名
MIX_CLOUD_STORAGE_CREDENTIAL_FILE="your-service-account.json"

ローカルでnpm run prod!

ここまで対応したら、

npm run prod

を実行しましょう。エラーなく処理が完了したらCloud Storageには下記のようなディレクトリやファイルができるはずです。

image.png

公式ページを参考にしてapp.jsapp.cssを公開しましょう。
image.png

これで、laravel-mixで作成したjscssの準備もできるようになりました:tada:
ちなみに、公式ページに記載がありますが、

一般公開で閲覧可能なオブジェクトはデフォルトで Cloud Storage ネットワークにキャッシュされるので、特に設定しなくても、Cloud Storage は基本的にコンテンツ配信ネットワーク(CDN)のように機能します。

とのことです。これはありがたいですね。

Cloud RunにデプロイするためのDockerfileを修正する。

ここまで準備が整ったら、Dokcerfileの修正は簡単です。下記の1行を追記しましょう。

RUN npm install && npm run prod

最終的なDockerfileは下記の通りになります。

FROM php:7.4-apache
WORKDIR /var/www/html

# 必要最低限のPHP拡張とNode.jsのインストール
RUN curl -sL https://deb.nodesource.com/setup_12.x | bash -
RUN apt-get update \
  && apt-get install -y libzip-dev libpq-dev mariadb-client unzip git libfreetype6-dev libjpeg62-turbo-dev libpng-dev \
  && docker-php-ext-configure gd --with-freetype=/usr/include/ --with-jpeg=/usr/include/ \
  && docker-php-ext-install zip pdo_mysql mysqli gd exif opcache \
  && a2enmod rewrite \
  && apt-get -y install vim \
  && apt-get install -y nodejs \
  && git clone https://github.com/phpredis/phpredis.git /usr/src/php/ext/redis \
  && docker-php-ext-install redis 

EXPOSE 80

ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin
ENV APACHE_LOG_DIR /var/log/apache2

# composerをインストール
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
COPY ./apache/*.conf /etc/apache2/sites-enabled/
COPY ./php/php.ini /usr/local/etc/php/php.ini
COPY ./app /var/www/html

# composer install を高速化するためのライブラリをグローバルインストール
RUN composer global require hirak/prestissimo
RUN composer install --optimize-autoloader --no-dev
RUN php artisan key:generate  \
  && php artisan config:cache \
  && php artisan view:cache

# ReactのモジュールをコンパイルしてCloud Storageにアップロード
RUN npm install && npm run prod

RUN chmod 777 -R /var/www/html/storage/framework/

あとは前回と同様に

gcloud builds submit --config cloudbuild.json --timeout="30m" --ignore-file ".gcloudignore"

を実行してコンテナイメージを作成してコンテナレジストリにpushできます。

Cloud Runの環境変数を修正してjscssはCloud Storageを参照するように変更する

Laravelのコンフィグファイルを修正する。

app/config/app.phpに下記の1行を追加します。(場所はどこでも大丈夫です)
これにより、mix関数が生成するベースURLを変更することができます。

'mix_url' => env('MIX_ASSET_URL', null),

.envの環境変数にAsset_URLを追記する

下記のようにMIX_ASSET_URL追加します。URLには、バケットの公開URLのpublicまでです。(スラッシュを付けない)

# CDNに配置されたBundleファイルのURL
MIX_ASSET_URL="https://storage.googleapis.com/{your_bucket_name}/public"

view:cacheをしないのであれば、下記のようにCloud Runの環境変数に設定をすることもできます。

image.png

また、ローカルでReactをビルドしたときの名残でpublic配下にmix-manifest.jsonがあると思います。これも消してしましましょう!これがあると、参照すべきファイルを間違えてしまう可能性があります。

これで、app.jsapp.cssはCloud Storageにあるファイルを参照するようになります。
様々なライブラリを使うようになったりして、バンドルしたファイルが大きくなればなるほど、非常に効果のある対応です。
こちらも、弊社プロジェクトでは数百ms単位での効果があり、体感できるほどのパフォーマンス向上につながりました:tada:

今回も長文にお付き合い頂きまして、ありがとうございました!
サーバレスなLaravel & Reactで快適開発体験を体験してみてくださいね。:thumbsup:

次回はこれらの仕組みの実現に必要な設定やコストについて触れていきたいと思います。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?