- 投稿日:2020-08-17T21:05:50+09:00
初心者のためのVue.jsの環境構築方法
- 投稿日:2020-08-17T18:47:49+09:00
日本語諸方言コーパスをDB化して遊ぶ (4) サービスの全体像を決める
連載記事です。第4回は前回お試しで作成した Laravel アプリを いったん反故にして 、サービスの全体像を決めて、ルーティングを作るところまで。完全に自分用の作業メモで、説明もいろいろ足りていないと思いますが、ご容赦ください。
- 第1回: 日本語諸方言コーパスをDB化して遊ぶ (1) 構成を考える
- 第2回: 日本語諸方言コーパスをDB化して遊ぶ (2) SQLite3 で DB 化
- 第3回: 日本語諸方言コーパスをDB化して遊ぶ (3) PHP Laravel で操作する
- 第4回: 日本語諸方言コーパスをDB化して遊ぶ (4) サービスの全体像を決める ←今ここ
- 第5回: 日本語諸方言コーパスをDB化して遊ぶ (5) データベースの移行とモデルの作成
- 第6回: 日本語諸方言コーパスをDB化して遊ぶ (6) 談話ごとの発話総覧を作る
- 第7回: 日本語諸方言コーパスをDB化して遊ぶ (7) 話者ごとの発話総覧を作る
- 第8回: 日本語諸方言コーパスをDB化して遊ぶ (8) ファイル形式変換機能をつける
- 第9回: 日本語諸方言コーパスをDB化して遊ぶ (9) Heroku でデプロイする
実現したい機能
COJADS は現在、コーパス検索アプリケーション「中納言」にてモニター版が利用可能ですので、これとは機能的に差別化したいところです。
「談話/話者」ごとの発話総覧
「中納言」版では検索機能をメインに据えており、テキストデータを「談話」や「話者」といったまとまりでは提供していません。また、前述のとおり COJADS の公式サイトで提供されている生データは「談話」を軸に全結合してあり、取り回しが不便です。したがって、これをエンティティごとに分離して、サイト上で閲覧できるようにする、たとえば「談話」だけでなく「話者」ごとに発話を総覧できる機能を提供することには、一定の価値があると考えられます。
「発話」の編集
わざわざ SQL を書くことなく、サイト上からデータベースを操作し、発話を編集する機能をつけます。ログもとります1。そこまで必要性は感じられませんが、データベースの勉強がてら実装したいと思います。
Excel と TextGrid の相互変換
COJADS では音声・方言テキスト・標準語テキストが発話を単位に紐づけられていますが、公式サイトでは CSV 形式の生データのみが配布されており、音声データや TextGrid 形式(音声分析ソフト Praat 専用形式)のテキストデータは配布されていません。音声と直接紐づいているのは TextGrid ファイルであり、CSV と音声ファイルがあったところでうまく対応を観察することができません。
また TextGrid がいずれ配布されたとしても、CSV データを各自で編集してしまうと対応が崩れてしまい、音声分析が困難になります。そこで Excel ファイルと TextGrid ファイルを相互変換する機能をつけたいと思います。
既に手元に Excel と TextGrid を相互変換する Python スクリプトがありましたので、これを最大限に活かすべく、
- ファイルをアップロードして
- サーバー上で Python スクリプトにかけて変換して
- それをダウンロードする
という仕組みにします。
使用技術
AWS を利用するほどでもないので、今回は Heroku のフリープランを利用して完全無料で上記の機能を実装します。ウェブフレームワークとしては前述のとおり PHP Laravel を使用します。また Heroku は本番環境で SQLite が使えないので2、データベースとしては Heroku PostgreSQL のフリープランを利用します。
Heroku は非常に簡便に利用開始できるサービスですが、その分ビルド後に root 権限が使えなかったり、特にフリープランでは制限されている部分も多く、初心者には難しい点がいくつかありましたので、そこらへんは第9回に説明を試みます。
また、せっかくなので SPA (Single Page Application) 化します。SPA を実現するフレームワークはいくつかありますが、今回は学習コストの低そうな Vue Router を採用します。下記のチュートリアルが大いに参考になりました。
画面遷移図
必要となるページを数え上げて、簡単な画面遷移図を作ります。Qiita で表示しても不快にならないよう、いくつかに分けて描いてみます。
エレガントではないですが、アイコニックで分かりやすく作画コストも低いので draw.io を使用しました。
「談話」ごとの発話総覧と編集機能は以下のようにしましょう。「談話」テーブルには非常に多くの情報が含まれている(ファイル記号、データ名、収録年月日、収録場所、収録担当者、編集担当者、話題、談話ジャンル)ため、これをすべて一画面に表示すると邪魔になります。そこで「談話一覧」ページには、データの性質に影響を与えない要素(収録担当者、編集担当者など)は表示しないようにして、「発話詳細」ページでだけ確認できるようにします。また、「発話編集」ページで更新処理を行なったら「発話詳細」ページに遷移させます。
「話者」ごとの発話総覧は、単純に発話をまとめるだけではつまらないので、簡単な文字列処理を噛ませて、発話ごとではなく文ごとに提示するようにしましょう。
ファイル変換画面は、とりあえずアップロードして、変換して、ダウンロードして、削除できればよいので、素朴なのを用意しておきます。
プロジェクトの準備
では作成していきましょう。まずは Laravel プロジェクトを作っていきます。やることは基本的に上記参考サイトと同じで、適当なフォルダで
composer
にプロジェクトを作ってもらい、必要なパッケージ等を Laravel (php artisan
) やnpm
の助けを借りて導入していきます。composer create-project laravel/laravel --prefer-dist cojads composer require laravel/ui php artisan ui vue npm install npm install --save vue-routerこれからプロジェクトの内実を作っていきますが、ガワ(ルーティングやテンプレート)から作り始めて、内実(コンポーネントやコントローラ)はあとで作りこむ、という手順を貫きます。
SPA のテンプレート作成
今回は SPA を作るので、ガワ(テンプレート)として唯一のページ
app.blade.php
を作成します。Laravel プロジェクトではウェブサイトとして通常アクセスした際のルーティングがweb.php
で規定されるので、まずここを編集して任意のパスをapp.blade.php
にルーティングします。ただし今回のサービスはあとで/storage
に直接アクセスする必要が出てくるので、/storage
以下にだけは特別なルーティングをかけないようにしておきます。routes/web.php<?php use Illuminate\Support\Facades\Route; Route::get('/{any}', function() { return view('app'); })->where('any', '^(?!storage).*');そうしたら SPA のガワ
app.blade.php
を作ります。Vue Router の処理で<header-component>
位置にヘッダーが、<footer-component>
位置にフッターが、<router-view>
位置に各ページの内容が読み込まれるようになっています。bootstrap で固定ヘッダーを作る関係で、
<rooter-view>
の上下に適当に余白を設けています(汚い)。resources/views/app.blade.php<!doctype html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <base href="/" /> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="robots" content="noindex"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'COJADS App') }}</title> <link href="{{ mix('/css/app.css') }}" rel="stylesheet"> </head> <body> <div id="app"> <header-component></header-component> <div class="px-5 py-5"><div class="py-3"> <router-view></router-view> </div></div> <footer-component></footer-component> </div> <script src="{{ mix('/js/app.js') }}" defer></script> </body> </html>コンポーネントのルーティング
続いてテンプレートに挿入されるコンポーネントのルーティングを行ないます。コンポーネントの登録とそのルーティングは
app.js
で管理されているので、ここを編集します。以下ではヘッダー・フッター・表紙ページ・談話一覧ページの要素を読み込んで適切なルーティングを行なっています。resources/js/app.jsimport VueRouter from "vue-router"; // register components import HeaderComponent from "./components/HeaderComponent"; import FooterComponent from "./components/FooterComponent"; import HomeComponent from "./components/HomeComponent"; import DiscourseIndexComponent from "./components/DiscourseIndexComponent"; require("./bootstrap"); window.Vue = require("vue"); Vue.use(VueRouter); // routing const router = new VueRouter({ mode: "history", routes: [ { // ルートにアクセスすると HomeComponent をロードする path: "/", name: "home", component: HomeComponent }, { // /discourse にアクセスすると DiscourseIndexComponent をロードする path: "/discourse", name: "discourse.index", component: DiscourseIndexComponent } ] }); // ヘッダー・フッター用のカスタムエレメントを定義して、コンポーネントを読み込む Vue.component("header-component", HeaderComponent); Vue.component("footer-component", FooterComponent); const app = new Vue({ el: "#app", router });各コンポーネントの作成
ルーティングの構想ができたらページの内容を作っていきます。先ほど
app.js
で使用することにした4つのコンポーネント(.vue
ファイル)を/resources/js/components
下に作成します(discourse
は次回)。以下のファイルを作成する+ resources/js/components/HeaderComponent.vue + resources/js/components/FooterComponent.vue + resources/js/components/HomeComponent.vue + resources/js/components/DiscourseIndexComponent.vue
ヘッダー
bootstrap でページ上部に固定するタイプのヘッダーを作ります。先取りになりますが、ヘッダーには
<router-link>
で各コンポーネントへのリンクを張っておきましょう。とはいえ、まだリンク先を用意していないので、この段階でコンパイルするなら適当にコメントアウトする必要があります。resources/js/components/HeaderComponent.vue<template> <header class="navbar fixed-top navbar-dark bg-dark"> <span class="navbar-brand mb-0 h1">COJADS APP</span> <div> <span> <router-link v-bind:to="{ name: 'home' }"> <button class="btn btn-success btn-sm">Top Page</button> </router-link> <router-link v-bind:to="{ name: 'discourse.index' }"> <button class="btn btn-success btn-sm">Discourse List</button> </router-link> <router-link v-bind:to="{ name: 'speaker.index' }"> <button class="btn btn-success btn-sm">Speaker List</button> </router-link> <router-link v-bind:to="{ name: 'convert' }"> <button class="btn btn-success btn-sm">Toolkits</button> </router-link> </span> </div> </header> </template> <script> export default {}; </script>フッター
フッターも適当に作ります。
resources/js/components/FooterComponent.vue<template> <footer class="navbar fixed-bottom navbar-dark bg-dark"> <span class="navbar-brand mb-0 h1">(c) 2020 @a_eau_</span> </footer> </template> <script> export default {}; </script>ホーム画面
特に書くこともないので、最小限だけ作ります。
resources/js/components/HomeComponent.vue<template> <div> Welcome to COJADS App! </div> </template> <script> export default {}; </script>コンパイル・ローカルサーバで確認
以上のコードを書き終えたら、一度コンパイルして確認しましょう。以下のコマンドを実行すると、もろもろの必要なものを取り込んでいい感じにコンパイルしてくれます。
cmdnpm run dev あるいは npm run productionコンパイルが済んだら Laravel の以下コマンドでローカルサーバを立ち上げて、
localhost:8000
にアクセスしてみましょう。404 や 500 エラーが出ずに想定通りのページが表示されれば成功です。cmdphp artisan serve次回
Heroku で SQLite が使えないことを知って PostgreSQL に乗り換える回です。
- 投稿日:2020-08-17T16:39:18+09:00
既存のLaravel/VueCLIプロジェクトにVuetifyを導入する
環境
- Laravel 7.25
- PostgreSQL 12.3
- vueCLI 4.4.1
やりたいこと
- 既存のLaravel+VueCLIプロジェクトに、後からVuetifyを導入する
- 公式サイトのインストール方法(https://vuetifyjs.com/ja/getting-started/quick-start/) がわかりにくい
手順
コマンド
- 公式サイトにはいろいろあるけど、この場合はこの2つでOK
npm install vuetify npm install sass sass-loader fibers deepmerge -D
- mdiアイコンを使うとき
npm install @mdi/font -Dresources/js/app.js
- ★がついた部分が加筆部分
/** * First we will load all of this project's JavaScript dependencies which * includes Vue and other libraries. It is a great starting point when * building robust, powerful web applications using Vue and Laravel. */ require('./bootstrap'); window.Vue = require('vue'); /** * The following block of code may be used to automatically register your * Vue components. It will recursively scan this directory for the Vue * components and automatically register them with their "basename". * * Eg. ./components/ExampleComponent.vue -> <example-component></example-component> */ const files = require.context('./', true, /\.vue$/i) files.keys().map(key => Vue.component(key.split('/').pop().split('.')[0], files(key).default)) import Vue from 'vue'; import Vuetify from 'vuetify'; //★ import 'vuetify/dist/vuetify.min.css'; //★ import '@mdi/font/css/materialdesignicons.css'; //★ Vue.use(Vuetify); //★ /** * Next, we will create a fresh Vue application instance and attach it to * the page. Then, you may begin adding components to this application * or customize the JavaScript scaffolding to fit your unique needs. */ // ★ export default new Vuetify({ icons: { iconfont: 'mdi' }, theme: { dark: true, } }) const app = new Vue({ el: '#app', vuetify: new Vuetify(), //★ });
- 投稿日:2020-08-17T13:49:06+09:00
Vue 3.0とgRPCを使ってTodoListを作ってみた
gRPCとは?
gRPCはオープンソース、RPCフレームワークをベースとして、最初はGoogleが開発されました。
インターフェース記述言語としてProtocol Buffersを使用し、protobufは構造化データをシリアル化するためのメカニズムです。
protoファイルでサービスとそのデータ構造を定義するだけで、gRPCがさまざまな言語でプラットフォームのクライアントとサーバーのStubsを自動的に生成します。
profobufを使用すると、JSONではなくバイナリを使用して資料を転送しています。
これにより、gRPCがはるかに高速で信頼性の高いものになります。
gRPCの他の主要な機能のいくつかは、双方向ストリーミングとフロー制御、BlockingまたはNonBlockingバインディング、および認証機能です。
gRPCはHTTP/2を使用して、シングルTCPコネクションの中で複数のストリームを開始することができます。
gRPCの詳細については、こちらをご覧ください:https://grpc.io/gRPC V.S. REST
Feature gRPC REST Portocol HTTP/2 (早い) HTTP/1.1 (遅い) Payload Protobuf (バイナリ、小さい) JSON (テキスト、大きい) API構造 厳格、必要 (.proto) ゆるい、選択 Code生成 内蔵 (protoc) 他のツール (Swagger) 安全性 TLS/SSL TLS/SSL ストリーミング 双方向ストリーミング クライアント -> サーバーリクエストだけ ブラウザのサポート 制限あり (grpc-webは必要) ほぼ全部 Protobuf と gRPC を使えば、REST API の GET、PUT やヘッダーなどを気にする必要はありません、そしてgRPCフレームワークによって生成されたStubsにはデータモデル用の記述が全部書いてるので、直接引用するだけで使えます。
開発環境とツール
- Protoc v3.12.4 -- Protobuf コンパイラー Stubs を生成する為に使ます。
- Node.js v14.2.0 -- バックエンドとVueのビルドに使います。
- Docker v19.03.12 -- envoyを動かす為に使ます。
- envoy v1.14 -- 普通WebからのHTTP/1.1をHTTP/2に変換する為のプロキシ。
- Vue.js 3.0.0-rc.5 -- 今回は Vue 3 を使ってフロントエンドを作成します。
- Docker Compose v1.26.2 -- 全部をDocker化する為に使います、なくでも動けます。
フォルダ構成
全体の流れ
- Protoファイルの作成
- Node.jsでバックエンド作成
- Envoy proxyの設定
- Client stubsの生成
- Clientの作成
- 動かしてみましょう
- Docker化
コードを書いてみましょう
1. Protoファイルの作成
ProtoファイルはgRPCの心臓と呼ばれる部分、ここでRequestとResponseとサービスを定義することによって、後でStubsファイルを自動的に生成することができます。
Protoファイルの構成は大体四つの部分に分けている。
1. Protoのバージョンを定義する
2. Packageの名前
3. サービス定義
4. メッセージ定義todo.protosyntax = "proto3"; package todo; service todoService { rpc addTodo(addTodoParams) returns (todoObject) {} rpc deleteTodo(deleteTodoParams) returns (deleteResponse) {} rpc getTodos(getTodoParams) returns (todoResponse) {} } // Request message getTodoParams{} message addTodoParams { string task = 1; } message deleteTodoParams { string id = 1; } // Response message todoObject { string id = 1; string task = 2; } message todoResponse { repeated todoObject todos = 1; //ここはArrayの中身にtodoObjectが複数ありますのこと。 } message deleteResponse { string message = 1; }2. Node.jsでバックエンド作成
環境設定:
npm:
bashnpm init -y npm i uuid grpc @grpc/proto-loader npm i -D nodemonyarn:
bashyarn init -y yarn add uuid grpc @grpc/proto-loader yarn add -D nodemonstartのnodeをnodemonに書き換える:
package.json{ "name": "server", "version": "1.0.0", "description": "A Node.js gRPC API Server", "main": "server.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon server.js" }, "author": "", "license": "MIT", "devDependencies": { "nodemon": "^2.0.4" }, "dependencies": { "@grpc/proto-loader": "^0.5.5", "grpc": "^1.24.3", "uuid": "^8.3.0" } }サーバーのコード内容:
server.js// proto ファイルのパス const todoProtoPath = './todo.proto'; // npm packageを導入 const grpc = require('grpc'); const protoLoader = require('@grpc/proto-loader'); const { v4: uuidv4 } = require('uuid'); // grpcの初期化 const packageDefinition = protoLoader.loadSync( todoProtoPath, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true, }, ); // packageを指定 const todoProto = grpc.loadPackageDefinition(packageDefinition).todo; // Todosの保存、リースダートしたら資料が消えます let Todos = []; const addTodo = (call, callback) => { const todoObject = { id: uuidv4(), task: call.request.task, }; console.log(call.request); Todos.push(todoObject); console.log(`Todo: ${todoObject.id} added!`); callback(null, todoObject); }; const getTodos = (call, callback) => { console.log('Get tasks'); console.log(Todos); callback(null, { todos: Todos }); }; const deleteTodo = (call, callback) => { Todos = Todos.filter((todo) => todo.id !== call.request.id); console.log(`Todo: ${call.request.id} deleted`); callback(null, { message: 'Success' }); }; const getServer = () => { const server = new grpc.Server(); // サービスを登録、名前はprotoファイルと同じなので省略できます server.addService(todoProto.todoService.service, { addTodo, getTodos, deleteTodo }); return server; }; if (require.main === module) { const server = getServer(); server.bind('0.0.0.0:9090', grpc.ServerCredentials.createInsecure()); server.start(); console.log('Server running at port: 9090'); }3. Envoy proxyの設定
Envoy proxyはサーバーとクライアントの中央にいるサービスです、主にはHTTP/1.1のコネクションをHTTP/2に変換するの役割です。
Dockerのイメージ設定ファイル
DockerfileFROM envoyproxy/envoy:v1.14-latest COPY ./envoy.yaml /etc/envoy/envoy.yaml CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yamlEnvoyの設定ファイル
envoy.yamladmin: access_log_path: /tmp/admin_access.log address: socket_address: { address: 0.0.0.0, port_value: 9901 } static_resources: listeners: - name: listener_0 address: socket_address: { address: 0.0.0.0, port_value: 8080 } filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager stat_prefix: ingress_http codec_type: AUTO route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: { prefix: "/" } route: cluster: todo_service max_grpc_timeout: 0s cors: allow_origin_string_match: - prefix: "*" allow_methods: GET, PUT, DELETE, POST, OPTIONS allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout max_age: "1728000" expose_headers: custom-header-1,grpc-status,grpc-message http_filters: - name: envoy.filters.http.grpc_web - name: envoy.filters.http.cors - name: envoy.filters.http.router clusters: - name: todo_service connect_timeout: 0.25s type: logical_dns http2_protocol_options: {} lb_policy: round_robin load_assignment: cluster_name: cluster_0 endpoints: - lb_endpoints: - endpoint: address: socket_address: # docker-composeを使うときserverに書き換えます address: host.docker.internal port_value: 9090gRPCサービスは9090 portで動かして、Envoyは8080 portでWebからのHTTP/1.1をHTTP/2に変換して9090に送るそいう仕組みです。
4. Client stubsの生成
protocをインストール : Protocol Buffer Compiler Installation
bash# Linux $ apt install -y protobuf-compiler $ protoc --version # MacOS using Homebrew $ brew install protobuf $ protoc --version先にVueのProjectを作成します。
vue-cliを使います。bashvue create clientそしてStubsを作ります
このコメンドで ./client/src に二つのJSファイルを生成する
- todo_pb.js // メッセージのType定義
- todo_grpc_web_pb.js // gRPCクライアント
bashprotoc -I server todo.proto \ --js_out=import_style=commonjs,binary:client/src \ --grpc-web_out=import_style=commonjs,mode=grpcwebtext:client/src5. Clientの作成
クライアントのTodoコンポーネントの中にtodo_pb.jsとtodo_grpc_web_pb.jsを導入して、todoServiceClient()を使ってlocalhost:8080のEnvoy proxyに接続します。
Todo.vueimport { ref } from 'vue' // クライアントが使う部分だけを導入する import { getTodoParams, addTodoParams, deleteTodoParams } from "../todo_pb.js"; import { todoServiceClient } from "../todo_grpc_web_pb.js"; import CloseIcon from './CloseIcon' export default { components:{ CloseIcon }, setup() { const todos = ref([]) const inputField = ref('') // 新しクライアントのインスタンスを作成 const client = new todoServiceClient("http://localhost:8080", null, null); const getTodos = () => { let getRequest = new getTodoParams(); client.getTodos(getRequest, {}, (err, response) => { if (err) console.log(err); console.log(response.toObject()); todos.value = response.toObject().todosList; }); } getTodos() const addTodo = () => { let addRequest = new addTodoParams(); addRequest.setTask(inputField.value); client.addTodo(addRequest, {}, (err) => { if (err) console.log(err); inputField.value = ""; getTodos(); }); } const deleteTodo = (todo) => { let deleteRequest = new deleteTodoParams(); deleteRequest.setId(todo.id); client.deleteTodo(deleteRequest, {}, (err, response) => { if (err) console.log(err); if (response.getMessage() === "Success") { getTodos(); } }); } return { todos, inputField, addTodo, deleteTodo } } }完成の参考 : Github
6. 動かしてみましょう
Back-Endを立ち上げて:
bash$ cd ./server $ npm startenovy proxy:
bash$ docker build -t envoy:v1 ./enovy $ docker run --rm -it -p 8080:8080 envoy:v1Front-End:
bash$ cd ./client $ yarn dev7. Docker化
Docker Compose一発で動かす為にDocker化します。
各フォルダにDockerfileと.dockerignore入れます。./serverフォルダ
DockerfileFROM node:lts-alpine # make the 'app' folder the current working directory WORKDIR /app # copy both 'package.json' and 'package-lock.json' (if available) COPY package*.json ./ # install project dependencies RUN npm install # copy project files and folders to the current working directory (i.e. 'app' folder) COPY . . EXPOSE 9090 CMD [ "node", "server.js" ].dockerignore.git .gitignore node_modules README.md./clientフォルダ
DockerfileFROM node:lts-alpine # install simple http server for serving static content RUN npm install -g http-server # make the 'app' folder the current working directory WORKDIR /app # copy both 'package.json' and 'package-lock.json' (if available) COPY package*.json ./ COPY yarn.lock ./ # install project dependencies RUN yarn install # copy project files and folders to the current working directory (i.e. 'app' folder) COPY . . # build app for production with minification RUN yarn run build EXPOSE 3000 CMD [ "http-server", "-p", "3000", "dist" ].dockerignore.git .gitignore node_modules README.md./docker-composer.ymlを作成します。
docker-compose.ymlversion: '3' services: web: build: ./client image: todo-grpc-vue-client:v1 ports: - 3000:3000 restart: unless-stopped networks: - grpc-todolist proxy: build: ./envoy image: todo-grpc-envoy:v1 ports: - 8080:8080 restart: unless-stopped networks: - grpc-todolist server: build: ./server image: todo-grpc-server:v1 restart: unless-stopped networks: - grpc-todolist networks: grpc-todolist: driver: bridgeEnvoyの設定ファイルのサーバーアドレス修正します
envoy.yamlendpoints: - lb_endpoints: - endpoint: address: socket_address: # host.docker.internalをserverに書き換える address: server port_value: 9090そしてビルドして立ち上げます。
bash# イメージをビルド $ docker-compose build # 特定のイメージをリビルドします $ docker-compose build --no-cache [service] # 立ち上げる $ docker-compose up # 背景で立ち上げる $ docker-compose up -d # ログを確認 $ docker-compose logsFront-Endに入ります:http://localhost:3000
最後
Qiitaで初めての投稿です、よろしくお願いします。
Githubでソースコードを公開しています、
なんが詰まったところがあれば参考してください:Github
- 投稿日:2020-08-17T11:41:08+09:00
AmplifyでホスティングしたVueのSPAアプリでサブディレクトリに直接アクセスできるようにする
AmplifyでホスティングしたVueのSPAアプリでサブディレクトリに直接アクセスできるようにする
はじめに
AmplifyはAWSのBaaSフレームワークです。BaaSフレームワークといえばFirebaseが有名です。確かにFirebaseはAmplifyよりも、クラウドサービスとしての各サービス(functions、DBなど)の管理をより意識せずに使える手軽さが便利で、自分で何でも選んでいい状態であれば選択肢の筆頭に上がるところです。
しかし会社としてAWSをメインで採用していて、新規Webサービスをスピード感をもって立ち上げる、といった際にはAmplifyが有力な選択肢になってくるんじゃないでしょうか。
AmplifyはAmplifyで便利ですし、Firebaseと違ってGraphQLサービスであるAppSyncをバックエンドとして扱えるので、個人開発者の皆さんもぜひAmplifyを視野に入れてみてください。弊社では現在、Amplifyを用いてVueのSPAアプリケーションを作成しております。
その中でSPAでありがちな、URLのサブディレクトリへの直接アクセスをどう処理するかで悩み、最終的にサブディレクトリへの直接アクセスが可能な設定を達成したので共有いたします。なお、本記事の内容の実現は基本的にこの記事を下敷きにしております。
筆者の@go6887様にはこの場を借りて感謝を申し上げます。How toを3行で
- Amplifyコンソールの「書き換えて、リダイレクト」設定で、404時に
/<*>
を/?redirect_path=<*>
にリダイレクトするように設定。- Vue router用のリダイレクト処理を記述。アクセス先URIに
redirect_path
が含まれている場合に、redirect_pathが指すサブディレクトリへリダイレクトするようにする。- Vue routerでルートページのbeforeEnterにリダイレクト処理を追加。
以下詳細になります。
前提
- Vueを用いて作成したSPAのWebアプリケーションである。
- Amplifyを用いてホスティングを行っている。
- Vue routerの設定をhistoryモードにしている。
- ベースURLは
https://works-hi.com
とする。- サブディレクトリとして以下を持つとする。
- mypage
- history
- setting
Amplifyコンソールでのリダイレクト設定
AWSコンソールからAmplifyを開くと、「すべてのアプリ」画面が開くかと思います。設定したいアプリの設定画面へ遷移し、左のサイドバー内から「書き換えて、リダイレクト」を開きます。
「編集」から編集画面を開き、リダイレクト設定を以下のように設定します。
送信元アドレス ターゲットアドレス 入力 /<*> /?redirect=path=<*> 404 入力を404にしたのは、SPAではサブディレクトリに直接アクセスした際に404になるためです。
ここの設定により、「送信元アドレス」にアクセスした際にHTTPステータスが「入力」のステータスになったら、「ターゲットアドレス」にリダイレクトする、ということが実現されます。beforeEnterのリダイレクト処理
基本的には参考にした記事と同じですが、Amplifyのリダイレクトの仕様を回避するために少々手を加えます。
guard.tsimport { NavigationGuard } from 'vue-router'; const queryKey = 'redirect_path'; export const redirect: NavigationGuard = (to, _, next) => { if (to.query[queryKey]) { const path = `/${to.query[queryKey]}`.replace('/index.html', ''); next({ path }); } else { next(); } };変わっている部分はreplaceによりpathから
/index.html
を削除している部分です。
Amplifyコンソールにおいて、404に対するリダイレクト処理では末尾に自動的に/index.html
が付与されてしまいます。
/index.html
を削除せずにリダイレクトすると、/setting/index.html
等へルーティングされることになり、結果的にルートページへリダイレクトされてしまいます。※SPAでないWebアプリケーションにおいては各サブディレクトリに対応するindex.htmlが存在するし、SPAではAmplifyの公式ドキュメントでも示されているように、サブディレクトリにアクセスされた場合にはルートディレクトリのindex.htmlに導くのが基本とされているためアシストのつもりなのでしょうが、今回のようなケースにとっては蛇足でしかないため、記載通りのリダイレクトをしてほしいものです。
Vue routerの設定
最後に、前セクションで作成したguard.tsをVue routerのルーティング設定のbeforeEnterに設定します。
typescript.router.tsimport Vue from 'vue'; import VueRouter from 'vue-router'; import Main from '@/containers/pages/Main.vue'; import Mypage from '@/containers/pages/Mypage.vue'; import History from '@/containers/pages/History.vue'; import Setting from '@/containers/pages/Setting.vue'; import PageNotFound from '@/containers/pages/PageNotFound.vue'; import { authenticate } from '@/router/authenticate'; import { redirect } from '@/router/guard'; Vue.use(VueRouter); const routes = [ { path: '/', component: Main, name: 'main', beforeEnter: redirect, }, { path: '/search', component: Mypage , name: 'mypage ', }, { path: '/history', component: History, name: 'history', }, { path: '/setting', component: Setting, name: 'setting', }, { path: '*', component: PageNotFound, name: 'notFound', }, ]; const router = new VueRouter({ mode: 'history', base: process.env.BASE_URL, routes, }); router.beforeEach(authenticate); export default router;※ルートディレクトリに対してのみbeforeEnterを設定しているのは、今回のケースではAmplifyコンソールのリダイレクト処理による向き先がルートディレクトリのみになるためです。
以上の設定により、ユーザーが例えば
https://works-hi.com/setting
に直にアクセスした場合、以下のようにリダイレクトされることになります。
- Amplifyコンソールの機能により、
https://works-hi.com/?redirect=path=setting/index.html
へリダイレクト。https://works-hi.com/?redirect=path=setting/index.html
はルートディレクトリなので、Vue routerによりbeforeEnterの処理が行われる。- guard.tsの処理により、
https://works-hi.com/setting
へルーティング。これで、AmplifyでホスティングしたVueのhistoryモードのSPAにおいて、サブディレクトリへの直接アクセスを実現することができました。
おわりに
参考記事でも言及されているように、Vueの公式ページでも、Amplifyの公式ドキュメントでも、その他のブログ等でも、基本的にSPAでは、サブディレクトリへの直接アクセスはルートページへ遷移させるように推奨しているケースが多いです。
しかし、例えばQiitaのようなコンテンツ共有サービスであったり、コーポレートサイトをSPAで実装する場合など、サブディレクトリへの直接アクセスを実現したいケースは結構あるんじゃないでしょうか。
ニーズはあるはずなのになんで情報があんまりないんだろう、ともやもやしていたところであったので、同じところで詰まった方々に少しでも本記事が役立てば幸いです。参考
https://docs.aws.amazon.com/ja_jp/amplify/latest/userguide/redirects.html
https://router.vuejs.org/ja/guide/essentials/history-mode.html
https://qiita.com/go6887/items/dcb7aa86ba6a006d4746
- 投稿日:2020-08-17T11:28:00+09:00
vue.js コンポーネント間のデータのやりとり
vueコンポーネント間でデータを送る方法は 親⇒子 と 子⇒親 の場合で異なる。
親⇒子 propsでデータを受け取る
子コンポーネントの
export default{}
内でpropsオプションでプロパティを用意する。child.vue<script> export default { props: { x: { type: Number, default: 1 }, } } </script>ここで
x
は数値型、初期値1で宣言しているが、
props: ['x']
と変数だけ用意してもいい。親コンポーネント側で子を呼び出す際に属性としてデータを渡すことができる。
parent.vue<template> <Child :x="10"></Child> </template>子⇒親 $emitでデータを送る
子コンポーネントでカスタムイベントである$emitを用意する。
child.vue<template> <div> <button @click="clickAction">x</button> </div> </template> <script> export default { props: { x: { type: Number, default: 1 }, }, methods: { clickAction() { this.$emit("clicked", this.x + 100); } } } </script>ここでは
<button>
を押した際のアクションとして$emitが発火される。送られた値は、親コンポーネント側では$eventで受け取ることができる。
parent.vue<template> <Child v-on:clicked="y = $event"></Child> </template>ここでは親の
y
という変数で受け取っている。