Nuxt + TypeScriptで、Vuexを扱う
概要
公式に記載があるvuex-module-decoratorsを使用して、Nuxt + TypeScriptの環境でVuexを扱う方法。
ストア - Nuxt TypeScript
環境
名称 | Version |
---|---|
Node | 14.10.1 |
Nuxt | 2.14.5 |
Vue | 2.6.12 |
TypeScript | 3.9.7 |
Vuetify | 2.3.10 |
nuxt-property-decorator | 2.8.8 |
vuex-module-decorators | 1.0.1 |
手順
npmのインストール
npm i vuex-module-decorators
ファイルの作成
~/store/index.ts
import { Store } from "vuex"; import { initialiseStores } from "~/utils/store-accessor"; const initializer = (store: Store<any>) => initialiseStores(store); export const plugins = [initializer]; export * from "~/utils/store-accessor";
~/utils/store-accessor.ts
import { Store } from "vuex"; import { getModule } from "vuex-module-decorators"; import client from "~/store/client"; let clientStore: client; function initialiseStores(store: Store<any>): void { clientStore = getModule(client, store); } export { initialiseStores, clientStore };
~/store/client.ts
import { Module, VuexModule, Mutation } from "vuex-module-decorators"; @Module({ name: "client", stateFactory: true, namespaced: true }) export default class Client extends VuexModule { private test: string = ""; @Mutation public setTest(test: string) { this.test = test; } public get getTest() { return this.test; } }
使用方法
Storeを扱う場合は、import文が必須
import { clientStore } from "~/store";
データの保存
client.tsで定義したMutationを使用してStoreにデータを保存する。
clientStore.setTest(格納したい値);
データの取得
clientStore.getTest;
備考
以下の方法でデータの保存は、エラーが発生するため出来ない。
clientStore.test = 格納したい値;
テストとして作成した画面のスクリーンショットとソースコード
ボタンを押下すると、入力フォームに入力した値をStoreに格納後、別画面に遷移する。遷移先の画面で、Storeの値を取得して出力する画面を作成。
<template> <div> <v-simple-table> <tbody> <tr> <td> <v-text-field v-model="inputTest" > </v-text-field> </td> </tr> </tbody> </v-simple-table> <v-btn @click="storeTestMethod">test</v-btn> </div> </template> <script lang="ts"> import { Component, Vue } from "nuxt-property-decorator"; import { clientStore } from "~/store"; @Component({}) export default class extends Vue { private inputTest: string = ""; private storeTestMethod(): void { clientStore.setTest(this.inputTest); this.$router.push("/test"); } } </script>
<template> <div> <div>{{ storeTestData }}</div> </div> </template> <script lang="ts"> import { Component, Vue } from "nuxt-property-decorator"; import { clientStore } from "~/store"; @Component({}) export default class extends Vue { private storeTestData: string = ""; beforeMount() { this.storeTestData = clientStore.getTest; } } </script>
NuxtのTypeScript化 〜nuxt-property-decorator導入編〜
概要
nuxt-property-decoratorを使用して、NuxtをTypeScript化する。
環境
名称 | Version |
---|---|
Node | 14.10.1 |
Nuxt | 2.14.5 |
Vue | 2.6.12 |
TypeScript | 3.9.7 |
Vuetify | 2.3.10 |
nuxt-property-decorator | 2.8.8 |
TypeScript化
パッケージをインストールする
npm i nuxt-property-decorator
vueファイルをTypeScript化する
TypeScript化前
<script> export default { layout: "test", filters: { toUpperCase(val) { return val.toUpperCase(); } }, data() { return { testData: "" } }, methods: { testMethod() { this.data = "test"; } } } </script>
TypeScript化後
<script lang="ts"> import { Component, Vue } from "nuxt-property-decorator"; @Component({ layout: "test", filters: { toUpperCase(val: string): string { return val.toUpperCase(); } } }) export default class extends Vue { private testData: string = ""; private testMethod(): void { this.testData = "test"; } } </script>
※Componentを何も使わない場合は、@Component({})と空で設定する。
nuxt.config.jsのTypeScript化
nuxt.config.jsをnuxt.config.tsにファイル名を変更する。
tsconfig.jsonにvuetifyの設定を追加する。
{ "compilerOptions": { "target": "ES2018", "module": "ESNext", "moduleResolution": "Node", "lib": [ "ESNext", "ESNext.AsyncIterable", "DOM" ], "esModuleInterop": true, "allowJs": true, "sourceMap": true, "strict": true, "noEmit": true, "experimentalDecorators": true, "baseUrl": ".", "paths": { "~/*": [ "./*" ], "@/*": [ "./*" ] }, "types": [ "@types/node", "@nuxt/types", "vuetify" ←この行を追加 ] }, "exclude": [ "node_modules", ".nuxt", "dist" ] }
buildプロパティのextendを使用している場合は、import文を追加してextendに型の設定をする。
import colors from 'vuetify/es5/util/colors' import { Configuration } from 'webpack' ←この行を追加 import { Context } from '@nuxt/types' ←この行を追加 export default { /* ** Nuxt rendering mode ** See https://nuxtjs.org/api/configuration-mode */ mode: 'universal', /* ** Nuxt target ** See https://nuxtjs.org/api/configuration-target */ target: 'server', /* ** Headers of the page ** See https://nuxtjs.org/api/configuration-head */ head: { titleTemplate: '%s - ' + process.env.npm_package_name, title: process.env.npm_package_name || '', meta: [ { charset: 'utf-8' }, { name: 'viewport', content: 'width=device-width, initial-scale=1' }, { hid: 'description', name: 'description', content: process.env.npm_package_description || '' } ], link: [ { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } ] }, /* ** Global CSS */ css: [ ], /* ** Plugins to load before mounting the App ** https://nuxtjs.org/guide/plugins */ plugins: [ ], /* ** Auto import components ** See https://nuxtjs.org/api/configuration-components */ components: true, /* ** Nuxt.js dev-modules */ buildModules: [ '@nuxt/typescript-build', '@nuxtjs/vuetify', ], /* ** Nuxt.js modules */ modules: [ // Doc: https://axios.nuxtjs.org/usage '@nuxtjs/axios', ], /* ** Axios module configuration ** See https://axios.nuxtjs.org/options */ axios: {}, /* ** vuetify module configuration ** https://github.com/nuxt-community/vuetify-module */ vuetify: { customVariables: ['~/assets/variables.scss'], theme: { dark: false, themes: { dark: { primary: colors.blue.darken2, accent: colors.grey.darken3, secondary: colors.amber.darken3, info: colors.teal.lighten1, warning: colors.amber.base, error: colors.deepOrange.accent4, success: colors.green.accent3 } } } }, /* ** Build configuration ** See https://nuxtjs.org/api/configuration-build/ */ build: { extend(config: Configuration, ctx: Context) { ←型の設定を追加 } } }
CloudFront + S3の構成で、特定のページだけキャッシュさせない
構成
特定のページだけキャッシュさせない方法
CloudFrontでの設定
Object Cachingで、Use Origin Cache Headersを設定する。
S3での設定
AWS コンソールで設定する場合
キャッシュさせたくないページのhtmlファイルを選択して、プロパティのメタデータからメタデータを追加する。
設定する内容は、Key「Cache-Control」Value「no-cache」
AWS CLIで設定する場合
aws s3api copy-object --bucket バケット名 --copy-source バケット名を含めたファイルのパス --key バケット名を含めないファイルのパス --metadata-directive REPLACE --cache-control "no-chache" --content-type "text/html"
※ メタデータを上書きするため、キャッシュの設定の他に設定する必要があるものがあれば設定する。
DockerでNuxt環境を構築する
環境
macOS Catalina 10.15.6
前提
Docker for Macをインストール済みであること。
インストールをしていない場合は、こちらからインストール。
ディレクトリ構成
|-- src
|-- docker-compose.yml
|-- Dockerfile
Dockerコンテナの作成
必要なファイルを準備する。
docker-compose.yml
version: '3' services: nuxt: container_name: nuxt build: . ports: - 3000:3000 volumes: - ./src/:/app tty: true
Dockerfile
FROM node:14.10.1 ENV LANG=C.UTF-8 TZ=Asia/Tokyo WORKDIR /app RUN npm install -g create-nuxt-app ENV HOST 0.0.0.0 EXPOSE 3000
コンテナの作成と起動をする。
$ docker-compose up -d
Nuxtプロジェクトの作成
先ほど作成したコンテナのIDを調べて、コンテナの中に入る。
$ docker ps $ docker exec -it コンテナのID /bin/sh
プロジェクトを作成する。
$ npx create-nuxt-app プロジェクト名 ? Project name: プロジェクト名 ? Programming language: TypeScript ? Package manage: Npm ? UI framework: Vuetify.js ? Nuxt.js modules: Axios ? Linting tools: - ? Testing framework: None ? Rendering mode: Universal (SSR / SSG) ? Deployment target: Server (Node.js hosting) ? Development tools: -
プロジェクトを起動する。
$ cd プロジェクト名 $ npm run dev
「http://localhost:3000」にアクセスする。
Amazon S3の静的ウェブサイトホスティングで、クエリパラメータが消える
事象
クエリパラメータを付与した状態のURLに直打ちでアクセスするとクエリパラメータが消えてしまう。
具体的には、以下のようにリダイレクトされる。
原因
以下のような流れで、ファイルを探しにいっている。
- https://xxxxxxxxxx.cloudfront.net/testというファイルがあればそれを表示する。
- https://xxxxxxxxxx.cloudfront.net/test/に「index.html」ファイルがあればそれを表示する。
※S3のStatic website hostingのインデックスドキュメントの設定を「index.html」とした場合。 - どちらも存在しない場合は、エラーとなる。
そのため、事象に書いたような形でクエリパラメータを渡すと1から2の流れで、リダイレクトされてクエリパラメータが消えてしまう。
解決策
クエリパラメータの前にスラッシュをつけるようにする。
Nuxtで暗号化した値をブラウザのJavaScriptで復号化して再度暗号化する
環境
Nuxt
名称 | Version |
---|---|
Node | 14.5.0 |
Nuxt | 2.13.3 |
Vue | 2.6.11 |
TypeScript | 3.8.3 |
Vuetify | 2.3.3 |
nuxt-property-decorator | 2.7.2 |
ブラウザのJavaScript
名称 | Version |
---|---|
jQuery | 3.5.1 |
Bootstrap | 4.5.2 |
使用する拡張機能
Nuxt
crypto
Nuxtで暗号化する方法
const S: string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const numArr32: number[] = Array.from(crypto.randomFillSync(new Uint8Array(32))); const numArr16: number[] = Array.from(crypto.randomFillSync(new Uint8Array(16))); const keyValue: string = numArr32.map((n: number)=>S[n%S.length]).join(""); const ivValue: string = numArr16.map((n: number)=>S[n%S.length]).join(""); const key: Uint8Array = new Buffer(keyValue, "utf-8"); const iv: Uint8Array = new Buffer(ivValue, "utf-8"); const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); let encrypted = cipher.update(暗号化したい値); encrypted += cipher.final("hex");
備考
keyValueとivValueは、ランダムな文字列を生成して格納。
ブラウザのJavaScriptで復号化する方法
const inputIv = Nuxtで暗号化した値; const inputKey = Nuxtで暗号化の際に使用したkeyValueの値; const inputIv = Nuxtで暗号化の際に使用したivValueの値; const key = new TextEncoder("utf-8").encode(inputKey); const iv = new TextEncoder("utf-8").encode(inputIv); const convertUint8Arr = new Uint8Array(inputVal.match(/.{1,2}/g).map(v => parseInt(v, 16))); window.crypto.subtle.importKey( "raw", key, "AES-CBC", true, ["decrypt"] ).then(key => { window.crypto.subtle.decrypt( { name: "AES-CBC", iv }, key, convertUint8Arr ).then(element => { const decryptoVal = new TextDecoder("utf-8").decode(element); }) })
ブラウザのJavaScriptで再度暗号化する方法
const inputVal = 暗号化したい値; const inputKey = Nuxtで暗号化の際に使用したkeyValueの値; const inputIv = Nuxtで暗号化の際に使用したivValueの値; const key = new TextEncoder("utf-8").encode(inputKey); const iv = new TextEncoder("utf-8").encode(inputIv); window.crypto.subtle.importKey( "raw", key, "AES-CBC", true, ["encrypt"] ).then(key => { window.crypto.subtle.encrypt( { name: "AES-CBC", iv }, key, new TextEncoder("utf-8").encode(inputVal) ).then(element => { const cryptoVal = Array.from(new Uint8Array(element)).map(num => num.toString(16).padStart(2, "0")).join(""); }) })
作成した画面のスクリーンショット
Nuxt
ブラウザのJavaScript
全体のソースコード
Nuxt
<template> <div> <v-text-field v-model="inputCrypto" label="暗号化したい文字列" > </v-text-field> <v-btn @click="convertCrypto">暗号化</v-btn> <div>暗号化: {{ outputCrypto }}</div> <div>Key: {{ useCryptoKeyValue }}</div> <div>Initial Vector: {{ useCryptoIvValue }}</div> </div> </template> <script lang="ts"> import { Component, Vue } from "nuxt-property-decorator"; const crypto = require("crypto"); @Component({}) export default class extends Vue { private inputCrypto: string = ""; private outputCrypto: string = ""; private useCryptoKeyValue: string = ""; private useCryptoIvValue: string = ""; private convertCrypto(): void { const S: string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const numArr32: number[] = Array.from(crypto.randomFillSync(new Uint8Array(32))); const numArr16: number[] = Array.from(crypto.randomFillSync(new Uint8Array(16))); const keyValue: string = numArr32.map((n: number)=>S[n%S.length]).join(""); const ivValue: string = numArr16.map((n: number)=>S[n%S.length]).join(""); const key: Uint8Array = new Buffer(keyValue, "utf-8"); const iv: Uint8Array = new Buffer(ivValue, "utf-8"); const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); let encrypted = cipher.update(this.inputCrypto); encrypted += cipher.final("hex"); this.useCryptoKeyValue = keyValue; this.useCryptoIvValue = ivValue; this.outputCrypto = encrypted; } } </script>
ブラウザのJavaScript
html
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"> <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script> <script type="text/javascript" src="./js/decrypto.js" defer="defer"></script> <title>復号化</title> </head> <body> <div class="container"> <div class="form-group"> <label for="cryptoVal">暗号化した値</label> <input type="text" id="cryptoVal" class="form-control"> </div> <div class="form-group"> <label for="cryptoKey">Key</label> <input type="text" id="cryptoKey" class="form-control"> </div> <div class="form-group"> <label for="cryptoIv">Initial Vector</label> <input type="text" id="cryptoIv" class="form-control"> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button id="decryptoBtn" class="btn btn-primary">復号化</button> </div> </div> <div class="form-group"> <label id="outputDecrypto" class="col-sm-2 control-label"></label> </div> <div class="form-group"> <label for="cryptoVal">暗号化する値</label> <input type="text" id="cryptoVal2" class="form-control"> </div> <div class="form-group"> <label for="cryptoKey">Key</label> <input type="text" id="cryptoKey2" class="form-control"> </div> <div class="form-group"> <label for="cryptoIv">Initial Vector</label> <input type="text" id="cryptoIv2" class="form-control"> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button id="cryptoBtn" class="btn btn-primary">暗号化</button> </div> </div> <div class="form-group"> <label id="outputCrypto" class="col-sm-2 control-label"></label> </div> </div> </body> </html>
JavaScript
$("#decryptoBtn").on("click", function() { const inputVal = $("#cryptoVal").val(); const inputKey = $("#cryptoKey").val(); const inputIv = $("#cryptoIv").val(); const key = new TextEncoder("utf-8").encode(inputKey); const iv = new TextEncoder("utf-8").encode(inputIv); const convertUint8Arr = new Uint8Array(inputVal.match(/.{1,2}/g).map(v => parseInt(v, 16))); window.crypto.subtle.importKey( "raw", key, "AES-CBC", true, ["decrypt"] ).then(key => { window.crypto.subtle.decrypt( { name: "AES-CBC", iv }, key, convertUint8Arr ).then(element => { const decryptoVal = new TextDecoder("utf-8").decode(element); $("#outputDecrypto").text(decryptoVal) }) }) }); $("#cryptoBtn").on("click", function() { const inputVal = $("#cryptoVal2").val(); const inputKey = $("#cryptoKey2").val(); const inputIv = $("#cryptoIv2").val(); const key = new TextEncoder("utf-8").encode(inputKey); const iv = new TextEncoder("utf-8").encode(inputIv); window.crypto.subtle.importKey( "raw", key, "AES-CBC", true, ["encrypt"] ).then(key => { window.crypto.subtle.encrypt( { name: "AES-CBC", iv }, key, new TextEncoder("utf-8").encode(inputVal) ).then(element => { const cryptoVal = Array.from(new Uint8Array(element)).map(num => num.toString(16).padStart(2, "0")).join(""); $("#outputCrypto").text(cryptoVal) }) }) });
Nuxtでハッシュ化・暗号化・復号化する
環境
名称 | Version |
---|---|
Node | 14.5.0 |
Nuxt | 2.13.3 |
Vue | 2.6.11 |
TypeScript | 3.8.3 |
Vuetify | 2.3.3 |
nuxt-property-decorator | 2.7.2 |
使用する拡張機能
crypto
ハッシュ化と暗号化
ハッシュ化
元となるデータをハッシュアルゴリズムによって、固定長の値に不可逆変換をすること。
暗号化
元となるデータを暗号化アルゴリズムによって、復号可能な値に変換をすること。
ハッシュ化する方法
const sha256 = crypto.createHash("sha256"); sha256.update(ハッシュ化したい値); sha256.digest("hex");
暗号化する方法
const S: string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const numArr32: number[] = Array.from(crypto.randomFillSync(new Uint8Array(32))); const numArr16: number[] = Array.from(crypto.randomFillSync(new Uint8Array(16))); const keyValue: string = numArr32.map((n: number)=>S[n%S.length]).join(""); const ivValue: string = numArr16.map((n: number)=>S[n%S.length]).join(""); const key: Uint8Array = new Buffer(keyValue, "utf-8"); const iv: Uint8Array = new Buffer(ivValue, "utf-8"); const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); let encrypted = cipher.update(暗号化したい値); encrypted += cipher.final("hex");
備考
keyValueとivValueは、ランダムな文字列を生成して格納。
復号化する方法
const key: Uint8Array = new Buffer(暗号化の際に使用したkeyValue, "utf-8"); const iv: Uint8Array = new Buffer(暗号化の際に使用したivValue, "utf-8"); const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv); let decipheredData = decipher.update(暗号化した値, "hex", "utf8"); decipheredData += decipher.final("utf8");
作成した画面のスクリーンショット
全体のソースコード
<template> <div> <v-text-field v-model="inputHash" label="ハッシュ値に変換したい文字列" > </v-text-field> <v-btn @click="convertHash">ハッシュ変換</v-btn> <div>ハッシュ値: {{ outputHash }}</div> <v-text-field v-model="inputCrypto" label="暗号化したい文字列" > </v-text-field> <v-btn @click="convertCrypto">暗号化</v-btn> <div>暗号化: {{ outputCrypto }}</div> <div>Key: {{ useCryptoKeyValue }}</div> <div>Initial Vector: {{ useCryptoIvValue }}</div> <v-text-field v-model="inputDecrypto" label="復号化したい暗号化値" > </v-text-field> <v-text-field v-model="inputKey" label="Key" > </v-text-field> <v-text-field v-model="inputIv" label="Initial Vector" > </v-text-field> <v-btn @click="convertDecrypto">復号化</v-btn> <div>復号化: {{ outputDecrypto }}</div> </div> </template> <script lang="ts"> import { Component, Vue } from "nuxt-property-decorator"; const crypto = require("crypto"); @Component({}) export default class extends Vue { private inputHash: string = ""; private outputHash: string = ""; private inputCrypto: string = ""; private outputCrypto: string = ""; private useCryptoKeyValue: string = ""; private useCryptoIvValue: string = ""; private inputDecrypto: string = ""; private inputKey: string = ""; private inputIv: string = ""; private outputDecrypto: string = ""; private convertHash(): void { const sha256 = crypto.createHash("sha256"); sha256.update(this.inputHash); this.outputHash = sha256.digest("hex"); } private convertCrypto(): void { const S: string = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; const numArr32: number[] = Array.from(crypto.randomFillSync(new Uint8Array(32))); const numArr16: number[] = Array.from(crypto.randomFillSync(new Uint8Array(16))); const keyValue: string = numArr32.map((n: number)=>S[n%S.length]).join(""); const ivValue: string = numArr16.map((n: number)=>S[n%S.length]).join(""); const key: Uint8Array = new Buffer(keyValue, "utf-8"); const iv: Uint8Array = new Buffer(ivValue, "utf-8"); const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); let encrypted = cipher.update(this.inputCrypto); encrypted += cipher.final("hex"); this.useCryptoKeyValue = keyValue; this.useCryptoIvValue = ivValue; this.outputCrypto = encrypted; } private convertDecrypto(): void { const key: Uint8Array = new Buffer(this.inputKey, "utf-8"); const iv: Uint8Array = new Buffer(this.inputIv, "utf-8"); const decipher = crypto.createDecipheriv("aes-256-cbc", key, iv); let decipheredData = decipher.update(this.inputDecrypto, "hex", "utf8"); decipheredData += decipher.final("utf8"); this.outputDecrypto = decipheredData; } } </script>