20200817のvue.jsに関する記事は6件です。

初心者のためのVue.jsの環境構築方法

初心者のためのVue.jsの環境構築方法

初心者向けのVue.jsの環境構築方法をまとめました。
Vue.jsをローカルで少し動かしてみたい方は以下の記事を参考にしてみてください。

今話題のJavaScriptフレームワーク「Vue.js」の簡単な始め方

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

日本語諸方言コーパスをDB化して遊ぶ (4) サービスの全体像を決める

連載記事です。第4回は前回お試しで作成した Laravel アプリを いったん反故にして 、サービスの全体像を決めて、ルーティングを作るところまで。完全に自分用の作業メモで、説明もいろいろ足りていないと思いますが、ご容赦ください。

実現したい機能

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 を使用しました。

「談話」ごとの発話総覧と編集機能は以下のようにしましょう。「談話」テーブルには非常に多くの情報が含まれている(ファイル記号、データ名、収録年月日、収録場所、収録担当者、編集担当者、話題、談話ジャンル)ため、これをすべて一画面に表示すると邪魔になります。そこで「談話一覧」ページには、データの性質に影響を与えない要素(収録担当者、編集担当者など)は表示しないようにして、「発話詳細」ページでだけ確認できるようにします。また、「発話編集」ページで更新処理を行なったら「発話詳細」ページに遷移させます。

func_1.png

「話者」ごとの発話総覧は、単純に発話をまとめるだけではつまらないので、簡単な文字列処理を噛ませて、発話ごとではなく文ごとに提示するようにしましょう。

func_2.png

ファイル変換画面は、とりあえずアップロードして、変換して、ダウンロードして、削除できればよいので、素朴なのを用意しておきます。

func_3.png

プロジェクトの準備

では作成していきましょう。まずは 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.js
import 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>

コンパイル・ローカルサーバで確認

以上のコードを書き終えたら、一度コンパイルして確認しましょう。以下のコマンドを実行すると、もろもろの必要なものを取り込んでいい感じにコンパイルしてくれます。

cmd
npm run dev あるいは npm run production

コンパイルが済んだら Laravel の以下コマンドでローカルサーバを立ち上げて、localhost:8000 にアクセスしてみましょう。404 や 500 エラーが出ずに想定通りのページが表示されれば成功です。

cmd
php artisan serve

次回

Heroku で SQLite が使えないことを知って PostgreSQL に乗り換える回です。


  1. ログ一覧ページも作ったのですが、解説に真新しいところもないので本連載記事からは割愛しました。 

  2. というか本番環境で SQLite を使えるサービスのほうが少なそう。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

既存のLaravel/VueCLIプロジェクトにVuetifyを導入する

環境

  • Laravel 7.25
  • PostgreSQL 12.3
  • vueCLI 4.4.1

やりたいこと

手順

コマンド

  • 公式サイトにはいろいろあるけど、この場合はこの2つでOK
npm install vuetify
npm install sass sass-loader fibers deepmerge -D
  • mdiアイコンを使うとき
npm install @mdi/font -D

resources/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(), //★
});

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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化する為に使います、なくでも動けます。

フォルダ構成

dya2g02.png

全体の流れ

  1. Protoファイルの作成
  2. Node.jsでバックエンド作成
  3. Envoy proxyの設定
  4. Client stubsの生成
  5. Clientの作成
  6. 動かしてみましょう
  7. Docker化

コードを書いてみましょう

1. Protoファイルの作成

ProtoファイルはgRPCの心臓と呼ばれる部分、ここでRequestとResponseとサービスを定義することによって、後でStubsファイルを自動的に生成することができます。
Protoファイルの構成は大体四つの部分に分けている。
1. Protoのバージョンを定義する
2. Packageの名前
3. サービス定義
4. メッセージ定義

todo.proto
syntax = "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:

bash
npm init -y
npm i uuid grpc @grpc/proto-loader
npm i -D nodemon

yarn:

bash
yarn init -y
yarn add uuid grpc @grpc/proto-loader
yarn add -D nodemon

startの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のイメージ設定ファイル

Dockerfile
FROM envoyproxy/envoy:v1.14-latest
COPY ./envoy.yaml /etc/envoy/envoy.yaml
CMD /usr/local/bin/envoy -c /etc/envoy/envoy.yaml

Envoyの設定ファイル

envoy.yaml
admin:
  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: 9090

gRPCサービスは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を使います。

bash
vue create client

そしてStubsを作ります
このコメンドで ./client/src に二つのJSファイルを生成する

  • todo_pb.js // メッセージのType定義
  • todo_grpc_web_pb.js // gRPCクライアント
bash
protoc -I server todo.proto \
    --js_out=import_style=commonjs,binary:client/src \
    --grpc-web_out=import_style=commonjs,mode=grpcwebtext:client/src

5. Clientの作成

クライアントのTodoコンポーネントの中にtodo_pb.jsとtodo_grpc_web_pb.jsを導入して、todoServiceClient()を使ってlocalhost:8080のEnvoy proxyに接続します。

Todo.vue
import { 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 start

enovy proxy:

bash
$ docker build -t envoy:v1 ./enovy
$ docker run --rm -it -p 8080:8080 envoy:v1

Front-End:

bash
$ cd ./client
$ yarn dev

成功すればこんな感じです:
giphy.gif

7. Docker化

Docker Compose一発で動かす為にDocker化します。
各フォルダにDockerfileと.dockerignore入れます。

./serverフォルダ

Dockerfile
FROM 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フォルダ

Dockerfile
FROM 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.yml
version: '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: bridge

Envoyの設定ファイルのサーバーアドレス修正します

envoy.yaml
endpoints:
  - 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 logs

Front-Endに入ります:http://localhost:3000

最後

Qiitaで初めての投稿です、よろしくお願いします。
Githubでソースコードを公開しています、
なんが詰まったところがあれば参考してください:Github

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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行で

  1. Amplifyコンソールの「書き換えて、リダイレクト」設定で、404時に/<*>/?redirect_path=<*>にリダイレクトするように設定。
  2. Vue router用のリダイレクト処理を記述。アクセス先URIにredirect_pathが含まれている場合に、redirect_pathが指すサブディレクトリへリダイレクトするようにする。
  3. Vue routerでルートページのbeforeEnterにリダイレクト処理を追加。

以下詳細になります。

前提

  • Vueを用いて作成したSPAのWebアプリケーションである。
  • Amplifyを用いてホスティングを行っている。
  • Vue routerの設定をhistoryモードにしている。
  • ベースURLはhttps://works-hi.comとする。
  • サブディレクトリとして以下を持つとする。
    • mypage
    • history
    • setting

Amplifyコンソールでのリダイレクト設定

AWSコンソールからAmplifyを開くと、「すべてのアプリ」画面が開くかと思います。設定したいアプリの設定画面へ遷移し、左のサイドバー内から「書き換えて、リダイレクト」を開きます。
2.png
「編集」から編集画面を開き、リダイレクト設定を以下のように設定します。

送信元アドレス ターゲットアドレス 入力
/<*> /?redirect=path=<*> 404

1.png

入力を404にしたのは、SPAではサブディレクトリに直接アクセスした際に404になるためです。
ここの設定により、「送信元アドレス」にアクセスした際にHTTPステータスが「入力」のステータスになったら、「ターゲットアドレス」にリダイレクトする、ということが実現されます。

beforeEnterのリダイレクト処理

基本的には参考にした記事と同じですが、Amplifyのリダイレクトの仕様を回避するために少々手を加えます。

guard.ts
import { 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.ts
import 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に直にアクセスした場合、以下のようにリダイレクトされることになります。

  1. Amplifyコンソールの機能により、https://works-hi.com/?redirect=path=setting/index.htmlへリダイレクト。
  2. https://works-hi.com/?redirect=path=setting/index.htmlはルートディレクトリなので、Vue routerによりbeforeEnterの処理が行われる。
  3. 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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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という変数で受け取っている。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む