20200518のvue.jsに関する記事は11件です。

Nuxt/Componentsモジュールを使ってみた

Nuxt/componentsとは

解説によれば、「importなどしなくても自動的にコンポーネントを読み込んでくれる」モジュールです。
便利そうなので早速使ってみましょう。

Nuxtインストールとモジュールのインストール

# Nuxt
yarn create nuxt-app <project-name>

# Nuxt/Components
yarn add --dev @nuxt/components

nuxt.config.jsに追加

export default {
  buildModules: [
    // TODO: Remove when upgrading to nuxt 2.13+
    '@nuxt/components'
  ]
}

※TypeScript使用している方は、すでに@nuxt/typescript-buildモジュールが入っていると思うので消さないようにご注意を

※Nuxt v2.13はまだ(2020/05/18現在)リリースされていないので大丈夫ですが、
今後は標準でこのモジュールがインストールされるようなので、アップグレードの際はご注意を。

Hello,world!してみる

index.vue
<template>
  <div class="container"><HelloWorld /></div>
</template>

index.vueに上記のようにテンプレートブロックだけ書きます。普通はscriptブロック内でimportしないと使えませんが、今回は書きません。

カスタムタグに合わせてHelloWorld.vueという名前のファイルをcomponentsに入れてみましょう。

/components/HelloWorld.vue
<template>
  <div>
    <h1>Hello, world!</h1>
  </div>
</template>

HelloWorld.vueもかなり簡単に書いてみました。やはりテンプレートブロックしか書きません。

結果

コメント 2020-05-18 225121.png

できました!
importしていないにも関わらず自動的にカスタムタグと同じvueファイルを読み取っています!

終わりに

最速紹介ということで使い方だけ紹介しましたがとても便利です
早く安定版標準になってほしいものです。

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

【Vue.js】v-if使用方法 備忘録

概要

v-ifの使用方法を記した備忘録です。

v-ifとは

HTML要素を条件分岐させる事ができるディレクティブの事。

文法

文法.html
<タグ v-if 条件></タグ>
<タグ v-if-else 条件></タグ>
<タグ v-else 条件></タグ>

v-ifを使ってみる

post_com.html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">  
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
    <title>Title</title>
</head>

<body>
    <div id="app">
        <label><input v-model="myText"></label>
        <!--0から140文字の間の入力があった場合の処理-->
        <p v-if="0 <= myText.length && myText.length <=140">
            {{myTextLen}}
        </p>
        <!--140文字以上が入力された場合の処理-->
        <p v-else-if="141 <= myText.length">
            <font color="red">
                {{myTextLen}}文字超過
            </font>
        </p>
        <p v-else>
        </p>
    </div>
    <script src="./post_com.js"></script>
</body>
</html>
post_com.js
const app = new Vue({
    el: "#app",
    data: {
        myText:""
    },

    computed:{
        //文字数の絶対値を140からマイナスカウントさせる処理
        myTextLen: function(){
            return Math.abs(this.myText.length - 140);
        }
    }
})

解説

上記PGMはフォームへの入力可能文字残数(ここでは140文字)を表示させるプログラム。
v-modelでデータを繋ぎcomputedで計算処理を外出しさせています。

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

docker-compose + vue.js + typescript 環境構築

元々知っているvue.jsに
最近仕事で使うdocker + typescriptを合わせた環境構築を行ってみた

基本参照はここかなり見ました。

os ubuntu 18.04

docker-compose用のyml生成

cd your/dev/dic
touch docker-compose.yml 
docker-compose.yml
version: '3'
services:
  node:
    image: node:12.7.0-alpine
    volumes:
      - .:/vuejs

nodeのversion指定 + マウント先はvuejsに指定

コンテナ内に入る

入った後にcli起動して初期インストール

docker-compose run node sh
cd vuejs
yarn global add @vue/cli

cli終わったら
vueのプロジェクトを作成

vue create .

vue/cil経由で環境構築(注意点有)

対話型で処理を進めて行くのですが、please pick a presetのときに
Manually select features を選んでください
(そうじゃないとtypescript入らない)

Vue CLI v4.1.1
? Generate project in current directory? Yes


Vue CLI v4.1.1
? Please pick a preset: Manually select features
? Check the features needed for your project: 
 ◉ Babel
 ◉ TypeScript
 ◉ Progressive Web App (PWA) Support
 ◉ Router
 ◉ Vuex
 ◉ CSS Pre-processors
 ◉ Linter / Formatter
 ◉ Unit Testing
 ◉ E2E Testing

作る環境によって必要なmoduleが違うでしょうが
とりあえず今回は全部チェック入れ

後は細かい設定聞かれますがわからないやつは基本ググってほしい
たぶんtest toolを jestとかフォーマット設定まともにしておけば
大丈夫そう

Vue CLI v4.1.1
? Generate project in current directory? Yes
Vue CLI v4.1.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, TS, PWA, Router, Vu
ex, CSS Pre-processors, Linter, Unit, E2E
? Use class-style component syntax? Yes
? Use Babel alongside TypeScript (required for modern mode
, auto-detected polyfills, transpiling JSX)? Yes
? Use history mode for router? (Requires proper server set
up for index fallback in production) Yes
? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS 
Modules are supported by default): Sass/SCSS (with node-sa
ss)
? Pick a linter / formatter config: TSLint
? Pick additional lint features: (Press <space> to select,
 <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Jest
? Pick a E2E testing solution: Cypress
? Where do you prefer placing config for Babel, ESLint, et
c.? In dedicated config files
? Save this as a preset for future projects? No

コンテナに戻るとプロジェクトが生成されているはず

dockerで動かす

対象ディレクトリ直下にdockerfileを作成

dockerfile
FROM node:12.7.0-alpine

WORKDIR /myapp

COPY package.json ./
COPY yarn.lock ./

RUN yarn install
docker-compose.yml
version: '3'
services:
  view:
    build: .
    command: yarn run serve
    volumes:
      - .:/myapp
      - /myapp/node_modules
    ports:
      - "8000:8080"
docker-compose up -d

後はlocalhost:8000でサイトが開けるようになっているはずです!

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

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版 その1~~

はじめに

「SPA」という言葉を耳にし、「SPAとは、Single Page Application(シングルページアプリケーション)のことで、単一のWebページでアプリケーションを構成する」とは、なんてスゲーんだと思いました。
では、こしらえた「スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた2」をこのSPAにしてみようではないかというのがことの始まりです。

データは前回と同じように吉野家さんの店舗データなのですが、内容は、適当に入れたデータなので、そこんところよろしくお願いします。吉野家がうまいのは事実です。
データを作成するに当たり吉野家さんのHPを見ていましたら、ふと気が付きました。
「うまい、やすい、はやい」だけでなく、「ずっと。」が付いてました。いいですねぇ。

なにでSPAにするか

Vue、Angular、Reactなどがあるようですが、今回は、
1. 学習コストが低い
2. 小規模である
ということから「Vue.js」で行うことに決めました。

※Vueうんぬんについては先人がいろいろ教えてくれてますので、そちらを参照してください。

ここでやっていないこと

コンポーネント化ということは今回行っておりません。
コンポーネント化するとソースが見やすくはなりそうだったのですが、親と子の間でのデータ受け渡しが面倒に感じられたので、今回は行いませんでした。今後、余力があればやってみたいと思います。

それなりに変更が必要(当たり前か)

前回から変更が必要になっていますので、それらを列挙します。

1. データの持ち方

データシートの2行目を追加しました。
これはhtml内のidやname情報に使用します。

2. doGet、doPost

doGetは必要です。初めにhtmlファイルを呼び出すために必要なので、その記述だけになります。
doPostは変更内容や次に表示するhtmlファイルの交通整理を行っていましたが、Postはされないですし、1ページで完結するので、doPostそのものが要らなくなります。

3. データ更新画面と新規登録画面のファイルが不要

前途の通りで更新画面と新規登録画面には遷移しませんので不要となります。
が、更新と新規はモーダル画面を表示し更新、登録するような画面設計にしました。
モーダルにするためのファイルは今回は用意しません。
vue_index.html内にモーダルの内容を記載しています。

4. 新規に作成したファイル1

vue_js.html。 普通ならVue.jsなんでしょうがGASなのでvue_js.htmlという名前にしておきました。
表示用のhtmlファイルからインクルードして使用します。
このファイルはVueで使用するデータをプロパティとして入れ物を用意しておくファイルになります。
また、表示用HTMLファイルからのonClickで呼ばれたメソッドを記述しておき、コード.gsのメソッドを呼び出す橋渡しをするファイルです。(戻り値があればそれを受けます)

5. 新規に作成したファイル2

vue_index.html。index.htmlでよかったのですが、Vueを使わないで作っていたものがあったのでvueで作っているファイルだよ、ということでvue_index.htmlとしました。
リスト表示、データ更新、新規登録の機能が入っていますが、データ更新と新規登録がだいたい同じになったので冗長が少しはなくなったのではないかと思います。

ファイル構成

ということでファイル構成はこのようになります
1. コード.gs
2. vue_index.html
3. vue_js.html
4. css.html

ハマったところ

ハマったところがあり、それを含めると長くなるので別で投稿しました。
もし、同じようなお悩みがある方の参考になれば幸いです。

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版、ハマったところinputの再描画編~~

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版、ハマったところcheckboxの再描画編~~

コード説明

コード.gs

基本的にコード.gsのやることは変わりません。前途の通り、doGet、doPostが変更になります。
後は、データの持ち方の都合上要らなくなったfunctionや追加したfunctionがあります。
まぁ、通常の書き方ということで。

初期表示

ファイルを1つずつ説明しようと思ったのですが、そうもいかないようなので、vue_indexファイルを基準に、その場所の関連する内容で説明しようと思います。

まずはheadから、ハマったナゴリでキャッシュを残さない設定が入っておりますw
cssファイルのcss.htmlをインクルードしています。(vue_jsファイルはソースの下でインクルードしてます)
そして、vueのリンクですね。(まぁ、ここはOKですね)

vue_index.html
  <head>
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <base target="_top">
    <?!= include('css'); ?>
    <script src="https://unpkg.com/vue"></script>
  </head>

bodyタグの次のdivタグのidでvueのvmを指定します。

vue_index.html
<body>
Vueで作成したページです
  <div id="vue_example">

上記でidに設定したvue_exampleをした↓のnew Vueのインスタンス名として「el:」にも設定しています。2つ同じ名称にしているところが多かったのでそのままにしていますが、2行目の「el:」がHTML内のタグのidと同じならいいんじゃないかと思います。(違ったらごめんなさい)
そして、dataで各プロパティを宣言します。
スプレッドシートからデータを取得すると、2次元配列になるので最初から2次元配列にしています。
初期表示の状態が分からなかったので[['初', '期', '表', '示', '中', 'です']]という配列を初期で設定しています。(なので、実行すると「初期表示中」と表示される)
何のプロパティなのかは以下のソースを参照願います。

vue_js.html
<script>
var vue_example = new Vue({       //ここと
    el: '#vue_example',           //ここ

    data:{
        listtitles:[['', '', '', '', '', 'です']],  // リストの項目名
        listitems: [['', '', '', '', '', 'です']],  // リストのデータ
        dispDetails: false,                                  // モーダル表示判定
        showContent: false,                                  // モーダル表示内の詳細データ表示判定
        searchword: '',                                      // 検索文字列
        refitem:[],                                          // 詳細データ
        initOptions:[['初期値']],                             // 詳細画面のマスターデータ
    },

続いてボタンですが、その前にinputのテキストの説明を。
「v-」とついているところがvueの設定箇所になります。
まず、「v-model="searchword"」は、モデルとしてsearchwordプロパティを使うということを表していて、このテキストボックスに文言を入れると、プロパティにその文言が入ってしまうんですね。スゴイ!

vue_index.html
    <input type=text name=searchword v-model="searchword" />

で、続いてボタンです。
「v-on:click」はクリックしたときにvue_js.html内に書かれたメソッドを実行しろ。ということになります。まぁ、これはそのまま受け入れられますよね。「検索クリア」時のメソッドの中に、searchwordプロパティを空にしろという命令を書いておきます。そうすると、searchwordプロパティが空になるのは当たり前ですが、テキストボックスも空になります。スゴイ!!
次の新規登録ボタンは、クリックするとモーダルが開くので説明は後程。

vue_index.html
    <button v-on:click="search_word()" class='btn-radius-blue'> 検索 </button>&nbsp;
    <button v-on:click="search_word_clea()" class='btn-radius-blue'> 検索クリア </button>&nbsp;
    <button v-on:click="add_disp()" class='btn-radius-blue'> 新規登録 </button><br><br>

次はデータのリスト表示ですが、まずテーブルの項目名から。
項目名はデータシートの1行目に入っています。
listtitlesにデータを入れています。
listtitlesは、2次元配列なので、以下のように2回まわしてデータを取得、表示しています。
trタグでv-forで回し、titlesに入れ
次のthタグ内でtitlesを更に回して、title で表示しています。
{{ title }}と2重カッコで囲むことにより文言を表示できます。簡単スゴイ!!!
下記のようにインデックス[idx]も使うことが可能です。(使ってないですが)

vue_index.html
    <table>
      <tr v-for="(titles,idx) in listtitles">
        <th v-for="title in titles">{{ title }}</th>
        <th>ボタン</th>
      </tr>

表示されるとこんな感じです。↓
image.png

続いてデータ部です。
データ部は、listitemsプロパティにデータが入っており、trタグで1回まわしています。
更に、tdタグでまわし、{{ tmp }}としてデータを表示しています。
そして最後に詳細画面を表示するためのボタンを設定しています。
buttonタグで「v-on:click」で「ref_item( idx )」を指定しています。
「idx」はループした時のインデックスになります。
クリックされたら、そのインデックスをパラメータに「ref_item」メソッドを実行することになります。

vue_index.html
      <tr v-for="(it,idx) in listitems">
        <td v-for="tmp in it">{{ tmp }}</td>
        <td><button v-on:click="ref_item( idx )" class='btn-radius-blue'>詳細表示</button></td>
      </tr>

ひとまず、リスト表示はこんな感じ。(更新日付の新しい方から10件表示にしています)

image.png

ここまでは初期表示なのですが、初期表示はvue_js.html内のcreateが呼ばれます。
この中で、データの取得と、項目名6個の取得を行っています。
以下の「//リスト取得」を例に説明すると
「google.script.run.withSuccessHandler」のような形で「コード.gs」内の「getSheetData」functionを呼びます。
読み込みに失敗すると「withFailureHandler」に入り、アラートで「データの初期取得に失敗しました。」と表示されます。
成功すると戻り値が「this.initData」メソッドのパラメータとして入ります。

vue_js.html
    // 初期表示
    created: function() {
               //リスト取得
               google.script.run.withSuccessHandler(this.initData)
                                .withFailureHandler(function(arg){
                                   alert("データの初期取得に失敗しました。");
                                 }).getSheetData();

                //リストの項目名称6個を取得    
                google.script.run.withSuccessHandler(this.initTitles)
                                 .withFailureHandler(function(arg){
                                    alert("リスト項目名の初期取得に失敗しました。");
                                 }).getItemNameList(6);
    } 

下記の「methods」内の「initData」functionが呼び出され、そのパラメータである「ary」にデータが入ります。そして「this.listitems = ary;」とあるように、listitems プロパティにデータがセットされます。後は、先に書きました「vue_index.html」の処理になります。

vue_js.html
    methods:{
        // データプロパティ
        initData: function(ary){       // このaryに入る
                        this.listitems = ary; 
                  },

ここまでで初期表示が終わりました。
長いので、いったんここで切ります。

次回はスプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版 その2~~になります。

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

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版~~

はじめに

「SPA」という言葉を耳にし、「SPAとは、Single Page Application(シングルページアプリケーション)のことで、単一のWebページでアプリケーションを構成する」とは、なんてスゲーんだと思いました。
では、こしらえた「スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた2」をこのSPAにしてみようではないかというのがことの始まりです。

データは前回と同じように吉野家さんの店舗データなのですが、内容は、適当に入れたデータなので、そこんところよろしくお願いします。吉野家がうまいのは事実です。
データを作成するに当たり吉野家さんのHPを見ていましたら、ふと気が付きました。
「うまい、やすい、はやい」だけでなく、「ずっと。」が付いてました。いいですねぇ。

なにでSPAにするか

Vue、Angular、Reactなどがあるようですが、今回は、
1. 学習コストが低い
2. 小規模である
ということから「Vue.js」で行うことに決めました。

※Vueうんぬんについては先人がいろいろ教えてくれてますので、そちらを参照してください。

ここでやっていないこと

コンポーネント化ということは今回行っておりません。
コンポーネント化するとソースが見やすくはなりそうだったのですが、親と子の間でのデータ受け渡しが面倒に感じられたので、今回は行いませんでした。今後、余力があればやってみたいと思います。

それなりに変更が必要(当たり前か)

前回から変更が必要になっていますので、それらを列挙します。

1. データの持ち方

データシートの2行目を追加しました。
これはhtml内のidやname情報に使用します。

2. doGet、doPost

doGetは必要です。初めにhtmlファイルを呼び出すために必要なので、その記述だけになります。
doPostは変更内容や次に表示するhtmlファイルの交通整理を行っていましたが、Postはされないですし、1ページで完結するので、doPostそのものが要らなくなります。

3. データ更新画面と新規登録画面のファイルが不要

前途の通りで更新画面と新規登録画面には遷移しませんので不要となります。
が、更新と新規はモーダル画面を表示し更新、登録するような画面設計にしました。
モーダルにするためのファイルは今回は用意しません。
vue_index.html内にモーダルの内容を記載しています。

4. 新規に作成したファイル1

vue_js.html。 普通ならVue.jsなんでしょうがGASなのでvue_js.htmlという名前にしておきました。
表示用のhtmlファイルからインクルードして使用します。
このファイルはVueで使用するデータをプロパティとして入れ物を用意しておくファイルになります。
また、表示用HTMLファイルからのonClickで呼ばれたメソッドを記述しておき、コード.gsのメソッドを呼び出す橋渡しをするファイルです。(戻り値があればそれを受けます)

5. 新規に作成したファイル2

vue_index.html。index.htmlでよかったのですが、Vueを使わないで作っていたものがあったのでvueで作っているファイルだよ、ということでvue_index.htmlとしました。
リスト表示、データ更新、新規登録の機能が入っていますが、データ更新と新規登録がだいたい同じになったので冗長が少しはなくなったのではないかと思います。

ファイル構成

ということでファイル構成はこのようになります
1. コード.gs
2. vue_index.html
3. vue_js.html
4. css.html

ハマったところ

ハマったところがあり、それを含めると長くなるので別で投稿しました。
もし、同じようなお悩みがある方の参考になれば幸いです。

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版、ハマったところinputの再描画編~~

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版、ハマったところcheckboxの再描画編~~

コード説明

コード.gs

基本的にコード.gsのやることは変わりません。前途の通り、doGet、doPostが変更になります。
後は、データの持ち方の都合上要らなくなったfunctionや追加したfunctionがあります。
まぁ、通常の書き方ということで。

初期表示

ファイルを1つずつ説明しようと思ったのですが、そうもいかないようなので、vue_indexファイルを基準に、その場所の関連する内容で説明しようと思います。

まずはheadから、ハマったナゴリでキャッシュを残さない設定が入っておりますw
cssファイルのcss.htmlをインクルードしています。(vue_jsファイルはソースの下でインクルードしてます)
そして、vueのリンクですね。(まぁ、ここはOKですね)

vue_index.html
  <head>
    <meta http-equiv="Pragma" content="no-cache">
    <meta http-equiv="Cache-Control" content="no-cache">
    <meta http-equiv="Expires" content="0">
    <base target="_top">
    <?!= include('css'); ?>
    <script src="https://unpkg.com/vue"></script>
  </head>

bodyタグの次のdivタグのidでvueのvmを指定します。

vue_index.html
<body>
Vueで作成したページです
  <div id="vue_example">

上記でidに設定したvue_exampleをした↓のnew Vueのインスタンス名として「el:」にも設定しています。2つ同じ名称にしているところが多かったのでそのままにしていますが、2行目の「el:」がHTML内のタグのidと同じならいいんじゃないかと思います。(違ったらごめんなさい)
そして、dataで各プロパティを宣言します。
スプレッドシートからデータを取得すると、2次元配列になるので最初から2次元配列にしています。
初期表示の状態が分からなかったので[['初', '期', '表', '示', '中', 'です']]という配列を初期で設定しています。(なので、実行すると「初期表示中」と表示される)
何のプロパティなのかは以下のソースを参照願います。

vue_js.html
<script>
var vue_example = new Vue({       //ここと
    el: '#vue_example',           //ここ

    data:{
        listtitles:[['', '', '', '', '', 'です']],  // リストの項目名
        listitems: [['', '', '', '', '', 'です']],  // リストのデータ
        dispDetails: false,                                  // モーダル表示判定
        showContent: false,                                  // モーダル表示内の詳細データ表示判定
        searchword: '',                                      // 検索文字列
        refitem:[],                                          // 詳細データ
        initOptions:[['初期値']],                             // 詳細画面のマスターデータ
    },

続いてボタンですが、その前にinputのテキストの説明を。
「v-」とついているところがvueの設定箇所になります。
まず、「v-model="searchword"」は、モデルとしてsearchwordプロパティを使うということを表していて、このテキストボックスに文言を入れると、プロパティにその文言が入ってしまうんですね。スゴイ!

vue_index.html
    <input type=text name=searchword v-model="searchword" />

で、続いてボタンです。
「v-on:click」はクリックしたときにvue_js.html内に書かれたメソッドを実行しろ。ということになります。まぁ、これはそのまま受け入れられますよね。「検索クリア」時のメソッドの中に、searchwordプロパティを空にしろという命令を書いておきます。そうすると、searchwordプロパティが空になるのは当たり前ですが、テキストボックスも空になります。スゴイ!!
次の新規登録ボタンは、クリックするとモーダルが開くので説明は後程。

vue_index.html
    <button v-on:click="search_word()" class='btn-radius-blue'> 検索 </button>&nbsp;
    <button v-on:click="search_word_clea()" class='btn-radius-blue'> 検索クリア </button>&nbsp;
    <button v-on:click="add_disp()" class='btn-radius-blue'> 新規登録 </button><br><br>

次はデータのリスト表示ですが、まずテーブルの項目名から。
項目名はデータシートの1行目に入っています。
listtitlesにデータを入れています。
listtitlesは、2次元配列なので、以下のように2回まわしてデータを取得、表示しています。
trタグでv-forで回し、titlesに入れ
次のthタグ内でtitlesを更に回して、title で表示しています。
{{ title }}と2重カッコで囲むことにより文言を表示できます。簡単スゴイ!!!
下記のようにインデックス[idx]も使うことが可能です。(使ってないですが)

vue_index.html
    <table>
      <tr v-for="(titles,idx) in listtitles">
        <th v-for="title in titles">{{ title }}</th>
        <th>ボタン</th>
      </tr>

表示されるとこんな感じです。↓
image.png

続いてデータ部です。
データ部は、listitemsプロパティにデータが入っており、trタグで1回まわしています。
更に、tdタグでまわし、{{ tmp }}としてデータを表示しています。
そして最後に詳細画面を表示するためのボタンを設定しています。
buttonタグで「v-on:click」で「ref_item( idx )」を指定しています。
「idx」はループした時のインデックスになります。
クリックされたら、そのインデックスをパラメータに「ref_item」メソッドを実行することになります。

vue_index.html
      <tr v-for="(it,idx) in listitems">
        <td v-for="tmp in it">{{ tmp }}</td>
        <td><button v-on:click="ref_item( idx )" class='btn-radius-blue'>詳細表示</button></td>
      </tr>

ひとまず、リスト表示はこんな感じ。(更新日付の新しい方から10件表示にしています)

image.png

ここまでは初期表示なのですが、初期表示はvue_js.html内のcreateが呼ばれます。
この中で、データの取得と、項目名6個の取得を行っています。
以下の「//リスト取得」を例に説明すると
「google.script.run.withSuccessHandler」のような形で「コード.gs」内の「getSheetData」functionを呼びます。
読み込みに失敗すると「withFailureHandler」に入り、アラートで「データの初期取得に失敗しました。」と表示されます。
成功すると戻り値が「this.initData」メソッドのパラメータとして入ります。

vue_js.html
    // 初期表示
    created: function() {
               //リスト取得
               google.script.run.withSuccessHandler(this.initData)
                                .withFailureHandler(function(arg){
                                   alert("データの初期取得に失敗しました。");
                                 }).getSheetData();

                //リストの項目名称6個を取得    
                google.script.run.withSuccessHandler(this.initTitles)
                                 .withFailureHandler(function(arg){
                                    alert("リスト項目名の初期取得に失敗しました。");
                                 }).getItemNameList(6);
    } 

下記の「methods」内の「initData」functionが呼び出され、そのパラメータである「ary」にデータが入ります。そして「this.listitems = ary;」とあるように、listitems プロパティにデータがセットされます。後は、先に書きました「vue_index.html」の処理になります。

vue_js.html
    methods:{
        // データプロパティ
        initData: function(ary){       // このaryに入る
                        this.listitems = ary; 
                  },

ここまでで初期表示が終わりました。
長いので、いったんここで切ります。

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

Cypress 自動テスト化

はじめに

新天地で、社内システムの自動テスト化について取り組ませていただいているおで記録してみます。
テストは、独自ランナーが使いやすい、Cypressです。
英語がまったく読めないので、苦戦・・・
Chromeの翻訳機能に助けられながら、やったことをメモしておきます。
OSはMacです。

自動テストの概念

自動テストについて調べた結果、概念はこんな感じです。

  • できるだけ簡単なコードで作成する
  • 機能別・テスト名・コード単位で検索しやすい
  • 簡単に更新できる
  • ひとつのものをみんなで共有出来る
  • 誰かに依存しない

セッションを保持したまま、複数のテストを実施する

まずは、ログインIDとパスワードを入れ、セッションを保持したまま別のテストを行う。
という入り口的な部分。
試行錯誤をしましたが、以下の2点を活用しました。

1.Cookieをファイル経由で保存する

参照サイト:Cypressで送る快適E2Eライフ

上記サイト様を参考にして、Cookie情報を別のファイルに出力し、それを読み込むという手法。
ログイン処理をカスタムコマンドにし、ログイン〜Cookieの出力まで行います。

カスタムコマンドについてはこちら ▶ 公式ドキュメント

cypress/support/commands.js
Cypress.Commands.add('login, () => {

  cy.url().then(url => {
    if(url === 'http://...'){

        console.log('relogin')

        cy.clearCookie('session_id')

        const login_id = Cypress.env('login_id') 
        const password = Cypress.env('password')

        cy.get('[name=login_id]').type(login_id).should('have.value', login_id)
        cy.get('[name=password]'). type(password,{ log:false }).should(el$ => {
          if(el$.val() !== password){
            throw new Error('Different value of typed password')
          }
        })
        cy.get('.submit').click()

        //セッションを保持
        cy.getCookie('ci_session').should('exist').then((cookie) => {
        cy.writeFile("cache/cookie/session_id.json", {
          value: cookie.value
        })

      })
    }
  })

});


cypress/integration/login_spec.js

describe('Login Action',() => {
    /**
     * セッション設定
     */
    before('Session Setting', () => {
        cy.readFile("cache/cookie/session_id.json").then(cs => {
            cy.setCookie("session_id", cs.value)
        });
        Cypress.Cookies.preserveOnce('session_id')
        cy.visit('URL')
    });

    /**
     * ログインチェック
     */
    beforeEach ('Login Check',() => {
        cy.login()
    })

    /**
     * テスト処理
     */
    it ('Test Case1', () => {
        cy.visit('/URL')
                // something assertion
        })
})

cypress/support/commands.jsで作成したカスタムコマンドは、
cypress/integration/login_spec.jsでセッションの有無からの再ログインという流れにすることで、beforeでファイルに出力されたCookie情報をreadし、セッションがある場合は、テストに進みます。
セッションがない場合は、cy.login()コマンドで、再度セッションを出力。
ログインする前は、clearCookie()で一回Cookieを削除してから再設定します。

2.Cypress.Cookies.preserveOnce('session_id') を使う

公式ドキュメント

cypress/integration/login_spec.js
ファイルに出力されたCookie情報を、保持しておくことが出来るコマンドです。

Cypress.Cookies.preserveOnce('session_id')
おわりに

他にも、セッションについては永続的に保持しておけるlocalStorageの利用もひとつの手段かと思いましたが、なかなか実装が出来ず。
Cypressは便利な分、不安定な要素や、実装できない要素等まだまだあるようです。
npmモジュールを入れると、ランナーが起動しなくなるし、正しく実行できるテストも、5回に1回はなんとかエラーが出ます。(ホットリロードすると正常)

まだまだ勉強することはたくさんありますが、セッションの保持が一個クリア出来たので、記録します。
シンプルな方法ですが、初心者なのでこれからこれから。

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

Cypress:セッションを保持した状態でテストする

はじめに

新天地で、社内システムの自動テスト化について取り組ませていただくことになりました。
テストは、独自ランナーが使いやすい、Cypressです。
英語がまったく読めないので、苦戦・・・
Chromeの翻訳機能に助けられながら、やったことをメモしておきます。
OSはMacです。

自動テストの概念

自動テストについて調べた結果、概念はこんな感じです。

  • できるだけ簡単なコードで作成する
  • 機能別・テスト名・コード単位で検索しやすい
  • 簡単に更新できる
  • ひとつのものをみんなで共有出来る
  • 誰かに依存しない

セッションを保持したまま、複数のテストを実施する

まずは、ログインIDとパスワードを入れ、セッションを保持したまま別のテストを行う。
という入り口的な部分。
試行錯誤をしましたが、以下の2点を活用しました。

1.Cookieをファイル経由で保存する

参照サイト:Cypressで送る快適E2Eライフ

上記サイト様を参考にして、Cookie情報を別のファイルに出力し、それを読み込むという手法。
ログイン処理をカスタムコマンドにし、ログイン〜Cookieの出力まで行います。

カスタムコマンドについてはこちら ▶ 公式ドキュメント

cypress/support/commands.js
Cypress.Commands.add('login, () => {

  cy.url().then(url => {
    if(url === 'http://...'){

        console.log('relogin')

        cy.clearCookie('session_id')

        const login_id = Cypress.env('login_id') 
        const password = Cypress.env('password')

        cy.get('[name=login_id]').type(login_id).should('have.value', login_id)
        cy.get('[name=password]'). type(password,{ log:false }).should(el$ => {
          if(el$.val() !== password){
            throw new Error('Different value of typed password')
          }
        })
        cy.get('.submit').click()

        //セッションを保持
        cy.getCookie('session_id').should('exist').then((cookie) => {
        cy.writeFile("cache/cookie/session_id.json", {
          value: cookie.value
        })

      })
    }
  })

});


cypress/support/commands.js
内容としては、もし、アクセスしたURLがログインフォームと同じだったらば、clearCookieして再ログインしてね。
セッションがあればスルーされる部分です。

また、ログインIDとパスワードは、cypress.jsonファイルに、環境変数としてセット。
真ん中の色々書いてある部分は、ログイン情報はセキュリティ上表示させないようにしてね、という処理。
ですので、ランナーのコマンドログにパスワードは表示されません。

 

cypress/integration/login_spec.js

describe('Login Action',() => {
    /**
     * セッション設定
     */
    before('Session Setting', () => {
        cy.readFile("cache/cookie/session_id.json").then(cs => {
            cy.setCookie("session_id", cs.value)
        });
    });

    /**
     * ログインチェック
     */
    beforeEach ('Login Check',() => {
          cy.login()
          Cypress.Cookies.preserveOnce('session_id')
    })

    /**
     * テスト処理
     */
    it ('Test Case1', () => {
        cy.visit('/URL')
        // something assertion
        })
})

cypress/integration/login_spec.js
内容としては、セッションがあればテスト開始!
セッションが無ければ、再ログイン(カスタムコマンド)処理をしてからテスト開始!
beforeに、ファイルに出力されたCookie情報をreadさせて、beforeEachで再ログインの有無をチェック。
因みにbeforeEachはすべてのテスト(it)に当たるので、毎回再ログインチェックしている。
別に要らないんですけどね。。。:joy:
余計な処理も、Cypress速いからいいじゃない。っていうことで。
 

2.Cypress.Cookies.preserveOnce('session_id') を使う

公式ドキュメント

cypress/integration/login_spec.js
ファイルに出力されたCookie情報を、保持しておくことが出来るコマンドです。

Cypress.Cookies.preserveOnce('session_id')
おわりに

他にも、セッションについては永続的に保持しておけるlocalStorageの利用もひとつの手段かと思いましたが、なかなか実装が出来ず。
Cypressは便利な分、不安定な要素や、実装できない要素等まだまだあるようです。
npmモジュールを入れると、ランナーが起動しなくなるし、正しく実行できるテストも、5回に1回はなんとかエラーが出ます。(ホットリロードすると正常)

まだまだ勉強することはたくさんありますが、セッションの保持が一個クリア出来たので、記録します。
シンプルな方法ですが、初心者なのでこれからこれから。

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

Vue.jsプロジェクト ルーターの設定

ルートの定義

router/index.js
import Home from '/views/Home.vue' //コンポーネント名 from ファイルのパス
import Page from '/views/Page.vue'

 const routes = [
  {
    path: '/',            //urlの末尾を設定
    name: 'Home',         //routeの名前。省略可能。詳しくはrouter-linkのところ
    component: Home       //上でimportした対応させるコンポーネント 大文字に注意!
  },
  { path:'/page',
    name:'firstpage',
    component:Page
  }
]

最初に読み込んでいるファイルはviewsのファイル。src/componentsのほうじゃない!:raised_hands_tone2:

$router.push

<script>
this.$router.push({name:'SerchResult'})   //path:'〇〇'でも可
</script>

router の機能の一つ。
違うurlに遷移する。書き方はいろいろある。

router-link

Home.vue
<template>
//下の2つはどちらも同じ!
 <router-link to='/page'>firstpageへのリンク</router-link>

 <router-link :to="{name:'firstpage'}">firstpageへのリンク</router-link>
</template>

下のコードのほうが、パスよりわかりやすい(気がする):v_tone2:
若干:があったりして凡ミスしてた。よく見ると違うね。
ルーターリンクも実は、ルータープッシュの機能を内部的に呼んでいるとのこと。
つまり、ルーターリンクを踏むことで、ルータープッシュしている。(公式より)

ルータープッシュとルーターリンク

どちらも、ページを切り替える。

router-link

HTMLのaタグみたいなもの。aタグと違って、ページを遷移しないので、早いように見える。らしい。
HTMLに書いてユーザーがポチッと押す。
templateの中に書く。

router.push

例えば、問い合わせフォームとかから、送信した後に、「送信が完了しました!」のページにうつるときに使う。
もしも。ルーターリンクを使ったら、ユーザーがポチッと押したら、仮に送信できていないくても完了しましたページには移ってしまう。
メソッドの中に書く。

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

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版、ハマったところcheckboxの再描画編~~

はじめに

Vueに対応していこう~。ということで始めたわけですが、
checkboxの再描画にもハマりましたんで残しておきます。
Vue版、ハマったところinputの再描画編~~の対応もあったのですが、checkboxなりのことがあったのでここに投稿します。

結論

ここを見ている方は、たぶん、同じ症状の方だと思うのでとっとと結論書きます。
checkboxは配列にする!これに尽きるのではないかと思われました。
そうです、初期値から配列です!空っぽでも、空の配列です!
あとkeyも設定してみましたので、チェックボックス一覧のデータは連想配列にしておきました。

なぜ配列だとうまくいくのか

逆に、配列にしないとtrue、falseが返ってくるだけなので、データの保存しようがないです。

配列は2つ用意

チェックボックスはラジオボタンと違って、複数チェックを可能にするために用いると思います。
そのため、
チェックボックス一覧の配列と
データの配列(初期表示でチェックをつけていないといけないデータ)の
2つに必要です
※checkbox自体も2つ以上ある場合があると思うので、「:id」「:name」にする箇所も一意にするために配列にする必要があるかと、、、
Aの中で選択とBの中で選択みたいな感じですね。

ソースはこんな感じです。

          <div v-for="(items,idx) in チェックボックス一覧の配列">
                <input :id="うまく一意となるように設定" 
                       :name="うまく一意となるように設定" 
                       :key="items.id" 
                       type=checkbox
                       v-model="データの配列"
                       v-bind:value="items.item"
                       :checked="データの配列.indexOf(items.item) > -1" />{{ items.item }}<br>
                       <!-- 初期のチェックがないらないので、v-modelv-bind:valueを併記する -->
          </div>

ちょっと説明

前途の通り、「:id」「:name」を設定し、
「チェックボックス一覧の配列」をv-forで回すのですが、この配列が連想配列になっているので
「:key」には、itemsのidを指定しています。
「type」は「checkbox」でいいですね。
「v-model」には、初期でチェック表示したいデータがVueのvmのrefits[4]を指定します。
この配列の中に、更にデータが配列で入っているイメージです。
「v-bind:value」には、v-forで回っているitemsの連想配列のitemを指定します。
そして初期のチェックをつけるために「:checked」には、
「refits[4].indexOf(items.item) > -1」左記の判定式を入れました。

v-modelは、v-bind:valueとv-bind:onを包括的に、、、みたいな記事もありましたが、
とりあえず、上記で私の試した感じだとうまくいっております。
v-modelとv-bind:valueに違うものを設定しているので、上記の記事と異なるのはわかりますが、なぜ動くのはいまいちよくわかっておりません。
理解できた方教えてください。

感想

表示のされ方が分かればデータを作成するだけなので、
ゴリゴリ書くより断然楽になりますね。

ハマったところinputの再描画編~~

全体の記事はこちら↓
スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版 その1~~

スプレッドシートをDB代わりにGASのWebアプリを作成しデータ更新させてみた。Vue版 その2~~

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

Nuxt.jsをFirebase hostingで動的にdeployする。

はじめに

何回煎じるんだって言うほどよく見る内容ですが、Firebaseを用いてデプロイする機会があったので、ある程度知識をまとめてみました。
あくまで備忘録として記すので、もしわかりにくい部分が会ったらご指摘お願いします。

また、今回の記事はこの記事を非常に参考にさせてもらいました。

使用環境

MacBook (version: 10.15.4)
Nuxt (version: 2.11.0)
firebase sparkプラン
firebase-tools (version: 8.2.0) 

目標

  • Nuxtで作ったアプリケーションを、動的コンテンツとしてデプロイしたい。
  • さらにBasic認証もかけれれば完璧。

0.概観

デプロイの流れ

スクリーンショット 2020-05-17 19.24.36.png

Firebase Hostingにアクセスします。
HostingされるとFirebase Functionsに必ずリライトします。
Nuxtアプリをレンダリングして、クライアントサイドに返します。

ディレクトリ構造

以下が最終的なアプリのディレクトリ構造になります。
ただし、今回関係ない部分は省略しています。

<プロジェクト名>
├── firebase.json
├── nuxt.config.js
~
~
├── functions
│   ├── index.js
│   └── node_modules
├── plugins
│   └── firebase.js
└── public
    └── index.html

1.Nuxtアプリ作成

yarnの場合
yarn create nuxt-app 生成ファイル名

npmの場合
npx create-nuxt-app <project-name>

とりあえずnuxtアプリを生成します。
詳しくは、こちらをご覧ください。

2.firebaseの初期化

今回は、firebase CLIなどは既にインストールされている状態のものとします。
firebaseの最初の導入に関してはこちらをご覧ください。

まず、以下のコマンドでfirebaseを初期化します。

firebase init

そうすると、例の如く何を初期化するか聞かれるので、今回はFunctions, Hositngを選択。

? Which Firebase CLI features do you want to set up for this folder? Press Space
 to select features, then Enter to confirm your choices. (Press <space> to selec
t, <a> to toggle all, <i> to invert selection)
❯◯ Database: Deploy Firebase Realtime Database Rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◯ Functions: Configure and deploy Cloud Functions
 ◯ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules
 ◯ Emulators: Set up local emulators for Firebase features

以降は色々聞かれるので、基本そのままEnter連打で構いません。

またHostinginitした時に/.firebaserc/firebase.jsonが生成されます。
念のため説明しておくと、/firebasercはデプロイするプロジェクトを設定しておくファイルで、/firebase.jsonはデプロイ時にどのような操作をするか決定するファイルになります。
/firebase.jsonに関しては 3. で取り扱います。

3.デプロイ時の操作の決定

上の 2. で話した通り/firebase.jsonの中身を弄って、デプロイ時にfirebase functionsに操作をリライトできるようにします。

/firebase.json
{
  "functions": {
    "functions": {
      "source": "functions"
    }
  },
  "hosting": {
    "public": ".nuxt/dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "ssr"
      }
    ]
  }
}

このように記述すると、全てのルートで後述するssr関数にリライトすることができます。

4.Functionsの依存関係の調整

/functions/package.json
{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "serve": "firebase serve --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
+ },
+  "engines": {
+    "node": "10"
+  },
  "dependencies": {
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
+   "nuxt": "^2.10.2"
  },
  "devDependencies": {
    "firebase-functions-test": "^0.1.6"
  },
  "private": true
}

nodeのバージョンはデフォルトで、8なので10にしておきます。
またfunctions内にはnuxtは入っていないので、

cd functions && npm install --save nuxt 

をしておきます。

5.Functionsの内容を記述する

3.で書いたように、Hostingされると全てのパスにおいてfunctions/index.jsにリライトされます。

/functions/index.js
const functions = require('firebase-functions');
const { Nuxt } = require('nuxt');
const nuxt = new Nuxt({
    buildDir: 'ssr',
    dev: false
});

exports.ssr = functions.https.onRequest(async (req, res) => {
    await nuxt.ready()
    return nuxt.render(req, res)
})

以上で、各々のルートでレンダリングするようにかけています。
4.で記述したssrはここでexportsしたものになります。
またここで指定したbuildDirはデプロイ時に作成するディレクトリ名になります。
またその際のパスは/functions/ssr/となります。

6.package.jsonでビルドコマンドを変更

今後デプロイがしやすくなるように、コマンドを調整します。

/package.json
{
  "name": "***********",
  "version": "1.0.0",
  "description": "My cat&#39;s meow Nuxt.js project",
  "author": "Shiba-You",
  "private": true,
  "scripts": {
    "dev": "nuxt",
+   "build": "nuxt build && npm run build:copy:ssr",
+   "build:copy:ssr": "rimraf functions/ssr && mkdirp functions/ssr && cp -R .nuxt/dist functions/ssr/dist",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
  },
  "dependencies": {
    "nuxt": "^2.0.0",
    "@nuxtjs/dotenv": "^1.4.0"
  },
  "devDependencies": {
    "@nuxtjs/vuetify": "^1.0.0",
    "@nuxtjs/eslint-config": "^2.0.0",
    "@nuxtjs/eslint-module": "^1.0.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^6.1.0",
    "eslint-plugin-nuxt": ">=0.4.2"
  }
}

以上のように変更・追加することで、/functions/ssr/distに静的ファイル群をコピーすることができます。

7.実行

現在、/functionsにいればプロジェクトファイル直下に戻った上で、ビルドしてJSファイルをミニファイします。

(functionsにいれば) cd ../ 
npm run build

実際にデプロイする前に、プレビューしておきます。

firebase serve

特に指定しなければ以下のように5000ポートが開いていると思うので、ローカル上で正しく動作できているか確認します。

✔  functions: functions emulator started at http://localhost:5001
i  functions: Watching "/Users/**********/*********/functions" for Cloud Functions...
i  hosting: Serving hosting files from: dist
✔  hosting: Local server: http://localhost:5000

問題なければ本番サーバーにデプロイします。

firebase deploy
? Would you like to proceed with deletion? Selecting no will continue the rest 
of the deployments. No
i  functions: continuing with other deployments.
✔  functions[ssr(us-central1)]: Successful update operation. 
i  hosting[hall-repoter-dev]: finalizing version...
✔  hosting[hall-repoter-dev]: version finalized
i  hosting[hall-repoter-dev]: releasing new version...
✔  hosting[hall-repoter-dev]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/***************/overview
Hosting URL: https://******************.web.app

Hosting URLに表示されているURLに自分のプロジェクトが動作していたらデプロイ成功です。

おまけ

以下個人的に詰まった点等を紹介します。

エラー例①: firebase serveしても期待通りに動いてくれない

スクリーンショット 2020-05-18 1.54.19.png

firebase serveしても上のように表示されるだけで期待通りに動いてくれない場合は、functions内の依存関係がおかしかったり、必要な物がインストール出来ていない可能性が高いです。

なので、まずは

cd functions && npm install firebase

をしてfirebaseが入っているか確かめます。
それでも治らない場合は

npm rm nuxt-sass-resources-loader --save-dev
npm i nuxt-sass-resources-loader

をしてみてください。
自分はこれで、エラーから抜け出すことができました。

エラー例②: firestoreからデータが持ってこれない

これはまた別件になりますが、ローカルでは動いていたfirestoreが動かないことがあります。(自分がそうでした)
なので、動くようにセキュリティルールを変更します。

ちなみに、セキュリティルールはfirebaseのコンソールの「Database」→「ルール」で開くことができます。

スクリーンショット 2020-05-18 2.02.49.png
ここを、以下のように変更します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}

ただし、このルールは誰でもDbを書き換えれるルールなので、その辺は注意してください。

Basic認証もデプロイしてみる

どうせならBasic認証もあったほうがいいので試してみました。

まずは必要なパッケージをインストールします。

cd functions && npm install --save basic-auth-connect express

またfunctions/index.jsも書き直します。

functions/index.js
const functions = require('firebase-functions');
const express = require('express');
const basicAuth = require('basic-auth-connect');
const { Nuxt } = require('nuxt');

const nuxt = new Nuxt({
  buildDir: 'ssr',
  dev: false
});

const USERNAME = '任意のユーザーID' 
const PASSWORD = '任意のパスワード'  

const app = express()

app.use(basicAuth(USERNAME, PASSWORD))

app.use(async (req, res) => {
  await nuxt.ready()
  nuxt.render(req, res)
})

exports.ssr = functions.https.onRequest(app)

以上のように変更するだけで、Basic認証が実装できます。

まとめ

今回はSSRをデプロイしてみましたが、自分がサーバーやらインフラやらに弱いことを痛感しました。
もう少しその辺を掘り下げて勉強してみます。

参考

https://agelog.hateblo.jp/entry/2019/11/25/001401
https://qiita.com/sugitata/items/be357fcaab744fd02a4f
https://qiita.com/ykoizumi0903/items/55fd678d261a8c308f0f
https://qiita.com/seya/items/225f859d775b31047000

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

SSRで作ったNuxtアプリをFirebase hostingで動的にデプロイする。

はじめに

何回煎じるんだって言うほどよく見る内容ですが、Firebaseを用いてデプロイする機会があったので、ある程度知識をまとめてみました。
あくまで備忘録として記すので、もしわかりにくい部分が会ったらご指摘お願いします。

また、今回の記事はこの記事を非常に参考にさせてもらいました。

使用環境

MacBook (version: 10.15.4)
Nuxt (version: 2.11.0)
firebase sparkプラン
firebase-tools (version: 8.2.0) 

目標

  • Nuxtで作ったアプリケーションを、動的コンテンツとしてデプロイしたい。
  • さらにBasic認証もかけれれば完璧。

0.概観

デプロイの流れ

スクリーンショット 2020-05-17 19.24.36.png

Firebase Hostingにアクセスします。
HostingされるとFirebase Functionsに必ずリライトします。
Nuxtアプリをレンダリングして、クライアントサイドに返します。

ディレクトリ構造

以下が最終的なアプリのディレクトリ構造になります。
ただし、今回関係ない部分は省略しています。

<プロジェクト名>
├── firebase.json
├── nuxt.config.js
~
~
├── functions
│   ├── index.js
│   └── node_modules
├── plugins
│   └── firebase.js
└── public
    └── index.html

1.Nuxtアプリ作成

yarnの場合
yarn create nuxt-app 生成ファイル名

npmの場合
npx create-nuxt-app <project-name>

とりあえずnuxtアプリを生成します。
詳しくは、こちらをご覧ください。

2.firebaseの初期化

今回は、firebase CLIなどは既にインストールされている状態のものとします。
firebaseの最初の導入に関してはこちらをご覧ください。

まず、以下のコマンドでfirebaseを初期化します。

firebase init

そうすると、例の如く何を初期化するか聞かれるので、今回はFunctions, Hositngを選択。

? Which Firebase CLI features do you want to set up for this folder? Press Space
 to select features, then Enter to confirm your choices. (Press <space> to selec
t, <a> to toggle all, <i> to invert selection)
❯◯ Database: Deploy Firebase Realtime Database Rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◯ Functions: Configure and deploy Cloud Functions
 ◯ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules
 ◯ Emulators: Set up local emulators for Firebase features

以降は色々聞かれるので、基本そのままEnter連打で構いません。

またHostinginitした時に/.firebaserc/firebase.jsonが生成されます。
念のため説明しておくと、/firebasercはデプロイするプロジェクトを設定しておくファイルで、/firebase.jsonはデプロイ時にどのような操作をするか決定するファイルになります。
/firebase.jsonに関しては 3. で取り扱います。

3.デプロイ時の操作の決定

上の 2. で話した通り/firebase.jsonの中身を弄って、デプロイ時にfirebase functionsに操作をリライトできるようにします。

/firebase.json
{
  "functions": {
    "functions": {
      "source": "functions"
    }
  },
  "hosting": {
    "public": ".nuxt/dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "**",
        "destination": "ssr"
      }
    ]
  }
}

このように記述すると、全てのルートで後述するssr関数にリライトすることができます。

4.Functionsの依存関係の調整

/functions/package.json
{
  "name": "functions",
  "description": "Cloud Functions for Firebase",
  "scripts": {
    "serve": "firebase serve --only functions",
    "shell": "firebase functions:shell",
    "start": "npm run shell",
    "deploy": "firebase deploy --only functions",
    "logs": "firebase functions:log"
+ },
+  "engines": {
+    "node": "10"
+  },
  "dependencies": {
    "firebase-admin": "^8.6.0",
    "firebase-functions": "^3.3.0",
+   "nuxt": "^2.10.2"
  },
  "devDependencies": {
    "firebase-functions-test": "^0.1.6"
  },
  "private": true
}

nodeのバージョンはデフォルトで、8なので10にしておきます。
またfunctions内にはnuxtは入っていないので、

cd functions && npm install --save nuxt 

をしておきます。

5.Functionsの内容を記述する

3.で書いたように、Hostingされると全てのパスにおいてfunctions/index.jsにリライトされます。

/functions/index.js
const functions = require('firebase-functions');
const { Nuxt } = require('nuxt');
const nuxt = new Nuxt({
    buildDir: 'ssr',
    dev: false
});

exports.ssr = functions.https.onRequest(async (req, res) => {
    await nuxt.ready()
    return nuxt.render(req, res)
})

以上で、各々のルートでレンダリングするようにかけています。
4.で記述したssrはここでexportsしたものになります。
またここで指定したbuildDirはデプロイ時に作成するディレクトリ名になります。
またその際のパスは/functions/ssr/となります。

6.package.jsonでビルドコマンドを変更

今後デプロイがしやすくなるように、コマンドを調整します。

/package.json
{
  "name": "***********",
  "version": "1.0.0",
  "description": "My cat&#39;s meow Nuxt.js project",
  "author": "Shiba-You",
  "private": true,
  "scripts": {
    "dev": "nuxt",
+   "build": "nuxt build && npm run build:copy:ssr",
+   "build:copy:ssr": "rimraf functions/ssr && mkdirp functions/ssr && cp -R .nuxt/dist functions/ssr/dist",
    "start": "nuxt start",
    "generate": "nuxt generate",
    "lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
  },
  "dependencies": {
    "nuxt": "^2.0.0",
    "@nuxtjs/dotenv": "^1.4.0"
  },
  "devDependencies": {
    "@nuxtjs/vuetify": "^1.0.0",
    "@nuxtjs/eslint-config": "^2.0.0",
    "@nuxtjs/eslint-module": "^1.0.0",
    "babel-eslint": "^10.0.1",
    "eslint": "^6.1.0",
    "eslint-plugin-nuxt": ">=0.4.2"
  }
}

以上のように変更・追加することで、/functions/ssr/distに静的ファイル群をコピーすることができます。

7.実行

現在、/functionsにいればプロジェクトファイル直下に戻った上で、ビルドしてJSファイルをミニファイします。

(functionsにいれば) cd ../ 
npm run build

実際にデプロイする前に、プレビューしておきます。

firebase serve

特に指定しなければ以下のように5000ポートが開いていると思うので、ローカル上で正しく動作できているか確認します。

✔  functions: functions emulator started at http://localhost:5001
i  functions: Watching "/Users/**********/*********/functions" for Cloud Functions...
i  hosting: Serving hosting files from: dist
✔  hosting: Local server: http://localhost:5000

問題なければ本番サーバーにデプロイします。

firebase deploy
? Would you like to proceed with deletion? Selecting no will continue the rest 
of the deployments. No
i  functions: continuing with other deployments.
✔  functions[ssr(us-central1)]: Successful update operation. 
i  hosting[hall-repoter-dev]: finalizing version...
✔  hosting[hall-repoter-dev]: version finalized
i  hosting[hall-repoter-dev]: releasing new version...
✔  hosting[hall-repoter-dev]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/***************/overview
Hosting URL: https://******************.web.app

Hosting URLに表示されているURLに自分のプロジェクトが動作していたらデプロイ成功です。

おまけ

以下個人的に詰まった点等を紹介します。

エラー例①: firebase serveしても期待通りに動いてくれない

スクリーンショット 2020-05-18 1.54.19.png

firebase serveしても上のように表示されるだけで期待通りに動いてくれない場合は、functions内の依存関係がおかしかったり、必要な物がインストール出来ていない可能性が高いです。

なので、まずは

cd functions && npm install firebase

をしてfirebaseが入っているか確かめます。
それでも治らない場合は

npm rm nuxt-sass-resources-loader --save-dev
npm i nuxt-sass-resources-loader

をしてみてください。
自分はこれで、エラーから抜け出すことができました。

エラー例②: firestoreからデータが持ってこれない

これはまた別件になりますが、ローカルでは動いていたfirestoreが動かないことがあります。(自分がそうでした)
なので、動くようにセキュリティルールを変更します。

ちなみに、セキュリティルールはfirebaseのコンソールの「Database」→「ルール」で開くことができます。

スクリーンショット 2020-05-18 2.02.49.png
ここを、以下のように変更します。

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write;
    }
  }
}

ただし、このルールは誰でもDbを書き換えれるルールなので、その辺は注意してください。

Basic認証もデプロイしてみる

どうせならBasic認証もあったほうがいいので試してみました。

まずは必要なパッケージをインストールします。

cd functions && npm install --save basic-auth-connect express

またfunctions/index.jsも書き直します。

functions/index.js
const functions = require('firebase-functions');
const express = require('express');
const basicAuth = require('basic-auth-connect');
const { Nuxt } = require('nuxt');

const nuxt = new Nuxt({
  buildDir: 'ssr',
  dev: false
});

const USERNAME = '任意のユーザーID' 
const PASSWORD = '任意のパスワード'  

const app = express()

app.use(basicAuth(USERNAME, PASSWORD))

app.use(async (req, res) => {
  await nuxt.ready()
  nuxt.render(req, res)
})

exports.ssr = functions.https.onRequest(app)

以上のように変更するだけで、Basic認証が実装できます。

まとめ

今回はSSRをデプロイしてみましたが、自分がサーバーやらインフラやらに弱いことを痛感しました。
もう少しその辺を掘り下げて勉強してみます。

参考

https://agelog.hateblo.jp/entry/2019/11/25/001401
https://qiita.com/sugitata/items/be357fcaab744fd02a4f
https://qiita.com/ykoizumi0903/items/55fd678d261a8c308f0f
https://qiita.com/seya/items/225f859d775b31047000

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