Note to siteru

調べたことについてメモしていく個人的なサイト

Golangについてのメモ

Testing Package

Golangの標準パッケージにある自動テストのためのもの ベンチマークを行うこともできる。

go test <target package>コマンドでテストを実行できる。

公式サイト

サンプルコード


import (
  "testing"
  "github.com/stretchr/testify/assert"
)

// テスト用の関数
func TestApple() {
  fmt.Println("Give me Apple")
}

func Example(t *testing.T) {
  correct := 42
  answer := TestTarget(100)
  if correct != answer {
    // テストに失敗
    t.Errorf("Bad answer... correct=%d, answer=%d", correct, answer)
  }

  // 標準ではアサート関数は用意されていないので
  // testifyというライブラリを使うといい
  assert.Equal(t, correct, answer, "Bad answer...")

  // サブテストの実行
  t.Run("Case=A", func(t *testing.T){  })
  t.Run("Case=A", func(t *testing.T){  })
  t.Run("Case=B", func(t *testing.T){  })
}

func SkipTest(t *testing.T) {
  t.Skip()
}

基本的な使い方

go testコマンドを実行するためには以下の項目を満たす必要がある。

  • "_test.go"で終わるファイルの作成。
    • このファイルはビルドの時に自動的に省かれる。
  • 上のファイルの中で関数名が大文字から始まる関数がテスト内容として実行される。
    • \*testing.Tを引数に取ることができる。
  • テスト失敗を告げるにはError, Failまたは関係するメソッドを使う。
  • `*testing.T`のSkipメソッドを使うとそのテストをスキップできる。

ベンチマーク

ベンチマークを行う際は以下の項目を追加で満たす必要がある。

  • go test-benchフラグを付ける
  • ベンチマークを行う関数はBenchmarkXxx(*testing.B)という形式を取る
    • Benchmarkの後に大文字から始まる名前を付けること
    • \*testing.Bを引数に取ることができる。
      • \*testing.Tと同様Skip関数が使える。

サブテスト

Runメソッドを使うことで実行される関数内でサブテスト/サブベンチマーク(以降サブテストと統一)を行うことができる

サブテストには名前を付けることができ、付けた名前は後述する-runオプションなどで利用できる。

ネストすることも可能で、Runメソッド内でRunメソッドを呼び出すとできる。

テストの前準備/後処理

テストする前に追加の前準備/後処理が必要になるケースがある場合は、テスト用ファイル内に、func TestMain(m *testing.M)を定義するといい。

定義した時は各テスト関数は直接呼び出されず、代わりにTestMain関数が呼び出される。 その際はTestMain関数内でm.Run()の結果をos.Exit()に渡すこと。

func TestMain(m *testing.M) {
  // コマンドライン引数を使用したい時は先にflag.Parse()を呼び出すこと
    os.Exit(m.Run())
}

ドキュメントだけでは使い方がよくわからなかったので、Githubで調べたところ、以下のパターンで使われていた。

  1. ドキュメントどおり
    func TestMain(m *testing.M) {
      os.Exit(m.Run);
    }
    // 変数に介するパターン
    func TestMain(m *testing.M) {
      r := m.Run()
      os.Exit(r);
    }
    
  2. mを他の関数に渡し、その中で色々する ここから拝借
    func hoge(m *testing.M) {
      defer testhelper.MustHaveNoChildProcess()
    
      m.Run()
    }
    
  3. 変わったものも 初め見た時戸惑ったが、goroutineを使っているコードもあった こちらから
    func Test_main(t *testing.T) {
        go main()
        exitCode = <-exitCh
    }
    func TestMain(m *testing.M) {
        m.Run()
        // can exit because cover profile is already written
        os.Exit(exitCode)
    }
    
    go main()はgoroutineというGolangの軽量スレッド内でmain関数を呼び出すという意味になるみたいGoroutines その下の<-exitChもgoroutine関連でChannelsと呼ばれる値の送受信のための型だそうだ

-runオプション

-runオプションを使うとファイル内の特定の関数/サブテストのみ実行できる。

以下、ドキュメントから拝借した。

go test -run ''      # Run all tests.
go test -run Foo     # Run top-level tests matching "Foo", such as "TestFooBar".
go test -run Foo/A=  # For top-level tests matching "Foo", run subtests matching "A=".
go test -run /A=1    # For all top-level tests, run subtests matching "A=1".```

補足事項

補足事項として以下のものがある

  • go test ./...を使うとプロジェクト内の全てのパッケージのテストを行える
    • ただし、バージョン1.9以降から
    • それ以前のバージョンのときは go test $(go list ./... | grep -v /vendor/)と入力するといい
  • テストデータは"testdata"と名付けられたディレクトリに入れる
    • そのディレクトリはgoツールから無視される仕様になっている
  • 標準エラー出力に何かを出力しても標準出力へ出力される
    • これは仕様で、goコマンドの標準エラーはテストをビルドした時のエラー出力に使われている

TODO

What?

  • string formater %T and %q

Web

Remind

TODO

  • IndexedDB
  • ServiceWorker API Cache
  • window.navigator
  • Stream API Home page

Service Worker

参考サイト

Service Workerの紹介

Service Workerとは?

上のリンクから

Service Worker はブラウザが Web ページとは別にバックグラウンドで実行するスクリプトで、Web ページやユーザーのインタラクションを必要としない機能を Web にもたらします。 既に現在、プッシュ通知バックグラウンド同期が提供されています。 さらに将来は定期的な同期、ジオフェンシングなども導入されるでしょう。 このチュートリアルで説明する機能は、ネットワーク リクエストへの介入や処理機能と、レスポンスのキャッシュをプログラムから操作できる機能です。

プログレッシブ ウェブアプリというWebアプリの作り方に利用されている。 はじめてのプログレッシブ ウェブアプリ

前提条件

  • Service Workerを対応しているブラウザを使う
  • HTTPSが必要

Chromeが現時点で全ての機能を対応しているので、検証にはそれを使うのが良さそう。 GitHub Pagesがデモを公開するにはいい環境だそうだ。

ライフサイクル

Service Workerには以下のようなライフサイクルがある。(画像も上のサイトから引用)

インストール

はじめのインストールにはWebページ上のJavascriptを利用する必要がある。

if ('serviceWorker' in navigator) {
  window.addEventListener('load', function() {
    navigator.serviceWorker.register('/sw.js').then(function(registration) {
      // Registration was successful
      console.log('ServiceWorker registration successful with scope: ', registration.scope);
    }, function(err) {
      // registration failed :(
      console.log('ServiceWorker registration failed: ', err);
    });
  });
}

基本的な流れは以下のものになる

ただし Service Workerを登録するタイミングによっては処理落ちする可能性があるので、全ての初期化処理が終わってからインストールした方がいい。

  1. ブラウザが対応しているかチェック
  2. Service Workerとして実行するJaveScriptファイルを指定

一度インストールされるとキャッシュされるため、以降再度ページにアクセスしてもキャッシュされたものが使われる。

キャッシュされたものがある状態でnavigator.serviceWorker.register()を呼び出しても何もしないので安心。

参考サイト Service Worker 登録

ファイルの更新

何らかの作業でインストールしているService Workerファイルの内容が変わった時は自動的に更新してくれる。(ただし、ファイルサイズが変わったときだけ)

更新の際は以下の流れを取る。注意点として再度ページを開かないと新しいものが実行されない。

  1. Service Worker の JavaScript ファイルを更新します。 ユーザーがサイトに移動してきたとき、ブラウザは Service Worker を定義するスクリプト ファイルをバックグラウンドで再度ダウンロードしようとします。 現在ブラウザが保持しているファイルとダウンロードしようとするファイルにバイトの差異がある場合、それは「新しい」と認識されます。
  2. 新しい Service Worker がスタートし、install イベントが起こります。
  3. この時点では、まだ古い Service Worker が現在のページを制御しているため、新しい Service Worker は waiting 状態になります。
  4. 開かれているページが閉じると、古い Service Worker は終了し、新しい Service Worker がページを制御するようになります。
  5. 新しい Service Worker がページを制御するようになると、activate イベントが起こります。

installイベントのときにキャッシュ管理を行うと古い方の処理に影響がでてしまうため、activateイベントの際にキャッシュ管理を行うのを推奨している。

Service Workerファイルの場所とfetchイベントの関係

navigator.serviceWorker.register()でService Workerをインストールするが、その際のファイルのパスによってService Workerが受け取るfetchイベントが変わってくることに注意。

ファイルパスはドメインと関連しており、例えば/example/sw.jsにあるService WorkerはURLに/exampleが含まれているページからのfetchイベントを受け取る。

/sw.jsだと、そのドメイン全てのページからのfetchイベントを受け取るようになる。

Service Workerのコールバック

installイベント

fetchイベント

fetchイベント

有効になっているService Workerの確認

Chrome

chrome://inspect/#service-workersから確認できる。

Firefox

about:debugging#workersから確認できる。

JavaScript

ファイル書き込み時の注意点

直ちにファイルに書き込んでほしいときはfs.fsyncを呼び出すこと。

参考記事

ファイルの書き込み関数を使った後すぐに実際のファイルに保存されるわけではないので、すぐに書き込んでほしいときはfs.fsyncSyncを使う。

すぐに書き込まれないのはパフォーマンス上の都合で、これはOSレベルの話でほかの言語でも同じことである。

fs.fsyncがファイル情報も書き出し、fs.fdatasyncはデータのみをファイルに書き込む。 そのため、純粋にデータのみを書き込んでほしいときはfs.fdatasyncを使うといい。

以下、参考記事から抜粋 fs.fsyncは非同期的に書き込むので、すぐに書き込んでほしいときはfs.fsyncSync()を使うこと。

const fs = require('fs')

fs.open('./fsync_test.txt', 'w', (err, fd) => {
    fs.write(fd, 'fsync() test\n', () => {

        // ここがポイント!!
        fs.fsyncSync(fd);

        response.writeHead(200, {'Content-Type': 'text/plain'})
        response.end('Write success\n');
        fs.close(fd, ()=>{});
    })
})

TypeScript

ES6でimport/exportがJSに導入されたが、TypeScriptのimport/exportとは下の感じで対応しているそうだ。

Difference between 'export' and 'export default' in JavaScript? [duplicate]

// Three different export styles
export foo;
export default foo;
export = foo;

// The three matching import styles
import {foo} from 'blah';
import foo from 'blah';
import * as foo from 'blah';

は下のように(雰囲気)変換されるみたい。

exports.foo = foo;
exports['default'] = foo;
module.exports = foo;

var foo = require('blah').foo;
var foo = require('blah')['default'];
var foo = require('blah');

使用しているモジュール一覧

気がつくと両手で数え切れないほどモジュールを使っているのでその一覧。

  • webpack
    • sass-loader
    • node-sass
  • babel
  • vue.js
  • vuetify
  • vuetify-loader
  • @vue/test-utils
  • nuxt.js
    • connect : Node.js用のHTTPサーバーフレームワーク
    • @nuxtjs/pwa : Nuxt.js用のProgressive Web Appsモジュール
    • consola : Node.jsのログ出力を拡張したもの。便利。Nuxtをインストールするとプロジェクトに追加しなくても使える。
    • @nuxtjs/vuetify
  • eslint
  • vue-eslint-parser
  • vue-property-decorator
  • prettier
  • axios : HTTPリクエスト
  • qs : URLのクエリ文字列の変換を安全に行うパッケージ
  • ava : ユニットテストフレームワーク
    • jsdom
  • bulma : cssオンリーのCSSフレームワーク
  • cross-env : OS間の環境変数の違いを吸収してくれるモジュール
  • express : Node.js用の最小限で柔軟なWebフレームワーク
    • express-session
    • body-parser
    • whatwg-fetch
  • redirect-ssl
  • sqlite : https://github.com/kriasoft/node-sqlite
  • Sequelize
  • moment
  • pub
    • pub-plain-loader
  • uuid
  • jsonwebtoken
  • nodemailer : node.js用のメール送信モジュール
    • striptags : 文字列からhtmlタグを消してくれるもの
  • shelljs : Node.jsからシェルコマンドを呼び出せるようにしてくれるモジュール
  • sleep : Sleepをnodejsで実現してくれるモジュール。テストコード上で使用する目的で使っている

調べて使う予定のもの

  • lerna : monorepoを簡単に実現できるようにした開発ツール
  • CodeceptJS : E2Eテストツール

Nuxtプロジェクトのセットアップマニュアル

インストール

プロジェクトディレクトリの親ディレクトリから始める。

yarn create nuxt-app <project-name>
  Uses a custom server framework > express
  Choose features to install > Progressive Web App(PWA) Support, Linter / Formatter, Prettier, Axios
  Use a custom UI framework > bulma
  Use a custom test framework > ava
  Choose rendering mode > Universal
  Choose a package manager > yarn

cd <project-name>
yarn add body-parser express-session path sqlite whatwg-getch sequelize
yarn add -D jsdom nodemon redirect-ssl sass-loader vue-eslint-parser

設定

package.json

  • scripts.lint--fixオプションを追加
  • scriptstest:update-snapshots: ava --update-snapshotsを追加

nuxt

nuxt.config.jsbuild.extend(config, ctx)を以下の内容に変更

if (ctx.isDev && ctx.isClient) {
  config.mode = 'development' // 追加

  config.module.rules.push({
    enforce: 'pre',
    test: /\.(js|vue)$/,
    loader: 'eslint-loader',
    exclude: /(node_modules)/
  })
}

https

次にhttpsの設定を行う。 開発用に秘密鍵と公開鍵を作成するが、公開するときにはこれらは使わないこと

以下のコマンドでキーを作成する

openssl req -newkey rsa:2048 -nodes -keyout develop.key -x509 -days 365 -out develop.crt

nuxt.config.jsserverに以下のものを追記

// ...
  server: {
    https: {
      key: fs.readFileSync(path.resolve(__dirname, 'develop.key')),
      cert: fs.readFileSync(path.resolve(__dirname, 'develop.crt')),
    }
  },
// ...

設定しただけだとHTTPS化しないので、server/index.jsの内容を書き換える。 Node.jsのhttpsを利用する形になる。

// server/index.js
// ... 
// Listen the server
//app.listen(port, host) // <- もともとあったコード
let server = https.createServer(nuxt.options.server.https, app)
server.listen(port, host)
// ... 

ava

ava.config.js

ava.config.jsに以下の項目を追加。 テスト用のファイルの拡張子には.test.jsを付ける。

export default {
  // ...
  files: [
    "test/**/*.test.js"
  ],
  snapshotDir: "ava-snapshots",
  // ...
}

jsdomを使うことによる不具合の修正

将来的には不要になるかもしれない。

test/helpers/setup.jsの末尾に次のコードを追加

// fix a overrided Date class by jsdom
// https://github.com/vuejs/vue-test-utils/issues/936
window.Date = Date

確認

ここまで来たら、動作チェックをする。

yarn test

以下のエラーが発生した時は、jsdomの設定を見直す。

TypeError: Super expression must either be null or a function

  _inherits (node_modules/@vue/component-compiler-utils/node_modules/prettier/index.js:1957:11)
  node_modules/@vue/component-compiler-utils/node_modules/prettier/index.js:40358:5
  node_modules/@vue/component-compiler-utils/node_modules/prettier/index.js:40378:4
  createCommonjsModule (node_modules/@vue/component-compiler-utils/node_modules/prettier/index.js:193:35)
  Object.<anonymous> (node_modules/@vue/component-compiler-utils/node_modules/prettier/index.js:40350:18)
  compile (node_modules/require-extension-hooks/src/hook.js:75:10)
  hook (node_modules/require-extension-hooks/src/hook.js:32:3)
  actuallyCompile (node_modules/@vue/component-compiler-utils/dist/compileTemplate.js:100:24)
  compileTemplate (node_modules/@vue/component-compiler-utils/dist/compileTemplate.js:31:16)
  getCompiledTemplate (node_modules/require-extension-hooks-vue/src/index.js:123:20)

成功したら、test/specsを削除する。

サーバーサイドの拡張をするとき

yarn nuxt-appでexpressを使用してプロジェクトを作成するとプロジェクトディレクトリにserverというディレクトリができる。 ファイルの更新があった時自動で更新してくれるため、そのディレクトリにサーバーサイドのコードを追加していく形を取る。

ただし、server/index.jsというファイルがyarn nuxt-appによって作られているので、混乱が生じないようにコードはserverの子ディレクトリを新しく作ってそこに追加していく形を取る。

新しくサーバーサイドのファイルを作った時はnuxt.config.jsserverMiddlewareにそのファイルを追加していくこと。

// ...
  serverMiddleware: [
    "~/server/child1/index.js",
  ],
// ...

以下のサーバーサイドのファイルのテンプレートを使用する。

//~/server/child1/index.js
import express from 'express'
import utils from '../utils' // server/utils.jsへのパス

app = express()

export default {
  path: utils.makeMiddlewarePath(__dirname), // ディレクトリ構造にあったパスを設定する
  handler: app,
}

認証機能

サーバーサイド側で認証機能を作りたいには以下の設定も追加する。

  serverMiddleware: {
    bodyParser.json(),
    session({
      secret: 'super-secret-key',
      resave: false,
      saveUninitialized: false,
      cookie: { maxAge: 60000 }
    }),
  },

確認

設定が終わったら、次のコマンドが上手く動作するか確認する。

yarn dev
yarn build && yarn start

Vue.js/Nuxt.js

プロジェクト構築

vue-cliを使う

開発環境の準備

# install yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list

sudo apt-get update && sudo apt-get install yarn

# install vue
sudo yarn -g install vue-cli

プロジェクトの作成

TypeScriptを使うためtypescript-templateを使っている

# install vue and create project using typescript
sudo npm install -g vue-cli
vue init nuxt-community/typescript-template <project-name>
cd <project-name>
yarn

開発時の作成しているページの確認

yarn run dev

今回はGithub pageを作るために使っているので静的ページの作成する。以下のコマンドで作れる。

yarn run generate

注意点として、vue init nuxt-community/typescript-templateを使って作ったプロジェクトはサーバーサイドレンダリングを使うように設定されている。

静的ページを作成する時はサーバーは使わないので、Nuxt.jsのモードをspaに指定する必要がある。

その方法はnuxt.config.jsexportmode: 'spa'を追記することでできる。

//nuxt.config.js
export default {
  mode: 'spa',
  // ... 以下、他の設定
}

SASSを使う

SASSを使う時はまずnode-sasssass-loaderをインストールする

yarn add --dev node-sass sass-loader

SASSの定義にはファイルを使うかVueのプリプロセッサを使うかの二通りの方法がある

ファイルを使う方法

nuxt.config.jsファイル内でSASSファイルを指定すればいい。

export default {
  css: [
    '@/assets/css/main.sass'
  ]
}

プリプロセッサを使う方法

こちらはVueファイルに埋め込む形になる。

//直接指定できる
<style lang="sass">
.red {
  color: red;
  .inner {
    background-color: black;
  }
}
</style>
// ファイルを読み込むこともできる
<style lang="sass" src="#/styles/hoge.sass"></style>

ESLintとPrettier

インストールには以下のコマンドを使う

yarn add --save-dev babel-eslint eslint eslint-config-prettier eslint-loader eslint-plugin-vue eslint-plugin-prettier prettier

その後に.eslintrc.jsをルートディレクトリに置くことでESLintの設定ができる。

nuxt.config.jsbuildを修正することで、ホットリロードモードの時にESLintを実行することができるので、必ず有効化すること。

build: {
  //ここで webpack config を拡張できます
  extend(config, ctx) {
    // Run ESLint on save
    if (ctx.isDev && ctx.isClient) {
      config.module.rules.push({
        enforce: "pre",
        test: /\.(js|vue)$/,
        loader: "eslint-loader",
        exclude: /(node_modules)/
      })
    }
  }
}

eslint-と-prettier

ファイルパスにおいての~と@の扱い

~@はWebpackが認識するパス記法のエイリアスになっている。

エイリアスには以下のものがある。

srcDirrootDirnuxt.config.jsで設定できる。

imgタグに画像のURLを指定する時の注意点(Nuxt.js)

v-bind:srcで設定するときと、直接srcを書き込む時でパスの指定方法が異なる。

webpackが関係している問題。(が、別のうまい方法がある気がする。)

v-bind:srcのとき

staticディレクトリに画像を保存した状態で下のコードみたいに書く

<img v-bind:src="`images/${path}`">

How to link img src to a rendered list #448

直接srcを書き込む時

この場合はassetsディレクトリに画像を保存した状態で以下のように書く。

<img src="~assets/images/QR_URL_itch.png">

css プロパティ プリプロセッサを使うには?

.VueファイルでSASS/SCSSを使う

プリプロセッサの使用

.Vueファイル内でCSSを定義する時にlang属性を付けるとSASS/SCSSなど使えるようになる。

その際にはyarnなどで別個でsass-loaderとnode-sassパッケージをインストールする必要がある。

<style lang="sass">
  /* ここにSassを書きます */
</style>

プロジェクト環境の構築

yarn create nuxt-app my-project
# いろいろな設定を行う

# vue
yarn add -D vue-propetry-decorator vue-eslint-parser

# typescript
yarn add -D nuxt-ts ts-loader typescript

ESLint対応

ESLintをVueに対応させるにはvue-eslint-parserをインストールする必要がある。

vue-eslint-parser

yarn add -D vue-eslint-parser

あとは.exlintrc.jsも修正すればOK。 eslintを実行する時に.vueも対象に含めるために、--extオプションを付けるようにすること。 (Nuxt.jsではデフォルトでそうなっている)

module.exports = {
  // ...
  parser: "vue-eslint-parser",
  // ...
  extends: [
    // ...
    'eslint:recommended',
    // ...
  ],
  // ...
} 

テスト

@vue/test-utilsavaを使って行う。 どちらもnuxt-appで自動的にインストールできるが、後からインストールする時は以下のコマンドを使う。

それとjsdomが必要になるので忘れずにインストールすること。

yarn add -D @vue/test-utils ava jsdom

※2019/2/15現在 jsdomが原因によるエラーが起きてVueコンポーネントをインポートするとエラーが発生する状態にある

その際はどこかで以下のコードを追加する必要がある。 ava.config.jsのrequireプロパティに指定したテスト前に実行されるスクリプトに追記するのがお手軽。

jsdom-global in mocha example may break modules bundled by rollup

window.Date = Date

avaをテスト実行用に使用し、@vue/test-utilsでVueコンポーネントの検証に使う形になる。

AVA

AVA Configuration

avaを使ったテストは以下の形になる。

import test from 'ava';

// 現在テストしているファイル名を表示する
console.log('Test currently being run:', test.meta.file)

//基本
test('test name', t => {
  //組み込みのアサーション関数
  t.pass('successed') //テストを成功させる
  t.fail('failed') //テストに失敗させる
  t.truthy(value, 'message')
  t.falsy(value, 'message')
  t.is(value, expected, 'message')
  t.not(value, expected, 'message')
  t.deepEqual(value, expected, 'message')
  t.notDeepEqual(value, expected, 'message')
  t.regex('abcde',/[a-e]+/)
  t.notRegex('abcde', /[A-E]+/)

  //前に記録したsnapshotとの比較を行うテスト
  // snapshotについてはあとで
  t.shapshot(expected, 'message')
  t.shapshot(expected, {id: 'specify snapshot name'}, 'message')

  // 例外のテスト
  const error = t.throws(()=> {
    throw new ExpectedExceptionType('')
  }, ExpectedExceptionType, 'message')
  //例外を投げない
  t.notThrows(() => {}, 'message')
  // asyncを使った例外のテスト。個々にあるもの以外にもパラメータがいくつかある
  await t.throwsAsync(async () => {
    throw new ExpectedExceptionType('check message')
  }, {instanceOf: ExpectedExceptionType, message: 'check message'})
  //例外を投げない
  await t.notThrowsAsync(promise);

})

//promiseを使ったもの
test('used promise test', t => {
  return somePromise().then(result => {
    t.is(result, 'apple')
  })
})

//asyncを使ったもの
test('used async test', t => {
  const result = await somePromise();
  t.is(result, 'apple')
})

//AVAにはビルドインのobservablesがある。
test('used observable test', t => {
  t.plan(3);
  return Observable.of(1, 2, 3, 4, 5, 6)
    .filter(n => {
      return n % 2 == 0;
    })
    .map(() => t.pass())
})

// onlyがついたものがあるとonlyがあるものだけがテストされる
test.only('only run this test', t => {
  t.pass()
})

// スキップするテスト
test.skip('skip this test', t => {
  
})

// 同時に実行させないテストの定義
// 並列に実行されるものの前にテストされる
test.serial('pass', t => {
  t.pass()
})

//Node.jsスタイルのエラーファーストコールバック
test.cb('Node.js-style error-first callback API', t => {
  fs.readFile('data.txt', t.end)
})

各テストにはHookを付けることもできる。

// テスト予定のものを定義
test.todo('todo')
test.only.todo('todo')

//全てのテストの前/後処理
test.before(t => {})
test.after(t => {})

//各テストの前/後処理
test.beforeEach(t => {})
test.afterEach(t => {})

//afterXXXの後ろにalwaysを付けると失敗しても必ず実行される
test.after.always(t => {})
test.afterEach.always(t => {})

// serialを付けるとserialなテストのみに対しての処理になる
test.serial.before.always(t => {})
test.serial.after.always(t => {})

Snapshot

Snapshot testing

AVAではファイルにある内容とテスト中の値を比較することができる。 この機能のことをSnapshotと呼ぶ。

Jestが発祥だそうだ。

Snapshotを使うと、1回目はテスト中に渡した値を元にテストファイルと同じ名前を持った2種類のファイルを作られ、渡した値がそこに記録される。

そして2回目以降は1回目に記録した値と比較され、異なっているとテストに失敗する。

例えば、main.jsというファイルで使うなら、以下のファイルが新しく作られる。

  • snapshots/main.js.snap : 比較対象となるファイル。手で書くのではなくテスト実行中のデータを保存する
  • snapshots/main.js.md : 上のファイルの内容

比較内容を更新したい時は以下のコマンドで行える。

ava --update-snapshots

デフォルトだと生成されるディレクトリはmain.jsと同じディレクトリに作られるが、設定で生成先を変更することができる。

//ava.config.js
export default {
  // ...
  snapshotDir: "ava-snapshots",
  // ...
}

Vue Test Utils

Wrapper(Vue Test Utils)

この中のselectorについては下で説明する。

import {mount, shallowMount} from '@vue/test-utils'
import test from 'ava'
import TargetVueComponent from './' //todo ルートパスの書き方。

test('test title', t => {
  const wrapper = mount(TargetVueComponent) // Vueコンポーネントを作成。
  // 大規模なコンポーネントで子要素にアクセスしない時はshallowMountを使うと効率的
  const shallowWrapper = shallowMount(TargetVueComponent) // 子コンポーネントを作成せずに作成。

  let instance = wrapper.vm // 実体はvmの中にある
  wrapper.html() // 生のHTMLソース
  wrapper.text() // テキスト
  wrapper.props() // VueコンポーネントのProps
  wrapper.attibutes().id // Domノードの属性にアクセスする
  wrapper.attibutes('id')
  wrapper.classes() // DomノードのCSSクラスのリスト
  wrapper.classes('bar') // barを取得

  wrapper.trigger('click', { /*引数*/}) // イベント実行
  wrapper.$emit('custom-event', 123) // イベント発行

  //状態確認
  wrapper.exists() //作成に成功したか?
  wrapper.is('div') // 指定したselectorがあるか?
  wrapper.isEmpty() // 子要素がないとtrue
  wrapper.isVueInstance() // Vueインスタンスか?
  wrapper.isVisible() // v-showで非表示になっているか?

  // 検索
  let exist = wrapper.contains('div') // コンポーネントの中にdivタグがあるか?
  let div = wrapper.find('div') // コンポーネントの内のdivタグを探す
  let divList = wrapper.findAll('div') // コンポーネントの内のdivタグを探す
  t.is(wrapper.isVueInstance(), true) // Vueインスタンスかチェック

  // 値設定
  // Vue関係
  wrapper.setMethods({clickMethod: hoge()})
  wrapper.setData({hoge: 'foo'})
  wrapper.setProps({foo: 'bar'}) //VueのPropsを設定
  // form関係
  wrapper.find('input[type="radio"]').setChecked() // ラジオボタンをチェックする
  wrapper.findAll('option').at(1).setSelected() // option要素を選択する
  wrapper.find('input').setValue('set to input element') // input要素に値を設定。v-modelにバインドされている
})

Selector

'@vue/test-utils'にはSelectorがあり、関数のいくつかはそれを利用できる。

Selectorを使うと以下のものが検索できる。

  • タグ
  • CSSクラス
  • タグの属性
  • タグid
  • Vueコンポーネント
    • $refによる検索もできる

Nuxt.jsを使わずにそれっぽい環境を構築する

Nuxt.jsとTypeScriptを組み合わせていく内によくわからないことになったので、Vue.jsのみでNuxt.jsと同じ環境っぽいものを構築する方法をメモする

目標

以下の機能が使える環境を目指す

  • yarn
  • Hot Reload機能(webpack)
  • TypeScript
  • テスト(mocha-webpack)
  • コードフォーマッター(ESLint&Prettier)

参考サイト

TypeScriptを利用する時の参考資料 TypeScript Vue Starter

DevServerを立てるための参考資料 DevServer webpack-dev-server

yarn

パッケージ依存関係管理ツール。 npmにセキュリティ的な問題があったので、その代替ツールとして誕生したもの。 とても速くて安全だそうだ。

ともかく、Javascriptでプロジェクトを作るにはまずパッケージ依存関係管理ツールを使用する。

yarnを使ってvueやwebpackやら色々なパッケージをインストールしていく。

Install

インストールは以下のコマンドを使う。 Installation

curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list

sudo apt-get update && sudo apt-get install yarn

使い方

#プロジェクト作成
yarn init

yarn add <option> <package-name>
yarn remove <package-name>

yarnコマンドでプロジェクトに登録されているパッケージを全てインストールできる。 gitとかからプロジェクトを持ってきた時に使う。

yarn
# or
yarn install

yarn addでパッケージを追加する時、追加で以下のオプションが指定できる。

  • 何も付けない: 普通の依存パッケージ。プロジェクトのコードが実行しているときも使われるパッケージとして登録する。
  • -D: 開発環境時のときだけ使用するパッケージとして使用する。 デプロイや公開するものには含まれない。
  • -P: 自分でパッケージを公開する際にも必要となるパッケージとして登録する
  • -O: 必ずしも使わないパッケージとして登録する

webpack

サイトで使用するファイルを設定した形でまとめてくれるツール。

複数のJavaScriptファイルを一つにまとめたり、リソースを埋め込んだりできる。

それ以外にも開発環境として便利な機能があって、Javascriptでサイトを使うなら是非使いたいツールである。

設定

webpack.config.jsファイルをプロジェクトのルートディレクトリに置いておくと認識してくれる。

無い場合はデフォルトのものが使用される。 デフォルトの設定

また、--configオプションで直接指定できる。

webpack --config myconfig.js

使用するファイルの設定

起点となるファイルを指定して、その出力先を決める形になる。 そのままだとJavaScriptのみをまとめるだけだが、html-webpack-pluginというプラグインを使うことでWebページのトップ画面となるHTMLファイルも出力してくれる。

また、xxx-loaderなどを利用することで、CSSや画像もまとめることもできる。

上で挙げた機能を使うにはyarnを使ってインストールする必要がある。

html-webpack-plugin css-loader file-laoder webpack-manifest-plugin

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');

module.exports = {
  // 一つのファイルにまとめる際にルートとなるファイルの指定
  entry: {
    app: './src/index.js',
  },
  // 出力先の指定
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.(png|svg|jpg|gif)$/,
        use: ['file-loader'],
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)$/,
        use: ['file-loader'],
      }
    ]
  },
  plugins: [
    new ManifestPlugin(),
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
      title: 'Development'
    }),
  ],
};

webpack watch

プロジェクト内のファイルの更新があったら自動的にビルドしてくれる機能。

webpack --watchで利用できるが、webpack-dev-serverパッケージという便利なものがあるので基本的にそちらを使う。

webpack-dev-serverの設定にはdevServerを主に使う。

Guide Watch Configuration

最低限以下の設定をしないと動作しないので注意

module.exports = {
  //最低限の機能
  mode: 'development',
  watch: true,
  watchOptions: {
    aggregateTimeout: 300,
    poll: 100,
  },
  //webpack-dev-serverの設定
  devServer: {
    contentBase: './dist',
  },
  //一つのファイルにまとめた時に元のファイルの場所がわかるようにするための設定
  devtool: 'inline-source-map',
};

developmentモードでないと動作しないが、デプロイするたびに必要な設定を手作業で変更していたら大変なので、以下のコードで動的に切り変えるようにする。

//webpack.config.js
var config = {
  // 変数に設定を格納する
}

module.exports = (env, argv) => {
  switch(argv.mode) {
    case 'development':
      config.devtool = 'source-map';
      break;
    case 'production':
    break;
  }
  return config;
}

それか、こちらのページから、以下のような複数のwebpackの設定ファイルを用意する方法もある。

個人的にはこちらの方が好み。

  • 共通の設定を書いたファイル(webpack.common.js)
  • 開発時の設定ファイル(webpack.dev.js)
  • 公開時の設定ファイル(webpack.prod.js)

webpack.common.jsを他のファイルからimportして使っている形になる。 また、内部でwebpack-mergeパッケージを使っているのでインストールする必要がある。

// webpack.common.js
// 共通の設定
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  entry: {
    app: './src/index.js'
  },
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new HtmlWebpackPlugin({
      title: 'Production'
    })
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
//webpack.dev.js
//開発時の設定
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'development',
  devtool: 'inline-source-map',
  devServer: {
    contentBase: './dist'
  }
});
//webpack.prod.js
//公開時の設定
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  mode: 'production'
});

webpack HMR

watch機能を使うと変更があった時、ブラウザで毎回ページを更新し直さないとその確認ができない。

Hot Module Replacement と呼ばれる機能を使うことで、自動的に更新してくれるようになり、より楽に作業が勧められるようになる。

ただし、Javascriptコードを追加で書かないといけない。

webpack HMR Guide API

webpackの設定は下のようなものになる。

// webpack.config.js
const webpack = require('webpack');

module.exports = {
  // ...
  //webpack-dev-serverの設定
  devServer: {
    contentBase: './dist',
    hot: true
  },
  plugins: [
    // ...
    new webpack.HotModuleReplacementPlugin(),
  ],
  // ...
};

設定ができたら、Javascriptのコードを追加していく。

// ...

// Hotリロードが有効かチェック
if (module.hot) {
  // ./print.jsが更新された時の処理を書いていく。
  module.hot.accept('./print.js', function() {
    console.log('Accepting the updated printMe module!');
    //
    // 望んだ形になるように、直接DOMを書き換える処理を書いていく形になる。
    //
  })
}

CSS/SASSのMinimumize

optimize-css-assets-webpack-pluginがなぜか使えなかったので、代わりにcssnanopostcssを使う。

なので、webpackとは別工程でCSSの生成を管理することになる。

また、SASSも使うので、変換の流れは以下のようなものになる。

  1. SASS -> CSS変換
  2. nanocssでファイルサイズを削減
  3. webpackによるプロジェクトへの組み込み

webpack以外の工程は以下のコマンドで行える。

sass sass/:src/css && postcss src/css/**/*.css --replace

sassとpostcssは共にファイル更新機能があるが、Vue.jsでインラインCSSをメインに使うかもしれないので、今のところは手動でコンパイルしていく。

ダイナミックインポート

EMACScript2015からimportを式の中にかけたり、Promiseみたいに使えるようになった。

これによって実行時に必要なものだけをブラウザに読み込めたりと便利になったそうだ。

この新しく追加されたimportの機能のことをダイナミックインポートと呼ばれている。

webpack code-splitting Webpack and dynamic import

importのところにある/* webpackChunkName: "lodash" */はwebpackのマジックコメントという機能である。

module-methods magic-comments

import printMe from './print.js'

function getComponent() {
  // コメントはwebpackのマジックコメント
  return import(/* webpackChunkName: "lodash" */ 'lodash').then(({default: _}) => {
    let element = document.createElement('div');
    element.innerHTML = _.join(['Hello', 'webpack']);
    printMe();// 今までのimportも使える
    return element;
  }).catch(error => 'An error occurred while loading the component');
}

getComponent().then(component => {
  document.body.appendChild(component)
})

async化もできる。

async function getComponent() {
  let element = document.createElement('div');

  const {default: _ } = await import(/* webpackChunkName: "lodash" */ 'lodash');
  element.innerHTML = _.join(['Hello', 'webpack']);

  return element;
}

getComponent().then(component => {
  document.body.appendChild(component)
}).catch(error => 'An error occurred while loading the component');

webpackのモジュール解析ツール

生成したモジュールの構成を解析してくれるツールをwebpack側で提供してくれている。

それらのツールを使う際は次のコマンドで生成するファイルが必要になる。

webpack --profile --json > stats.json

TypeScript

TypeScriptを使う時は以下のパッケージをインストールする

yan add -D typescript ts-loader

インストールが終わったらTypeScriptの設定ファイルをプロジェクトに追加する。

{
  "compilerOptions": {
    "outDir": "./dist/",
    "sourceMap": true, // ソースマップを有効化するにはwebpack側にで設定をすること
    "noImplicitAny": true,
    "module": "es6",
    "target": "es5",
    "jsx": "react",
    "allowJs": true
  }
}

Webpackの設定ファイルも修正する。

const path = require('path');

module.exports = {
  entry: './src/index.ts',
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: 'ts-loader',
        exclude: /node_modules/
      }
    ]
  },
  resolve: {
    extensions: [ '.tsx', '.ts', '.js' ]
  },
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};

Vuetify

Vue.js用のMaterial Component Framework

公式サイト

Grid System

12分割グリッドとFlexを利用したレイアウトが提供されている。

グリッドはv-containerとv-layout、v-flexを組み合わせて表現され、v-containerを親として左から右にかけて親子関係がある。

一応必ず組み合わせなくても動作はするし、入れ子にすることもできるため親子関係が逆転しても問題なく動作する。 が、それぞれ役割があるのでうっかりそれを忘れると痛い目にあう。

  • v-container: ページ中央にセンタリングする。 fluidプロパティを渡すと横幅をフルに展開する。
  • v-layout: 子要素の並び方の制御を行う。セクションの分割に使用する。
  • v-flex: 主にセクションの横幅やオフセットを指定するために使用する。flex: 1 1 autoを自動的に設定してくれる。

個人的にはv-layoutが一番使用されると思う。

ディスプレイサイズ

Vuetifyのグリッドシステムにはディスプレイのサイズに合わせて複数の単位が存在し、Breakpointと呼ばれている。

詳しくは以下を参照。

Breakpoints Display

Breakpoints

以下のものがある。

*がついている数値はデスクトップ上で-16pxされる。

  • xs - < 600px
  • sm - 600px > < 960px
  • md - 960px > < 1264*
  • lg - 1264 > < 1904px*
  • xl - > 1904px*

vuetifyでは$vuetify.breakpointオブジェクトを提供しており、現在のディスプレイを簡単に取得できるようになっている。

{
  xs: boolean
  xsOnly: boolean
  sm: boolean
  smOnly: boolean
  smAndDown: boolean
  smAndUp: boolean
  md: boolean
  mdOnly: boolean
  mdAndDown: boolean
  mdAndUp: boolean
  lg: boolean
  lgOnly: boolean
  lgAndDown: boolean
  lgAndUp: boolean
  xl: boolean
  xlOnly: boolean
  name: ('xs' | 'sm' | 'md' | 'lg' | 'xl')
  width: number
  height: number
  thresholds: { xs: number, sm: number, md: number, lg: number }
  scrollbarWidth: number
}

非表示設定

hidden-{breakpoint}-{condition}を使うことでディスプレイのサイズに合わせて要素を非表示にすることができる。

condition一覧

  • only : 指定したbreakpointのみ非表示にする
  • and-down : 指定したbreakpoint以下のものを非表示にする
  • and-up : 指定したbreakpoint以上のものを非表示にする

overflow-hiddenまたはoverflow-{axis}-hiddenではみ出したものを非表示にできる。

axis一覧

  • x : X軸
  • y : Y軸

テキストアライメント

text-{breakpoint}-{position}の形式で指定できる。

{breakpoint}で指定したサイズ以上で適応される。

そうでなかったら、左詰めになる。

<p class="text-lg-right">Right align on large viewport sizes</p>
<p class="text-md-center">Center align on medium viewport sizes</p>
<p class="text-sm-left">Left align on small viewport sizes</p>
<p class="text-xs-center">Center align on all viewport sizes</p>
<p class="text-xs-right">Right align on all viewport sizes</p>

タイポグラフィ

VuetifyではMaterial Design用のフォントサイズを用意している。

  • .display-4 - Good for <h1>
  • .display-3 - Good for <h2>
  • .display-2 - Good for <h3>
  • .display-1 - Good for <h4>
  • .headline - Good for <h5>
  • .title - Good for <h6>
  • .subheading - Good for supporting text
  • .body-2 - Regular body text with additional weight
  • .body-1 - Regular body text
  • .caption - Smaller size text

線の細さもある

  • .font-weight-thin - Sets font-weight to 100
  • .font-weight-light - Sets font-weight to 300
  • .font-weight-regular - Sets font-weight to 400
  • .font-weight-medium - Sets font-weight to 500
  • .font-weight-bold - Sets font-weight to 700
  • .font-weight-black - Sets font-weight to 900

斜体には以下のものを使う

  • .font-italic

cssのtext-transformにも対応している。

  • .text-capitalize - Sets text-transform to capitalize
  • .text-lowercase - Sets text-transform to lowercase
  • .text-none - Sets text-transform to none
  • .text-uppercase - Sets text-transform to uppercase

折り返し指定もある。

  • .text-no-wrap - Sets whitespace to no-wra
  • .text-truncate - Truncates overflowed text

Vuetifyでは色をクラスに設定するだけで簡単に変更できるようになっている。

Colors

以下の形式を取る。

  • {color} : 背景色の指定
  • {color}--{text} : テキストカラーの指定

テキストカラーはSassのlighten/darkenに対応している。 上の色指定と合わせて指定する。

  • text--{lighten|darken|accent}-{1-4}

※lightenの数値は{1-5}の範囲になる。 ※accentは一部の色は対応していない。

<div class="red"></div>
<span class="red--text"></span>
<div class="red-- text text--lighten-5"></div>

Javascriptからも使用できる。

// src/index.js

// Libraries
import Vue from 'vue'
import Vuetify from 'vuetify'

// Helpers
import colors from 'vuetify/es5/util/colors'

Vue.use(Vuetify, {
  theme: {
    primary: colors.red.darken1, // #E53935
    secondary: colors.red.lighten4, // #FFCDD2
    accent: colors.indigo.base // #3F51B5
  }
})

色一覧

定義はこちらにある。

  • red
  • pink
  • purple
  • deep-purple
  • indigo
  • blue
  • light-blue
  • cyan
  • teal
  • green
  • light-green
  • lime
  • yellow
  • amber
  • orange
  • deep-orange

accentがないもの

  • brown
  • blue-gray
  • grey

固定

  • black
  • white
  • transparent

Browser Storage

Web Storage API を使用する

Local Storage

手軽にデータを記録することができる。

Local Storageは開発者ツールから簡単に書き換えできるので、重要なデータを記録することには使わないこと。

代わりに、IDBやSimpleDBなどのJavaScriptモジュールを使うこと。

使い方

Storageインタフェースを使う。

使い方は簡単で、記録したいデータのキーとその値を指定するだけでいい。 ただし、自動的に文字列に変換される。

一応現在使用できるか確認をしておいたほうがいい。


// set
window.localStorage.setItem('key1', 100);
window.localStorage.setItem('color', "#FF0000");

// get
let color = window.localStorage.getItem('color'); //<- "#FF0000"

for(let index=0; window.localStorage.length; ++index) {
    let value = window.localStorage.key(index); // get with index
    console.log(value);
}

window.localStorage.removeItem('color');

window.localStorage.clear();

ページで定められたプロトコルに違反するとSecurityErrorという例外を投げる。

関係するページ Same-origin policy

Session Storage

こちらもStorageインタフェースを使う。

window.sessionStorageからアクセスできる。

window.sessionStorageに保存されたデータはページのセッション終了時に消される。

Vue Router

Vue.js公式ルータ。 Vue.jsを使うならこれも使うべき。

Nuxt.jsの内部でも使われている。 Nuxt.jsで設定したい時はnuxt.config.jsのrouterプロパティを使うといい。

ドキュメント

インストール

yarn add -D vue-router

モジュールシステムを使う時はVue.use()を使って明示的にルータをインストールする必要がある。

import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

使い方

Js側で設定を行い、aタグを<router-link>に置き換える形になる。

<router-link>

router-link

aタグの上位互換。 HTML5で追加されたHistory APIを使ってくれるので、Vue.jsを使うならこちらを使ったほうがいい。

<!-- router.push()を使う -->
<router-link to="/foo">Go to Foo</router-link>

<!-- replaceを付けると履歴に残さず、URLを変更する(中でrouter.replace()を使う) -->
<router-link to="/foo" replace>Replace to Foo</router-link>

<!-- appendを付けると現在のURLに対する相対パスになる -->
<router-link to="/foo" append>Replace to XXX/Foo</router-link>

<!-- exactを付けるとパスが完全に一致したときにだけアクティブクラス化になる -->
<router-link to="/foo" exact>Go to Foo(with exact)</router-link>

<!-- 好きなタグでaタグをラップできる -->
<router-link tag="li" to="/foo">
  <a>/foo</a>
</router-link>

ただしjs側でコードを書く必要があるので、そのコードは下の方で示す。

// 公式ドキュメントから
// 0. モジュールシステムを使っている場合 (例: vue-cli 経由で)、Vue と VueRouter をインポートし、`Vue.use(VueRouter)` を呼び出します。

// 1. ルートコンポーネントを定義します
// 他のファイルからインポートすることもできます
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }

// 2. ルートをいくつか定義します
// 各ルートは 1 つのコンポーネントとマッピングされる必要があります。
// このコンポーネントは実際の `Vue.extend()`、
// またはコンポーネントオプションのオブジェクトでも構いません。
// ネストされたルートに関しては後で説明します
const routes = [
  { path: '/foo', component: Foo },
  { path: '/bar', component: Bar }
]

// 3. ルーターインスタンスを作成して、ルートオプションを渡します
// 追加のオプションをここで指定できますが、
// この例ではシンプルにしましょう
const router = new VueRouter({
  routes // `routes: routes` の短縮表記
})

// 4. root となるインスタンスを作成してマウントします
// アプリケーション全体がルーターを認知できるように、
// ルーターをインジェクトすることを忘れないでください。
const app = new Vue({
  router
}).$mount('#app')

// これで開始です!

アクティブクラス

axios

ブラウザとNode.jsでHttpリクエストをラップしてくれているモジュール。 将来的にはFetch APIに置き換わるかもしれないが、これを書いている時点でまだ完全にサポートされていないのでAxiosが好んで使われている。

axios

使い方

すごくわかりやすい。

Promise/Asyncに対応している。

const axios = require('axios')

// Promise
axios.get('example.com')
  .then(response => {
    // 成功した時
  })
  .catch(error => {
    // 失敗した時
  })
  .then(() => {
    // 成功/失敗にかかわらず最後に実行するコード
  })

//Async/Await
async function getData() {
  try {
    const reponse = await axios.get('example.com');
    console.log(reponse)
  } catch (error) {
    console.error(error)
  }
}

// パラメータ付き
axios.get('example.com/user?id=123')
axios.get('example.com/user', {param: {id: 123}})

// POSTリクエスト
axios.post('example.com/user', {userName: 'XXX', password: 'YYY'})

リクエストの設定


//axiosでは以下の関数で全てのHttpリクエストを実行できる。
// axios.get()などはこれの省略形
// configについては下を参照
axios(config)

// axios.defaultに値を設定すると、グローバル設定となり全てのリクエストに反映される。
let config = {
  //  以下の設定でURLは https://example.com/user?id=123 になる。
  method: 'get',
  url: 'user',
  baseURL: 'https://example.com',
  params: {
    param: {id: 123}
  },
  //以下、色々な設定

  // ヘッダーの追加設定
  headers: {'X-Requested-With': 'XMLHttpRequest'},
  
  // リクエストの本体部分
  data: { hoge: 'foo'},

  // タイムアウト(ミリ秒)
  timeout: 1000,

  //レスポンスの設定
  responseType: 'json',
  responseEncoding: 'utf8',

  // CSRF対策で使われるもの
  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',

  // リクエストを送る前にparamsをURLのクエリ文字列に変換する処理をカスタマイズする時に使う。
  paramsSerializer: function(params) {},

  //認証関係
  // オリジン間リソース共有 (CORS)で使われるパラメータ
  withCredentials: false, // <- default

  //HTTP Basic auth
  auth: {
    username: 'janedoe',
    password: 's00pers3cret'
  },

  //アップロード/ダウンロード実行中のイベント
  onUploadProgress: function(progressEvent) {}
  onDownLoadProgress: function(progressEvent) {}

  //Httpリクエストの内容のデータサイズの上限(byte単位)
  maxContentLength: 2000,

  // リクエストのキャンセルする時に使うキャンセルトークンの設定
  cancelToken: new CancelToken(function (cancel) {
  })

  //リクエストのレスポンスステートの検証
  validateStatus: function(status) {
    return status >= 200 && status < 300;
  },

  // その他
  maxRedirects: 5, // default
  socketPath: null, // default
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },
}

レスポンスの内容

Httpリクエストに成功した時のresponseの値は以下の要素を持つ

  • data : レスポンスデータ。ここにリクエストの結果が入っている。
  • status : HTTPステータス
  • statusText : ステータスのテキスト
  • headers : レスポンスのヘッダー情報
  • config : リクエストした時axiosに渡した設定
  • request : このレスポンスに対応しているリクエストの内容

エラーハンドリング

エラーが起きた時に引数として渡される変数を使うとリクエストかレスポンスどちらでエラーが起きたか判別できる。

下で登場しているerrorのメンバ

  • response
  • request
  • message
  • config
axios.get('/user/12345')
  .catch(function (error) {
    if (error.response) {
      //レスポンスの結果がエラーの時
      //設定の validateStatus でエラーになるHttpステータスの範囲を変更できる。
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      //レスポンスが受け取れなかった時
      //error.requestは環境に応じて以下のインスタンスを取る
      //  ブラウザ: XMLHttpRequest
      //  node.js: http.ClientRequest
      console.log(error.request);
    } else {
      //リクエストを準備している時にエラーが起きた時
      console.log('Error', error.message);
    }
    console.log(error.config);
  });

キャンセル

リクエストをキャンセルすることができる。

キャンセルする時はリクエストを送る際に設定のcancelTokenaxios.CancelToke.source.tokenを渡す必要がある。

渡した後にsource.cancel()を呼び出すことでキャンセルできる。

キャンセルした時はエラーが発生したと扱われ、キャンセルであることを見分けるにはaxios.isCancel(error)を使うといい。

const CancelToken = axios.CancelToken;
const source = CancelToken.source();

axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // handle error
  }
});

axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})

source.cancel('Operation canceled by the user.');

また以下のような方法もある。

const CancelToken = axios.CancelToken;
let cancel;

axios.get('/user/12345', {
  cancelToken: new CancelToken(function executor(c) {
    // An executor function receives a cancel function as a parameter
    cancel = c;
  })
});

// cancel the request
cancel();

複数のリクエストをまとめて送る

//複数のリクエストをまとめて送ることもできる。
// そのとき、送るリクエストは関数の戻り値にすること
function getUser() { return axios.get('url1'); }
function getPost() { return axios.get('url2'); }
axios.all([getUser, getPost])
  .then(axios.spread((user, post) => {
    // レスポンスの処理
  }))
  .catch()

割り込み処理

axios.interceptorsを使うとリクエスト/レスポンスに割り込み処理を挟むことができる。

// リクエストへの割り込み
axios.interceptors.request.use(function (config) {
    // 送る前
    return config;
  }, function (error) {
    // エラーが起きた時
    return Promise.reject(error);
  });

// レスポンスへの割り込み
axios.interceptors.response.use(function (response) {
    // レスポンスを処理する前
    return response;
  }, function (error) {
    // エラーが起きた時
    return Promise.reject(error);
  });

axios.interceptors.request.use()axios.interceptors.response.use()に空の関数を渡すことで、後から登録した関数を取り除くことができる

リクエストのインスタンス化

axios.create()を使うことでリクエストのインスタンスを作ることもできる 引数には設定を渡すことができる。

あとから設定を変える時はinstance.defaultsを使うとできるが、リクエストを投げる時にも設定を指定でき、そちらが優先される。

また、インスタンスにも割り込み処理を登録できる。(interceptors)

let instance = axios.create()
instance.defaults.timeout = 1000
instance.get({timeout: 5000}) //GETリクエスト

instance.interceptors.request.use(config => {
  //割り込み処理
})

node.js

Nodejs HTTP

Node.jsにおけるHTTPサーバーの使い方についてメモしていく

Node.jsベースのHTTPサーバーのフレームワークの裏側ではこのようなコードがあることを覚えておく。

Anatomy of an HTTP Transaction

基本

サーバーの作成にはNode.jsのhttpモジュールを使う。

requestresponseがHTTPリクエストとレスポンスに対応している。

const http = require('http')

const server = http.createServer((request, response) => {
  //リクエストの基本情報を取得
  const {method, url, headers} = request

  //リクエストの本体を解析する
  //on('data')とon('end')を組み合わせるのが定番のパターンになる
  let body = [];
  request.on('error', err => {
    console.error(err)
  }).on('data', chunk => {
    body.push(chunk)
  }).on('end', () => {
    body = Buffer.concat(body).toString()

    //レスポンスはここで行うの定番っぽい
    response.on('error', err => {
      console.error(err)
    })

    //リクエストのヘッダーを設定
    response.writeHead(200, {
      'Content-Type': 'application/json',
      'X-Powered-By': 'bacon'
    })
    // 以下のように分割しても設定できる
    response.status(200)
    response.setHeader('Content-Type', 'application/json');
    response.setHeader('X-Powered-By': 'bacon');

    //リクエスト本体を書き込む
    response.write('{Apple: 100, Orange: 200, Grope: 300}')
    response.end()
  })
})

//サーバーをHTTPリクエスト待ちにする
server.listen(port, host)

例) Echoサーバー

参考ページはEchoサーバーの例を複数出してエラー処理について説明していた。

errorイベントにコールバックを設定することでエラー処理をカスタマイズできる。

以下、最終的なコード例

const http = require('http');

http.createServer((request, response) => {
  //エラーが起きたときのイベント設定
  request.on('error', (err) => {
    console.error(err);
    response.statusCode = 400;
    response.end();
  });
  response.on('error', (err) => {
    console.error(err);
  });

  //処理本体
  if (request.method === 'POST' && request.url === '/echo') {
    //エラー処理とは無関係だが
    // requestとresponseはそれぞれnode.jsのReadableStreamとWritableStreamから派生しているのでこうゆうこともできる
    request.pipe(response);
  } else {
    response.statusCode = 404;
    response.end();
  }
}).listen(8080);

Sequelize

node.jsのためのPromiseベースのORMライブラリ。

ORMはObject-relational mappingの略語でデータベースとオブジェクト指向の言語の間の橋渡しを行うもののことである。

初期化

ローカルに建てるかURL越しからアクセスするか選択できる。

dialectオプションで使用するデータベースを指定する。指定できるデータベースは以下のもの。 指定したデータベースによって使用できるオプションやSequelizeの機能に差が出てくる。

  • mysql
  • postgres
  • sqlite
  • mssql

Sequelize

const Sequelize = require('sequelize');

const sequelize = new Sequelize('databaseName', {
  dialect: 'mysql',
  host: 'my.server.tld',
  post: 9876
})

const sequelize = new Sequelize('www.example.com:9821/db_name', {
  dialect: 'mysql', // 使用するデータベース
})

レプリケーションにも対応している。

const sequelize = new Sequelize('database', null, null, {
  dialect: 'mysql',
  port: 3306
  replication: {
    read: [
      { host: '8.8.8.8', username: 'read-username', password: 'some-password' },
      { host: '9.9.9.9', username: 'another-username', password: null }
    ],
    write: { host: '1.1.1.1', username: 'write-username', password: 'any-password' }
  },
  pool: { // If you want to override the options used for the read/write pool you can do so here
    max: 20,
    idle: 30000
  },
})

生クエリ

Sequelize.query()で生のSQL文を実行できる。

SQL文内のパラメータの置き換えを行うにはreplacementsオプションを使う。

  • ?, 配列を渡す時に使う
  • :<name>, オブジェクトを渡す時に使う。

Sequelize.query

sequelize.query('SELECT * FROM Data', options)
  .then(rows => console.log(rows))

// パラメータ付き
sequelize
  .query(
    'SELECT * FROM projects WHERE status = :status ',
    { raw: true, replacements: { status: 'active' } }
  )
  .then(projects => {
    console.log(projects)
  })

sequelize
  .query('SELECT 1', {
    // A function (or false) for logging your queries
    // Will get called for every SQL query that gets send
    // to the server.
    logging: console.log,

    // If plain is true, then sequelize will only return the first
    // record of the result set. In case of false it will all records.
    plain: false,

    // Set this to true if you don't have a model definition for your query.
    raw: false,

    // The type of query you are executing. The query type affects how results are formatted before they are passed back.
    type: Sequelize.QueryTypes.SELECT
  })

モデル

ORMなので、テーブルの内容をモデルとしてマッピングできる。

マッピングする内容はキーをカラム名、値をデータベースの型として持つオブジェクトを渡すことで指定できる。

定義オプション 指定できるデータベースの型はこちら

モデルを使ったデータの検索、作成などはこちらを参照

使用方法 Query

const Users = sequelize.define('users', {
  firstName: Sequelize.STRING,
  lastName: Sequelize.STRING,
  description: Sequelize.TEXT
})

Users.sync() //テーブルの同期。なければ作成する
Users.drop() //テーブル削除

// 作成
//テーブルに追加せずインスタンス作成
let instanceNonPersistent = Users.build({firstName: 'hoge', lastName: 'foo', description: 'bar rab'})
//こちらはテーブルに追加する
let instance = Users.create({firstName: 'hoge', lastName: 'foo', description: 'bar rab'})

//検索
let instance2 = Users.findById(10)

//データ更新
instance.update({description: 'foooooof'}).then(() => {})

instance2.decription = 'hogehogehoge'
instance2.save().then(() => {})

// 現在のデータベースの内容と同期
instance.reload()

instance2.destroy()

//複数個に対する処理
Users.bulkCreate()
Users.update([{}, ...])
Users.destroy([user, user2,...])

//イベントハンドリング
Users.[sync|drop]().then(() => {
  // OK
}),catch(error => {console.error(error)})

//クラスのようにメソッドを追加できる
Users.classLevelMethod = function() {
  return 'foo'
}
// インスタンス単位
Users.prototype.instanceLevelMethod = function() { return 'bar'}

他のファイルに定義されたモデルを読み込むこともできる。

例えば以下のような内容のproject.jsがある場合

module.exports = (sequelize, DataTypes) => {
  return sequelize.define("project", {
    name: DataTypes.STRING,
    description: DataTypes.TEXT
  })
}

sequelize.import()を使うことでそのモデルを使うことができる。

const Project = sequelize.import('project.js')

アクセサの定義

アクセサも定義できる。

カラムにアクセスする時は以下の関数を使うこと

  • this.getDataValue('name')
  • this.setDataValue('name', value)

定義の方法

  • カラム定義のオプションとして定義
    const Users = sequelize.define('users', {
      firstName: {
        type: Sequelize.STRING,
        allowNull: false,
        get() {
          const firstName = this.getDataValue('firstName')
          return 'firstName is ' + firstName
        },
        set(val) {
          this.setDataValue('firstName', val.toUpperCase())
        }
      },
      lastName: Sequelize.STRING,
      description: Sequelize.TEXT
    })
    
  • sequelize.define()のオプションとして定義
    const Users = sequelize.define('users', {
      firstName: Sequelize.STRING,
      lastName: Sequelize.STRING,
      description: Sequelize.TEXT
    }, {
      getterMethods: {
        fullName() { return this.firstName + ' ' + this.lastName }
      },
      setterMethods: {
        fullName(value ) {
          const names = value.split(' ')
    
          this.setDataValue('firstName', names.slice(0, -1).join(' '))
          this.setDataValue('lastName', names.slice(-1).join(' '))
        }
      }
    })
    

バリデーション

各カラムにはバリデーションを指定することができる。

アクセサと同じくカラム単位またはsequelize.define()のオプションとして定義できる。

const ValidateMe = sequelize.define('foo', {
  email: {
    type: Sequelize.STRING,
    validate: { isEmail: true }
  }
}, {
  //モデル全体のバリデーション
  validate: {
    checkEntity() {
      if (! (email typeof string)) {
        throw new Error('!!Occur error!!')
      }
    }
  }
});

使用できるバリデータ一覧

validator.jsを内部で利用しているので、使用できるバリデータを確認する時はそちらも参照すること。

const ValidateMe = sequelize.define('foo', {
  foo: {
    type: Sequelize.STRING,
    validate: {
      is: ["^[a-z]+$",'i'],     // will only allow letters
      is: /^[a-z]+$/i,          // same as the previous example using real RegExp
      not: ["[a-z]",'i'],       // will not allow letters
      isEmail: true,            // checks for email format (foo@bar.com)
      isUrl: true,              // checks for url format (http://foo.com)
      isIP: true,               // checks for IPv4 (129.89.23.1) or IPv6 format
      isIPv4: true,             // checks for IPv4 (129.89.23.1)
      isIPv6: true,             // checks for IPv6 format
      isAlpha: true,            // will only allow letters
      isAlphanumeric: true,     // will only allow alphanumeric characters, so "_abc" will fail
      isNumeric: true,          // will only allow numbers
      isInt: true,              // checks for valid integers
      isFloat: true,            // checks for valid floating point numbers
      isDecimal: true,          // checks for any numbers
      isLowercase: true,        // checks for lowercase
      isUppercase: true,        // checks for uppercase
      notNull: true,            // won't allow null
      isNull: true,             // only allows null
      notEmpty: true,           // don't allow empty strings
      equals: 'specific value', // only allow a specific value
      contains: 'foo',          // force specific substrings
      notIn: [['foo', 'bar']],  // check the value is not one of these
      isIn: [['foo', 'bar']],   // check the value is one of these
      notContains: 'bar',       // don't allow specific substrings
      len: [2,10],              // only allow values with length between 2 and 10
      isUUID: 4,                // only allow uuids
      isDate: true,             // only allow date strings
      isAfter: "2011-11-05",    // only allow date strings after a specific date
      isBefore: "2011-11-05",   // only allow date strings before a specific date
      max: 23,                  // only allow values <= 23
      min: 23,                  // only allow values >= 23
      isCreditCard: true,       // check for valid credit card numbers

      // custom validations are also possible:
      isEven(value) {
        if (parseInt(value) % 2 != 0) {
          throw new Error('Only even values are allowed!')
          // we also are in the model's context here, so this.otherField
          // would get the value of otherField if it existed
        }
      }
    }
  }
});

自動追加されるカラム

sequelize.define()を使いテーブルを定義するとデフォルトでID(id,主キー)と作成日時(createdAt)、更新日時(updatedAt)が追加される。

タイムスタンプ

作成日時と更新日時はtimestampsオプションにfalseを設定すると自動的には追加はされない。 またcreatedAtupdateAtオプションにfalseを指定することでも追加されない。

ただし作成日時と更新日時はマイグレーションを使用する時使われていることに注意。

paranoidオプションをtureにすることで削除日時(deletedAt)を追加することもできる。 これがあるとデータを削除した時、その日時が記録されデータベース上からは削除されなくなる。

ここで挙げたタイムスタンプで使われるカラムはオプションから作成される名前を変更することができる。 またtimestampsオプションがfalseなら、これらの機能は無効化される。

  • createdAtオプション, 文字列を与えると作成日時のカラム名を変更できる
  • updateAtオプション, 文字列を与えると更新日時のカラム名を変更できる
  • paranoidオプション, trueなら削除日時(deletedAt)カラムを追加する。
  • deletedAtオプション, 文字列を与えると削除日時のカラム名を変更できる
const Foo = sequelize.define('foo',  { /* bla */ }, {
  // don't forget to enable timestamps!
  timestamps: true,

  // I don't want createdAt
  createdAt: false,

  // I want updatedAt to actually be called updateTimestamp
  updatedAt: 'updateTimestamp',

  // And deletedAt to be called destroyTime (remember to enable paranoid for this to work)
  deletedAt: 'destroyTime',
  paranoid: true
})

モデル間関係

Migrations

データべースのマイグレーションを行うにはsequelize-cliを使用する。

マイグレーションを利用することでデータベースの変更を記録することができるので、データベースの修正はマイグレーションを通じて行うことを推奨する。

yarn add -D sequelize-cli
# 空プロジェクトを作成する
node_modules/.bin/sequelize init

空プロジェクトを作成したら、以下のフォルダーといくつかのファイルが作成される。

  • config, 設定関係
  • models, モデル関係
  • migrations, マイグレーション関係
  • seeders, シードファイル関係

プロジェクトのルートディレクトリに.sequelizercファイルを作ることで上のファイルの位置を変更できる。

例えば以下のようなファイル配置にしたい時は

  • config -> config/database.json
  • models -> db/models
  • migrations -> db/seeders
  • seeders -> db/migrations

.sequelizercにこのように書く

const path = require('path')

module.exports = {
  'config': path.resolve('config', 'database.json'),
  'models-path': path.resolve('db', 'models'),
  'seeders-path': path.resolve('db', 'seeders'),
  'migrations-path': path.resolve('db', 'migrations')
}

設定

データベースにアクセスするために設定ファイルが必要になる。

設定ファイルはjsonjsファイルで書くことができる。 jsファイルだと動的に設定を変えられるので、基本こちらを使うと思う。

設定ファイルの内容はSequelizeインスタンスに渡すパラメータと同じである。

また、設定ファイル内のパスのルートはプロジェクトルートディレクトリになる。

module.exports = {
  development: {
    username: "root",
    password: null,
    database: "database_development",
    host: "127.0.0.1",
    dialect: "sqlite"
  },
  test: {
    username: "root",
    password: null,
    database: "database_test",
    host: "127.0.0.1",
    dialect: "sqlite"
  },
  production: {
    username: "root",
    password: null,
    database: "database_test",
    host: "127.0.0.1",
    dialect: "sqlite"
  }
}

モデル作成と修正

model:generateでモデルを作成できる。

テーブルの名前とその要素は--name--attributesオプションで指定できる。 --attributesオプションで指定できる型はSequelizeが用意している型と同じである。

DataType

node_modules/.bin/sequelize model:generate \
  --name User --attributes firstName:string,lastName:string,email:string

モデルの修正

一度作ったモデルの更新を行うには以下のコマンドでマイグレートファイルを作成し、その中に更新内容を書く必要がある。

<filename>には行う変更がわかるものにしたほうがいいと思う。

node_modules/.bin/sequelize migration:generate --name <filename>

作成したファイルにモデルの変更内容を書くにはQueryInterfaceを利用する。

ただ、各々の関数の戻り値はPromiseになるようになればいい。

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.addColumn('users', "id", Sequelize.INTEGER);
  },
  down: (queryInterface, Sequelize) => {
    return queryInterface. removeColumn('users', 'id')
  }
};

マイグレーションの実行

モデルを作っただけではデータベースに反映されないので、db:migrateを実行する。

もし、以前のマイグレーションの状態に戻したくなったら、db:migrate:undoを使用する。

# 最新の状態にする
node_modules/.bin/sequelize db:migrate

# 一つ前の状態に戻る
node_modules/.bin/sequelize db:migrate:undo

# 初期状態に戻る
node_modules/.bin/sequelize db:migrate:undo:all

シード

シードを使うとデータベースにシードに記述したデータを挿入することができる。

利用するにはまずシードファイルを作成し、内容を記述していく。

その後、db:seedオプションでシード内容を元にデータベースを更新する。 ただし2回以上実行すると、その回数分だけデータが追加されるので注意すること

もし取り消したかったら、db:seed:undoオプションを使うといい。

# シードファイルを作成
node_modules/.bin/sequelize seed:generate --name demo-user
# シード適応
node_modules/.bin/sequelize db:seed --seed <seed-path>...
node_modules/.bin/sequelize db:seed:all
# シード適応を無効にする
node_modules/.bin/sequelize db:seed:undo --seed <seed-path>...
node_modules/.bin/sequelize db:seed:undo:all

シードファイルの内容は以下のようなものになる。

こちらも関数の戻り地はPromiseになるようにし、QueryInterfaceを利用できる。

'use strict';

module.exports = {
  up: (queryInterface, Sequelize) => {
    return queryInterface.bulkInsert('Users',[
      {firstName: "Tom", lastName: "Sum", email: "a@bs.com", createdAt: new Date(), updatedAt: new Date()},
      {firstName: "Sum", lastName: "Tom", email: "a@bs.com", createdAt: new Date(), updatedAt: new Date()}
    ])
  },

  down: (queryInterface, Sequelize) => {
    return queryInterface.bulkDelete('Users', null, {});
  }
};

OpenLayers

便利サイト

  • geojson.io 位置情報の編集に
  • epsg.io 好きな空間参照系の変換のときにproj4.jsと組み合わせて使う

マーカーの見た目を変える方法

import Map from 'ol/Map.js';
import View from 'ol/View.js';
import Tile from 'ol/source/Tile.js'
import OSM from 'ol/source/OSM.js'
import proj from 'ol/proj.js'

let feature = new Feature({
  geometry: new Point()
})
let map = new ol.Map({
  target: 'map',
  layers: [
    new Tile({
        source: new OSM()
    })
  ],
  view: new View({
      center: proj.fromLonLat([139.745433,35.658581]),
      zoom: 16
  })
})

GeoJSONからデータを読み込んで指定した空間参照系に変換するコード

import format from 'ol/format'

const features = (new format.GeoJSON()).readFeatures(loadGeoJSONFileForTest())
for (const f of features) {
  const geom = f.getGeometry()
  if (!geom) continue
  geom.transform(/* from */ 'EPSG:4326', /* to */'EPSG:3857')
}

ちなみにある座標値を変換したいときはol.proj.transform(coordinate, from, to)を使うといい

import proj from 'ol/proj'
const coordinate = [123.123, 123.123]
proj.transform(coordinate, /* from */ 'EPSG:4326', /* to */'EPSG:3857')

ドラッグアンドドロップで座標データを読み込ませたいときのコード

import DragAndDrop from 'ol/interaction/DragAndDrop'
import source from 'ol/source'
import layer from 'ol/layer'
import format from 'ol/format'

function initDragAndDrop(map) {
  const source = new source.Vector()
  const layer = new layer.Vector({
    source: source
  })
  map.addLayer(layer)
  map.addInteraction(new DragAndDrop({
    source: source,
    formatConstructors: [format.GeoJSON]
  }))
}

Webpack

画像などリソースファイルが複数出力されるときの対処方法

module.rulesの中に同じ拡張子を含む設定が複数ないか確認する。

症状としては、一つのファイルに対して複数のファイルが出力され、片方は一方のファイルをエクスポートするJSファイルになっているかもしれない。

CSS

Fontawesomeアイコンのサイズを変更してもアイコンが中央に来るようにするためのパラメータ。

Fontawesomeアイコン専用というわけではなく、なんにでも使える方法

参考サイト

親要素 {
  position: relative;
}

Fontawesomeを指定したi {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translateX(-50%) translateY(-50%);
}

Docker

操作早見表

  • インストール docker pull mongo
  • インストールされているイメージの確認 docker image list
  • 公開されているイメージの検索 docker search <keyward>
  • 実行 docker run -name <image-name> mongo:<tag> -p <hostOS port>:<conatiner port>
  • デーモンとして実行 docker run -name <image-name> -d mongo:<tag>
  • 実行中のコンテナ確認 docker ps
  • 実行中のコンテナ確認 docker stats or docker stats --no-stream
  • コンテナの停止 docker stop <container-name>
  • コンテナの再開 docker restart <container-name>
  • 停止中のコンテナを取り除く docker remove <container-name>
  • 実行中のコンテナ削除 docker kill <container-name>
  • コンテナの設定の更新 docker update <container-name>
  • 指定したコンテナにコマンド実行する docker exec <container-name> <command> <args...>
  • 指定したコンテナのログを確認する docker logs <container-name>
  • 指定したコンテナで起動しているプロセスを表示する docker top <container-name>

全てのコンテナ/イメージを削除する

docker rm $(docker ps -a -q)

Delete all images

docker rmi $(docker images -q)

Dockerfileからイメージを作成

次のコマンドでカレントディレクトリにあるDockerfileからイメージを作成する。

docker build -t <repository-name>:<tag> .

Data Volume

通常コンテナを削除するとその中のデータも一緒に消されるが、データボリュームとして保存したものは削除されない。

Manage data in Docker

データボリュームには二通りの方法がある。

  • Volume Docker Areaという場所に保存する(Linuxだと/var/lib/docker/volumes)
  • Bind mounts host OS上の好きなディレクトリに保存する

Linuxではtmpfsというメモリ上に保存する方法もある。

Volumes

ボリュームはdocker volume createで明示的に作成するかコンテナ作成時に一緒に作成できる。

ボリュームはHost OS上に格納され、コンテナの中にボリュームがマウントされたときはボリュームがあるディレクトリもコンテナへマウントされる。 これは実際にマウントと似ている。

マウントされたボリュームはDockerによって管理され、Host OSから離れる。

ボリュームは複数のコンテナへ同時にマウントされることも出来る。

docker volume prune .で使用していないボリュームを取り除くことも出来る。

ボリュームをマウントするとき名前をつけるか無名のままでいくか選択できる。 無名ボリュームにはDockerは他とかぶらないランダムな名前をつけてくれる。

ボリュームもボリュームドライバーの使用をサポートしている。

例)

  • docker volume create <volume-name> 作成
  • docker volume ls リストアップ
  • docker volume inspect <volume-name> 詳しい情報の表示
  • docker volume rm <volume-name> 削除

docker inspect <container-name>で表示されるMounts項目でも使用しているボリュームを確認できる。

使い方

-v,--volume--mountの二種類のオプションがある。

--mountはDocker 17.06からは追加されたもので単一のコンテナにボリュームを指定できる。

両者に機能的な違いはないので、新しいユーザーには--mountのほうが単純なので使うことを勧めている。

・使い方

  • --mount ,区切りの複数のKey/Valueから成る。KeyとValueの間は=を入れる。 指定できるKeyには以下のものがある。

    1. type マウントのタイプ。bindvolumetmpfsを指定できる。
    2. source|src マウントのソース。 ボリューム名を指定する。無名ボリュームの場合は省略できる。
    3. destination|dst|target コンテナ内でのマウント先となるファイル/ディレクトリパス
    4. readonly 任意のオプション。読み取り専用としてBind mountsを設定する。
    5. volume-opt 任意のオプション。複数あってもいい。Key/Valueのペアの形を取る。 例) docker run --mount 'type=volume,src=<volume-name>,dst=<container-path>,volume-driver=local'
  • -v :区切った3つのフィールドから成る。

    1. ボリューム名。無名ボリュームの場合は省略できる。
    2. マウント先となるコンテナ内のパス
    3. 任意。,区切りのオプション。 例)docker run -v <volume-name>:<container-path>
  • docker run -v <volume-name>:<> ... ボリュームを指定してコンテナを実行

  • docker run -v <volume-name or id> --volume-driver <driver-name> ボリュームのドライバーも指定してコンテナを実行。が、--mountを使うことを推奨してる。

  • docker run --volumes-from 他のコンテナが使っているボリュームを使用してコンテナを実行

Bind mounts

昔からある方法。

Bind mountを使うとHost OSのディレクトリやファイルはコンテナにマウントされる。 マウントされたファイルなどはHost OS上のフルパスによって参照される。

これらのファイルはDockerホストには存在してなくてもよく、実行中に作成することも出来る。

Bind mountはよいパフォーマンスを持つが、可能なディレクトリ構造はHost OSに依存してしまう。

新しいDockerアプリを作るときは名前付きのボリュームを使ってほしい。

Bind mountはDocker CLIコマンドで直接管理することはできない。

Dockerfile 命令一覧

COPY

COPY

COPY [--chown=:] ... COPY [--chown=:] ["",... ""]

からへファイル/ディレクトリをコピーする

: コピーするファイル。 : 出力先のディレクトリ。WORKDIRからの相対パスまたは絶対パス

WORKDIR

WORKDIR

WORKDIR /path/to/workdir

RUNやCMD、ENTRYPOINT、COPY、ADD等の作業ディレクトリを指定する

CMD

CMD

CMD ["executable","param1","param2"] (exec form, this is the preferred form) CMD ["param1","param2"] (as default parameters to ENTRYPOINT) CMD command param1 param2 (shell form)

ERROR: error while removing network: network <network-name> id <network-id> has active endpointsと出た時の対処法

何らかの理由で<network-name>がDockerコンテナと接続していることが原因。

docker network inspect <network-name>

で<network-name>の接続情報を表示し、接続しているコンテナを探す。

情報はJSON形式で表示され、Containersキーに接続中のコンテナが記述されている。

確認できたらdocker stop <container-name>で接続中のコンテナを停止させ、再度<network-name>を停止させると無事目的を達成できる。

docker network inspect <network-name>
docker stop <container-name>
docker network rm <network-name>

起動しているDockerコンテナ環境を確認したい時

docker exec -it <container-id> bash

CUIに慣れておくと作業がやりやすいと思う。

SSH

permission denied (publickey). と出たときの対処法

sshキーの管理をしているssh-agentが使おうとしているキーの場所がわからないためエラーが出ているみたい。 なので、使うキーの場所を教えてあげると解決する。

以下、Githubから引用

eval "$(ssh-agent -s)"
ssh-add <ssh-path>

また余談になるが、ssh-agentのmanドキュメントによれば、sshコマンドでも同じことができるそうだ。

sshを実行する際にオプションにAddKeysToAgentを指定してあげるといいみたい。

ssh -o AddKeysToAgent=yes

いちいちオプションに指定するのが面倒な場合は設定ファイルに指定するといい。

以下はsshが使用する設定の優先順位。

  1. コマンドライン引数 ssh -o <options>
  2. ユーザー設定 ~/.ssh/config
  3. PC全体の設定 /etc/ssh/ssh_config

AddKeysToAgentについてはman ssh_configに書かれていた。

ssh-agentが実行されている限り、設定したキーとそのパスワードは有効になる的なことが書かれている。

man ssh_configから引用

AddKeysToAgent

Specifies whether keys should be automatically added to a running ssh-agent(1). If this option is set to yes and a key is loaded from a file, the key and its passphrase are added to the agent with the default lifetime, as if by ssh-add(1). If this option is set to ask, ssh(1) will require confirmation using the SSH_ASKPASS program before adding a key (see ssh-add(1) for details). If this option is set to confirm, each use of the key must be confirmed, as if the -c option was specified to ssh-add(1). If this option is set to no, no keys are added to the agent. The argument must be yes, confirm, ask, or no (the default).