この記事はWEBアプリケーションをNuxt.js実装し、CodeBuildでビルドしS3にアップロード、S3ではホスティングせずにCloudFront経由で読み込めるようにした実装を記録しています。データの受け渡し元APIはRailsにて実装していますが、そちらに関してはこの記事では言及しません。
環境
- APIサーバー
Ruby on Rails 5.2.3(APIモード)
Ruby 2.6.3
- SPAフレームワーク
Nuxt.js + Typescript
- lint
ESLint + Prettier
- ビルド
AWS CodeBuild
- ホスティング
S3 + CloudFront
- ログ
Sentry
アプリケーション本体実装(Nuxt.js)
Homebrewは入っているとして、nodebrewのインストール
brew update
brew install nodebrew
nodebrew setup
~/.bash_profileに下記を追加
export PATH=$HOME/.nodebrew/current/bin:$PATH
yarnをinstall
nodebrew install-binary stable
nodebrew use stable
nodebrew list
brew install yarn --ignore-dependencies
yarn -v
nuxt.jsのアプリを作成(プロジェクト名は[sample_nuxt_app])
いくつか質問されます。
$ yarn create-nuxt-app sample_nuxt_app
create-nuxt-app v2.8.0
✨ Generating Nuxt.js project in path_to_sample_nuxt_app
? Project name sample_nuxt_app
? Project description 説明文を入力
? Author name 著者を入力
? Choose the package manager Yarn
? Choose UI framework None(vuefyやbootstrapなど色々選べるが今回はデザインあるのでNoneを選択)
? Choose custom server framework None (Recommended)
? Choose Nuxt.js modules Axios
? Choose linting tools ESLintとPrettier
? Choose test framework Jest
? Choose rendering mode Single Page App(サーバーレンダリングも選べるが今回はSPA)
AxiosとLint, PrettierはSpaceKeyを押して選ばないと入らない。エンターキーだとスルーされる。ややこしい!
$ cd sample_nuxt_app
$ yarn dev
で一旦動く。
↓
typescript化
まず、nuxtのバージョンをあげたい。
package.jsonのなかみを新しくする。
$ ncu
ncuがはいってなければ
$ yarn global add npm-check-updates
はいったら
$ ncu
Checking path_to/sample_nuxt_app/package.json
[====================] 20/20 100%
@nuxtjs/axios ^5.3.6 → ^5.5.4
nuxt ^2.0.0 → ^2.8.1
@nuxtjs/eslint-config ^0.0.1 → ^1.0.1
@nuxtjs/eslint-module ^0.0.1 → ^0.2.1
babel-eslint ^10.0.1 → ^10.0.2
eslint ^5.15.1 → ^6.0.1
eslint-plugin-import >=2.16.0 → >=2.18.0
eslint-plugin-jest >=22.3.0 → >=22.7.2
eslint-plugin-node >=8.0.1 → >=9.1.0
eslint-plugin-nuxt >=0.4.2 → >=0.4.3
eslint-plugin-promise >=4.0.1 → >=4.2.1
eslint-plugin-vue ^5.2.2 → ^5.2.3
babel-jest ^24.1.0 → ^24.8.0
jest ^24.1.0 → ^24.8.0
vue-jest ^3.0.3 → ^3.0.4
nodemon ^1.18.9 → ^1.19.1
Run ncu -u to upgrade package.json
こうなるので問題なければ
ncu -u
書き換わったら、一回全部消して入れ直す。
rm -rf node_modules yarn.lock
yarn install
typescript本体
yarn add typescript ts-node @nuxt/typescript
設定ファイル作成・編集
nuxt.config.js → nuxt.config.ts
もともとがこれで↓
export default {
...中身
}
こうする↓
import NuxtConfiguration from '@nuxt/config'
const nuxtConfig: NuxtConfiguration = {
...中身
}
export default nuxtConfig
+その中身にこれを追加
build: {
extend(config, ctx) {
if (ctx.isDev && ctx.isClient) {
if (!config.module) return // undefinedの場合、pushせずにreturnするように追加
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
})
}
}
}
tsconfig.json
ECMAバージョン周りでエラーが出る場合があるので
{target: es2015, module esNext} とした
{
"compilerOptions": {
"baseUrl": ".",
"types": [
"@types/node",
"@nuxt/vue-app"
],
"paths": { "@/*": [ "./*" ], "~/*": [ "./*" ] },
"target": "es2015",
"strict": true,
"module": "esNext",
"moduleResolution": "node",
"experimentalDecorators": true
}
}
-
parserOptions
ESLintについてのところで記載 -
rules(コーディングルールになるので自由にどうぞ)
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 2 : 0,
'space-before-function-paren': [2, {
'anonymous': 'always',
'named': 'always',
'asyncArrow': 'always'
}],
'comma-dangle': ['error', 'only-multiline'],
}
vueのクラスコンポーネントで書くように変更
nuxtで拡張したものを使わず本家の方で問題ない
$ yarn add vue-class-component vue-property-decorator vuex-class
それぞれモジュールをtypescriptで記述
- index.vue
scriptの箇所を修正
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import Logo from '~/components/Logo.vue'
@Component({
components: {
Logo
}
})
export default class Index extends Vue {}
</script>
- logo.vue
script を追記
<script lang="ts">
import { Vue } from 'vue-property-decorator'
export default class Logo extends Vue {}
</script>
ESLintについてはこちら参照
$ yarn add -D @typescript-eslint/parser @typescript-eslint/eslint-plugin
.eslintrc.jsを修正
サイトにはこのように。
module.exports = {
root: true,
env: {
browser: true,
node: true
},
parserOptions: {
parser: '@typescript-eslint/parser',
sourceType: 'module',
project: './tsconfig.json',
ecmaFeatures: { "legacyDecorators": true }
},
extends: [
'@nuxtjs',
'plugin:nuxt/recommended',
'plugin:prettier/recommended',
'prettier/vue',
'prettier/@typescript-eslint'
],
plugins: [
'prettier',
'@typescript-eslint'
],
// add your custom rules here
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error"
}
}
他にもエラーあったのでこのようにした。
module.exports = {
root: true,
env: {
browser: true,
node: true,
commonjs: true,
es6: true
},
parserOptions: {
"parser": '@typescript-eslint/parser',
"sourceType": "module",
"project": './tsconfig.json',
"ecmaVersion": 2018,
"ecmaFeatures": {
"legacyDecorators": true,
},
},
extends: [
'@nuxtjs',
'plugin:nuxt/recommended',
'plugin:prettier/recommended',
'prettier/vue',
// 'prettier',
],
plugins: [
'prettier',
'@typescript-eslint'
],
// add your custom rules here
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "error",
'no-console': process.env.NODE_ENV === 'production' ? 2 : 0,
'comma-dangle': ['error', 'only-multiline'],
}
}
S3, CloudFrontの構築
公式に詳しく載ってる
S3
バケットを作成し、アクセス権限はprivateにしておく。
CloudFront
こちらに従って作成
他の参考サイト
エラーページは大事(らしい)
1. S3 バケットを作成する(プライベートバケットでOK)
バケット名をとっとくAWS_BUCKET_NAME="sample_bucket"
2. CloudFront distribution を作成する
distributionのIDを取得
AWS_CLOUDFRONT="UPPERCASEDID"
3. セキュリティアクセスを設定する
IAMのポリシーを作成、そのポリシーをアタッチしたユーザーを作成。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::bucket_name/*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObjectAcl",
"s3:GetObject",
"s3:AbortMultipartUpload",
"s3:DeleteObject",
"s3:PutObjectAcl",
"s3:ListMultipartUploadParts"
],
"Resource": "arn:aws:s3:::bucket_name/*"
},
{
"Sid": "VisualEditor2",
"Effect": "Allow",
"Action": [
"cloudfront:UnknownOperation",
"cloudfront:ListInvalidations",
"cloudfront:GetInvalidation",
"cloudfront:CreateInvalidation"
],
"Resource": "*"
}
]
}
AWSアクセスキーとシークレットをとっとく。
AWS_ACCESS_KEY_ID="key_id_value"
AWS_SECRET_ACCESS_KEY="secret_value"
ローカルDeploy用のスクリプトを作成
deploy.sh
#!/bin/bash
export AWS_ACCESS_KEY_ID="key_id_value"
export AWS_SECRET_ACCESS_KEY="secret_value"
export AWS_BUCKET_NAME="sample_bucket"
export AWS_CLOUDFRONT="UPPERCASEDID"
export AWS_DEFAULT_REGION="ap-northeast-1"
# nvm(node version manager)を読み込み、node(.nvmrc 内のバージョン)をインストールし、npm パッケージをインストールします。
[ -s "$HOME/.nvm/nvm.sh" ] && source "$HOME/.nvm/nvm.sh" && nvm use
# まだインストールされていない場合は npm をインストールする
[ ! -d "node_modules" ] && yarn install
yarn run generate
gulp deploy
- 権限変更
chmod +x deploy.sh
- .gitignoreに追加
echo "
# Don't commit build files
node_modules
dist
.nuxt
.awspublish
deploy.sh
" >> .gitignore
- yarnでgulpをインストール
yarn add --save-dev gulp gulp-awspublish gulp-cloudfront-invalidate-aws-publish concurrent-transform
yarn global add gulp
yarn add gulp (こっちもしないと使えなかった)
- gulpfile.js を作成(中身は上記nuxtの公式から。)
やってることとしては、 - yarnをインストール
- yarnでいろいろインストール
-
gulp-awspublish
でS3にアップロード。 -
gulp-cloudfront-invalidate-aws-publish
でCloudFrontのキャッシュを削除。
var gulp = require('gulp');
var awspublish = require('gulp-awspublish');
var cloudfront = require('gulp-cloudfront-invalidate-aws-publish');
var parallelize = require('concurrent-transform');
// https://docs.aws.amazon.com/cli/latest/userguide/cli-environment.html
var config = {
// 必須
params: {
Bucket: process.env.AWS_BUCKET_NAME
},
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
signatureVersion: 'v3'
},
// 任意
deleteOldVersions: false, // PRODUCTION で使用しない
distribution: process.env.AWS_CLOUDFRONT, // CloudFront distribution ID
region: process.env.AWS_DEFAULT_REGION,
headers: { /*'Cache-Control': 'max-age=315360000, no-transform, public',*/ },
// 適切なデフォルト値 - これらのファイル及びディレクトリは gitignore されている
distDir: 'dist',
indexRootPath: true,
cacheFileName: '.awspublish',
concurrentUploads: 10,
wait: true, // CloudFront のキャッシュ削除が完了するまでの時間(約30〜60秒)
}
gulp.task('deploy', function() {
// S3 オプションを使用して新しい publisher を作成する
// http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property
var publisher = awspublish.create(config);
var g = gulp.src('./' + config.distDir + '/**');
// publisher は、上記で指定した Content-Length、Content-Type、および他のヘッダーを追加する
// 指定しない場合、はデフォルトで x-amz-acl が public-read に設定される
g = g.pipe(parallelize(publisher.publish(config.headers), config.concurrentUploads))
// CDN のキャッシュを削除する
if (config.distribution) {
console.log('Configured with CloudFront distribution');
g = g.pipe(cloudfront(config));
} else {
console.log('No CloudFront distribution configured - skipping CDN invalidation');
}
// 削除したファイルを同期する
if (config.deleteOldVersions) g = g.pipe(publisher.sync());
// 連続したアップロードを高速化するためにキャッシュファイルを作成する
g = g.pipe(publisher.cache());
// アップロードの更新をコンソールに出力する
g = g.pipe(awspublish.reporter());
return g;
});
- スクリプトを実行(ローカルからデプロイ)
./deploy.sh
CodeBuild構築
ここから先はAWS CodeBuildを使用してGithubをコミットフックとした自動デプロイの実装です。
CodeBuildの画面にアクセス。
-
プロジェクト名、説明文
-
バッジはかっこいいのでonにした。(アプリのREADME.mdにデプロイステータスのバッジをつけることができる)
-
送信元:ソースをgithubにし、Repoを選択。
ここではgithubのPersonal Access Tokenを使用しました。(登録すると、githubと連携してRepoを選択できるようになる。) -
ウェブフックイベント
これをOnにすると、PushやPRマージをフックにしてデプロイを自動化できる。ブランチ指定も可能
「これらの条件でビルドを開始」をクリックし開いたところで下記を設定
[イベントタイプ](複数選択可)をプッシュ
とPULL_REQUEST_MERGED
に。
[HEAD_REF - オプショナル]に^refs/heads/master$
と入力 -
環境
[マネージド型イメージ]を選択
[Ubuntu]
[Standard]
[aws/codebuild/standard:2.0]
[最新のもの]
の順に設定
オプションを開き、タイムアウトを10分程度に。(SPAでそんなに時間かからない?)
証明書はここでは省略。
VPCにはアクセスしないのでなし。
コンピューティングはいちばんしょぼいもので十分3 GB メモリ、2 vCPU
環境変数に下記を指定
AWS_BUCKET_NAME
: バケット名
AWS_CLOUDFRONT
: distributionのID
API_URL_BROWSER
: CloudFrontで設定したDNS、なければCloudFrontで発行されてるURL(一旦)
DEPLOY_ENV
: "S3" と記載 -
ロール:先に作ってあればそれをセット。なければ新しいロール名を設定し、あとでポリシーをセットする。
使うポリシーはS3とCloudFrontの設定ができるPolicy(前述のポリシーを付与で良い)
(ASMなど使う場合はそのポリシーも付与する。) -
buildspec(ビルド仕様)
ビルドはリポジトリのルートに配置したbuildspec.yml
か、フォームに直接記述で指定できる。
git管理したいのでリポジトリに配置。
名前を変えることもできるがデフォルトでいいと思う。 -
アーティファクトなし
-
CloudWatchLog: 使う場合は設定
設定は以上、masterにプッシュして動作確認!(できたかな?)
Sentry実装
ログマネージメントを行いたいので、Sentryを採用。
こちらがとてもわかりやすくまとまってます。
- Sentryのサイトで新規登録+プロジェクト新規作成。
言語、フレームワークを選択するような感じになってるけど選択しなくてもいい(やり方が出るだけ)+nuxt.jsない。
プロジェクト名などを入力し、DSNを取得できればそれでいい。
- モジュールインストール
$ yarn add @nuxtjs/sentry
- 設定
// •••
modules: [ '@nuxtjs/sentry' ],
sentry: { dsn: 'https://aaaaaaaaaa@sentry.io/000000' }
なお、直書きじゃない場合、envを使うのだが、これが少し厄介。
例えば
sentry: { dsn: process.env.SENTRY_DSN }
とかやって、CodeBuildの環境変数にセットしてもうまく読めない。
下記サイトを参考にしてみてください。
[CodeBuild]buildspec.ymlでの環境変数指定方法あれこれまとめ
Sentry で Nuxt.js のエラー検知 + 環境変数の扱いに関する Tips
- ログを出してみる
typescriptでログを出すには、moduleインポートが必要。
import { captureException } from '@sentry/browser'
として
captureException(new Error('Sentryから始まるエラーマネジメント'))
以上となります!
Frontendの実装はほとんど初めてになりますので間違いなどあればご指摘いただけると幸いです!
参考にさせていただいた記事
nuxt.js公式
nuxt.js公式(S3+CloudFront)
nuxt.js+Typescript
S3+CloudFront+CodeBuild
SentryとNuxt.js