20191221のvue.jsに関する記事は13件です。

【Vue.js】ローディング画面の実装方法(サンプルコード付き)

はじめに

Vue.jsでローディング画面を実装する方法をサンプルコード付きでまとめました。

今回ローディングのモジュールはvue-loading-templateを使っています。

image.png
デモはこちらから確認出来ます。

環境

OS: macOS Catalina 10.15.1
Vue: 2.6.10
vue-cli: 4.1.1
vue-router: 2.6.10
vuex: 3.1.2
vuetify: 2.1.0

vue-loading-templateのインストール

$ yarn add vue-loading-template

1.src/App.vue

App.vue
<template>
  <v-app>
    <Loading v-show="loading"></Loading>
    <Home v-show="!loading"></Home>
  </v-app>
</template>

<script>

import Loading from '@/components/Loading'
import Home from './views/Home'

export default {
  name: 'App',
  data() {
    return {
      loading: true,
    }
  },
  mounted() {
    setTimeout(() => {
      this.loading = false;
    }, 1000);
  },
  components: {
    Loading,
    Home,
  },
};
</script>

前提

  • Vuetifyを使用しているので、<v-app>でくくられている

  • Loadingローディング画面コンポーネント

  • Homeアプリケーション本体をまとめているコンポーネント

ローディング画面の切り替え方法

  • v-showを使用し、変数loadingtruefalseかでローディング画面の表示・非表示を行う

  • 切り替えはmountedを使用

  • mountedはDOMの作成が完了した段階で発動するので、
    DOMの作成が完了した段階でloadingfalseになり、ローディング画面が非表示になる

2.src/components/Loading.vue

こちらがローディング画面のコンポーネントです。

Loading.vue
<template>
  <div v-show="loading">
    <div class="fullview">
      <div class="loading-spacer"></div>
      <vue-loading 
        type="spiningDubbles"
        color="#aaa"
        :size="{ width: '100px', height: '100px' }"
        >
      </vue-loading>
    </div>
  </div>
</template>

<script>
import { VueLoading } from 'vue-loading-template'

export default {
  name: 'loading',
  components: {
    VueLoading,
  },

}
</script>

<style>
.fullview {
  width: 100%;
  height: 100%;
  background: #fefefe;
  position: fixed;
  top: 0;
  left: 0;
}
.loading-spacer {
  height: 30%;
}
</style>
  • VueLoadingをインポート

  • <vue-loading>内で各種オプションを指定し、ローディングアニメーションの種類・色や大きさを指定

  • <div class="loading-spacer">はローディングアニメーションを画面上部から任意の位置に持ってくるために利用

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

ローディングアニメーションはspiningDubblesが好きです:v:

参考にさせて頂いたサイト(いつもありがとうございます)

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

v-onでイベント情報を取得する ❏Vue.js❏

今回はある場所にマウスが乗ったら、そこのイベント情報を取得してX軸とY軸を表示させます。

Screenshot from Gyazo



開発環境はJSFiddle
https://qiita.com/ITmanbow/items/9ae48d37aa5b847f1b3b

html
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

<div id="app">
  <p @mousemove="changeMousePosition">マウスを乗せる</p>
  <p>x:{{ x }}</p>
  <p>y:{{ y }}</p>
</div>
javascript
new Vue({
  el: "#app",
  data: {
    x:0,
    y:0
  },
  methods: {
    changeMousePosition(event) {
      this.x = event.clientX;
      this.y = event.clientY;
    }
  }
})

v-onは@に省略
mousemoveイベントにchangeMousePositionメソッド
メソッドの引数にeventを取るとイベントの情報が取得できます。

console.log(event)イベント情報を確認してみると...
798bf01fc6466b9f3b5da03bac08044d.png
この下にもズラーッと情報がありますが、今回はclientXclientYを使いました。



ではまた!

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

WebStorm など JetBrains IntelliJ系エディタで、vueファイルの<script>タグ内の1段インデントをさせない (ESLint, TSLint 対応)

WebStorm で Vue.js の単一ファイルコンポーネントを書いているとき、デフォルトだと Command + Option + L の Reformat Code 時、

<script>
  import ....
  export default {
    ...

このように、<script> タグ内で1段階インデントしてしまいます。

こうなっていると、ESLint でも TSLint でも警告が出ます。

ESLint: Expected indentation of 0 spaces but found 2.(indent)

torico-corp-nuxt____workspace_torico_torico-corp-nuxt__-_____layouts_default_vue__torico-corp-nuxt_.png

インデントさせないようにするには、

Command+,(Preference) → Editor / Code Style / HTML の
Do not indent children of: に、script を追加します。

Preferences.png

Preferences.png

これで、インデントは自動追加されず、すでにインデントされているものは Reformat Code時にアンインデントされるようになります。

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

Vue + jestでmarkdownのファイルをインポートする方法

Vueでjestを使ってmarkdownをimportした際の unexpected tokenのエラーに遭遇したのでその回避方法です。

$npm run unit
test/unit/specs/Markdown.spec.js
● Test suite failed to run
    Jest encountered an unexpected token
Markdown.vue
...
<script>
import marked from 'marked'
import md from '../assets/sample.md' //importでエラーが発生

export default {
  name: 'Markdown',
  computed: {
    compiledMarkdownText: function () {
      return marked(md.source)
    }
  }
}
</script>

原因は、markdownのファイルをimportした際に変換がかかりエラーになっています。
そこで、markdownの場合は変換の対象から除外し、モックファイルを読み込むようにjestのmoduleNameMapperに設定を追加します。
https://jestjs.io/docs/ja/configuration#modulenamemapper-objectstring-string
https://jestjs.io/docs/ja/webpack#静的アセットの管理

jest.config.js
  moduleNameMapper: {
    '\\.(md)$': '<rootDir>/test/mocks/file_mock.js'
  },
file_mock.js
module.exports = {
    file: 'file_mock.js',
    source: 'stub' //ファイルのテキストをsourceに記載
}

テストコードでもモックファイルを参照する場合は
file_mock.jsをimportします。

Markdown.spec.js
import { mount } from '@vue/test-utils'
import Markdown from '@/components/Markdown'
import md from '../../mocks/file_mock.js'

describe('Markdown.vue', () => {
  it('markdown should be html', () => {
    const wrapper = mount(Markdown)
    expect(wrapper.vm.compiledMarkdownText).toBe('<p>stub</p>\n') 
  })
})

markdownだけでなく他のアッセトファイルを参照する場合もmoduleNameMapperに拡張子を追加することで対処できます。

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

【webpack】npm run dev でbuild.jsが読み込めない

症状

Javascript + Vue.js + Webpack で開発しているプログラムで、npm run devが急に動かなくなった!!大変だー!!

「急に」という場合は大体急ではなく原因があるのですが、今回はいまだに原因が見つかっていません。

以下詳しい症状です。
1. 昨日までは動いていた
2. npm run dev 自体にエラーは出ない
3. npm run build は成功する
4. npm run dev を Ctrl + C でkillしてもポートが解放されない(killできていない)
5. localhost:8080などに置いてあるビルド後のファイル(build.jsなど)が読み込めない
6. Activity monitorでnodeのCPU占領率がすごいことになる。

と以上のような症状が出た人向けの記事です。

環境

  • OS: Mac OS Mojave
  • webpack: 4
  • node: v13.5.0
  • vue-cli: 2

お分かりの方もいると思いますが、vue-cliが2系だった頃のプロジェクトで、webpackやnodeを必要に駆られてアップグレードしています。
ここが問題で、@vue/cli(3系)で作ったプロジェクトではこうしたエラーは確認していません。

やったこと

パッケージのバージョン統一

こちらを参考にwebpack, webpack-cli, webpack-dev-serverのバージョンの組み合わせを適切なものにしましたが、失敗

以前のgitのバージョンをrevert

最悪これでいけるだろうという手段。
失敗
nodeのバージョンも変えていないのになぜ。。?

解決策

新たにgithubからcloneしてきてnpm run devしたらいけました。
違いはなんでしょうか。。

webpackやnodeのバージョンも同じなので、キャッシュがいたずらしている?
→ 削除しましたがダメなようです。

何かご存知の方いましたらコメントお願いします。

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

5分で分かるVue.jsの基礎

参考

Vue Mastery
The Net Ninja Vue JS 2 Tutorial

Vue.js とは

Vue.jsとはユーザーインターフェースを構築するためのフレームワークアプリケーションの基本的な機能やルールを提供する骨組み)で、主にSPA(シングルページアプリケーション)を構築する時に向いています。「学習コストの低さ」「スケールの柔軟性」「公式ドキュメントの充実」が魅力 :chicken::fire:

SPA(シングルページアプリケーション)とは

JavaScriptでDOMを操作しページを切り替える単一ページで構成されるWebアプリケーション

DOM / 仮装DOM とは

DOM : Document Object Modelの略で、JavaScriptからHTMLやCSSのデータを取得、操作するための仕組み。htmlドキュメントをツリー構造として表現したもの。

index.html
<html>
  <head>
    <title>Vue.js</title>
  </head>
  <body>
    <h1>DOMとは</h1>
  </body>
</html>

上記のマークアップは下記のような「DOMツリー」として表現されます。

index.html
html
├── head
│   └──title
│       └──Vue.js-lesson2
└── body
    └── h1
        ├──DOMとは
        └──h1タグのコメント

仮装DOM : 仮想DOMはバインディングしたデータを元に作られ、仮想DOMを元にDOMを作成する。
仮想DOMを利用する目的の1つとして、描画処理の性能を向上させる狙いがあり、実際のDOMでの描画処理では、仮想DOMの差分検出アルゴリズム描画処理前の仮想Nodeツリーから更新対象を抽出する。
描画対象となるDOMに対して、Nodeの新規追加・更新・削除といったDOM操作を行う。

Vueアプリケーションの作成

アプリケーション作成のために、コンストラクタ関数vueを使ってルートとなるvueインスタンスを作成する。vue インスタンスはアプリケーションの心臓部分

main.js
var app = new Vue ({
  // オプション
})

※ 慣例的にappvmなどの変数名が使用される。

オプションの構成

main.js
var app = new Vue ({
  el: '#app',
  data: {
    message: 'Vue.js'
  },
  computed: {
  },
  method: {
  },
  created:  function(){
  }
})

index.html
<div id='app'></div> //ここが対象要素となる
  • elオプション : マウントするDOM要素(アプリケーションを紐付ける要素のセレクタ)
  • data: アプリケーションで使用するデータです。オブジェクトや配列も登録できる。ここで定義されるプロパティはリアクティブなプロパティへと変換される。
main.js
var app = new Vue ({
  el: '#app',
  data: {
    message: 'Vue.js' //変化を検知できるようになる
  }
})

リアクティブなプロパティ / データとは?

DOMの同期を自動で行う「データバインディング」を行うには、
テンプレートで使用するデータは「リアクティブなデータ」として定義する必要がある。
リアクティブなデータとはVue.jsによって取得した時(get)、または代入した時(set)のフック処理が登録された"反応のできるデータ"のこと。

データバインディング

データと描画を同期させる仕組みのことを「データバイディング」と呼ぶ。
HTMLで作られたUIを操作するのに、viewの管理(DOMの更新など) は必要不可欠。

ライブラリを一切利用せずにJavaScriptでDOMを更新した時は、

app.js
var el = document.getElementById('text') //要素を探す
el.innerText = '新しいテキスト'              //要素を更新する

上記の処理で、UIのパターンごとにDOMを更新するのは、
UIのパターンが増えれば、増えるほど大変になっていってしまう・・
のでこういう自動的に描画が同期できるシステムはとても便利。

computed : 算出プロパティ

算出プロパティは処理に名前をつけて、テンプレート上で変数の様に使うことができる関数。

【 特徴 】

  • 一度計算を行った場合、再度関数を呼び出しても依存しているデータに変更がない限りキャッシュを返す(変更箇所だけが返される)
  • プロパティ呼び出しの時は ()必要
  • リアクティブなプロパティが変更された時に呼ばれるのがcomputedプロパティ

methods : メソッド

オブジェクトの操作を処理を定義したもの

【 特徴 】

・ 計算結果をキャッシュしない
・ そのため再描画されるたびにもう一度、計算が実行される
・ イベントに反応する

created Vue.jsのライフサイクル

ライフサイクルとは、Vueインスタンスのはじまり(初期化)からおわり(破棄)されるまでの一定のサイクルのこと。あらかじめ登録された処理を、Vueインスタンスの特定のタイミングで差し込みます。
こうした処理を割り込ませる仕組みを「フック」と呼ぶ。

コンポーネントのライフサイクルフックがどの時点で実行されるかは以下になります。
ライフサイクルダイアグラム

メソッド 説明
beforeCreated インスタンスが作成され、初期化される前に実行
created インスタンスが作成され、初期化された後に実行
beforeMount DOMがマウントされる直前に実行
mounted DOMがマウントされる直後に実行
beforeUpdate リアクティブプロパティが変更されて、変更されたデータがDOMに反映される前に実行
updated リアクティブプロパティが変更されて、変更されたデータがDOMに反映された後に実行
beforeDestroy Vueインスタントが破棄される前に実行
destroyed Vueインスタンスが破棄された後に呼び出される

created : リアクティブなプロパティの初期化が完了しているので、dataやmethodsを参照することができる。APIにリクエストを送信してデータを送信し、リアクティブプロパティに追加していく処理はcreated内に記述していくと良い。

Vue.jsの基本機能

テキストと属性のデータバインディング

Mustache構文 {{}}

main.js
var app = new Vue ({
  el: '#app',
  data: {
    message: 'Vue.js',
    list: ['ねこ', 'いぬ', 'とり']
  },
  computed: {
  },
  method: {
  },
  created:  function(){
  }
})

dataオプションに定義したmessageプロパティの値を画面に表示したいときは、
{{}}マスターシュ構文(Mustache:口髭) で囲みテンプレートに記述します。

index.html
<h1>{{message}}</h1> // Vue.js と表示される

<h1>{{list[0]}}</h1>  // ねこ と表示される 

ディレクティブ

「Vue.js」では、ディレクティブと呼ばれるv-接頭辞で始まる属性をHTML内で使用できます。
ディレクティブは主にデータバインディングを行うために使用される。

v-bind ディレクティブ

簡単なテキストのデータバインディングであればMustache構文を使えましたが、html要素の属性をデータバインディングする場合にはMustache構文はエラーとなり使えません。

index.html
<div id="error">
  <a href="{{ url }}">Google</a> // 属性では展開されない!
</div>

app.js
var app = new Vue({
  el: "#example-error",
  data: {
    url: "https://google.com"
  }
});

そこで使用するのが v-bindディレクティブ です。
v-bindディレクティブ を使えばDOM要素の属性を動的に切り替えることができます。

index.html
<タグ v-bind:属性=“プロパティ”></タグ>
index.html
<div id="example">
  <a v-bind:href="url">Google</a>
</div>
app.js
var app = new Vue({
  el: "#example",
  data: {
    url: "https://google.com"
  }
});

v-bind ディレクティブの省略

v-bind は省略して、:のみで省略できます。

index.html
<div id="example">
  <a :href="url">Google</a>
</div>

v-on ディレクティブ

v-onメソッドイベント処理をするためのディレクティブです。

(例) clickイベントを設定する

index.html
<div id="app">
  <p>{{ count }}回クリック</p>
  <button v-on:click="increment">このボタンでカウントを増やす</button> //increment メソッドが呼び出される
</div>

main.js
var app = new Vue ({
  el: '#app',
  data: {
    count: 0
  },
  method: {
    increment: function(){
      this.count += 1 // thisはdata内のリアクティブデータを指す
    }
  },
})

v-onディレクティブの省略記法について

v-on は省略して、@で省略できます。

index.html
<div id="app">
  <p>{{ count }}回クリック</p>
  <button @click="increment">このボタンでカウントを増やす</button> //increment メソッドが呼び出される
</div>

v-show ディレクティブ

v-showディレクティブは、式が真のときのみ要素を表示するディレクティブです。

index.html
<div id="app">
 <div v-show="ok">
    <p>Hello</p>
  </div>
</div>
main.js
var app = new Vue({
  el: '#app',
  data: {
    ok: true // 切り替える
  }
})
v-ifv-showの違いと使い分け

v-if :条件を満たさなかった場合、要素はDOmレベルで削除される。

スクリーンショット 2019-12-21 14.29.42.png

v-show :条件を満たさなかった場合、display;noneが付与され、非表示になる。

スクリーンショット 2019-12-21 14.31.09.png

条件分岐

vue.jsはテンプレートベースで条件分岐を記述します。
v-ifディレクティブは、v-showディレクティブ同様に式の真偽を判定して要素を表示させるディレクティブ

index.html
<p v-if="id === 1"> 条件がtrueの時の表示 </p>
<p v-else-if="id === 2"> 上記がfalseで、else-ifでの条件がTrueの表示 </p> //今回はこのpタグが表示される
<p v-else> 上記のどちらの条件もfalseの時の表示 </p>

main.js
var app = new Vue ({
  el: '#app',
  data: {
    message: 'Vue.js',
    id: 2, // ← ここ
  },
})

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

保守性や堅牢性を高める!モダンフロントエンド開発に必要な周辺技術をまとめてみました

はじめに

前日もくもく会で一緒になったエンジニア初学者の方を見て、ポートフォリオとしてフロントエンド開発はちゃんとできてはいるものの、実務において必要な保守性や堅牢性の高いコードについての意識がどうしても不足しているなという印象を受けました。

もちろん初学者の方にそのようなことを求めるのは酷な話なので知らないからダメ、ということを言うつもりは毛頭ありません。
が、職業としてエンジニアを目指すためにフロントエンド開発をやっている場合、保守性や堅牢性を意識すると企業からの評価が段違いなのではないかと思うのです。

そこで今回の記事では単にフロントエンドが開発できるというスキルだけではなく、実務における保守性や堅牢性を意識したフロントエンドを開発するために有効になるであろう技術について説明してみたいと思います。

対象者

  • プログラミング初学者の方で、モダンなJavaScriptフレームワークを使ってフロントエンド開発を行っている方
  • これからモダンなJavaScriptによるフロントエンド技術をプロダクトに導入しようとしている企業のエンジニア
  • ReactやVueを導入はしたが、コードの管理やバグが頻出したりして悩んでいる方

紹介する技術・ライブラリの一覧

保守性や管理性を向上させるために有効なものとして、ざっと下記が挙げられます。

  • eslint, prettierによるコード解析およびコードフォーマット
  • TypeScriptによる静的片付け
  • ReactHooks.useReducer, Vuexによる状態の一元管理
  • Storybookによるコンポーネントのカタログ管理
  • Atomic Design等のコンポーネント分割ポリシー
  • Jestによるメソッドの単体テスト
  • Testing Library(or Enzyme)によるコンポーネントの振る舞いテスト
  • CypressによるE2E(End to End)テスト
  • CIパイプライン上でのlintおよび単体テストの実行による自動検査、テストの実行 ではそれぞれの技術の概要と、導入したときのメリットについてみていきます。

ESLint, prettier

lintとは?何が嬉しい?

lint とは、主にC言語のソースコードに対し、コンパイラよりも詳細かつ厳密なチェックを行うプログラムである。
静的解析ツールとも呼ばれる。
lint - Wikipedia

実行するのに問題はないが、不具合の原因になるようなコードだったり、不要な記述についてチェックし、あらかじめ開発者に警告ないしエラーとして通知することでそれの解消をしてくれるツールがLintです。

ESLintにおいては静的解析を行う際に確認するルールを自由に設定することができ、例えば下記のようなことができます。

  • no-console: consoleを使っていないこと
  • no-empty: 空のブロックを使っていないこと
  • no-func-assign: functionを再定義していないこと
  • no-unreachable: 到達可能なコードが記述されていないこと
  • no-eval: evalを使わないこと

等々ですね。
さらにプラグインを利用すればReactやJSXの記述を確認したり、様々なことが可能です。

Prettierとは?何が嬉しい?

Prettierはコードフォーマッターのためのライブラリで、様々な言語のフォーマットが可能です。

ESLintがコードの問題を解消するのに対し、prettierではコードの整形を主眼としてしています。

業務におけるプログラミングではチーム開発が基本で、他人のコードを読んだり変更したりすることが多い。

そのため開発者ごとにコードの記述が異なると、コードの可読性が下がって効率が下がってしまいます。

Prettierを導入することで、チーム開発において重要なコードのフォーマットを維持し、自動化もさせることができます。

Further Reading - 参考

TypeScript

TypeScriptとは?何が嬉しい?

TypeScriptはMicrosoftが主体となって開発している、JavaScriptのスーパーセット言語です。

その名の通り、JavaScriptに型を与えた言語で、動的型付けのJavaScriptに対して厳密な静的型付けを与えます。

動的型付け言語は変数の型が動的に変わり、その特性上スピード感のある開発が可能ですが、その代わりに意図しない型に変換されて不具合が発生したりすることもあり、安全性という意味では静的型付け言語に劣ります。

今のところフロントエンド開発においてJavaScriptは不動の地位を占めていますが、裏を返すと動的型付けのJavaScriptしか選べなかったということでもありました。

TypeScriptはそのようなフロントエンドに対して、静的型付け機能を与え、型の不整合による不具合の発生を予防することを可能にします。

また、TypeScriptは実行前にJavaScriptに変換されます。
そのためOptional Chainingなどの機能をブラウザサポートを考えることなく利用することができ、新しい機能をフル活用することも可能です。

Further Reading

Rudex, Vuex, ReactHooks.useReducer等によるステート管理

ステート管理とは?何が嬉しい?

ReactとVueにはpropsとstateという値があり、stateはその名の通りコンポーネントの状態を表しています。

このstateを使うポリシーを定めていないと、stateの管理ができず不具合の温床になることがあります。

例えばあるコンポーネントの値を使って別のコンポーネントの動作が決定され、そしてそのコンポーネントの動作によってまた別のコンポーネントの動作が...というようなことが起こり、動作のロジックが複雑になり、デバッグが困難になります。

この問題を解決するためにstateを一元管理(Single source of truth)し、state管理をシンプルにするという手法が取られることがあります。

そしてそれを実現するためのツールが、ReduxやVuex(Vue)、useReducer(React)です。

例えばReduxというライブラリでは3つの原則を掲げていて、

  • Single source of truch(単一の正しい情報源)
  • State is read-only(Stateは読み込みのみ)
  • Changes are made with pure functions(変更は純粋関数を利用して行われる)

というものになっており、この原則によって安全な状態管理が実現されます。

ステート管理を一元的に行うことで現在のアプリケーションの状態を明確にし、複雑な状態を持たせず管理しやすくなり、stateによる不具合を抑制しやすくなります。

Further Reading

Storybookによるコンポーネントのカタログ管理

Storybookとは?何が嬉しい?

Storybook is an open source tool for developing UI components in isolation for React,
Storybookは独立した状態でReact, Vue, そしてAngularのUIコンポーネントを開発するためのオープンソースツールです。

Storybook 公式サイト より
上記の公式サイトの紹介にあるように、Storybookはフロントエンドフレームワークにおけるコンポーネント開発のためのツールで、コンポーネント単位での開発と管理を可能にします。

これの何が嬉しいかというと、一つのコンポーネントの修正のために、ページ全体を開いて操作をする必要がなくなる、ということです。

例えばテーブル中に存在するソート順を変更するボタンを押した際に、各行がそれぞれ指定した列で並んでいるか、というのを確認したい時に、今までのやり方ではトップページを表示して、テーブルのあるページに移動して、ボタンを押して...ということを毎回やる必要がありました。

しかしStorybookを使えば、そのテーブルコンポーネントだけを表示させることができ、さらに指定したpropsをあらかじめ与えた状態にすることも可能です。

そのため、特定の状態のコンポーネントをあらかじめ用意して、それを一目で見ることも可能というわけです。

これによってコンポーネントの確認、修正のために不要な操作をする必要がなく、モダンフロントエンドにおけるコンポーネントの管理が格段に楽になります。

下記に記載しているStorybook Demoをみると、Storybookがどのようなものか一目で分かるかと思います。

Further Reading

Atomic Design等のコンポーネント管理ポリシー

Atomic Designとは?何が嬉しい?

Atomic design is methodology for creating design systems. There are five distinct levels in atomic design:
Atomic Designはデザインシステムのための方法論である。
Atomic Designにおいては5つの別個のレベルが存在する。

Atomic Design - Brad Frost
Atomic Designはデザインにおける部品を化学的な概念に当てはめたもので、部品構成の単位を明瞭にしたものです。

元々はデザインガイドとして作られたが、それをフロント開発に応用しているプロダクトが増えています。

Atomic Designの何がいいかというと、コンポーネントの大きさによってコンポーネントを分類することで、管理しやすくするという点です。

Prettierの説明においてはコードの記述が開発者によってバラバラになるのを防ぐメリットがある、ということを説明しましたが、Atomic Designを使えば、開発者ごとにばらけがちなコンポーネントの大きさを統一し、再利用しやすくすることができるというわけです。

Further Reading

Jestによるメソッドの単体テスト

Jestとは?何が嬉しい?

JestはJavaScriptにおける、ユニットテストのためのツールです。

ユニットとは「単位」の意味で、ユニットテストでは小さい部品のテストを実現することができます。

基本的にはJavaScriptやTypeScriptの関数、メソッドの中で記述したロジックがテスト対象となり、関数に与えた引数やAPIリクエストの返り値ごとに、それらが期待した通りの動作になっているかを確認します。

ユニットテストを導入することで、文字通り小さい単位のテストを実施することができるため、それらをつなぎ合わせて全体のアプリケーションを動作させた時の動作が保証しやすくなります。

先ほどStorybookの項目ではページ全体ではなくコンポーネント単位での確認が可能と言いましたが、Jestではそれのメソッドバージョンと言い換えるといいかもしれません。

古典的にはページを表示して、画面を動かした時に実行されるメソッドが正しく動いているかを手動で確認する必要があったりしますが、ユニットテスト済みのメソッドのロジックに関してはテストする必要はないと言っていいでしょう。

Further Reading

Testing Library(or Enzyme)によるコンポーネントの振る舞いテスト

Testing Library, Enzymeとは?何が嬉しい?

こちらもJestの話と似ていますが、こちらは関数やメソッドではなく、画面に表示するコンポーネントをテスト対象としています。

Storybookではコンポーネントの表示がどうなるかを確認することができましたが、あくまで開発者が自分の目で確認する必要がありました。

Testing LibraryやEnzymeを利用することで開発者の目視ではなく、コード上でコンポーネントがどのように動作するかを保証させることが可能になります。

そのため後述するCIパイプラインにおける自動テストなどに組み込みやすく、コードを開発してPRを出した時にテストを実行し、常に動作が問題ないことを保証することが可能になったりします。

Further Reading

CypressやSeleniumによるE2E(End to End)テスト

Cypress, Seleniumとは?何が嬉しい?

Cypress, SeleniumはE2E(End to End)テストのためのライブラリで、E2Eテストはアプリケーション全体の動作をテストするものです。

JestやTesting Libraryはユニットやコンポーネントという小さな単位でのテストでしたが、E2Eテストではアプリケーションが全体として期待通りの動作をしてくれるのか、ということをテストします。

ユニットという単位では動作しているものの、それらをつなげた時に期待する動作をやっているかを保証してくれる機構はJestとTesting Libraryだけでは実現できません。

各種操作を画面上で行った時などのシナリオ全体での動作を保証させることが可能になるというわけです。

Further Reading

CIパイプライン上でのlintおよび単体テストの実行による自動検査、テストの実行

CIパイプラインとは?何が嬉しい?

CIとはDevOpsにおける考え方で、継続的インテグレーション(Continuous Integration)の略語です。

言葉としては難しいですが、やっていることとしては要するに新しく書いたコードをマージした時にアプリケーションが正しく動作することを確認する仕組み、と考えれば良いでしょう。

GitHubなどでCIの仕組みを導入すると、PRを出したときに自動テストを実行させ、テストが失敗したときはPRをマージできない状態にしたり、slackにテストが失敗したことを通知させたりします。

ここまでで紹介したlint, jest, testing libraryなどをCIで実行できるようにすれば、PRを出した時にコード解析とテストが自動で実行され、コードの品質やメソッド・コンポーネントの動作が常にテストされた状態のリポジトリを維持することが可能になります。

CIのためのツールとしてはCircleCIやJenkins, AWSではCodeシリーズなどがあります。

Further Reading

最後に

以上、各種関連言語、ライブラリ等について紹介させていただきました。

これらを利用することで職業エンジニアを目指している方であれば、実用的な技術を使ったポートフォリオを構築して企業からの評価を高めることができるでしょうし、実務で利用すれば保守性が高くかつ堅牢なアプリケーションを継続的に開発することが可能になります。

もちろん導入自体にもコストがかかりますし、これら全てを導入する必要はありませんが、時間とリソースが許す限り導入を検討し、そして導入するのがいいでしょう。

(時間的余裕があれば、実際にここで並べた技術を用いてフロントエンドアプリケーションを開発するハンズオン記事を書きたいと思いますが、それはまた次回...)

ここ間違ってない?これも使った方がいんじゃね?というものがあれば教えてください!

それでは、ありがとうございました!

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

【Vue】S3+CloudFront環境で

はじめに

Vue-cliで作成したSPAサイト(Routerでページの振り分けを行っているサイト)をS3+CloudFront環境で動作させている。この状況でS3で設定しているDefault Topページ以外のページをリロードすると以下のようなエラーが出力されて表示できない。

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error data-livestyle-extension="available">
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>3A494C8D44686E0B</RequestId>
<HostId>
2Md7g0VQi8VqOpfMODf3ppFzZIWDioRilTv900Mvk6cLBCB5Tia/virxZloyDTQrferhb9PKn/A=
</HostId>
</Error>

原因

SPAにて指定されているパスが物理パスではなく仮想パスであることが原因の可能性がある。

解決策

エラー時のリダイレクト先をindex.htmlにする。
対象のCloudFront>Error Pagesタブ>Custom Error Responseにて以下のように設定する。
スクリーンショット 2019-12-21 11.05.48.png

おわりに

こちらを参考にしました。

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

【Vue】S3+CloudFront環境でTOPページ以外をリロードすると開けない

はじめに

Vue-cliで作成したSPAサイト(Routerでページの振り分けを行っているサイト)をS3+CloudFront環境で動作させている。この状況でS3で設定しているDefault Topページ以外のページをリロードすると以下のようなエラーが出力されて表示できない。

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Error data-livestyle-extension="available">
<Code>AccessDenied</Code>
<Message>Access Denied</Message>
<RequestId>3A494C8D44686E0B</RequestId>
<HostId>
2Md7g0VQi8VqOpfMODf3ppFzZIWDioRilTv900Mvk6cLBCB5Tia/virxZloyDTQrferhb9PKn/A=
</HostId>
</Error>

原因

SPAにて指定されているパスが物理パスではなく仮想パスであることが原因の可能性がある。

解決策

エラー時のリダイレクト先をindex.htmlにする。
対象のCloudFront>Error Pagesタブ>Custom Error Responseにて以下のように設定する。
スクリーンショット 2019-12-21 11.05.48.png

おわりに

こちらを参考にしました。

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

Vue.jsで"ボタン"を作る

はじめに

この記事は、SLP KBIT Advent Calender 2019の22日目の記事です。

Vue.jsのコンポーネントについての理解を深めたいので、基本的な機能を持つボタンを自作してみたいと思います。
Vue.jsのインストールとプロジェクト作成はvue-cliでコマンド叩くだけなので省略します。

使用環境

Node.js v12.13.1
npm 6.12.1
@vue/cli 4.0.5

とりあえず作ってみる

クリックしたときに親コンポーネントにイベントを通知するようにします。
子コンポーネントから親コンポーネントにイベントを通知するには、$emitを使います。
第一引数でイベント名を登録します。第二引数以降で変数を渡すことも可能ですが、今回は使いません。
親コンポーネント側では適当なメソッドを作成してイベントが発火したことを確認します。

App.vue
<template>
  <div>
    <MyButton @click="click('my-button')"></MyButton>
  </div>
</template>

<script>
import MyButton from './components/MyButton.vue';

export default {
  components: {
    MyButton
  },
  methods: {
    click(message) {
      alert(message);
    }
  }
}
</script>
MyButton.vue
<template>
  <div @click="click">ボタン</div>
</template>

<script>
export default {
  methods: {
    click() {
      this.$emit('click');
    }
  }
}
</script>

これでボタンが完成しました。

とはいかない

さすがにこれだけではただクリックできるだけのテキストです。
これがボタンだとユーザに知らせるため、少しデザインを変更してみましょう。
MyButton.vueにstyleを追加して、ボタンっぽく見せてみます。
ソースコードの重複する部分は省略します。

MyButton.vue
<template>
  <div class="button" @click="click">ボタン</div>
</template>

<script>
...
</script>

<style lang="scss" scoped>
  .button {
    padding-left: 2px;
    padding-right: 2px;
    display: inline-block;
    border: 2px solid rgb(255,194,64);
    background: linear-gradient(to bottom, rgb(255,244,92), rgb(240,229,86)); 
  }
</style>

my-button.png
陰影をつけて立体感を出したら少しだけそれっぽくなりました。
黄色なのは黄色が好きだからです。

もっとボタンっぽくする

マウスホバー時とクリック時のデザインを追加して、このボタンが押せること、ボタンを押したことを知らせます。
まずborderを半透明にしておき、マウスホバー時にはっきり表示させようと思います。
カーソルも矢印から指に変えてわかりやすくします。
クリック時には陰影を反転し、ボタンが押せたことをわかりやすくします。

v-bind:classにオブジェクトを渡すことでクラスを動的に切り替えることができます。
isClicked、isEnteredの真偽値によって、それぞれのクラスが適用されるかが決まります。
cssは後に読み込まれたものが優先されるので、わざわざbuttonクラスを外す必要はありません。
それぞれのイベントが発火したときに値を切り替えることで実現します。

MyButton.vue
<template>
  <div
    class="button"
    v-bind:class="{clicked: isClicked, entered: isEntered}"
    @click="click"
    @mousedown="mousedown"
    @mouseup="mouseup"
    @mouseenter="mouseenter"
    @mouseleave="mouseleave"
  >ボタン</div>
</template>

<script>
export default {
  data() {
    return {
      isClicked: false,
      isEntered: false
    }
  },
  methods: {
    click() {...},
    mousedown() {
        this.isClicked = true;
    },
    mouseup() {
      this.isClicked = false;
    },
    mouseenter() {
        this.isEntered = true;
    },
    mouseleave() {
      this.isEntered = false;
    }
  }
}
</script>

<style lang="scss" scoped>
  .button {
    padding-left: 2px;
    padding-right: 2px;
    display: inline-block;
    border: 2px solid rgba(255,194,64,.5);
    background: linear-gradient(to bottom, rgb(255,244,92), rgb(240,229,86)); 
  }

  .clicked {
    background: linear-gradient(to top, rgb(255,244,92), rgb(240,229,86));
  }

  .entered {
    cursor: pointer;
    border: 2px solid rgba(255,194,64,1);
  }
</style>

AwesomeScreenshot-2019-12-21-1576875356822.gif
それっぽーい。
もう見た目は完全にボタンですね。

ラベルを変更する

次に親コンポーネントからボタンのラベルを変更できるようにします。
slotをつかうことで、子コンポーネントの<slot></slot>が、親コンポーネントのタグの中の要素で置換されます。

App.vue
<template>
  <div>
    <MyButton @click="click('my-button')">スロット</MyButton>
  </div>
</template>

<script>
...
</script>
MyButton.vue
<template>
  <div
    ...
  >
    <slot></slot>
  </div>
</template>

<script>
...
</script>

<style lang="scss" scoped>
...
</style>

my-button2.png
これで任意のラベルのボタンが作れるようになりました。

状態を追加する

もう少し機能を追加していきます。
バリデーションなどで条件を満たさないと押せないボタンってありますよね。
そのために子コンポーネント側でdisableの状態を作成します。
親コンポーネントから真偽値を渡し、子コンポーネントではpropsで受け状態を操作します。
disableがtrueの場合は、ボタン全体を半透明にし、ボタンの機能を使えないようにします。
値を渡す場合はdisable="true"のように書きますが、真偽値の場合はdisableと記述するだけでtrueを渡すことができます。

App.vue
<template>
  <div>
    <MyButton @click="click('my-button')">ボタン</MyButton>
    <MyButton @click="click('my-button')" disable>disable</MyButton>
  </div>
</template>

<script>
...
</script>
MyButton.vue
<template>
  <div
    class="button"
    v-bind:class="{clicked: isClicked, entered: isEntered, disabled: disable}"
    @click="click"
    @mousedown="mousedown"
    @mouseup="mouseup"
    @mouseenter="mouseenter"
    @mouseleave="mouseleave"
  >
    <slot></slot>
  </div>
</template>

<script>
export default {
  data() {...},
  props: {
    disable: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    click() {
      if (!this.disable) {
        this.$emit('click');
      }
    },
    mousedown() {
      if (!this.disable) {
        this.isClicked = true;
      }
    },
    mouseup() {...},
    mouseenter() {
      if (!this.disable) {
        this.isEntered = true;
      }
    },
    mouseleave() {...}
  }
}
</script>

<style lang="scss" scoped>
  .button {...}
  .clicked {...}
  .entered {...}
  .disabled {
    cursor: default;
    opacity: .6;
  }
</style>

AwesomeScreenshot-2019-12-21-1576875874421.gif
disable状態を実装できました。
これで文句なしにボタンといえるでしょう。

おわりに

今回は$emitやpropsを使ってボタンを作成しました。
他にも、propsで指定の色名を受け取って色の変更などができるといいですね。
Vue.jsは面白いので全人類が一度は触ってみるべきだと思います。

あとみんなどうやってきれいにgif撮ってるんですかね。

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

【入門】Laravel×Vue.js①〜セットアップ編〜

はじめに

PHPのフレームワークであるLaravelで作成したアプリケーションに
JavaScriptのフレームワークであるVue.jsを連携させる方法について説明します。

Node.jsのインストール

Node.jsのパッケージ管理ツールnpmを使うので、
Vue.jsを利用するためにはNode.jsが必要です。

まず、https://nodejs.org/ja/からインストールしましょう
推奨版、最新版どちらでも構わないので、インストールしてください。
スクリーンショット 2019-12-21 3.22.29.png

Vue.jsをアプリケーションへインストールする

インストールするパッケージ一覧が記述されているpackage.jsonを変更した後、
npmを使ってVue.jsをインストール、ビルドします。

package.jsonの変更

package.jsonにはインストールするパッケージがリストとして記述されています。
Vue.js関連のパッケージはvuevue-template-compilerです。

package.json
{
    "private": true,
    "scripts": {
        "dev": "npm run development",
        "development": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "watch-poll": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --watch-poll --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
        "hot": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
        "prod": "npm run production",
        "production": "node node_modules/cross-env/dist/bin/cross-env.js NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
    },
    "devDependencies": {
        "axios": "^0.19.0",
        "bootstrap": "^4.0.0",
        "cross-env": "^5.2.0",
        "jquery": "^3.2",
        "laravel-mix": "^4.0.7",
        "lodash": "^4.17.5",
        "popper.js": "^1.12",
        "resolve-url-loader": "^2.3.1",
        "sass": "^1.15.2",
        "sass-loader": "^7.1.0",
        "tar": "^4.4.8",
        "vue": "^2.5.17",
        "vue-template-compiler": "^2.6.10"
    },
    "dependencies": {
        "node-sass": "^4.12.0",
        "quill": "^1.3.6"
    }
}

Vue.jsのインストール

上のパッケージをインストールしましょう

ターミナル
$ npm install

Vue.jsのビルド

インストールしただけではVue.jsは使えないので、ビルドしていきましょう

ターミナル
$ npm run dev

コンポーネントの登録

作成されたコンポーネントをしましょう

resources/app.js
require('./bootstrap');

window.Vue = require('vue');

Vue.component('example-component', require('./components/ExampleComponent.vue').default);

const app = new Vue({
    el: '#app'
});
Vue.component('example-component', require('./components/ExampleComponent.vue').default);

上記の記述でresources/js/components/ExampleComponent.vue
example-componentという名前で登録します

ビルド

Vue.js関連のファイルを変更した時はビルドしなければ、変更は反映されません。

ターミナル
$ npm run dev

コンポーネント

サンプルのコンポーネントを確認しましょう
ない場合は手動で作成してください

resources/js/components/ExampleComponent.vue
<template>
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card">
                <div class="card-header">Example Component</div>
                <div class="card_body">
                    I'm an example component.
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        mouted() {
            console.log('Component moundted.')
        }
    }
</script>

内容については関係ないので、今回は説明を省きます。
単純なことなので、時間がある時に一度見てください。

ビューファイルでテンプレートを参照する

コンポーネントをビューファイルで参照する方法について説明します。
適当なファイルに挿入して見てください

resources/views/****.blade.php
<head>
    <link href="{{ mix('/css/app.css') }}" rel="stylesheet" type="text/css">
    <meta name="csrf-token" content="{{csrf_token()}}">
</head>
<div id="app">
    <example-component></example-component>
</div>
<script src="{{ mix('/js/app.js')  }}"></script>

スタイルシート

<link href="{{ mix('/css/app.css')  }}" rel="stylesheet" type="text/css">

linkタグでスタイルシートを参照しています
しかし、スタイルシートはresources/csspublic/cssに2つあります。
そのため、mix('/css/app.css")で2つのスタイルシートをまとめて読み込むことができます
 

CSRFトークン

<meta name="csrf-token" content="{{csrf_token()}}">

LaravelでVue.jsを利用する時はCSRFトークンを利用することが推奨されています。
特に理由がない場合は記述しましょう

コンポーネントの参照

<div id="app">
    <example-component></example-component>
</div>

コンポーネントはid="app"の領域で使用できます
example-componentタグで上で作成したコンポーネントを呼び出します。

スクリプトファイル

<script src="{{ mix('/js/app.js')  }}"></script>

スタイルシートと同様に、mix('/css/app.css")で2つのスクリプトファイルをまとめて読み込んでいます。
必ず<example-component>の下に記述してください。

動作確認

最後に動作確認をしましょう
忘れずにビルドしましょう

ターミナル
$ npm run dev

上記のような内容が表示されれば完了です!!

疑問、気になるところがございましたら、質問、コメントよろしくお願いします!!!

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

Nuxt.js で TypeScript を使って一通り試したら色々ハマった件

はじめに

これは Nuxt.js で TypeScript を使って一通り試そうとしたら、
色々なところでつまずいてしまったのでメモに残すお話です。

また、この記事は Nuxt.js Advent Calendar 2019 21日目の記事です。

今までに Vue.js の経験はありますが Nuxt.js は初めて触ります。

nuxt-community/starter-templatecreate-nuxt-app の違いもよくわからない状態でした。
まぁ、、、公式を見ると create-nuxt-app を使う手順になっており、nuxt-community/starter-template は、deprecated になっていたので、今は create-nuxt-app を使うんだろう。と思っている程度です。

作業環境の情報

ターミナル
$ npm -v
6.13.2

$ node -v
v12.13.1

$ yarn -v
1.21.0

$ create-nuxt-app -v
create-nuxt-app/2.12.0 darwin-x64 node-v12.13.1

Nuxt(SPA) プロジェクト作成

この辺はささーっと。

ターミナル
$ create-nuxt-app tutorial

create-nuxt-app v2.12.0
✨  Generating Nuxt.js project in tutorial
? Project name tutorial
? Project description My super-duper Nuxt.js project
? Author name chibi929
? Choose the package manager Yarn
? Choose UI framework Buefy
? Choose custom server framework Express
? Choose Nuxt.js modules Axios
? Choose linting tools ESLint, Prettier
? Choose test framework Jest
? Choose rendering mode Single Page App
? Choose development tools jsconfig.json (Recommended for VS Code)
yarn run v1.21.0
$ eslint --ext .js,.vue --ignore-path .gitignore . --fix
✨  Done in 3.89s.

?  Successfully created project tutorial

  To get started:

    cd tutorial
    yarn dev

  To build & start for production:

    cd tutorial
    yarn build
    yarn start

  To test:

    cd tutorial
    yarn test
  • UI framework: なんとなく Buefy 選択
  • Server framework: SPA だけどなんとなく Express を選択
  • linting tool: Lint、Formatter が欲しいので ESLint と Prettier 両方を選択

起動してみる

ターミナル
$ cd tutorial
$ yarn dev
yarn run v1.21.0
$ cross-env NODE_ENV=development nodemon server/index.js --watch server
[nodemon] 1.19.4
[nodemon] to restart at any time, enter `rs`
[nodemon] watching dir(s): server/**/*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node server/index.js`
ℹ Preparing project for development                                                                                                                                                                                                                                         17:33:31
ℹ Initial build may take a while                                                                                                                                                                                                                                            17:33:31
✔ Builder initialized                                                                                                                                                                                                                                                       17:33:31
✔ Nuxt files generated                                                                                                                                                                                                                                                      17:33:31

✔ Client
  Compiled successfully in 7.14s

ℹ Waiting for file changes                                                                                                                                                                                                                                                  17:33:39

 READY  Server listening on http://localhost:3000     

ここまでは大丈夫。
Vue CLI 3 以降と違って、CLI で TypeScript 用のプロジェクトが作れないのが辛いだけ。

TypeScript 対応してみよう

https://typescript.nuxtjs.org/ に記載されている通りに進めるだけ。
しかし、所々で自分はハマってしまったので合わせてメモ。

Introduction

Introduction ページ を見てみると以下のパッケージが必要そう。

  • @nuxt/types
    • これは下2つに含まれているっぽいので敢えてインストールする必要はなさそうだ。
  • @nuxt/typescript-build
  • @nuxt/typescript-runtime

Introduction

Setup

Setup ページ では @nuxt/typescript-build のインストール・設定を行う。
やっぱり Introduction に記載されていた @nuxt/types は気にしなくて良さそう。
手順通りに以下を行う。

  • Installation
  • Configuration
    • nuxt.config.js
    • tsconfig.json
    • vue-shim.d.ts

メモ: Configuration - nuxt.config.js

// nuxt.config.js
export default {
buildModules: ['@nuxt/typescript-build']
}

と、記載されていたので buildModules を上記のように変更するのかと思ったが、
上書きではなくて追加するように修正したので、一応メモ。

nuxt.config.js
  /*
   ** Nuxt.js dev-modules
   */
  buildModules: [
    // Doc: https://github.com/nuxt-community/eslint-module
    '@nuxtjs/eslint-module',
+   '@nuxt/typescript-build'
  ],

動作確認のためもう一度 yarn dev してみる。
特に問題は出ないはず...

Runtime (optional)

TypeScript runtime is needed for files not compiled by Webpack, such as nuxt.config file, local modules and serverMiddlewares.

ということなので nuxt.config.js を TS 化するときに必要そう。
Introduction に記載されていた @nuxt/typescript-runtime は Optional だったということか...
このページは、Optional になっていたので一旦無視する。 (後でハマることになるとは...)

Lint

Lint ページ では、
Introduction では未登場のモジュール( @nuxtjs/eslint-config-typescript )のインストールと設定を行う。
手順は Setup と大体同じで以下を行う。

  • Installation
  • Configuration
    • .eslintrc.js
    • package.json

メモ: Configuration - .eslintrc.js

WARNING

As it will make ESlint use a TypeScript parser ( @typescript-eslint/parser ), please ensure parserOptions.parser option is not overriden either by you or by another configuration you're extending.

If you were using babel-eslint as parser, just remove it from your .eslintrc.js and your dependencies.

という WARNING が記載されていた。
parserOptions.parser を見てみると babel-eslint が記載されていた。
消さないとダメらしい。一応以下のように修正する。

.eslintrc.js
- parserOptions: {
-   parser: 'babel-eslint'
- },
  extends: [
    '@nuxtjs',
    'prettier',
    'prettier/vue',
    'plugin:prettier/recommended',
    'plugin:nuxt/recommended',
+   '@nuxtjs/eslint-config-typescript'
  ],

メモ: Configuration - package.json

"lint": "eslint --ext .ts,.js,.vue ."

と、記載されていたが、
.ts だけを追加記載する形にした。

package.json
  "scripts": {
    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
    "build": "nuxt build",
    "start": "cross-env NODE_ENV=production node server/index.js",
    "generate": "nuxt generate",
-   "lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
+   "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore .",
    "test": "jest"
  },

とりあえず Lint を実行しておこう。
その結果がこれである。

ターミナル
$ yarn lint
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/layouts/default.vue
  10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing
  44:7   error    Missing space before function parentheses             space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 6 problems (5 errors, 1 warning)
  3 errors and 1 warning potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

5 errors, 1 warning...

激闘の ESLint と Prettier

これからがほんとうの地獄だ...

--fix をとりあえず付けてやってみれば少しは改善しそう...的なことが書いてあるからやってみる。

--fix 1回目

ターミナル
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix

/tmp/tutorial/layouts/default.vue
  10:63  warning  Expected no space before '>', but found    vue/html-closing-bracket-spacing
  10:64  error    Insert `/`                                 prettier/prettier
  44:7   error    Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 7 problems (6 errors, 1 warning)
  4 errors and 1 warning potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

6 errors, 1 warning...
増えたやん...

--fix 2回目

ターミナル
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix

/tmp/tutorial/layouts/default.vue
  10:63  error  Insert `·/`                                prettier/prettier
  44:7   error  Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 6 problems (6 errors, 0 warnings)
  4 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

6 errors, 0 warnings...
改善する気配がない...

--fix 3回目

ターミナル
$ yarn lint --fix
yarn run v1.21.0
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore . --fix

/tmp/tutorial/layouts/default.vue
  10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing
  44:7   error    Missing space before function parentheses             space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 6 problems (5 errors, 1 warning)
  3 errors and 1 warning potentially fixable with the `--fix` option.

error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Lint Error の内容まで振り出しに戻る...

何も考えずに --fix しただけなのでちゃんと見てみる

初めは ESLint と Prettier がバッティングしていることに気づかず苦しみ、
--fix した時の diff などを見ながら何度も実行して苦しみ...

一旦冷静になってエラーの1つだけに着目してみると...

ターミナル
# 初回
# ESLint さんは self-closing を許していないっぽい
10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing

# 1回目の --fix 後
## --fix 効果で self-closing の / が消滅したけどスペースが残ったっぽくて怒ってる ESLint さん
10:63  warning  Expected no space before '>', but found    vue/html-closing-bracket-spacing
## 一方、Prettier さんは self-closing の / を入れろよ!と怒っていらっしゃる
10:64  error    Insert `/`                                 prettier/prettier

# 2回目の --fix 後
## --fix 効果で残ってたスペースが消えたっぽくて ESLint さんの怒りが静まる
## 一方、'スペース' と / を入れろよ!と激怒する Prettier さん
10:63  error  Insert `·/`                                prettier/prettier

# 3回目の --fix 後
## Prettier さんの言い分が通ったのかスペースと / が復活し、振り出しに戻る
10:11  warning  Disallow self-closing on HTML void elements (<img/>)  vue/html-self-closing

とりあえず、自分のスタイルにルールを設定すれば良いんじゃね?
と、考え直す。

vue/html-self-closing

Enforce self-closing style
(self-closing style を強制する)

https://eslint.vuejs.org/rules/html-self-closing.html
self-closing は許容したいので、ESLint さんの設定を変えよう。

html.void ("never" by default) ... The style of well-known HTML void elements.

この設定が良くなさそうだな。

.eslintrc.js
  // add your custom rules here
  rules: {
-   'nuxt/no-cjs-in-config': 'off'
+   'nuxt/no-cjs-in-config': 'off',
+   'vue/html-self-closing': ['error', {
+     'html': {
+       'void': 'always'
+     }
+   }]
  }


折りたたみ (Lint 実行ログ)
ターミナル
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/layouts/default.vue
  44:7  error  Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error  Missing space before function parentheses                                 space-before-function-paren
  60:12  error  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  error  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 5 problems (5 errors, 0 warnings)
  3 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.


5 errors, 0 warnings
1個改善!

@typescript-eslint/no-unused-vars

Disallow unused variables
(未使用の変数を許可しない)

https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unused-vars.md
変数が未使用なだけで怒るのは、業務でもない限りちょっと怒りすぎだろう。
Warning くらいに変えておこう。

.eslintrc.js
  rules: {
    'nuxt/no-cjs-in-config': 'off',
    'vue/html-self-closing': ['error', {
      'html': {
        'void': 'always'
      }
-   }]
+   }],
+   'no-unused-vars': 'off',
+   '@typescript-eslint/no-unused-vars': 'warn'
  }


折りたたみ (Lint 実行ログ)
ターミナル
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/layouts/default.vue
  44:7  error  Missing space before function parentheses  space-before-function-paren

/tmp/tutorial/nuxt.config.js
  60:11  error    Missing space before function parentheses                                 space-before-function-paren
  60:12  warning  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  warning  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

/tmp/tutorial/server/index.js
  10:21  error  Missing space before function parentheses  space-before-function-paren

✖ 5 problems (3 errors, 2 warnings)
  3 errors and 0 warnings potentially fixable with the `--fix` option.

error Command failed with exit code 1.


3 errors, 2 warnings
よしよし。2つ warning 化。

space-before-function-paren

Require or disallow a space before function parenthesis
(関数の括弧の前にスペースを必要とするか、許可しない)

https://eslint.org/docs/rules/space-before-function-paren

  • anonymous is for anonymous function expressions (e.g. function () {}).
  • named is for named function expressions (e.g. function foo () {}).
  • asyncArrow is for async arrow function expressions (e.g. async () => {}).

自分のスタイルでは以下のようなルールにすれば良いかな。。。

.eslintrc.js
    // note you must disable the base rule as it can report incorrect errors
    'no-unused-vars': 'off',
-   '@typescript-eslint/no-unused-vars': 'warn'
+   '@typescript-eslint/no-unused-vars': 'warn',
+   'space-before-function-paren': ['error', {
+     'anonymous': 'never',
+     'named': 'never',
+     'asyncArrow': 'always'
+   }]
  }


折りたたみ (Lint 実行ログ)
ターミナル
$ yarn lint
yarn run v1.19.2
$ eslint --ext .ts,.js,.vue --ignore-path .gitignore .

/tmp/tutorial/nuxt.config.js
  60:12  warning  'config' is defined but never used. Allowed unused args must match /^_/u  @typescript-eslint/no-unused-vars
  60:20  warning  'ctx' is defined but never used. Allowed unused args must match /^_/u     @typescript-eslint/no-unused-vars

✖ 2 problems (0 errors, 2 warnings)

Done in 3.74s.


自分で設定した warning (no-unused-vars) 以外、消えた!

TypeScript で記述してみよう

いよいよ TypeScript 編突入である。
型がないと怖くて生きていけない!!

https://typescript.nuxtjs.org/cookbook/components/ を参考に...
元々 Class API スタイルを使おうとしてたが、全部やってみた!

Options API スタイル

components/Options.vue
<template>
  <div>
    Name: {{ fullName }}
    Message: {{ message }}
  </div>
</template>

<script lang="ts">
import Vue, { PropOptions } from 'vue'

interface User {
  firstName: string
  lastName: number
}

export default Vue.extend({
  name: 'OptionsAPIComponent',

  props: {
    user: {
      type: Object,
      required: true
    } as PropOptions<User>
  },

  data() {
    return {
      message: 'This is a message'
    }
  },

  computed: {
    fullName(): string {
      return `${this.user.firstName} ${this.user.lastName}`
    }
  }
})
</script>
pages/index.vue
  <template>
      ~略~
+     <options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
    </section>
  </template>

  <script>
  import Card from '~/components/Card'
+ import Options from '~/components/Options'

  export default {
    name: 'HomePage',

    components: {
      Card,
+     Options
    }
  }
  </script>

(こちらはあえてそのまま JavaScript スタイルで...)

メモ: ESLint/Prettier 連合との再戦

yarn dev を実行したところエラーが...
Prettier め...

ターミナル
/tmp/tutorial/components/Options.vue
  2:8  error  Replace `⏎····Name:·{{·fullName·}}⏎····Message:·{{·message·}}⏎··` with `Name:·{{·fullName·}}·Message:·{{·message·}}`  prettier/prettier

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

面倒なのでまたもやとりあえず --fix

components/Options.vue
  <template>
-   <div>
-     Name: {{ fullName }}
-     Message: {{ message }}
-   </div>
+   <div>Name: {{ fullName }} Message: {{ message }}</div>
  </template>

これでエラーなくなるならとりあえずはいいか。
そのうちフォーマッターのスタイルは直したいけど。

Options API スタイル

反映された!!

Class API スタイル

Using vue-class-component through vue-property-decorator

Composition API は見覚えがないが、
Class API 見覚えがあるので、先にこっちから...
Vue CLI3 で作った TypeScript でも Class-Component Style で触ったデコレーター!

ターミナル
$ yarn add -D vue-property-decorator
components/Class.vue
<template>
  <div>Name: {{ fullName }} Message: {{ message }}</div>
</template>

<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator'

interface User {
  firstName: string
  lastName: number
}

@Component
export default class ClassAPIComponent extends Vue {
  @Prop({ type: Object, required: true }) readonly user!: User

  message: string = 'This is a message'

  get fullName(): string {
    return `${this.user.firstName} ${this.user.lastName}`
  }
}
</script>
pages/index.vue
  <template>
      ~略~
      <options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
+     <ClassAPIComponent :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
    </section>
  </template>

  <script>
  import Card from '~/components/Card'
  import Options from '~/components/Options'
+ import ClassAPIComponent from '~/components/Class'

  export default {
    name: 'HomePage',

    components: {
      Card,
      Options,
+     ClassAPIComponent
    }
  }
  </script>

Class API スタイル

2行目の文字列がそれだ。上手くいった!

メモ: experimentalDecorators

但し、以下のエラーが出ているので tsconfig.json に追加しておこう

ターミナル
 ERROR  ERROR in /tmp/tutorial/components/Class.vue(14,22):                                                                                                                                                  nuxt:typescript 06:59:04
14:22 Experimental support for decorators is a feature that is subject to change in a future release. Set the 'experimentalDecorators' option in your 'tsconfig' or 'jsconfig' to remove this warning.
tsconfig.json
  {
    "compilerOptions": {
      ~略~
+     "experimentalDecorators": true,
      ~略~
  }

Composition API スタイル

Using @vue/composition-api plugin

これは、知らない記法だった。
普段 Vue では前述の Class API スタイルを使っているし使う機会はなさそう...

ターミナル
yarn add @vue/composition-api
plugin/composition-api.ts
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'

Vue.use(VueCompositionApi)
nuxt.config.js
  /*
   ** Plugins to load before mounting the App
   */
- plugins: [],
+ plugins: ['@/plugins/composition-api'],
components/Composition.vue
<template>
  <div>Name: {{ fullName }} Message: {{ message }}</div>
</template>

<script lang="ts">
import { createComponent, computed, ref } from '@vue/composition-api'

interface User {
  firstName: string
  lastName: number
}

export default createComponent({
  props: {
    user: {
      type: Object as () => User,
      required: true
    }
  },

  setup({ user }) {
    const fullName = computed(() => `${user.firstName} ${user.lastName}`)
    const message = ref('This is a message')

    return {
      fullName,
      message
    }
  }
})
</script>
pages/index.vue
      <options :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
      <ClassAPIComponent :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
+     <Composition :user="{ firstName: 'Chibi', lastName: 'Kinoko' }" />
    </section>
  </template>

  <script>
  import Card from '~/components/Card'
  import Options from '~/components/Options'
  import ClassAPIComponent from '~/components/Class'
+ import Composition from '~/components/Composition'

  export default {
    name: 'HomePage',

    components: {
      Card,
      Options,
      ClassAPIComponent,
+     Composition
    }
  }
  </script>

Composition スタイル

SPA だけど Express を使ってみよう

server/index.js
 const express = require('express')
 const consola = require('consola')
 const { Nuxt, Builder } = require('nuxt')
 const app = express()

 // Import and Set Nuxt.js options
 const config = require('../nuxt.config.js')
 config.dev = process.env.NODE_ENV !== 'production'

 async function start() {
+  app.get('/test', (req, res, next) => {
+    const param = { test: 'Hello World!' }
+    res.send(param)
+  })

   // Init Nuxt.js
   const nuxt = new Nuxt(config)
   ~略~
 }
 start()

yarn dev 後に動作確認

ターミナル
$ curl http://localhost:3000/test
{"test":"Hello World!"}

メモ: 記述位置は大事らしい

以下の位置に GET API を記述したところ上手くいかなかった

server/index.js
 ~略~

 async function start() {
   ~略~

   // Give nuxt middleware to express
   app.use(nuxt.render)
+  app.get('/test', (req, res, next) => {
+    const param = { test: 'Hello World!' }
+    res.send(param)
+  })

   ~略~
 }
 start()
ターミナル
# 失敗バージョン
##############################

$ curl http://localhost:3000/test
<!doctype html>
<html >
~略~

メモ: GET / もダメらしい

GET / を登録してしまうと、ページへのアクセスができなくなる。
そりゃそうか。

メモ: this.$axios が使えない

component/Class.vue
    ~略~
+   mounted() {
+     this.$axios
+       .get('/test')
+       .then((res) => {
+         console.log(res)
+       })
+       .catch((err) => {
+         console.log(err)
+       })
+   }
  }
  </script>
ターミナル
 ERROR  ERROR in /tmp/tutorial/components/Class.vue(25,10):                                                                                                                                                  nuxt:typescript 20:58:26
25:10 Property '$axios' does not exist on type 'ClassAPIComponent'.
    23 |   mounted() {
    24 |     console.log('mounted')
  > 25 |     this.$axios
       |          ^
    26 |       .get('/test')
    27 |       .then((res) => {
    28 |         console.log(res)

tsconfig.json に追記が必要

tsconfig.json
  {
    "compilerOptions": {
      ~略~
      "types": [
        "@types/node", 
        "@nuxt/types", 
+       "@nuxtjs/axios"
      ]
    },

Store を TypeScript で書いてみよう

https://typescript.nuxtjs.org/cookbook/store.html

とりあえず Vanilla JS の方で良いや...と...
ちなみにここも自分の中では激ハマりポイントである。
上記のページを参考に store/index.ts を準備できたら、
任意の Component の mounted() でログを出してみる。

mounted() {
  console.log(this.$store.state.counter)
}
ターミナル
client.js?06a0:76 TypeError: Cannot read property 'state' of undefined
    at VueComponent.mounted (Class.vue?e831:34)

しかし、どうも this.$store が undefined みたい...

次に、Nuxt.js のページを参考に...
普通の Vuex... というわけではないが Nuxt Typescript よりは普通に近い形で、型に怒られたら any でにげる。

store/index.ts
export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state: any) {
    state.counter++
  }
}
ターミナル
client.js?06a0:76 TypeError: Cannot read property 'state' of undefined
    at VueComponent.mounted (Class.vue?e831:34)

しかし、やっぱり 'state' of undefined
どうにも this.$store が未定義状態だなー...

試しに JS ファイルにしてみると...

store/index.js
export const state = () => ({
  counter: 0
})

export const mutations = {
  increment(state) {
    state.counter++
  }
}

動くやん...?

Runtime 編突入

でもやっぱり TypeScript で書きたい!
TypeScript のセットアップの中で、ひとつだけ js 系を ts 系に置き換えるためのものなのに
やっていないものを思い出す。そう、オプショナルだったRuntime...

TypeScript runtime is needed for files not compiled by Webpack, such as nuxt.config file, local modules and serverMiddlewares.

と書いてあったものなので、
Store 系にも影響するかはわからない。けどやってみよう。そう思った。

そして、もう一度Runtimeページでセットアップ方法を確認する。
内容は Setup とほとんど同で以下の通りである。

  • Installation
  • Usage
    • package.json

モジュールのインストールは特に問題ない。ひとつだけ注意するとしたら

TIP

Note that this package is installed as dependency and not devDependency like @nuxt/typescript-build, cause @nuxt/typescript-runtime is needed for production

devDependencies には入れちゃいけない。

ターミナル
yarn add @nuxt/typescript-runtime

package.json の書き換えは結構困った。
ドキュメントによると nuxt-ts コマンドに書きかえろよ。と...

package.json
{
  ~略~
  "scripts": {
    "dev": "nuxt-ts",
    "build": "nuxt-ts build",
    "generate": "nuxt-ts generate",
    "start": "nuxt-ts start"
  },
  ~略~
}

しかし、自分の package.json は buildgenerate は良いが...
devstart には cross-env なるものが使われている...
果たして以下のように全部書き換えて良いものなのか?

package.json
  {
    ~略~
    "scripts": {
-     "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+     "dev": "nuxt-ts",
-     "build": "nuxt build",
+     "build": "nuxt-ts build",
-     "start": "cross-env NODE_ENV=production node server/index.js",
+     "start": "nuxt-ts start"
-     "generate": "nuxt generate"
+     "generate": "nuxt-ts generate"
    },
    ~略~
  }

とりあえず、なんか怖いと思ったので devstart は変えないことにした。
cross-env を使ったまま server/index.js を起動する形のまま置いておく。

package.json
  {
    ~略~
    "scripts": {
      "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
-     "build": "nuxt build",
+     "build": "nuxt-ts build",
      "start": "cross-env NODE_ENV=production node server/index.js",
-     "generate": "nuxt generate"
+     "generate": "nuxt-ts generate"
  },
  ~略~
}

とりあえず yarn dev を実行しても問題なさそう。
いよいよ store/index.js を TypeScript に置き換えてみる
と、言っても any の型を付けるだけである

store/index.ts
 export const state = () => ({
   counter: 0
 })

 export const mutations = {
-  increment(state) {
+  increment(state: any) {
     state.counter++
   }
 }

yarn dev を実行してみると、どうやら this.$store はまだ読み込めていない。
しかたなく nuxt-ts を実行してみると...
this.$store が読み込め、 counter の値が表示できた。

さて、ここで問題なのは
nuxt-ts で動かした時に Express で作成した API はどうなるのか?
server/index.jsnuxt コマンドは何をやっているのかを追ってみることにした。

server/index.js が何をしているのか?

server/index.js は短いコードなのですぐ読める。

Nuxt インスタンスを生成して Builder.build を実行する。
だったり Nuxt.ready を実行する。
ついでに、Express の API を使う感じだ。

nuxt コマンドが何をしているのか?

@nuxt/cli を追ってみると色々汎用的にガチャガチャやってるが、
結局は cli-devBuilder.build したり Nuxt.ready を実行しているだけである。

つまり答えは、 server/index.js を動かしてないから、
単純に Express が立っていない状態になるのである。たぶん!!

ターミナル
$ curl http://localhost:3000/test
<!doctype html>
<html >

安心して nuxt-ts に書き換えよう

package.json
 {
   ~略~
   "scripts": {
-    "dev": "cross-env NODE_ENV=development nodemon server/index.js --watch server",
+    "dev": "nuxt-ts",
     "build": "nuxt-ts build",
-    "start": "cross-env NODE_ENV=production node server/index.js",
+    "start": "nuxt-ts start"
     "generate": "nuxt-ts generate"
   },
   ~略~
 }

こうして、Express を犠牲にして、
Store を TypeScript で書くことができた。

ServerMiddleware を使ってみよう

さて、そしたら次に気になってしまうのは
Nuxt.js で TypeScript を使ったとき、Express はどうやって使うのか。

Nuxt.js v2.9にTypeScriptとExpress.jsを対応してみた
API: serverMiddleware プロパティ

を参考にした。

nuxt.config.jsserverMiddleware の項目を追加すれば良いらしい。
まずは server/index.js を書き換えよう。

server/index.ts
import express from 'express'
import bodyParser from 'body-parser'
const app = express()

app.use(bodyParser.json())
app.get('/test', (req, res, next) => {
  const param = { test: 'Hello World!' }
  res.send(param)
})

export default app

Nuxt 系の処理が全部不要(nuxt-ts コマンドに任せた)なので、
ごっそり消して Express の処理だけ書きます。
Express のインスタンスは export しておきます。

次に nuxt.config.jsserverMiddleware の項目を追加します。

nuxt.config.js
 {
   ~略~
+  serverMiddleware: [
+    { path: '/api', handler: '@/server/index.ts' }
+  ]
 }

ts ファイルが直接設定できるんだなー。
きっと Runtime 編を突破したおかげですね!

ターミナル
$ curl http://localhost:3000/api/test
{"test":"Hello World!"}

実行してみると普通に叩けました。

Middleware を使ってみよう

API: serverMiddleware プロパティ を参考してたら Middleware も目に入ってしまった。

API: middleware プロパティ
ルーティング - ミドルウェア

どうやら、ルーティング時に呼び出すことができるものらしい。
sereverMiddleware 同様、 nuxt.config.jsソースコード をちょちょっと触るだけみたい。

ルートが変更された時に呼ばれる Middleware

middleware/hoge.ts
export default async function() {
  console.log('Middleware: hoge')
}
nuxt.config.js
   serverMiddleware: [
     { path: '/api', handler: '@/server/index.ts' }
   ],
+  router: {
+    middleware: 'hoge'
+  }
 }

これだけで、ルーティング変更時に毎度 console.log('Middleware: hoge') が実行されるのである。

特定のルートに変更された時に呼ばれる Middleware

前述の nuxt.config.js に書く Middleware は、
全てのルートにおいて、呼び出し時に動く Middleware である。
特定のページに行った時のみ実行したい場合はどうするか。

以下の通りである。
とりあえず hoge.ts を使うとわかりづらくなってしまうので foo.ts を用意する。

middleware/foo.ts
export default async function() {
  console.log('Middleware: foo')
}

次にルーティング対象の pages 以下に手を入れる

pages/inspire.vue
 ~略~
 </template>
+ <script lang="ts">
+ import { Vue, Component, Prop } from 'vue-property-decorator'
+ 
+ @Component({
+   middleware: 'foo'
+ })
+ export default class Inspire extends Vue {}
+ </script>

これで、inspire.vue ルーティングを変更したときに foo が呼び出される。

これで stats ミドルウェアはすべてのルート変更時に呼び出されるようになります。
同様に、特定のレイアウトもしくはページ内にもミドルウェアを追加することができます:

注意点としてはルーティング - ミドルウェアに記載されている通り、
layouts もしくは pages のものしか使えなそう。
ここで試している Class API スタイルの Component に middleware を追加してみたけど、
foo は実行されなかった。

Store にデコレーターを使ってみよう

さて Store は最初 Vanilla JS を使ってしまいましたが、
vuex-module-decorator を使った記述が推されているっぽい。

まずはモジュールをインストールしておく

ターミナル
yarn add -D vuex-module-decorators

そして Store | Nuxt TypeScriptModule re-use, use with NuxtJS を参考に Store を書き換えてみることにする

store/example.ts
import { Module, VuexModule, Mutation } from 'vuex-module-decorators'

@Module({ stateFactory: true })
class MyModule extends VuexModule {
  counter = 0

  @Mutation
  increment() {
    this.counter++
  }
}
utils/store-accessor.ts
import { Store } from 'vuex'
import { getModule } from 'vuex-module-decorators'
import example from '~/store/example'

let exampleStore: example

function initialiseStores(store: Store<any>): void {
  exampleStore = getModule(example, store)
}

export {
  initialiseStores,
  exampleStore,
}
store/index.ts
import { Store } from 'vuex'
import { initialiseStores } from '~/utils/store-accessor'
const initializer = (store: Store<any>) => initialiseStores(store)
export const plugins = [initializer]
export * from '~/utils/store-accessor'

メモ: example が見つからない

ターミナル
ERROR in /tmp/tutorial/utils/store-accessor.ts(3,8):                                                                                                                                                 nuxt:typescript 23:40:46
3:8 Module '"/tmp/tutorial/store/example"' has no default export.
    1 | import { Store } from 'vuex'
    2 | import { getModule } from 'vuex-module-decorators'
  > 3 | import example from '~/store/example'

ドキュメント通りにやるとエラーになる。
store/example.ts は export してないから当然か。

store/example.ts
  @Module({ stateFactory: true })
- class MyModule extends VuexModule {
+ export default class MyModule extends VuexModule {
    counter = 0

メモ: module name が設定されていない

意気揚々と yarn dev !!
http://localhost:3000 をブラウザで開いてみると
なんと真っ白!

Developer Tool を開いて確認すると

Error: ERR_GET_MODULE_NAME : Could not get module accessor.
      Make sure your module has name, we can't make accessors for unnamed modules
      i.e. @Module({ name: 'something' })
    at getModuleName (index.js?6fc5:24)
    at getModule (index.js?6fc5:41)
    at initialiseStores (store-accessor.ts?8dac:8)
    at initializer (index.ts?598a:3)
    at eval (vuex.esm.js?2f62:353)
    at Array.forEach (<anonymous>)
    at new Store (vuex.esm.js?2f62:353)
    at createStore (store.js?6c6b:42)
    at createApp$ (index.js?f26e:51)
    at tryCatch (runtime.js?96cf:45)

@Module({ name: 'something' }) のように設定してくれ。とのこと。
vuex-module-decorator 使うと名前空間付きモジュールしか設定できないのかなー?

store/example.ts
- @Module({ stateFactory: true })
+ @Module({
+   stateFactory: true,
+   namespaced: true,
+   name: 'example'
+ })
  export default class MyModule extends VuexModule {
    counter = 0

のように書き換えて再度実行!
任意の Component では以下のようなログ出力を行なっています。

import { exampleStore } from '~/store'

mounted() {
  console.log(this.$store.state.counter)
  console.log(this.$store.state.example.counter)
  console.log(exampleStore.counter)
}

// => undefined
// => 0
// => 0

やっぱり名前空間付きモジュールになっている。
ちなみに store/example.tsnamespaced: truenamespaced: false に書き換えてもダメでした。
vuex-module-decorator を使うとルートモジュールは作れないのかな?

ひとまず store/index.ts を以下のように書き換えたら
ルートモジュールの store/index.ts 及び、
名前空間付きモジュールの store/example.ts の両方が使えるようになりました。

store/index.ts
  import { Store } from 'vuex'
  import { initialiseStores } from '~/utils/store-accessor'
  const initializer = (store: Store<any>) => initialiseStores(store)
  export const plugins = [initializer]
  export * from '~/utils/store-accessor'
+ 
+ export const state = () => ({
+   counter: 0
+ })
+ 
+ export const mutations = {
+   increment(state: any) {
+     state.counter++
+   }
+ }

まとめ

これだけ一通りやって、たくさん躓き、
後輩の雑用を軽減するアプリを作成しました。

ちなみに、この Nuxt プロジェクトは、最初に作成している通り、
SPA なので、最終的には nuxt-ts generate を実行して静的ホスティングをしております。
つまり、無駄に使った serverMiddleware は動いていません。
まぁ、動いても本記事に書いたレベルの {"test":"Hello World!"} 程度の戻り値が出るだけなんですけどね。

しかし、実験コードが入れっぱなしなので、
Developer Tool を開くと以下のエラーが出ているオマケ付きです。
まぁ、後輩が使うだけだし、悪影響ないし、ヨシ

http://example.com/api/test net::ERR_CONNECTION_REFUSED

ということで、

  • 一番ハマるのは Linter + Prettier
  • Runtime は Optional だけど TypeScript を使うなら入れておけ
  • $axios もそうだが、時々型定義でハマる (本記事には書いてないけど $buefy も型定義エラーになった)
  • 何かやろうとすると多少なりとも動かない!

[やってないこと]

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

【Vue.js】Vuetifyのインストール方法と基本的な使い方

はじめに

Vue.jsのUIコンポーネントライブラリであるVuetifyのインストール方法と代表的な使い方をまとめました。

公式サイトURL
↑ 完全ではないですが日本語化も進んでいます。
解説やデモが各コンポーネント毎に掲載されていて、見始めたら止まりません。笑

環境

OS: macOS Catalina 10.15.1
vue: 2.6.10
vue-cli: 4.1.1
vue-router: 2.6.10
vuex: 3.1.2
vuetify: 2.1.0

インストール

※Vue-CLIでVueの環境は構築済とします。

$ vue create my-app

$ vue add vuetify

これだけで使える状態になっています。

CDN版もあります。

1.レスポンシブデザインの指定

xs~xlまで5段階用意されています。
image.png

クラスの末尾にnoneflexで非表示、表示の指定が可能で、例えば

  • 全ての大きさで非表示:d-none
  • Small 以上なら表示:d-sm-flex

となります。

これを組み合わせると、
スマホでは非表示にしてPCなら表示したい要素であれば、

<div class="d-none d-sm-flex">
  ...
</div>

のようにすればOKです。

その他レスポンシブ関連の詳細はこちらへ。
CSS Display helpers — Vuetify.js

2.カラー

通常の#FFEBEEのようなカラーコード指定に加え、
red lighten-5のようにも書けます。

具体的な色合いは以下リンクから確認可能です。
Material color palette — Vuetify.js
image.png

3.UI component

使用例:ドロップダウンメニュー

各コンポーネントはv-〇〇で命名されており、それらを入れ子にして好きな形に組み上げていきます。

例えば、以下の<v-menu>内を書くことで、CSS・JavaScriptを一切書かずにドロップダウンメニューを実装出来てしまいます。

楽!

↓公式のデモはこちら
App-bar component — Vuetify.js

sample.vue
<template>
<v-bar>
...
<v-menu
  left
  bottom
>
  <template v-slot:activator="{ on }">
    <v-btn icon v-on="on">
      <v-icon>mdi-dots-vertical</v-icon>
    </v-btn>
  </template>

  <v-list>
    <v-list-item
      v-for="n in 5"
      :key="n"
      @click="() => {}"
    >
      <v-list-item-title>Option {{ n }}</v-list-item-title>
    </v-list-item>
  </v-list>
</v-menu>
</v-bar>
...
</template>

4.Directives

使用例:Intersection observerも簡単に使える

v-intersectというディレクティブを使用することで、スクロールして指定範囲に入ったらクラスを付与とかも簡単に実装できます。

以下例では、<v-card>の要素が画面の範囲内に入ってきたら<v-avatar>の色が変わるようになっています。

楽!

↓公式のデモはこちら
Intersection observer directive — Vuetify.js

sample.vue
<template>
  <div>
    <div>
      <v-avatar
        :color="isIntersecting ? 'success' : 'error'"
      ></v-avatar>
    </div>
    <v-card
      v-intersect="onIntersect"
    >
      ...
    </v-card>
  </div>
</template>

<script>
  export default {
    data: () => ({
      isIntersecting: false,
    }),

    methods: {
      onIntersect (entries, observer) {
        this.isIntersecting = entries[0].isIntersecting
      },
    },
  }
</script>

おわりに

最後まで読んで頂きありがとうございました:bow_tone1:

その他、指定出来るスタイルや使えるUIコンポーネントが多数揃っていて不自由しなさそうです。
良いデザインが余りにも簡単に実装出来るのでビックリ:sweat_smile:

参考にさせて頂いたサイト(いつもありがとうございます)

マテリアルデザインコンポーネントフレームワーク — Vuetify.js

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