20190521のvue.jsに関する記事は9件です。

[Vue.js]Phpstorm 絶対パスでcomponentのパスを指定

やりたいこと

絶対パスでcomponentのパスを指定する。
プログラムのエラーは出ないが、エディタ(私が使用しているものはPhpstormです)で
「文法違いますよ」というエラー(黄色の破線)が出る。
紛らわしいので、なんとかしたい。

修正前

<script>
// @ is an alias to /src

import TheClientHeader from '../../components/object/project/TheClientHeader' //←★修正前コレ
export default {
  name: 'Home',
  components: { TheClientFooter },
}
</script>

修正後

<script>
// @ is an alias to /src

import TheClientHeader from '@/components/object/project/TheClientHeader' //←★修正後コレ(文法エラーの表記は出ていない)
export default {
  name: 'Home',
  components: { TheClientFooter },
}
</script>

解決方法

import-resolver.jsというファイルをドキュメントルート直下に作成し、
以下を記述

/* eslint-disable no-undef */
System.config({
  paths: {
    '@/*': 'src/*',
  },
})
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Table上のリソースを定期ポーリングして入れ替えていくための自分なりの実装案 on Vue.js

前提

前回こんなことを書きました。

vue.jsで、v-forしながらそのindexを利用してclassを切り替えたいような場合

ここでは、

  • リソースの一覧をTable表示し
  • その上で各行に存在するリソースの操作メニューを別コンポーネントに切り出した

ということをやりました。

ここまでやるなら、リソース1つ1つ(table.tbody.trに相当)もComponentに切り出しちゃうほうが良いですよね。

で、そうしておくと、リソースのポーリングなんてのもすごくやりやすくなるなぁと思ったので、ちょっと書いてみます。

今回の記事の位置づけ

  • 前回に加えてTableの各行に相当するリソースも別コンポーネントに切り出す
  • その上でリソースの状態に応じてリソースを定期的にポーリングする
  • ・・・という機能の実装案としてのメモ

例えば、

  • OpenStackでインスタンスを作ったとします
  • けどインスタンスがACTIVEになるまで、一覧上のリソースはぐるぐるリソースの状態をポーリングし続けますよね
  • ではこれを実際に作るとどうなるかな

というような記事です。

分割していくとこうなった

結果的に、以下のような3つのコンポーネントに分かれました。
なお前回も書きましたが、私はBulma.jsを使ってますので、要素もそれを踏まえて読んでいただければ。

なお、今回は操作メニューについてのコードはあまり関係がないので省略します。

以下実際のコードです。
ポイントはコードの下に書きます。

List.vue

Tableでリソースの一覧を表示するためのコンポーネントです。
Tableの定義をして、子コンポーネントを呼び出し、そこにAPIで取得したリソースを埋め込むのがお仕事です。

<template>
  <span>
    <table class="table is-hoverable is-fullwidth">
      <thead>
        <tr>
          <th>{{ $t('ID') }}</th>
          <th>{{ $t('Name') }}</th>
          <th>{{ $t('Description') }}</th>
          <th>{{ $t('Status') }}</th>
          <th>{{ $t('') }}</th>
        </tr>
      </thead>
      <tbody>
        <list-element
          v-for="r in resources"
          :id="r.id"
          :key="r.id"
          :name="r.name"
          :description="r.description"
          :status="r.status"
          @resource-status-update="updateResource(r.id)"
        /> <!-- ポイント(1) -->
      </tbody>
    </table>
  </span>
</template>

<script>
import client from '~/clients'
import ListElement from './ListElement'

export default {
  components: {
    ListElement,
  },
  data() {
    return {
      resources: null,
    }
  },
  async mounted() {
    try {
      let res = await client.listResources()
      this.resources = res.data
    } catch (error) {
      (省略)
    }
  },
  methods: {
    updateResource: async function(pollingTargetID) { // ポイント(2)
      console.log(`Received polling request about ${pollingTargetID}`)
      let res = await client.getResource(pollingTargetID)
      let polledResource = res.data
      let copiedResources = [...this.resources]
      for (let i = 0; i < this.resources.length; i++) {
        if (this.resources[i].id == pollingTargetID) {
          copiedResources[i] = polledResource
        }
      }
      this.resources = copiedResources
    },
  },
}
</script>

ListElement.vue

上の List.vuetr に相当する部分です。
このコンポーネントをv-forしてリソースの一覧を記述します。

この子の仕事は、

  • 親から与えられたリソース情報(プロパティ経由)を用いたtrタグのレンダ
  • trに記載されるリソースがポーリングに値する状態であれば、ポーリング要求を親に(つまりList.vueに)飛ばす(※1)

です。

余談:直接子コンポーネントで書き換えちゃえばいいじゃない・・・と思いがち(自分も思ってた)

ここで※1についてですが、子コンポーネントでポーリングしてリソース情報を書き換えてしまえばいいのでは・・・と思いがちなのですが、親から与えられたプロパティを子コンポーネント側で書き込むのは非推奨なのですね。
以下のようなWarningが出てしまいます。

[Vue warn]: Avoid mutating a prop directly since the value
will be overwritten whenever the parent component re-renders.
Instead, use a data or computed property based on the prop's value.
Prop being mutated: "propsValue"

いろいろ記事はあると思いますが例えば https://stackoverflow.com/questions/40780730/vue-js-changing-props とか。
Vue.js自体のドキュメントにも記載があったはず。

ということで、親から変更を かけてもらう 必要があるわけです。

ということでコードコード。

<template>
  <tr :class="{ 'is-status-update': isStatusUpdate }">
    <td>{{ id }}</td>
    <td>{{ name }}</td>
    <td>{{ description }}</td>
    <td>
      {{ status }}
      <progress v-if="isStatusUpdate" class="progress is-small is-link" max="100">
        15%
      </progress>
    </td>
    <td>
      <list-operation :id="id" :status="status" />
    </td>
  </tr>
</template>

<script>
import ListOperation from './ListOperation' // 前回説明したところ

export default {
  components: {
    ListOperation, // 前回説明したところ
  },
  props: {
    id: {
      type: String,
      default: '',
    },
    name: {
      type: String,
      default: '',
    },
    description: {
      type: String,
      default: '',
    },
    status: {
      type: String,
      default: '',
    },
  },
  data() {
    return {
      pendingStatus: ['PENDING_CREATE', 'PENDING_DELETE'] // 作成中、削除中みたいな意味
    }
  },
  computed: {
    isStatusUpdate: function() {
      return this.pendingStatus.includes(this.status)
    },
  },
  mounted() {
    const self = this
    // start polling request to parent component
    setInterval(function() { // ポイント(3)
      if (settings.pendingStatus.includes(self.status)) {
        console.log(`polling request has emitted about ${self.id}`)
        self.$emit('resource-status-update')
      }
    }, 2500) // 2500 msでポーリング
  },
}
</script>

コードのポイント

ポイント(1) @ 親コンポーネント

ここで子コンポーネントをv-forすることで要素をレンダしてます。
その際、リソースの情報をプロパティ経由で渡しています。
また、 resource-status-update というイベントに対するリスナを登録し、その際 updateResource(r.id) とすることで、該当のリソースのIDを利用して関数を呼び出しています。

ポイント(2) @ 親コンポーネント

ここで client.getResource(pollingTargetID) を定義してます。

  • この関数はリスナなので、子コンポーネントからの要求に基づき発火します
  • pollingTargetIDはv-for内のkeyなので、リソースごとに異なります
  • client.getResoure というのは、axios経由でリソースを取得するための関数です
  • これが取得できたら、親コンポーネントのリソース一覧である、 this.resources の一部を入れ替えています

ポイント(3) @ 子コンポーネント

  • mounted(DOMがマウントされた時点)で、setIntervalを仕込みます
  • setIntervalの中では、単にイベントだけを送出します(to 親コンポーネント)。(逆にそれ以外は何もしません)
  • ただし、リソースのstatusが特定の状態であるときのみです。その特定の状態が、this.pendingStatus であり、実際には ['PENDING_CREATE', 'PENDING_DELETE'] という2つの状態です。

つまりどういう動きになるの?

  1. 親コンポーネントはリソースの一覧をaxios経由で取得します
  2. これをv-forを使って、Table上に一覧化します。一覧のtrは子コンポーネントです
  3. この子コンポーネントの1つのv-forループには、イベントリスナを登録しています。このリスナ経由で、子要素からの要求に基づき関数が実行されます。
  4. その関数というのは、特定のリソースを取得するためのaxiosを利用した関数です
  5. axios経由でリソースが取得できたら、上記1の時点でのリソースの一部を置き換えます
  6. で、そもそも3で登録した関数というのはどうやると発火されるのかというと、子要素のmountedにて定義したsetIntervalでタイマーされてます
  7. 上記の6は、リソースのstatusが特定の状態である間継続されます

とりあえずこんな感じで実装してうまく動いてます。

あとはstatusの状態に応じてtableのcssとかを切り替えたりして、それっぽく見せればいいのですが、それは見栄えの話なのでここでは省略します。

以上です。

自分の中でvue.jsでこの手のロジック実装する際どうするのがいいのかなぁ・・・と当初からの悩みだったので、比較的きれいに実装できてよかったかなと。(・・・と僕は勝手ながら思っている)

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

WSL 上で Vue.js を用いた Android (iOS) アプリを開発する

はじめに

仕事中、睡魔と戦いながら、Android アプリを作ろうと思いました。

ところが私は、C# は触った程度だし Java はコンソールアプリしかやったことがなく GUI はわかりません。そこで調べてみると、HTML で作れるそうじゃないですか。社会人二年目で Web 系しかしていない私にはありがたい話です。

もう少し欲を出して、業務で使用している Vue.js を使えないかなーと思ったら、ちょうどいいのがあるじゃないですか。

というわけで、Windows Subsystem for Linux (WSL) 上で Vue.js を用いて Android アプリを開発するための手順を紹介します。

つかうもの

  • WSL : Windows 環境がカオスにならないようにするため
  • Vue.js : 今回使いたかったもの
  • nativescript : Javascript でアプリ開発できるようにするすごいやつ
  • アンドロイド実機 : 開発時の実機との連携がすごい

手順

WSL の有効化から実機での起動まで端折らずにやるよ。

  1. WSL 環境の構築
  2. Ubuntu への各種インストール

1. WSL 環境の構築

まずは WSL を有効化し、Ubuntu をインストールします。

  1. コントロールパネル > プログラムのアンインストール を選択

    WSL1.png

  2. 左側 windows の機能の有効化または無効化を選択

    WSL2.png

  3. Windows Subsystem for Linux にチェックを入れ有効化

    WSL3.png

  4. インストール完了次第再起動

  5. (念の為)設定 > 更新とセキュリティ > 開発者向け > 開発者モードを有効にする

    WSL4.png

  6. スタートから Microsoft Store を開く

    WSL5.png

  7. 「Ubuntu」で検索し、Ubuntu を選択

    WSL6.png

  8. 入手をクリックでインストール

    WSL7.png

  9. スタートから Ubuntu を開く

    WSL8.png

  10. インストール完了を待ち、ユーザー名とパスワードを入力(パスワードは表示されないので注意)

    WSL9.png

  11. リポジトリを日本のサーバに変更(sudo 利用時、パスワード入力を求められます)

    $ sudo sed -i -e 's%http://.*.ubuntu.com%http://ftp.jaist.ac.jp/pub/Linux%g' /etc/apt/sources.list
    
  12. パッケージのアップデート

    $ sudo apt update
    $ sudo apt upgrade
    
  13. Windows との共有ディレクトリを作成

    $ ln -s /mnt/c/Users/(username)/shareWithUbuntu ~/shareWithWindows # (username)はWindows側のユーザ名に置き換え
    

以上で WSL 環境の構築は完了です。WSL 環境をリセットしたい場合は
スタート > Ubuntu 右クリック > アプリの設定 > リセット
から可能です。リセット後は Ubuntu を起動するとまたアカウント設定から始まります。環境破壊と再構築、簡単ですね。

2. Ubuntu への各種インストール

NativeScript-Vue での開発に必要なものをインストールしていきます。

  1. Node.js のインストール

    $ sudo apt install -y nodejs npm # node、npmのインストール
    $ sudo npm install n -g          # n package導入
    $ sudo n stable                  # nを使用してnodeのstable版をインストール
    $ sudo apt purge -y nodejs npm   # 最初に入れた古いnode、npmは削除
    $ exec $SHELL -l                 # 再ログイン
    $ node -v                        # バージョン確認
    
  2. NativeScript CLI のインストール

    $ sudo npm install -g nativescript
    
  3. G++ (C++ のコンパイラ)のインストール

    $ sudo apt install g++
    
  4. JDK8 の設定

    $ sudo apt install openjdk-8-jdk         # JDK8インストール
    $ sudo update-alternatives --config java # JAVAのバージョンが複数あればJDK8を選択
    
  5. 環境変数 JAVA_HOME の追加

    1. ~/.bashrc を開く

      $ vi ~/.bashrc
      
    2. Iキーで編集モードに入り、一番下に以下の行を追加

      $ export JAVA_HOME=$(update-alternatives --query javac | sed -n -e 's/Best: *\(.*\)\/bin\/javac/\1/p')
      
    3. ESCキーを押し、:wq と入力

  6. Android SDK のダウンロード(Windows 側での操作

    1. ここ の下の方の「Command line tools only」から Linux のものをダウンロード

      WSL10.png

    2. ダウンロードした zip ファイルを解凍

    3. 解凍した中にある tools ディレクトリを shareWithUbuntu ディレクトリに移動

  7. /usr/lib/android/sdk/ ディレクトリを作成

    $ sudo mkdir -p /usr/lib/android/sdk/
    
  8. shareWithWindows ディレクトリ内の tools ディレクトリを /usr/lib/android/sdk/ に移動

    $ sudo mv ~/shareWithWindows/tools /usr/lib/android/sdk/
    
  9. 環境変数 ANDROID_HOME の追加

    1. ~/.bashrc を開く

      $ vi ~/.bashrc
      
    2. Iキーで編集モードに入り、一番下に以下の行を追加

      $ export ANDROID_HOME="/usr/lib/android/sdk/"
      $ export PATH="${PATH}:${ANDROID_HOME}tools/:${ANDROID_HOME}platform-tools/"
      
    3. ESCキーを押し、:wq と入力

  10. 再ログイン

    $ exec $SHELL -l
    
  11. 必要なパッケージのインストール

    $ sudo $ANDROID_HOME/tools/bin/sdkmanager "tools" "emulator" "platform-tools" "platforms;android-28" "build-tools;28.0.3" "extras;android;m2repository" "extras;google;m2repository"
    
  12. 再ログイン

    $ exec $SHELL -l
    
  13. 以下のコマンドを実行し、No issues were detected. が出力されれば成功

    $ tns doctor # 統計情報を自動送信して良いか聞かれるが n で構わない
    

NativeScript 関連のインストールはここまで。

3. アプリケーションの作成

アプリケーションを作成します。

  1. Vue 関連パッケージのインストール

    $ sudo npm install -g @vue/cli @vue/cli-init
    
  2. プロジェクトを作成するディレクトリを作成、移動

    $ mkdir workspace && cd workspace
    
  3. プロジェクトの作成

    $ vue init nativescript-vue/vue-cli-template sample-app
    
    # 以下対話形式(vue-devtools以外はデフォルトでもOK)
    ? Project name sample-app                                              # プロジェクト名
    ? Project description A native application built with NativeScript-Vue # プロジェクトの説明
    ? Application name NativeScript-Vue Application                        # アプリ名
    ? Unique application identifier org.nativescript.application           # アプリケーション任意のID
    ? Project version 1.0.0                                                # プロジェクトのバージョン
    ? Author                                                               # 開発者
    ? License MIT                                                          # ライセンスの種類
    ? Select the programming language javascript                           # 使用する言語
    ? Select a preset (more coming soon) Simple                            # 用意するアプリのテンプレート
    ? Install vuex? (state management) Yes                                 # Vuexを使用するか(デフォルトはNo)
    ? Install vue-devtools? No                                             # Vuedevtoolsを使用するか(デフォルトはYes)
    ? Color scheme none                                                    # カラーテーマ
    
  4. プロジェクトディレクトリに移動し、パッケージインストール

    $ cd sample-app && npm install
    
  5. アプリを起動

    $ tns preview --bundle
    

    WSL11.png

プロジェクトの作成からアプリ実行までは簡単ですね。コンソールに表示される QR コードは後ほど利用するので消さないでください。

実機で表示

作成したアプリケーションを実機に表示します。ここでは、Android 側にアプリケーションの導入が必要となります。

  1. Google Play から NativeScript Playground をインストール
  2. Google Play から NativeScript Preview をインストール

    WSL12.png

  3. NativeScript Playground を開き、 Scan QR code をタップ

    WSL13.png

  4. コンソールの QR コードを読み込む

  5. 完了

    WSL14.png

QR の読み込み後に少し時間がかかりますが、個人的には QR 読み込みだけで実機で試せるのは革命的です。

おわりに

NativeScript-Vue のおかげで睡魔に打ち勝つことができました。が、この記事を書いている間は何度も睡魔に負けています。何か実用的なものを NativeScript-Vue で作成してまた記事を上げたいですが、今度は仕事が忙しくなり睡魔に襲われる機会が減りそうなので、なかったことになるかもしれないです。

ここを見れば誰でも同じことができるように心がけていますが、訳わかんねー記述があればコメントください。まあ私も素人ですが。

参考サイト

Windows Subsystem for Linuxをインストールしてみよう!
Ubuntuに最新のNode.jsを難なくインストールする
NativeScript-Vue / Installation(英語)
NativeScript Advanced Setup: Linux(英語)
Windows 10のWindows Subsystem for Linux(WSL)を日常的に活用する

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

WSL 上で Vue.js を用いた Android アプリを開発する

はじめに

仕事中、睡魔と戦いながら、Android アプリを作ろうと思いました。

ところが私は、C# は触った程度だし Java はコンソールアプリしかやったことがなく GUI はわかりません。そこで調べてみると、HTML で作れるそうじゃないですか。社会人二年目で Web 系しかしていない私にはありがたい話です。

もう少し欲を出して、業務で使用している Vue.js を使えないかなーと思ったら、ちょうどいいのがあるじゃないですか。

というわけで、Windows Subsystem for Linux (WSL) 上で Vue.js を用いて Android アプリを開発するための手順を紹介します。

つかうもの

  • WSL : Windows 環境がカオスにならないようにするため
  • Vue.js : 今回使いたかったもの
  • nativescript : Javascript でアプリ開発できるようにするすごいやつ
  • アンドロイド実機 : 開発時の実機との連携がすごい

手順

WSL の有効化から実機での起動まで端折らずにやるよ。

  1. WSL 環境の構築
  2. Ubuntu への各種インストール

1. WSL 環境の構築

まずは WSL を有効化し、Ubuntu をインストールします。

  1. コントロールパネル > プログラムのアンインストール を選択

    WSL1.png

  2. 左側 windows の機能の有効化または無効化を選択

    WSL2.png

  3. Windows Subsystem for Linux にチェックを入れ有効化

    WSL3.png

  4. インストール完了次第再起動

  5. (念の為)設定 > 更新とセキュリティ > 開発者向け > 開発者モードを有効にする

    WSL4.png

  6. スタートから Microsoft Store を開く

    WSL5.png

  7. 「Ubuntu」で検索し、Ubuntu を選択

    WSL6.png

  8. 入手をクリックでインストール

    WSL7.png

  9. スタートから Ubuntu を開く

    WSL8.png

  10. インストール完了を待ち、ユーザー名とパスワードを入力(パスワードは表示されないので注意)

    WSL9.png

  11. リポジトリを日本のサーバに変更(sudo 利用時、パスワード入力を求められます)

    $ sudo sed -i -e 's%http://.*.ubuntu.com%http://ftp.jaist.ac.jp/pub/Linux%g' /etc/apt/sources.list
    
  12. パッケージのアップデート

    $ sudo apt update
    $ sudo apt upgrade
    
  13. Windows との共有ディレクトリを作成

    $ ln -s /mnt/c/Users/(username)/shareWithUbuntu ~/shareWithWindows # (username)はWindows側のユーザ名に置き換え
    

以上で WSL 環境の構築は完了です。WSL 環境をリセットしたい場合は
スタート > Ubuntu 右クリック > アプリの設定 > リセット
から可能です。リセット後は Ubuntu を起動するとまたアカウント設定から始まります。環境破壊と再構築、簡単ですね。

2. Ubuntu への各種インストール

NativeScript-Vue での開発に必要なものをインストールしていきます。

  1. Node.js のインストール

    $ sudo apt install -y nodejs npm # node、npmのインストール
    $ sudo npm install n -g          # n package導入
    $ sudo n stable                  # nを使用してnodeのstable版をインストール
    $ sudo apt purge -y nodejs npm   # 最初に入れた古いnode、npmは削除
    $ exec $SHELL -l                 # 再ログイン
    $ node -v                        # バージョン確認
    
  2. NativeScript CLI のインストール

    $ sudo npm install -g nativescript
    
  3. G++ (C++ のコンパイラ)のインストール

    $ sudo apt install g++
    
  4. JDK8 の設定

    $ sudo apt install openjdk-8-jdk         # JDK8インストール
    $ sudo update-alternatives --config java # JAVAのバージョンが複数あればJDK8を選択
    
  5. 環境変数 JAVA_HOME の追加

    1. ~/.bashrc を開く

      $ vi ~/.bashrc
      
    2. Iキーで編集モードに入り、一番下に以下の行を追加

      $ export JAVA_HOME=$(update-alternatives --query javac | sed -n -e 's/Best: *\(.*\)\/bin\/javac/\1/p')
      
    3. ESCキーを押し、:wq と入力

  6. Android SDK のダウンロード(Windows 側での操作

    1. ここ の下の方の「Command line tools only」から Linux のものをダウンロード

      WSL10.png

    2. ダウンロードした zip ファイルを解凍

    3. 解凍した中にある tools ディレクトリを shareWithUbuntu ディレクトリに移動

  7. /usr/lib/android/sdk/ ディレクトリを作成

    $ sudo mkdir -p /usr/lib/android/sdk/
    
  8. shareWithWindows ディレクトリ内の tools ディレクトリを /usr/lib/android/sdk/ に移動

    $ sudo mv ~/shareWithWindows/tools /usr/lib/android/sdk/
    
  9. 環境変数 ANDROID_HOME の追加

    1. ~/.bashrc を開く

      $ vi ~/.bashrc
      
    2. Iキーで編集モードに入り、一番下に以下の行を追加

      $ export ANDROID_HOME="/usr/lib/android/sdk/"
      $ export PATH="${PATH}:${ANDROID_HOME}tools/:${ANDROID_HOME}platform-tools/"
      
    3. ESCキーを押し、:wq と入力

  10. 再ログイン

    $ exec $SHELL -l
    
  11. 必要なパッケージのインストール

    $ sudo $ANDROID_HOME/tools/bin/sdkmanager "tools" "emulator" "platform-tools" "platforms;android-28" "build-tools;28.0.3" "extras;android;m2repository" "extras;google;m2repository"
    
  12. 再ログイン

    $ exec $SHELL -l
    
  13. 以下のコマンドを実行し、No issues were detected. が出力されれば成功

    $ tns doctor # 統計情報を自動送信して良いか聞かれるが n で構わない
    

NativeScript 関連のインストールはここまで。

3. アプリケーションの作成

アプリケーションを作成します。

  1. Vue 関連パッケージのインストール

    $ sudo npm install -g @vue/cli @vue/cli-init
    
  2. プロジェクトを作成するディレクトリを作成、移動

    $ mkdir workspace && cd workspace
    
  3. プロジェクトの作成

    $ vue init nativescript-vue/vue-cli-template sample-app
    
    # 以下対話形式(vue-devtools以外はデフォルトでもOK)
    ? Project name sample-app                                              # プロジェクト名
    ? Project description A native application built with NativeScript-Vue # プロジェクトの説明
    ? Application name NativeScript-Vue Application                        # アプリ名
    ? Unique application identifier org.nativescript.application           # アプリケーション任意のID
    ? Project version 1.0.0                                                # プロジェクトのバージョン
    ? Author                                                               # 開発者
    ? License MIT                                                          # ライセンスの種類
    ? Select the programming language javascript                           # 使用する言語
    ? Select a preset (more coming soon) Simple                            # 用意するアプリのテンプレート
    ? Install vuex? (state management) Yes                                 # Vuexを使用するか(デフォルトはNo)
    ? Install vue-devtools? No                                             # Vuedevtoolsを使用するか(デフォルトはYes)
    ? Color scheme none                                                    # カラーテーマ
    
  4. プロジェクトディレクトリに移動し、パッケージインストール

    $ cd sample-app && npm install
    
  5. アプリを起動

    $ tns preview --bundle
    

    WSL11.png

プロジェクトの作成からアプリ実行までは簡単ですね。コンソールに表示される QR コードは後ほど利用するので消さないでください。

実機で表示

作成したアプリケーションを実機に表示します。ここでは、Android 側にアプリケーションの導入が必要となります。

  1. Google Play から NativeScript Playground をインストール
  2. Google Play から NativeScript Preview をインストール

    WSL12.png

  3. NativeScript Playground を開き、 Scan QR code をタップ

    WSL13.png

  4. コンソールの QR コードを読み込む

  5. 完了

    WSL14.png

QR の読み込み後に少し時間がかかりますが、個人的には QR 読み込みだけで実機で試せるのは革命的です。

おわりに

NativeScript-Vue のおかげで睡魔に打ち勝つことができました。が、この記事を書いている間は何度も睡魔に負けています。何か実用的なものを NativeScript-Vue で作成してまた記事を上げたいですが、今度は仕事が忙しくなり睡魔に襲われる機会が減りそうなので、なかったことになるかもしれないです。

ここを見れば誰でも同じことができるように心がけていますが、訳わかんねー記述があればコメントください。まあ私も素人ですが。

参考サイト

Windows Subsystem for Linuxをインストールしてみよう!
Ubuntuに最新のNode.jsを難なくインストールする
NativeScript-Vue / Installation(英語)
NativeScript Advanced Setup: Linux(英語)
Windows 10のWindows Subsystem for Linux(WSL)を日常的に活用する

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

【Vue.js】WSL 上で Vue.js を用いた Android アプリを開発する

はじめに

仕事中、睡魔と戦いながら、Android アプリを作ろうと思いました。

ところが私は、C# は触った程度だし Java はコンソールアプリしかやったことがなく GUI はわかりません。そこで調べてみると、HTML で作れるそうじゃないですか。社会人二年目で Web 系しかしていない私にはありがたい話です。

もう少し欲を出して、業務で使用している Vue.js を使えないかなーと思ったら、ちょうどいいのがあるじゃないですか。

というわけで、Windows Subsystem for Linux (WSL) 上で Vue.js を用いて Android アプリを開発するための手順を紹介します。

つかうもの

  • WSL : Windows 環境がカオスにならないようにするため
  • Vue.js : 今回使いたかったもの
  • nativescript : Javascript でアプリ開発できるようにするすごいやつ
  • アンドロイド実機 : 開発時の実機との連携がすごい

手順

WSL の有効化から実機での起動まで端折らずにやるよ。

  1. WSL 環境の構築
  2. Ubuntu への各種インストール

1. WSL 環境の構築

まずは WSL を有効化し、Ubuntu をインストールします。

  1. コントロールパネル > プログラムのアンインストール を選択

    WSL1.png

  2. 左側 windows の機能の有効化または無効化を選択

    WSL2.png

  3. Windows Subsystem for Linux にチェックを入れ有効化

    WSL3.png

  4. インストール完了次第再起動

  5. (念の為)設定 > 更新とセキュリティ > 開発者向け > 開発者モードを有効にする

    WSL4.png

  6. スタートから Microsoft Store を開く

    WSL5.png

  7. 「Ubuntu」で検索し、Ubuntu を選択

    WSL6.png

  8. 入手をクリックでインストール

    WSL7.png

  9. スタートから Ubuntu を開く

    WSL8.png

  10. インストール完了を待ち、ユーザー名とパスワードを入力(パスワードは表示されないので注意)

    WSL9.png

  11. リポジトリを日本のサーバに変更(sudo 利用時、パスワード入力を求められます)

    $ sudo sed -i -e 's%http://.*.ubuntu.com%http://ftp.jaist.ac.jp/pub/Linux%g' /etc/apt/sources.list
    
  12. パッケージのアップデート

    $ sudo apt update
    $ sudo apt upgrade
    
  13. Windows との共有ディレクトリを作成

    $ ln -s /mnt/c/Users/(username)/shareWithUbuntu ~/shareWithWindows # (username)はWindows側のユーザ名に置き換え
    

以上で WSL 環境の構築は完了です。WSL 環境をリセットしたい場合は
スタート > Ubuntu 右クリック > アプリの設定 > リセット
から可能です。リセット後は Ubuntu を起動するとまたアカウント設定から始まります。環境破壊と再構築、簡単ですね。

2. Ubuntu への各種インストール

NativeScript-Vue での開発に必要なものをインストールしていきます。

  1. Node.js のインストール

    $ sudo apt install -y nodejs npm # node、npmのインストール
    $ sudo npm install n -g          # n package導入
    $ sudo n stable                  # nを使用してnodeのstable版をインストール
    $ sudo apt purge -y nodejs npm   # 最初に入れた古いnode、npmは削除
    $ exec $SHELL -l                 # 再ログイン
    $ node -v                        # バージョン確認
    
  2. NativeScript CLI のインストール

    $ sudo npm install -g nativescript
    
  3. G++ (C++ のコンパイラ)のインストール

    $ sudo apt install g++
    
  4. JDK8 の設定

    $ sudo apt install openjdk-8-jdk         # JDK8インストール
    $ sudo update-alternatives --config java # JAVAのバージョンが複数あればJDK8を選択
    
  5. 環境変数 JAVA_HOME の追加

    1. ~/.bashrc を開く

      $ vi ~/.bashrc
      
    2. Iキーで編集モードに入り、一番下に以下の行を追加

      $ export JAVA_HOME=$(update-alternatives --query javac | sed -n -e 's/Best: *\(.*\)\/bin\/javac/\1/p')
      
    3. ESCキーを押し、:wq と入力

  6. Android SDK のダウンロード(Windows 側での操作

    1. ここ の下の方の「Command line tools only」から Linux のものをダウンロード

      WSL10.png

    2. ダウンロードした zip ファイルを解凍

    3. 解凍した中にある tools ディレクトリを shareWithUbuntu ディレクトリに移動

  7. /usr/lib/android/sdk/ ディレクトリを作成

    $ sudo mkdir -p /usr/lib/android/sdk/
    
  8. shareWithWindows ディレクトリ内の tools ディレクトリを /usr/lib/android/sdk/ に移動

    $ sudo mv ~/shareWithWindows/tools /usr/lib/android/sdk/
    
  9. 環境変数 ANDROID_HOME の追加

    1. ~/.bashrc を開く

      $ vi ~/.bashrc
      
    2. Iキーで編集モードに入り、一番下に以下の行を追加

      $ export ANDROID_HOME="/usr/lib/android/sdk/"
      $ export PATH="${PATH}:${ANDROID_HOME}tools/:${ANDROID_HOME}platform-tools/"
      
    3. ESCキーを押し、:wq と入力

  10. 再ログイン

    $ exec $SHELL -l
    
  11. 必要なパッケージのインストール

    $ sudo $ANDROID_HOME/tools/bin/sdkmanager "tools" "emulator" "platform-tools" "platforms;android-28" "build-tools;28.0.3" "extras;android;m2repository" "extras;google;m2repository"
    
  12. 再ログイン

    $ exec $SHELL -l
    
  13. 以下のコマンドを実行し、No issues were detected. が出力されれば成功

    $ tns doctor # 統計情報を自動送信して良いか聞かれるが n で構わない
    

NativeScript 関連のインストールはここまで。

3. アプリケーションの作成

アプリケーションを作成します。

  1. Vue 関連パッケージのインストール

    $ sudo npm install -g @vue/cli @vue/cli-init
    
  2. プロジェクトを作成するディレクトリを作成、移動

    $ mkdir workspace && cd workspace
    
  3. プロジェクトの作成

    $ vue init nativescript-vue/vue-cli-template sample-app
    
    # 以下対話形式(vue-devtools以外はデフォルトでもOK)
    ? Project name sample-app                                              # プロジェクト名
    ? Project description A native application built with NativeScript-Vue # プロジェクトの説明
    ? Application name NativeScript-Vue Application                        # アプリ名
    ? Unique application identifier org.nativescript.application           # アプリケーション任意のID
    ? Project version 1.0.0                                                # プロジェクトのバージョン
    ? Author                                                               # 開発者
    ? License MIT                                                          # ライセンスの種類
    ? Select the programming language javascript                           # 使用する言語
    ? Select a preset (more coming soon) Simple                            # 用意するアプリのテンプレート
    ? Install vuex? (state management) Yes                                 # Vuexを使用するか(デフォルトはNo)
    ? Install vue-devtools? No                                             # Vuedevtoolsを使用するか(デフォルトはYes)
    ? Color scheme none                                                    # カラーテーマ
    
  4. プロジェクトディレクトリに移動し、パッケージインストール

    $ cd sample-app && npm install
    
  5. アプリを起動

    $ tns preview --bundle
    

    WSL11.png

プロジェクトの作成からアプリ実行までは簡単ですね。コンソールに表示される QR コードは後ほど利用するので消さないでください。

実機で表示

作成したアプリケーションを実機に表示します。ここでは、Android 側にアプリケーションの導入が必要となります。

  1. Google Play から NativeScript Playground をインストール
  2. Google Play から NativeScript Preview をインストール

    WSL12.png

  3. NativeScript Playground を開き、 Scan QR code をタップ

    WSL13.png

  4. コンソールの QR コードを読み込む

  5. 完了

    WSL14.png

QR の読み込み後に少し時間がかかりますが、個人的には QR 読み込みだけで実機で試せるのは革命的です。

おわりに

NativeScript-Vue のおかげで睡魔に打ち勝つことができました。が、この記事を書いている間は何度も睡魔に負けています。何か実用的なものを NativeScript-Vue で作成してまた記事を上げたいですが、今度は仕事が忙しくなり睡魔に襲われる機会が減りそうなので、なかったことになるかもしれないです。

ここを見れば誰でも同じことができるように心がけていますが、訳わかんねー記述があればコメントください。まあ私も素人ですが。

参考サイト

Windows Subsystem for Linuxをインストールしてみよう!
Ubuntuに最新のNode.jsを難なくインストールする
NativeScript-Vue / Installation(英語)
NativeScript Advanced Setup: Linux(英語)
Windows 10のWindows Subsystem for Linux(WSL)を日常的に活用する

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

フロントエンドでもtrivyを使って脆弱性対策したい!

伝えたいこと

  • フロントエンドもコンテナ経由でアクセスさせよう
  • コンテナならtrivyを使ってヤバそうな脆弱性を知ることができる
  • trivyならCIに組み込むことも簡単
  • multi-stage buildの場合は工夫が必要
  • いまの状況でできる範囲の脆弱性対策からはじめよう

今回の記事でできること

trivy_flow.png

CI上で、本番用のフロントエンドのコンテナをmulti-satge buildでつくり、
trivyを利用して脆弱性を検知するところまでを目指します。

最後に、trivyを利用したレベル別の運用イメージも提案します。

導入

週末、trivyというコンテナ向けの脆弱性検知ツールが正式リリースされました。公開が5日目の5/21 12:00時点で 900star以上獲得しています。

trivyの詳細は原作者である @knqyf263 さんの「CIで使えるコンテナの脆弱性スキャナ」という記事を参照ください。

ただフロントエンドの方は「脆弱性はインフラ側が対応してくれるから、自分には関係ない」と思っていないでしょうか。
そのような方をメインに、今回は以下の内容を紹介します。

  • コンテナを使ってフロントエンドの本番環境を構築する
  • trivyを利用して、脆弱性が含まれていないかチェックする
  • trivyを通じた脆弱性の運用方法を学ぶ

僕は普段はフロントエンドも担当するエンジニアで、セキュリティに対して体系だって学んだことはありません。ただ、trivyのコミッタでもあり、脆弱性の運用も少し知っているので、フロントエンドでもDevSecOpsを定着させるきっかけになるといいなと考えています。

ソースコード

今回使うコードは、すべて以下のレポジトリにあります。

https://github.com/tomoyamachi/trivy-with-react
reactrivy.png

この記事では触れませんが、開発環境でもコンテナ経由でホットリロードするような仕組みになっています。

実践 : プロジェクト作成~trivyの導入

ここからはプロジェクトの作成と、CIにtrivyを組み込むまでの手順を書きます

1. プロジェクトの作成

今回はCreate React Appを利用して、Reactのプロジェクトをまず作成します。
そして、デフォルトではパッケージ管理ツールにyarnが利用されているので、今回はプロジェクトのパッケージ管理をnpmで実行します。

$ npx create-react-app trivy-with-react
$ cd trivy-with-react
$ rm -fr node_modules yarn.lock
$ npm install

2. 本番用コンテナの作成

  1. 本番用のコードを生成
  2. nginxコンテナに生成したコードを移し、nginxを起動

という手順をとります。

通常であれば1ファイル

そして通常2つのステップは、multi-stage buildを利用して、1つのDockerfileでコンテナを作成します。

Dockerfile
# build environment
FROM node:12.2.0-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json /app/package.json
RUN npm install --silent
RUN npm install react-scripts@3.0.1 -g --silent
COPY . /app
RUN npm run build

# production environment
FROM nginx:1.16.0-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

from Dockerizing a React App

1ファイルでmulti-stage buildの問題点

multi-stage buildは1つのDockerfileで最終成果物のimageだけを出力してくれるので、通常はベストプラクティスです。
その場合、以下のように、builder側のイメージがIMAGE IDでしか特定できない形で作成されているのがわかります。

$ docker images
REPOSITORY         TAG    IMAGE ID ...
trivy-with-react   test   96c4fdfac287 # nginx側
<none>             <none> 41fd97016d25 # builder側

しかし、今回、脆弱性はbuilder側のコンテナにもあることに気がついた方もいるでしょう。package-lock.json もそうですし、一般的ではないですが、Static Linkでビルドする場合も考えられます。

trivyはREPOSITORY:TAGの形式でコンテナイメージを指定してスキャンするので、multi-stage buildにしてしまうと、builder側の脆弱性が放置されてしまいます。

そのため、builder側と、nginx側のコンテナイメージを独立させて、参照可能にしたいと思います。具体的には、Dockerfileを分けることで、それぞれのイメージにタグを付けます。
また、2回コマンド実行するのも微妙なのでmakefileを作成します。

※ もしも1つのDockerfileでmulti-stage buildをし、builder側のイメージ名を指定することができるのであれば、切実に情報がほしいです!

trivy用の構成

コードを見てもらえばわかりますが、Dockerfileをbuilder用(Dockerfile.builder)と、nginx用(Dockerfile.prod)に分けて、makefileは以下のようにしています。

Dockerfile.builder
FROM node:12.2.0-alpine as builder
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json /app/package.json
RUN npm install --silent
RUN npm install react-scripts@3.0.1 -g --silent
COPY . /app
RUN npm run build
Dockerfile.prod
ARG tag
FROM local-builder:${tag} as builder

FROM nginx:1.16.0-alpine
ARG tag
COPY --from=builder /app/build /usr/share/nginx/html
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/nginx.conf /etc/nginx/conf.d
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
makefile
.PHONY: build all

TAG := $(shell git rev-parse HEAD)

all: build
build:
    docker build . -f Dockerfile.builder -t local-builder:$(TAG)
    docker build . --build-arg tag=$(TAG) -f Dockerfile.prod -t built:$(TAG)

このため、以下のようにデフォルトではコミットハッシュごとにコンテナを作成し、変数TAGが指定されていればそれを利用します。

$ make build  # 最新のコミットハッシュをコンテナのタグに利用
$ make build TAG=hoge # hogeをコンテナのタグに指定

実際のレポジトリでは、SPA用にnginx.confを置いたりしているので、必要な方はご確認ください。

1つのDockerfileで、同様のことができる方法をご存知の方いたら教えてください...

ビルドしたコンテナのテスト

では、さっそくビルドしたコンテナにアクセスしてみましょう。

$ make build TAG=test
$ docker run -p 8000:80 built:test

以上を実行し、 http://localhost:8000 を開くと、画面が表示されることが確認できました。

localhost.png

ついでに、nginxでSPA対応できているかを確認するため、 http://localhost:8000/spa/test が開けることを確認します。

localhost_spa

3. trivyでスキャン

では、builder側とnginx側をスキャンしましょう。
trivyはインストールしてある前提ですが、makefileに以下のコードを追加します。

buildと同じようにTAGを指定することで、指定したイメージをスキャンできるようになります。

ci-scan:
    trivy --exit-code 1 --severity HIGH,CRITICAL  --quiet --auto-refresh local-builder:$(TAG)
    trivy --exit-code 1 --quiet --auto-refresh built:$(TAG)

--exit-codeオプションで脆弱性を検知した場合の終了コードを指定できるので、スキャンはしたいけど、CIは止めたくない人は --exit-code 0 (デフォルトなので省略可) を利用しましょう。

4. CircleCIで実行

CircleCIの場合は以下のようにします。
trivyの脆弱性データの取得が重いので、脆弱性データなどはキャッシュに保存すると2回目以降のスキャンが高速になります。

version: 2
jobs:
  build:
    docker:
      - image: docker:18.09-git
    steps:
      - checkout
      - setup_remote_docker
      - restore_cache:
          key: vulnerability-db
      - run:
          name: Build image
          command: |
            apk add --update --no-cache make curl
            make build TAG=${CIRCLE_SHA1}
      - run:
          name: Install latest trivy
          command: |
            VERSION=$(
                curl --silent "https://api.github.com/repos/knqyf263/trivy/releases/latest" | \
                grep '"tag_name":' | \
                sed -E 's/.*"v([^"]+)".*/\1/'
            )
            wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz
            tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz
            mv trivy /usr/local/bin
      - run:
          name: Scan local images with trivy
          command: |
            make ci-scan TAG=${CIRCLE_SHA1}
      - save_cache:
          key: vulnerability-db
          paths:
            - $HOME/.cache/trivy
workflows:
  version: 2
  release:
    jobs:
      - build

正常終了の場合、以下のようになります。
builder側とnginx側のどちらにも脆弱性がなかったということがわかりました。

ci-success.png

脆弱性があった場合CIを止めたい

脆弱性が見つかった場合に、CIを止めたい場合は--exit-code 1オプションを利用します。このオプションにより、脆弱性が見つかった場合、異常終了(終了コードが1)になります。
また、 severityオプションを利用することで、検知したい脆弱性の危険度を指定できます。

trivy --exit-code 1 --severity HIGH,CRITICAL \
  --quiet --auto-refresh \ 
  local-builder:$(TAG)

この状態で脆弱性のあるパッケージを追加して結果を見ると、CIが失敗することが確認できました!

ci-error.png
https://circleci.com/gh/tomoyamachi/trivy-with-react/8

trivyを使った運用スタイル

さて、では脆弱性が見つかった場合、どのようにすればよいでしょうか?

こうしたほうがいい、というのはありますが、それぞれの運用方針にそって決めればいいと思います。

以下に、「とりあえず導入」「ちょっと気になる」「がんばる」のコースとそれぞれによさそうな運用方針をまとめました。

とりあえず導入 ちょっと気になる がんばる
検知時のCI とめない(--exit-code 0) とめる(--exit-code 1) とめる(--exit-code 1)
対応方針 対応せずSlackなどに通知 バージョンアップして、駄目なら無視する (.trivyignore) 脆弱性を調べて、必要なら対応
検知タイミング ビルド時 ビルド時 ビルド時&定期実行

全く興味なくても、導入するだけ導入したい、という人がいてもいいと思うので、できる範囲ですすめられるといいかなと思います。

以下は、「とりあえず導入」以上の興味がある人に対しての方法論になります。

1. 脆弱性を調べ、本当にCRITICALかをチェック

まず、その脆弱性が自分のアプリケーションにとって本当に問題になるのかを理解しましょう。
NVDなどで脆弱性の内容を調べる方法もあるし、まわりの詳しい人に聞くのもありです。
自分で調べる場合、脆弱性が危険かどうかの判断は このスライドの「トリアージ」以降の章が役立つかと思います。

2. 重要ではない場合は、その脆弱性を無視する

プロジェクトに .trivyignore を作成し、無視したい脆弱性ID(以下、VULNERABILITY ID)を記載すると、そのIDを無視してくれます。
さきほどの例でいうと、以下のように指定すれば、それ以降同じVULNERABILITY IDは検知されなくなります。

.trivyignore
NSWG-ECO-328
CVE-2019-5428
CVE-2019-11358

この対応で先程停止していたワークフローがとおるようになったのがわかります。
https://circleci.com/gh/tomoyamachi/trivy-with-react/10

3. 重要な場合は...

重要な場合、バージョンアップをすれば良いと思うのですが、そうではない場合、自分で修正したコードを利用する方法などもあります。

アプリケーションパッケージの場合

たとえば、npmのパッケージであれば、 この記事の方法で でforkした自分のレポジトリを入れることが可能です。

ただし、その場合、バージョン情報が以下のようになり、そのパッケージの脆弱性チェックができなくなる点に留意ください。

"jquery": {
      "version": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git#438b1a3e8a52d3e4efd8aba45498477038849c97",
      "from": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git"
    },

OSのパッケージの場合

検出されるパッケージバージョン自体は変わらないので、patchfileを当てるコマンドを実行するのと同時に、.trivyignore に対象のVULNERABILITY IDを追加してください。

まとめ

今回は、以下のことを書きました。

  • フロントエンドをコンテナで運用する
  • trivyで脆弱性対策できる
  • 脆弱性のおおまかな運用方法

なお、コンテナになっていない場合は、Vulsという素晴らしい脆弱性スキャナがあるので、こちらをご利用ください。動作中のコンテナもスキャン可能です。
すこし導入に準備が必要ですが、あなたのサーバは本当に安全ですか?今もっともイケてる脆弱性検知ツールVulsを使ってみた を参考にお試しください。

謝辞

記事を書く際に、 @knqyf263 さん、 @sadayuki-matsuno さん、 @codehex さんにご意見をいただきました。ありがとうございました!!

以上です。

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

フロントエンドでもtrivyを使って脆弱性対策したい!

伝えたいこと

  • フロントエンドもコンテナ経由でアクセスさせよう
  • コンテナならtrivyを使ってヤバそうな脆弱性を知ることができる
  • trivyならCIに組み込むことも簡単
  • multi-stage buildの場合は工夫が必要
  • いまの状況でできる範囲の脆弱性対策からはじめよう

今回の記事でできること

trivy_flow.png

CI上で、本番用のフロントエンドのコンテナをmulti-satge buildでつくり、
trivyを利用して脆弱性を検知するところまでを目指します。

最後に、trivyを利用したレベル別の運用イメージも提案します。

導入

週末、trivyというコンテナ向けの脆弱性検知ツールが正式リリースされました。公開が5日目の5/21 12:00時点で 900star以上獲得しています。

trivyの詳細は原作者である @knqyf263 さんの「CIで使えるコンテナの脆弱性スキャナ」という記事を参照ください。

今回は、フロントエンドをメインに、今回は以下の内容を紹介します。

  • コンテナを使ってフロントエンドの本番環境を構築する
  • trivyを利用して、脆弱性が含まれていないかチェックする
  • trivyを通じた脆弱性の運用方法を学ぶ

僕も、普段はフロントエンドも担当するエンジニアで、セキュリティに対して体系だって学んだことはありません。ただ、trivyのコミッタでもあり、脆弱性の運用も少し知っているので、フロントエンドでもDevSecOpsを定着させるきっかけになるといいなと考えています。

ソースコード

今回使うコードは、すべて以下のレポジトリにあります。

https://github.com/tomoyamachi/trivy-with-react
reactrivy.png

この記事では触れませんが、開発環境でもコンテナ経由でホットリロードするような仕組みになっています。

実践 : プロジェクト作成~trivyの導入

ここからはプロジェクトの作成と、CIにtrivyを組み込むまでの手順を書きます

1. プロジェクトの作成

今回はCreate React Appを利用して、Reactのプロジェクトをまず作成します。
そして、デフォルトではパッケージ管理ツールにyarnが利用されているので、今回はプロジェクトのパッケージ管理をnpmで実行します。

$ npx create-react-app trivy-with-react
$ cd trivy-with-react
$ rm -fr node_modules yarn.lock
$ npm install

2. 本番用コンテナの作成

  1. 本番用のコードを生成
  2. nginxコンテナに生成したコードを移し、nginxを起動

という手順をとります。

通常であれば1ファイル

そして通常2つのステップは、multi-stage buildを利用して、1つのDockerfileでコンテナを作成します。

Dockerfile
# build environment
FROM node:12.2.0-alpine as build
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json /app/package.json
RUN npm install --silent
RUN npm install react-scripts@3.0.1 -g --silent
COPY . /app
RUN npm run build

# production environment
FROM nginx:1.16.0-alpine
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

from Dockerizing a React App

1ファイルでmulti-stage buildの問題点

multi-stage buildは1つのDockerfileで最終成果物のimageだけを出力してくれるので、通常はベストプラクティスです。
その場合、以下のように、builder側のイメージがIMAGE IDでしか特定できない形で作成されているのがわかります。

$ docker images
REPOSITORY         TAG    IMAGE ID ...
trivy-with-react   test   96c4fdfac287 # nginx側
<none>             <none> 41fd97016d25 # builder側

しかし、今回、脆弱性はbuilder側のコンテナにもあることに気がついた方もいるでしょう。package-lock.json もそうですし、一般的ではないですが、Static Linkでビルドする場合も考えられます。

trivyはREPOSITORY:TAGの形式でコンテナイメージを指定してスキャンするので、multi-stage buildにしてしまうと、builder側の脆弱性が放置されてしまいます。

そのため、builder側と、nginx側のコンテナイメージを独立させて、参照可能にしたいと思います。具体的には、Dockerfileを分けることで、それぞれのイメージにタグを付けます。
また、2回コマンド実行するのも微妙なのでmakefileを作成します。

※ もしも1つのDockerfileでmulti-stage buildをし、builder側のイメージ名を指定することができるのであれば、切実に情報がほしいです!

trivy用の構成

コードを見てもらえばわかりますが、Dockerfileをbuilder用(Dockerfile.builder)と、nginx用(Dockerfile.prod)に分けて、makefileは以下のようにしています。

Dockerfile.builder
FROM node:12.2.0-alpine as builder
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json /app/package.json
RUN npm install --silent
RUN npm install react-scripts@3.0.1 -g --silent
COPY . /app
RUN npm run build
Dockerfile.prod
ARG tag
FROM local-builder:${tag} as builder

FROM nginx:1.16.0-alpine
ARG tag
COPY --from=builder /app/build /usr/share/nginx/html
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/nginx.conf /etc/nginx/conf.d
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
makefile
.PHONY: build all

TAG := $(shell git rev-parse HEAD)

all: build
build:
    docker build . -f Dockerfile.builder -t local-builder:$(TAG)
    docker build . --build-arg tag=$(TAG) -f Dockerfile.prod -t built:$(TAG)

このため、以下のようにデフォルトではコミットハッシュごとにコンテナを作成し、変数TAGが指定されていればそれを利用します。

$ make build  # 最新のコミットハッシュをコンテナのタグに利用
$ make build TAG=hoge # hogeをコンテナのタグに指定

実際のレポジトリでは、SPA用にnginx.confを置いたりしているので、必要な方はご確認ください。

1つのDockerfileで、同様のことができる方法をご存知の方いたら教えてください...

ビルドしたコンテナのテスト

では、さっそくビルドしたコンテナにアクセスしてみましょう。

$ make build TAG=test
$ docker run -p 8000:80 built:test

以上を実行し、 http://localhost:8000 を開くと、画面が表示されることが確認できました。

localhost.png

ついでに、nginxでSPA対応できているかを確認するため、 http://localhost:8000/spa/test が開けることを確認します。

localhost_spa

3. trivyでスキャン

では、builder側とnginx側をスキャンしましょう。
trivyはインストールしてある前提ですが、makefileに以下のコードを追加します。

buildと同じようにTAGを指定することで、指定したイメージをスキャンできるようになります。

ci-scan:
    trivy --exit-code 1 --severity HIGH,CRITICAL  --quiet --auto-refresh local-builder:$(TAG)
    trivy --exit-code 1 --quiet --auto-refresh built:$(TAG)

--exit-codeオプションで脆弱性を検知した場合の終了コードを指定できるので、スキャンはしたいけど、CIは止めたくない人は --exit-code 0 (デフォルトなので省略可) を利用しましょう。

4. CircleCIで実行

CircleCIの場合は以下のようにします。
trivyの脆弱性データの取得が重いので、脆弱性データなどはキャッシュに保存すると2回目以降のスキャンが高速になります。

version: 2
jobs:
  build:
    docker:
      - image: docker:18.09-git
    steps:
      - checkout
      - setup_remote_docker
      - restore_cache:
          key: vulnerability-db
      - run:
          name: Build image
          command: |
            apk add --update --no-cache make curl
            make build TAG=${CIRCLE_SHA1}
      - run:
          name: Install latest trivy
          command: |
            VERSION=$(
              curl -I https://github.com/knqyf263/trivy/releases/latest | \
              grep -o '/tag/v[0-9]\+.[0-9]\+\.[0-9]\+' | \
              sed -E 's:/tag/v([0-9\.]+):\1:'
            )
            wget https://github.com/knqyf263/trivy/releases/download/v${VERSION}/trivy_${VERSION}_Linux-64bit.tar.gz
            tar zxvf trivy_${VERSION}_Linux-64bit.tar.gz
            mv trivy /usr/local/bin
      - run:
          name: Scan local images with trivy
          command: |
            make ci-scan TAG=${CIRCLE_SHA1}
      - save_cache:
          key: vulnerability-db
          paths:
            - $HOME/.cache/trivy
workflows:
  version: 2
  release:
    jobs:
      - build

正常終了の場合、以下のようになります。
builder側とnginx側のどちらにも脆弱性がなかったということがわかりました。

ci-success.png

脆弱性があった場合CIを止めたい

脆弱性が見つかった場合に、CIを止めたい場合は--exit-code 1オプションを利用します。このオプションにより、脆弱性が見つかった場合、異常終了(終了コードが1)になります。
また、 severityオプションを利用することで、検知したい脆弱性の危険度を指定できます。

trivy --exit-code 1 --severity HIGH,CRITICAL \
  --quiet --auto-refresh \ 
  local-builder:$(TAG)

この状態で脆弱性のあるパッケージを追加して結果を見ると、CIが失敗することが確認できました!

ci-error.png
https://circleci.com/gh/tomoyamachi/trivy-with-react/8

trivyを使った運用スタイル

さて、では脆弱性が見つかった場合、どのようにすればよいでしょうか?

こうしたほうがいい、というのはありますが、それぞれの運用方針にそって決めればいいと思います。

以下に、「とりあえず導入」「ちょっと気になる」「がんばる」のコースとそれぞれによさそうな運用方針をまとめました。

とりあえず導入 ちょっと気になる がんばる
検知時のCI とめない(--exit-code 0) とめる(--exit-code 1) とめる(--exit-code 1)
対応方針 対応せずSlackなどに通知 バージョンアップして、駄目なら無視する (.trivyignore) 脆弱性を調べて、必要なら対応
検知タイミング ビルド時 ビルド時 ビルド時&定期実行

全く興味なくても、導入するだけ導入したい、という人がいてもいいと思うので、できる範囲ですすめられるといいかなと思います。

以下は、「とりあえず導入」以上の興味がある人に対しての方法論になります。

1. 脆弱性を調べ、本当にCRITICALかをチェック

まず、その脆弱性が自分のアプリケーションにとって本当に問題になるのかを理解しましょう。
NVDなどで脆弱性の内容を調べる方法もあるし、まわりの詳しい人に聞くのもありです。
自分で調べる場合、脆弱性が危険かどうかの判断は このスライドの「トリアージ」以降の章が役立つかと思います。

2. 重要ではない場合は、その脆弱性を無視する

プロジェクトに .trivyignore を作成し、無視したい脆弱性ID(以下、VULNERABILITY ID)を記載すると、そのIDを無視してくれます。
さきほどの例でいうと、以下のように指定すれば、それ以降同じVULNERABILITY IDは検知されなくなります。

.trivyignore
NSWG-ECO-328
CVE-2019-5428
CVE-2019-11358

この対応で先程停止していたワークフローがとおるようになったのがわかります。
https://circleci.com/gh/tomoyamachi/trivy-with-react/10

3. 重要な場合は...

重要な場合、バージョンアップをすれば良いと思うのですが、そうではない場合、自分で修正したコードを利用する方法などもあります。

アプリケーションパッケージの場合

たとえば、npmのパッケージであれば、 この記事の方法で でforkした自分のレポジトリを入れることが可能です。

ただし、その場合、バージョン情報が以下のようになり、そのパッケージの脆弱性チェックができなくなる点に留意ください。

"jquery": {
      "version": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git#438b1a3e8a52d3e4efd8aba45498477038849c97",
      "from": "git+https://<token>:x-oauth-basic@github.com/tomoyamachi/jquery.git"
    },

OSのパッケージの場合

検出されるパッケージバージョン自体は変わらないので、patchfileを当てるコマンドを実行するのと同時に、.trivyignore に対象のVULNERABILITY IDを追加してください。

まとめ

今回は、以下のことを書きました。

  • フロントエンドをコンテナで運用する
  • trivyで脆弱性対策できる
  • 脆弱性のおおまかな運用方法

ところで、 記事を書いているときにnpmを使うのであれば、npm auditで簡単に脆弱性を取得できることを知りました… が、環境側の脆弱性もチェックできるので、コンテナ化しているのであれば、trivyを使ってみてください。
S3などにホストしている場合は、npm auditだけでよさそうです。

なお、コンテナになっていない場合で実行環境をチェックしたい場合、Vulsという素晴らしい脆弱性スキャナがあるので、こちらをご利用ください。動作中のコンテナもスキャン可能です。
すこし導入に準備が必要ですが、あなたのサーバは本当に安全ですか?今もっともイケてる脆弱性検知ツールVulsを使ってみた を参考にお試しください。

謝辞

記事を書く際に、 @knqyf263 さん、 @sadayuki-matsuno さん、 @codehex さんにご意見をいただきました。ありがとうございました!!

以上です。

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

Vue 使いなら秒で覚えられる Svelte 入門

Svelteって何?

すごく速くていい感じの JS フレームワークです。
詳しい説明はこちらの記事をご参照ください。

当記事では Svelte の基本的な文法を Vue と比較しながら解説していきます。

Vueとの類似点

シンタックスはかなりVueに近いです。
以下ではVueの構成要素ごとに、Svelteではどう書けるかを解説します。
(使用バージョンはv3.4.2です)

data

Vueで言うところのdataはそのまま変数として定義すればいいだけです。

HTML内で表示するには波括弧1つで囲みます。

<h1>Hello {name}</h1>

<script>
  let name = 'world';
</script>

これで'Hello world'と表示されます。

またVueと同様に波括弧の中には JS 文を書くことができます。

<h1>Hello {name.toUpperCase()}</h1>
<!-- Hello WORLD と表示される -->

<script>
  let name = 'world';
</script>

v-bind

単方向バインドも波括弧で囲んだ中に入れるだけです。

<input type="text" value="{value}">

<script>
  let value = 'world';
</script>

また、属性名と変数名が等しい場合は次のような略記も可能です。

<input type="text" {value}>

<script>
  let value = 'world';
</script>

v-model

ここがVue使いにとっては少し紛らわしいのですが、
双方向バインディングの場合は属性にbind:を付けます。

<input type="text" bind:value="{value}">

<script>
  let value = 'world';
</script>

methods

dataに当たるものが普通の変数なので、methodsも普通の関数になります。

クリックイベントにバインドするにはon:clickディレクティブを用います。
他のイベントも同様です。

<button on:click="{handleClick}">ボタン</button>
ボタンは{count}回押されました。

<script>
  let count = 0
  const handleClick = () => {
    count += 1
  }
</script>

computed

computedは少しだけ独特な記法($:)を用います。
$:の後に書かれた文は、その文の中で参照された変数が変化するたびに再実行されます。

$: let computedProp = prop * 2

つまり上の例では、propが変化するたびに、computedPropertyprop * 2が代入されるようになっています。まさにcomputedですね。

全体の例も置いておきます。
この例ではisEvencountから算出されるようになっています。

<button on:click="{handleClick}">ボタン</button>
あなたはボタンを{isEven ? '偶数' : '奇数'}回押しています。

<script>
  let count = 0
  // computed property
  $: isEven = count % 2 === 0

  const handleClick = () => {
    count += 1
  }
</script>

また、computedでは複数行の処理を書くことがよくあると思いますが、
そういう場合波括弧で囲んでブロックを作ればOKです。

  let count = 0
  let double = 0    
  // computed: double
  $: {
    console.log(count)
    double = count * 2
  }

あるいは関数を作ってそこで処理を行ってもいいです。

  let count = 0;
  let double = 0;

  $: computeDouble(count)

  const computeDouble = (count) => {
    console.log(count)
    double = count * 2
  }

ちなみに$:記法は一見へんてこに見えますが、実は正当なJavaScriptです(label文)。

watch

実はwatchも先ほどの$:記法で書くことができます。
実際computedwatchも「データに変化があるたびに何かを行う」という点で共通していますね。

<button on:click="{handleClick}">ボタン</button>
count:  {count}
count2: {count2}

<script>
  let count = 0
  let count2 = 0
  // countが変化するたびcount2をインクリメント
  $: incrementCount2(count)

  const handleClick = () => {
    count += 1
  }

  const IncrementCount2 = () => {
    if(count > 0) count2 += 1
  }
</script>

この例では、countが変化するたびにincrementCount2()が呼ばれ、count21増加するようになっています。

その他

非常に単純なので説明は割愛しますが v-htmlv-ifv-for に当たるシンタックスも用意されています。

リンク先は英語ですが、英語嫌いの方でもコードを見れば一発で理解できるかと思います。

終わりに

以上 Vue の基本的な構文に沿ってざっと説明してきました。

かなり Vue に似た構文で、更に簡潔に書ける部分もあることがわかっていただけたかと思います。

次の JS フレームワークの潮流になるかどうかはわかりませんが、なった場合 Vue 使いはちょっとだけ有利かもしれませんね。

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

Vue使いなら秒で覚えられるSvelte入門

Svelteって何?

すごく速くていい感じの JS フレームワークです。
詳しい説明はこちらの記事をご参照ください。

当記事では Svelte の基本的な文法を Vue と比較しながら解説していきます。

Vueとの比較

以下では Vue の構成要素ごとに、Svelteではどう書けるかを解説します。
コードは全てREPLでサクッと試すことができます。
(Svelteのバージョン:v3.4.2)

data

Vueで言うところのdataはそのまま変数として宣言すればいいだけです。

HTML内で表示するには波括弧1つで囲みます。

<h1>Hello {name}</h1>

<script>
  let name = 'world';
</script>

これで'Hello world'と表示されます。

またVueと同様に波括弧の中には JS 文を書くことができます。

<h1>Hello {name.toUpperCase()}</h1>
<!-- Hello WORLD と表示される -->

<script>
  let name = 'world';
</script>

v-bind

単方向バインドも波括弧で囲んだ中に入れるだけです。

<input type="text" value="{value}">

<script>
  let value = 'world';
</script>

また、属性名と変数名が等しい場合は次のような略記も可能です。

<input type="text" {value}>

<script>
  let value = 'world';
</script>

v-model

ここが Vue 使いにとっては少し紛らわしいのですが、
双方向バインディングの場合は属性にbind:を付けます。

<input type="text" bind:value="{value}">

<script>
  let value = 'world';
</script>

methods

dataに当たるものが普通の変数なのでmethodsも普通の関数になります。

クリックイベントにバインドするにはon:clickディレクティブを用います。
他のイベントも同様です。

<button on:click="{handleClick}">ボタン</button>
ボタンは{count}回押されました。

<script>
  let count = 0
  const handleClick = () => {
    count += 1
  }
</script>

computed

computedは少しだけ独特な記法$:を用います。
$:の後に書かれた文は、その文の中で参照された変数が変化するたびに再実行されます。

$: computedProp = prop * 2

つまり上の例では、propが変化するたびに、computedPropprop * 2が代入されるようになっています。

全体の例も置いておきます。
この例ではisEvencountから算出されるようになっています。

<button on:click="{handleClick}">ボタン</button>
あなたはボタンを{isEven ? '偶数' : '奇数'}回押しています。

<script>
  let count = 0
  // computed property
  $: isEven = count % 2 === 0

  const handleClick = () => {
    count += 1
  }
</script>

また、computedでは複数行の処理を書くことがよくあると思いますが、
そういう場合波括弧で囲んでブロックを作ればOKです。

  let count = 0
  let doubled = 0   
  // computed: doubled
  $: {
    console.log(count)
    doubled = count * 2
  }

あるいは関数を作ってそこで処理を行ってもいいです。

  let count = 0;
  let doubled = 0;

  $: computeDouble(count)

  const computeDouble = (count) => {
    console.log(count)
    doubled = count * 2
  }

ちなみに$:記法は一見へんてこに見えますが、実は正当なJavaScriptです(label文)。

watch

実はwatchも先ほどの$:記法で書くことができます。
実際computedwatchも「データに変化があるたびに何かを行う」という点で共通していますね。

<button on:click="{handleClick}">ボタン</button>
count:  {count}
count2: {count2}

<script>
  let count = 0
  let count2 = 0
  // countが変化するたびcount2をインクリメント
  $: incrementCount2(count)

  const handleClick = () => {
    count += 1
  }

  const incrementCount2 = () => {
    if(count > 0) count2 += 1
  }
</script>

この例では、countが変化するたびにincrementCount2()が呼ばれ、count21増加するようになっています。

その他

非常に単純なので説明は割愛しますが v-htmlv-ifv-for に当たるシンタックスも用意されています。

リンク先は英語ですが、英語嫌いの方でもコードを見れば一発で理解できるかと思います。

終わりに

以上 Vue の基本的な構文に沿ってざっと説明してきました。

かなり Vue に似た構文で、更に簡潔に書ける部分もあることがわかっていただけたかと思います。

次の JS フレームワークの潮流になるかどうかはわかりませんが、なった場合 でも Vue 使いなら簡単に移行できそうです。

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