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

Nuxt.jsにStorybookのインストールとaddonの導入まで(StoriesOf APIではなく、CSF記法で)

新規でNuxt(Vue)のプロジェクトを立ち上げる機会があったので、初めてStorybookを導入してみました。
現在Storybookのバージョンは5.2.xですが、公式が推奨するストーリーの記述方式がComponent Story Format (CSF)に変更されたようです。
(v5.1以前はStoriesOf APIが一般的。v5.2でも問題なく使用できる)
Component Story Format (CSF)
StoriesOf API
初導入にあたり参考情報を探していたのですが、公式ドキュメントもネットの情報もStoriesOf APIでの記載例が多かったので(当たり前ですが)、良い機会だと思い基本的な情報ですがCSFでの記法でまとめでみました。

目次
【インストールと基本設定】
1. Nuxt(Vue)プロジェクトにStorybookをインストール
2. package.jsonの設定
3. Storybook用webpackの設定
・Nuxtプロジェクトで使用している@や~などのエイリアス設定
・Typescriptの使用設定
・sass(scss)の使用設定、かつグローバルなCSS用変数やmixinを読み込む
・css内からassets以下のファイルにアクセスできるように設定
4. 簡単なストーリーの記述

【代表的なaddonの追加】
1. knobs
2. actions
3. notes
4. viewport
5. a11y
6. backgrounds

インストールと基本設定

1. Nuxt(Vue)プロジェクトにStorybookをインストール

Storybook for Vue
CLIを使用して半自動でインストールする方法と、必要なモジュールを自分で追加していく方法がありますが、CLIでインストールする方がやはり楽です。
CLIを使用すると雛形のStoryファイルが生成されるのでそれを見るだけでも基本的な使用方法はわかると思います。

$ npx -p @storybook/cli sb init

上記の書き方で、プロジェクトが何のフレームワークで構成されているか自動で判定して必要なモジュール群をインストールしてくれます(心配な場合はオプションでフレームワーク名を追加してください。--type vue)
作ったばかりのNuxtプロジェクトですが、以下のモジュールが追加されました。

+ @babel/core@7.7.2
+ babel-loader@8.0.6
+ babel-preset-vue@2.0.2
+ @storybook/addons@5.2.6
+ @storybook/addon-actions@5.2.6
+ @storybook/addon-links@5.2.6
+ @storybook/vue@5.2.6

さらに、storybookの設定ファイルなどを格納する.storybookとストーリーファイルを格納するstoriesの2つのディレクトリも同時に生成されます。

2. package.jsonの設定

実行する際のコマンドを追加します。が、CLIでインストールするとこの記述も追加されていますので、特に編集はいりません。Dockerなどを使用している関係でポート番号を変更したいなどありましたら編集してください。

"scripts": {
  "storybook": "start-storybook -p 6006",
  "build-storybook": "build-storybook"
}

これで、下記コマンドによる実行ができるようになりました。

$ npm run story

3. Storybook用webpackの設定

StorybookはNuxtなどと実行環境が別(この言い方で良いのか..?)なので、専用にwebpackの設定も行う必要があります。以下のファイルを編集していきます。CLIによって自動生成されるはずですが、なかったら追加してください。
.storybook/webpackconfig.js

・Nuxtプロジェクトで使用している@~などのエイリアス設定

これをしないとコンポーネントの読み込みなどでエイリアスを使用している場合にエラーになるので追記しましょう。

 .storybook/webpackconfig.js
const path = require('path')
const rootPath = path.resolve(__dirname, '../')

module.exports = ({ config }) => {

  config.resolve.alias['~'] = rootPath
  config.resolve.alias['@'] = rootPath

  return config
}
・Typescriptの使用設定

Typescriptを使用する場合に。素のjsでプロジェクトを記述している場合は無視してください。

 .storybook/webpackconfig.js
config.module.rules.push({
  test: /\.ts/,
  use: [
    {
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/],
        transpileOnly: true
      },
    }
  ],
});
・sass(scss)の使用設定、かつグローバルなCSS用変数やmixinを読み込む

Storybookで表示するコンポーネント内で、scssの変数やmixinを使用している場合はここで指定します。

 .storybook/webpackconfig.js
config.module.rules.push({
  test: /\.scss$/,
    use: [
      {
        loader: 'style-loader'
      },
      {
        loader: 'css-loader'
      },
      {
        loader: 'sass-loader'
      },
      {
        loader: 'sass-resources-loader',
        options: {
          resources: [ 
            './assets/scss/app.scss',
          ],
          rootPath
        }
      },
    ],
});
・css内からassets以下のファイルにアクセスできるように設定

assetsディレクトリ以下の画像やフォントにcssからアクセスできるように設定します。

 .storybook/webpackconfig.js
config.resolve.modules = [
  ...(config.resolve.modules || []),
  rootPath
];

基本的には、以上で設定は終わりです。

4. 簡単なストーリーの記述

MyButtonコンポーネントを表示する、ごく基本的なストーリーの記述方を書きます。
Component Story Format (CSF)での書き方は、実際に記述してみれば結構簡単かと思います。
ストーリーは.storybook/config.jsの中にある以下の記述によって、/stories以下の.stories.jsがついたファイルが自動で読み込まれます。

 .storybook/config.js
// automatically import all files ending in *.stories.js
configure(require.context('../stories/', true, /\.stories\.js$/), module)
sample.stories.js
import MyButton from '@/components/MyButton'

// コンポーネントのメタデータを記述
// アドオンのでコレータやパラメータなどもここに書く
export default {
  // スラッシュで区切ると、Atoms>Buttonと階層構造で表示できます
  // 他にも記述方法はあるが割愛。公式ドキュメントを確認してください
  // タイトルは一意であること
  title: 'Atoms/Button',
  decorators: [ ... ],
  parameters: { ... }
}

// Common Link Btnがストーリーの名前となります。
export const CommonLinkBtn = () => ({
  components: MyButton,
  // 適宜htmlなりを記述
  template: `
  <div id="storybook-container">
    <div class='component-wrapper'>
      <h5>MyButton</h5>
      <Button>{{ label }}</ButtonLink>
    </div>
  </div>
  `
})

// 下記のように名称を変更できます
// CommonLinkBtn.story = { name: 'hoge' }


代表的なaddonの追加

追加していくaddonは以下です。全てStorybookの公式です

アドオン 機能 github
addon-knobs 動的にpropsの値を変更して、コンポーネントの挙動を確認できる @storybook/knobs
actions onClickなどのUIアクションに対してのログを確認できる @storybook/addon-actions
notes text,html,マークダウンでコンポーネントの説明を追加できる @storybook/notes
viewport 表示サイズの変更 @storybook/viewport
a11y コンポーネントのアクセシビリティチェック @storybook/addon-a11y
backgrounds ストーリーの背景色変更 @storybook/addon-backgrounds

上記のアドオンをインストール&Storyに追加

$ npm i -D @storybook/knobs @storybook/addon-actions @storybook/notes @storybook/viewport @storybook/addon-a11y @storybook/addon-backgrounds

インストールしたアドオンをimportします。

 .storybook/config.js
// actionsとlinksはCLIによるインストール時に追加される。
// linksについては省略
import '@storybook/addon-links/register'
import '@storybook/addon-knobs/register'
import '@storybook/addon-actions/register'
import '@storybook/addon-notes/register'
import '@storybook/addon-viewport/register'
import '@storybook/addon-a11y/register'
import '@storybook/addon-backgrounds/register'

1. knobs

propsを設定したコンポーネントを用意します。

MyButton.vue
<template>
  <button :disabled="isDisabled">
    <slot></slot>
  </button>
</template>

<script>
export default {
  props: {
    isDisabled: {
      type: Boolean,
      default: false
    }
  }
}
</script>

ちなみにpropsだけでなく、slotの値の挿入などもOKです。

MyButton.story.js
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import MyButton from '@/components/MyButton.vue';

export default {
  title: 'Storybook Knobs',
  decorators: [ withKnobs ]
}

export const Button = () => ({
  components: { MyButton },
  props: {
    isDisabled: {
      // default: 与えるデータの型('ラベル', 初期値)
      default: boolean('Disabled', false)
    },
    text: {
      default: text('Text', 'Hello Storybook')
    }
  },
  template: `<MyButton :isDisabled="isDisabled">{{ text }}</MyButton>`
})

2. actions

下記のようなカスタムイベントを設定したコンポーネントを準備します。

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

<script>
export default {
  methods: {
    sampleEvent (e) {
      this.$emit('clicked', e)
    }
  }
}
</script>

上記をactionsを使用して表示するストーリーは以下。

MyButton.stories.js
import { action } from '@storybook/addon-actions'
import MyButton from '@/components/MyButton.vue';

export default {
  title: 'Storybook Actions',
}

export const PostBtn = () => ({
  components: { Button },
  // カスタムイベントが発火すると、以下のactionが実行されます。
  methods: {
    log: action('Post!')
  },
  template: `<ButtonPost @clicked="log" />`
})

actionが実行された様子
スクリーンショット 2019-11-26 19.55.42.png

3. notes

parametersにテキストなり、マークダウンファイルを指定すればOKです。

sample.stories.js
import markdownNotes from './someMarkdownText.md';

export default {
  title: 'Storybook Notes',
  // テキストの場合
  parameters: { notes: 'Some Notes' },
  // マークダウンファイルの場合
  parameters: { notes: { markdown: markdownNotes } }
}

__(省略)__

4. viewport

Storybook全体に適用する場合は、.storybook/config.jsに設定を追記します。
公式ドキュメントの例に沿って、Kindle端末の画面サイズを追加します。

 .storybook/config.js
import { addParameters } from '@storybook/vue';
// viewportが初期設定しているデバイスのパラメータリストを読み込む
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';

// Kindleのパラメータを設定
const newViewports = {
  kindleFire2: {
    name: 'Kindle Fire 2',
    styles: {
      width: '600px',
      height: '963px'
    }
  },
  kindleFireHD: {
    name: 'Kindle Fire HD',
    styles: {
      width: '533px',
      height: '801px'
    }
  }
}

// 初期パラメータと上記で設定したパラメータオブジェクトを追加
// ...INITIAL_VIEWPORTS,を再度追加しているのは表示順を
// INITIAL_VIEWPORTS > newViewportsにするため
addParameters({
  viewport: {
    viewports: {
      ...INITIAL_VIEWPORTS,
      ...newViewports,
    }
  }
})

// 上記の設定はconfigure(require.context~より前に記載すること
configure(require.context('../stories/', true, /\.stories\.js$/), module)

5. a11y

チェックしたいコンポーネントに読み込み、decolatorsに追加すればOK

sample.stories.js
import { withA11y } from '@storybook/addon-a11y'

export default {
  title: 'Sample',
  decorators: [ withA11y ]
}

__(省略)__

6. backgrounds

全体に適用する場合はconfig.jsに記述追加

 .storybook/config.js
import { addParameters } from '@storybook/vue'

addParameters({
  backgrounds: [
    { name: 'Sample BG 1', value: '#CCCCCC', default: true },
    { name: 'Sample BG 2', value: '#000000' },
  ],
});

// 上記の設定はconfigure(require.context~より前に記載すること
configure(require.context('../stories/', true, /\.stories\.js$/), module)

以上、私もまだ深いところまで触っていない&導入したばかりで間違い等あるかもしれませんが、基本的なところは抑えられているかと思います:pushpin:

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

【Nuxt.js / Storybook】インストールとaddonの導入まで(StoriesOf APIではなく最新ver推奨のCSF記法で)

新規でNuxt(Vue)のプロジェクトを立ち上げる機会があったので、初めてStorybookを導入してみました。
現在Storybookのバージョンは5.2.xですが、公式が推奨するストーリーの記述方式がComponent Story Format (CSF)に変更されたようです。
(v5.1以前はStoriesOf APIが一般的。v5.2でも問題なく使用できる)
Component Story Format (CSF)
StoriesOf API
初導入にあたり参考情報を探していたのですが、公式ドキュメントもネットの情報もStoriesOf APIでの記載例が多かったので(当たり前ですが)、良い機会だと思い基本的な情報ですがCSFでの記法でまとめでみました。

1.インストールと基本設定

Storybook for Vue
CLIを使用して半自動でインストールする方法と、必要なモジュールを自分で追加していく方法がありますが、CLIでインストールする方がやはり楽です。

1.1 NuxtプロジェクトにStorybookをCLIでインストール

CLIを使用すると雛形のStoryファイルが生成されるのでそれを見るだけでも基本的な使用方法はわかると思います。

$ npx -p @storybook/cli sb init

上記の書き方で、プロジェクトが何のフレームワークで構成されているか自動で判定して必要なモジュール群をインストールしてくれます(心配な場合はオプションでフレームワーク名を追加してください。--type vue)
作ったばかりのNuxtプロジェクトですが、以下のモジュールが追加されました。

+ @babel/core@7.7.2
+ babel-loader@8.0.6
+ babel-preset-vue@2.0.2
+ @storybook/addons@5.2.6
+ @storybook/addon-actions@5.2.6
+ @storybook/addon-links@5.2.6
+ @storybook/vue@5.2.6

さらに、storybookの設定ファイルなどを格納する.storybookとストーリーファイルを格納するstoriesの2つのディレクトリも同時に生成されます。

1.2 package.jsonの設定

実行する際のコマンドを追加します。が、CLIでインストールするとこの記述も追加されていますので、特に編集はいりません。Dockerなどを使用している関係でポート番号を変更したいなどありましたら編集してください。

"scripts": {
  "storybook": "start-storybook -p 6006",
  "build-storybook": "build-storybook"
}

これで、下記コマンドによる実行ができるようになりました。

$ npm run story

1.3 Storybook用webpackの設定

StorybookはNuxtなどと実行環境が別(この言い方で良いのか..?)なので、専用にwebpackの設定も行う必要があります。以下のファイルを編集していきます。CLIによって自動生成されるはずですが、なかったら追加してください。
.storybook/webpackconfig.js

1.3.1 Nuxtプロジェクトで使用している@~などのエイリアス設定

これをしないとコンポーネントの読み込みなどでエイリアスを使用している場合にエラーになるので追記しましょう。

 .storybook/webpackconfig.js
const path = require('path')
const rootPath = path.resolve(__dirname, '../')

module.exports = ({ config }) => {

  config.resolve.alias['~'] = rootPath
  config.resolve.alias['@'] = rootPath

  return config
}

1.3.2 Typescriptの使用設定

Typescriptを使用する場合に。素のjsでプロジェクトを記述している場合は無視してください。

 .storybook/webpackconfig.js
config.module.rules.push({
  test: /\.ts/,
  use: [
    {
      loader: 'ts-loader',
      options: {
        appendTsSuffixTo: [/\.vue$/],
        transpileOnly: true
      },
    }
  ],
});

1.3.3 sass(scss)の使用設定、かつグローバルなCSS用変数やmixinを読み込む

Storybookで表示するコンポーネント内で、scssの変数やmixinを使用している場合はここで指定します。

 .storybook/webpackconfig.js
config.module.rules.push({
  test: /\.scss$/,
    use: [
      {
        loader: 'style-loader'
      },
      {
        loader: 'css-loader'
      },
      {
        loader: 'sass-loader'
      },
      {
        loader: 'sass-resources-loader',
        options: {
          resources: [ 
            './assets/scss/app.scss',
          ],
          rootPath
        }
      },
    ],
});

1.3.4 css内からassets以下のファイルにアクセスできるように設定

assetsディレクトリ以下の画像やフォントにcssからアクセスできるように設定します。

 .storybook/webpackconfig.js
config.resolve.modules = [
  ...(config.resolve.modules || []),
  rootPath
];

基本的には、以上で設定は終わりです。

1.4 簡単なストーリーの記述

MyButtonコンポーネントを表示する、ごく基本的なストーリーの記述方を書きます。
Component Story Format (CSF)での書き方は、実際に記述してみれば結構簡単かと思います。
ストーリーは.storybook/config.jsの中にある以下の記述によって、/stories以下の.stories.jsがついたファイルが自動で読み込まれます。

 .storybook/config.js
// automatically import all files ending in *.stories.js
configure(require.context('../stories/', true, /\.stories\.js$/), module)
sample.stories.js
import MyButton from '@/components/MyButton'

// コンポーネントのメタデータを記述
// アドオンのでコレータやパラメータなどもここに書く
export default {
  // スラッシュで区切ると、Atoms>Buttonと階層構造で表示できます
  // 他にも記述方法はあるが割愛。公式ドキュメントを確認してください
  // タイトルは一意であること
  title: 'Atoms/Button',
  decorators: [ ... ],
  parameters: { ... }
}

// Common Link Btnがストーリーの名前となります。
export const CommonLinkBtn = () => ({
  components: MyButton,
  // 適宜htmlなりを記述
  template: `
  <div id="storybook-container">
    <div class='component-wrapper'>
      <h5>MyButton</h5>
      <Button>{{ label }}</ButtonLink>
    </div>
  </div>
  `
})

// 下記のように名称を変更できます
// CommonLinkBtn.story = { name: 'hoge' }


2. 代表的なaddonの追加

追加していくaddonは以下です。全てStorybookの公式です

アドオン 機能 github
addon-knobs 動的にpropsの値を変更して、コンポーネントの挙動を確認できる @storybook/knobs
actions onClickなどのUIアクションに対してのログを確認できる @storybook/addon-actions
notes text,html,マークダウンでコンポーネントの説明を追加できる @storybook/notes
viewport 表示サイズの変更 @storybook/viewport
a11y コンポーネントのアクセシビリティチェック @storybook/addon-a11y
backgrounds ストーリーの背景色変更 @storybook/addon-backgrounds

上記のアドオンをインストール&Storyに追加

$ npm i -D @storybook/knobs @storybook/addon-actions @storybook/notes @storybook/viewport @storybook/addon-a11y @storybook/addon-backgrounds

インストールしたアドオンをimportします。

 .storybook/config.js
// actionsとlinksはCLIによるインストール時に追加される。
// linksについては省略
import '@storybook/addon-links/register'
import '@storybook/addon-knobs/register'
import '@storybook/addon-actions/register'
import '@storybook/addon-notes/register'
import '@storybook/addon-viewport/register'
import '@storybook/addon-a11y/register'
import '@storybook/addon-backgrounds/register'

2.1 knobs

propsを設定したコンポーネントを用意します。

MyButton.vue
<template>
  <button :disabled="isDisabled">
    <slot></slot>
  </button>
</template>

<script>
export default {
  props: {
    isDisabled: {
      type: Boolean,
      default: false
    }
  }
}
</script>

ちなみにpropsだけでなく、slotの値の挿入などもOKです。

MyButton.story.js
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import MyButton from '@/components/MyButton.vue';

export default {
  title: 'Storybook Knobs',
  decorators: [ withKnobs ]
}

export const Button = () => ({
  components: { MyButton },
  props: {
    isDisabled: {
      // default: 与えるデータの型('ラベル', 初期値)
      default: boolean('Disabled', false)
    },
    text: {
      default: text('Text', 'Hello Storybook')
    }
  },
  template: `<MyButton :isDisabled="isDisabled">{{ text }}</MyButton>`
})

2.2 actions

下記のようなカスタムイベントを設定したコンポーネントを準備します。

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

<script>
export default {
  methods: {
    sampleEvent (e) {
      this.$emit('clicked', e)
    }
  }
}
</script>

上記をactionsを使用して表示するストーリーは以下。

MyButton.stories.js
import { action } from '@storybook/addon-actions'
import MyButton from '@/components/MyButton.vue';

export default {
  title: 'Storybook Actions',
}

export const PostBtn = () => ({
  components: { Button },
  // カスタムイベントが発火すると、以下のactionが実行されます。
  methods: {
    log: action('Post!')
  },
  template: `<ButtonPost @clicked="log" />`
})

actionが実行された様子
スクリーンショット 2019-11-26 19.55.42.png

2.3 notes

parametersにテキストなり、マークダウンファイルを指定すればOKです。

sample.stories.js
import markdownNotes from './someMarkdownText.md';

export default {
  title: 'Storybook Notes',
  // テキストの場合
  parameters: { notes: 'Some Notes' },
  // マークダウンファイルの場合
  parameters: { notes: { markdown: markdownNotes } }
}

__(省略)__

2.4 viewport

Storybook全体に適用する場合は、.storybook/config.jsに設定を追記します。
公式ドキュメントの例に沿って、Kindle端末の画面サイズを追加します。

 .storybook/config.js
import { addParameters } from '@storybook/vue';
// viewportが初期設定しているデバイスのパラメータリストを読み込む
import { INITIAL_VIEWPORTS } from '@storybook/addon-viewport';

// Kindleのパラメータを設定
const newViewports = {
  kindleFire2: {
    name: 'Kindle Fire 2',
    styles: {
      width: '600px',
      height: '963px'
    }
  },
  kindleFireHD: {
    name: 'Kindle Fire HD',
    styles: {
      width: '533px',
      height: '801px'
    }
  }
}

// 初期パラメータと上記で設定したパラメータオブジェクトを追加
// ...INITIAL_VIEWPORTS,を再度追加しているのは表示順を
// INITIAL_VIEWPORTS > newViewportsにするため
addParameters({
  viewport: {
    viewports: {
      ...INITIAL_VIEWPORTS,
      ...newViewports,
    }
  }
})

// 上記の設定はconfigure(require.context~より前に記載すること
configure(require.context('../stories/', true, /\.stories\.js$/), module)

2.5 a11y

チェックしたいコンポーネントに読み込み、decolatorsに追加すればOK

sample.stories.js
import { withA11y } from '@storybook/addon-a11y'

export default {
  title: 'Sample',
  decorators: [ withA11y ]
}

__(省略)__

2.6 backgrounds

全体に適用する場合はconfig.jsに記述追加

 .storybook/config.js
import { addParameters } from '@storybook/vue'

addParameters({
  backgrounds: [
    { name: 'Sample BG 1', value: '#CCCCCC', default: true },
    { name: 'Sample BG 2', value: '#000000' },
  ],
});

// 上記の設定はconfigure(require.context~より前に記載すること
configure(require.context('../stories/', true, /\.stories\.js$/), module)

以上、私もまだ深いところまで触っていない&導入したばかりで間違い等あるかもしれませんが、基本的なところは抑えられているかと思います:pushpin:

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

はじめてのVue.js

概要

社内の技術共有会で使用したもの。
自分の勉強も兼ねつつ、Vueを触ったことない方向けに作成したものです。

What's Vue.js ?

  • jsのフレームワークのひとつ
    • 他だとAngular, Reactあたりが有名
  • プログレッシブフレームワークである。
    • モノリシックではないので、開発レベルに合わせて段階的に導入することが可能
  • ECMAScript5対応ブラウザで使用してね
    • 骨董品みたいなブラウザで使用するのは厳しいです
    • せめてIE9から IE8以下? 知らない子ですね

Hello World

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title> わくわくVue </title>
<!-- 公式公開されているVue.js を読み込む -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>

    <div id="app">
        <p>{{ message }}</p>
        <button v-on:click="displayMsg">Click!!</button>
      </div>

<script>
const app = new Vue({
  el: '#app',
  data: {
    message: ''
  },
  methods: {
    displayMsg : function () {
      this.message = "Hello World!!";
    }
  }
})
</script>

</body>
</html>

上記htmlを実行すると味気ないボタンが出ます。ボタン押下で「こんにちは世界」。

上記コードについて

const app = new Vue({
  el: '#app', // 1
  data: {
    message: '' // 2
  },
  methods: {
    displayMsg : function () { // 3
      this.message = "Hello World!!";
    }
  }
})

js側

  • 1 : el
    • html上でVueインスタンスの管理下に置く要素。セレクタで指定。
  • 2 : data
    • Vueインスタンスがもつプロパティ
    • v-bind, v-model等でバインディングされるデータを保持
  • 3 : methods
    • Vueインスタンスが保持するメソッド
    • イベントのハンドリングを行う際に使用。
    • methodsオブジェクト下にメソッド名 : 関数で定義。
    • アロー関数には気を付けろ、、、
<div id="app"> // 1
<p>{{ message }}</p> // 2
<button v-on:click="displayMsg">Click!!</button> // 3
</div>

html側

  • 1 : id="app"
    • Vueインスタンスで管理下に置いた要素。
  • 2 : {{ message }}
    • データバインディングの対象となる項目。今回はappインスタンスのmessageとバインディング。
    • Mustache構文(二重中括弧)で記載すること。
  • 3 : v-on:click="displayMsg"
    • v-で始まる特別な記法をディレクティブと呼ぶ。属性値の変化があったときに作用する。
    • 今回のv-onはdomイベントの受け取りを実施。:の後に記述されたイベントに対しバインディングする。 -v-on:clickでクリックされた時にdisplayMsgメソッドを実行。
    • v-on:click@clickと略すことも可能。

その他の主なVueのディレクティブ

v-text

任意の文字列オブジェクトを指定要素に展開する

<span v-text="msg"></span>
<span>{{msg}}</span>

は両方同じ

v-html

<span>hoge</span>みたいな文字列を単なる文字列ではなく生htmlとして展開したいときに使用する

脆弱性とかを生むものでもあるのでご利用は計画的に

v-show

評価式に応じて指定要素のCSSプロパティdisplayに作用する

<div v-show="hoge">ほげ</div> <!-- hogeがtrueなら表示 -->

v-if, v-else, v-if-else

評価式に応じて指定要素を描画するか決定する

v-showと違って要素すら描画しない!! 用途に合わせて使い分けるのが吉。

<div v-if="hoge">  <!-- hogeがtrueなら描画 -->
  ほげ
</div>
<div v-else-if="fuga">  <!-- fugaがtrueなら描画 -->
  ふが
</div>
<div v-else> <!-- hoge,fugaがfalseなら描画, v-if, v-else-ifの直後でない場合は認識されないです -->
  not ほげふが
</div>

v-for

配列、オブジェクトを元にループを実行し要素を複数回描画する

<html>
<ul id="example">
  <li v-for="hoge in hoges">
    {{ hoge }} <!-- ほげ1、ほげ2がリスト形式で描画される -->
  </li>
</ul>
<script>
const hogeVue = new Vue({
  el: '#example',
  data: {
    hoges: ['ほげ1', 'ほげ2']
  }
})
</script>
</html>

所感

関わっている案件で使用していますが、ちゃんと使い分けとか考えてみると面白いと感じました。
次回の社内共有会ではコンポーネントやライフサイクルあたりを話そうかなあと思ってます。僕の知識が足りなくて今回の社内共有会でうまくまとめられなかったのは内緒
これからもぼちぼち頑張ろうと思います。

参考

Vue公式様
とほほ様

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

Nuxt.jsで文字列からコンポーネントを描画してSSRする

この記事では"<template><MyComponent /></template>"のような文字列データを、Vueコンポーネントを正しく解釈した上で画面上に描画してSSRすることを目指します。

これを実現したい主要なモチベーションとしては、ブログ投稿を管理するシステムを構築するシーン等を想定してもらうとわかりやすいかもしれません。「 ブログの投稿をVueの文法で書きたい 」かつ「 投稿は検索エンジンにインデックスされてほしい 」ようなケースをこの記事では想定しています。

HTML文字列をVueのテンプレートとして描画する

HTMLを記述した文字列オブジェクトをそのまま描画するだけであればv-htmlで実現することができます。しかし、ここではHTML文字列中に<MyComponent>が含まれていることが問題です。これをそのままDOMに挿入しても、当然ブラウザが正しく解釈することはできません。

ここでは、以下のような単純なブログページを作るものとします。

pages/blog/index.vue
<template>
  <header></header>
  <main>
    <!-- 
    ここに
    "<article>
      <MyComponent />
    </article>"
    みたいな文字列を挟んで、正しくレンダリングしてほしい
    -->
  </main>
  <footer></footer>
</template>

この問題に関しては自前でrenderをゴリゴリ書いてもよいですが、v-runtime-templateというライブラリが広く使われているので、これを用いることで解決できます。

(導入方法等の詳細はREADMEに記載があるので省略します)

v-runtime-templateを用いて文字列からコンポーネントを描画する例は以下の通りです。

pages/blog/index.vue
<template>
  <header></header>
  <main>
    <v-runtime-template :template="template" />
  </main>
  <footer></footer>
</template>

<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'
import VRuntimeTemplate from 'v-runtime-template'
import MyComponent from '~/components/MyComponent.vue'

@Component({
  components: {
    VRuntimeTemplate,
    MyComponent
  }
})
export default class extends Vue {
  template = '<article><MyComponent /></article>'
}
</script>

これで(一番単純な例ですが)文字列をVueのテンプレートとして解釈して正しく描画することができました。
ここではtemplateへ値を直接代入していますが、実際にはasyncDatafetch内でサーバー応答をバインドすることによって問題なくSSRできます。問題になるのは子コンポーネントであるMyComponentの描画にサーバー上の情報などコンポーネントの外の情報を使う必要がある場合で、これを正しくSSRするにはもう少し考慮が必要になります。

v-runtime-templateで解決されるHTMLをSSRする

以下のようにMyComponentが内部でサーバー呼び出しを行うようなケースではSSRされません。(Nuxtを使うプロジェクトではあまりこういう設計にならない気もしますが)

components/MyComponent.vue
<template>
  <div>
    {{ content }}
  </div>
</template>

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

@Component({})
export default class MyComponent extends Vue {
  content = ''

  async created() {
    this.content = await axios.get('/from/server')
  }
}
</script>

createdフックが動作するのはクライアントサイドですのでこの方針ではSSRは動作しません。サーバー側で処理が行われる(ページコンポーネントの)asyncDatafetchの中で値を設定する必要があります。

方針1: propsで渡す

ページコンポーネントのasyncDataで取得した値を子コンポーネントのpropsに渡せばSSRが動作します。

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

@Component({})
export default class MyComponent extends Vue {
  @Prop()
  content = ''
}
</script>
pages/blog/index.vue
<script lang="ts">
import axios from 'axios'
import { Vue, Component } from 'nuxt-property-decorator'
import VRuntimeTemplate from 'v-runtime-template'
import MyComponent from '~/components/MyComponent.vue'

@Component({
  components: {
    VRuntimeTemplate,
    MyComponent
  }
})
export default class extends Vue {
  async asyncData() {
    // templateの文字列の中にバインドを書く
    template = '<article><MyComponent :content="content" /></article>'
    content = await axios.get('/from/server')
    return {
      template,
      content
    }
  }
}
</script>

ただし、これではコンポーネントの引数体系に合わせてテンプレートの内容を書き換える必要があるため、テンプレートのメンテナンス性が高くありません。
また、1階層の子コンポーネントであればこの程度ですが、ネストしたコンポーネントを考えるとpropsのバケツリレーが大変なことになりそうです。

方針2: ストアで渡す

Vuexを使用可能な環境であればこの方針が一番管理しやすいと思います。

components/MyComponent.vue
<script lang="ts">
import { Vue, Component } from 'nuxt-property-decorator'

@Component({})
export default class MyComponent extends Vue {
  content = this.$store.getters.content
}
</script>
pages/blog/index.vue
<script lang="ts">
import axios from 'axios'
import { Vue, Component } from 'nuxt-property-decorator'
import VRuntimeTemplate from 'v-runtime-template'
import MyComponent from '~/components/MyComponent.vue'

@Component({
  components: {
    VRuntimeTemplate,
    MyComponent
  }
})
export default class extends Vue {
  async asyncData() {
    // ストアを使えばpropsをバインドする必要がない
    template = '<article><MyComponent /></article>'
    content = await axios.get('/from/server')
    this.$store.commit('content', content)
    return {
      template
    }
  }
}
</script>

以上のようにしてHTMLのテンプレート文字列をVueのコンポーネントとして描画し、SSRを行う事ができました。

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

[Vue.js]フォームからデータ登録後にフォームを初期化する

データをフォームから送信後データが残っていると、同じデータを登録してしまう再度可能性が
あるため登録後はフォームを初期化する実装をしました。

コードは非同期でレスポンスが帰ってきた箇所のみしてあります。

vue.js
...
<script>

methods:

.....
save() {
this.save
response => {
this.dataReset() //レスポンスが返ってきたらデータを初期化
}

//初期化のデータを呼び出すメソッド
dataReset() {
 this.save.title = null,
 this.save.body = null
}

</script>

空のデータがセットされたメソッドを呼び出すことで登録後はフォームが初期化するようになります。

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

『Laravel + Vue.js』プロジェクト導入前の3つの心得

はじめに

Vue #2 Advent Calendar 2019 の7日目の記事です

この記事は
企業レベルで『プロジェクトとしてVue.jsを導入してみての感想』の共有を行うことが目的です、
そしてその中でも私の感じた『Laravel + Vue.jsプロジェクト導入前の3つの心得』についてご紹介致します。
記事内容に、間違いなどございましたらコメント欄でやさしくリプライしていただければ嬉しいです(^^♪
今回初投稿なので・・・

前提条件

・ウェブフレームワークは既にLaravelを使用
・プロジェクト目的は、速度改善、脱スパゲッティーコード化、新規技術導入などを含む
・具体的なプロジェクト内容は、スマホのウェブページ、コンポーネント化
・Vue.jsの単一コンポーネントの利用(CDNではない)

TL;DR

上記の条件下で私の感じた3つの心得

その1:JSのバンドルサイズ意識しよう
その2:ライブラリ導入の選定は早めに行おう
その3:なんでもVue.jsでやろうとせずスコープを決めよう

では詳しく紹介していきます

心得その1:JSのバンドルサイズを意識してコーディングしよう

単一コンポーネントを利用する際、Laravelでは「laravelmix」と呼ばれるwebpackの進化系みたいなもの(warpper)を利用してアセットのコンパイルを行わないといけないのですが、
この際、バンドルされたJSファイルがすぐに肥大化するので最初から意識してコーディングしてねという話です。

laravemixの設定ファイルはデフォルトでは以下みたいな感じになっています(Laravel 5.x系)

webpack.mix.js
const mix = require('laravel-mix');

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');

webpack知識の重要性

事前に「laravelmix抜き」の"本来のwebpackでVue.js"を使用する場合は、利用者さんがきちんとwebpackを理解してVue.jsを利用している可能性が高いかもしれませんが
あなたがもし今回初めてVue.jsを利用するVue.js初学者さんでwebpack利用経験がないのであれば、
「laravelmixで利用するwebpack」が別物だということを心得ておく必要があるかもしれません。

実際Laravelで利用するlaravelmixは、webpackへの詳細な知識がなくてもある程度簡単に動作してくれます。
しかしwebpackについて理解なしにwebpackのwrapperのlaravelmixから利用するのは危険です。
あなたがもしwebpackの知識をきちんと理解しているのであればこの設定ファイルの設定見直しだけでもページの速度改善になります。
ちなみにwebpackの設定はlaravelmixより詳細に設定項目があり、より細かに調整ができます。

バンドルサイズ肥大化の原因

webpack.mix.js
mix.js('resources/js/app.js', 'public/js')
    .sass('resources/sass/app.scss', 'public/css');

上記の設定ファイルの中を見てみましょう。
laravelmixでバンドルする対象のファイルの記述が設定されていますね。
(resources/js/app.jsがVue.jsのVueインスタンスファイルにあたります。)
こちらのファイルが1ファイルにバンドルされて、public/js配下にコンパイルされます。(npm run~コマンド実行時)
肥大化する原因は私なりですが以下の2つのいずれかです。

原因1:ライブラリの追加しすぎ
バンドルしたJSファイルはデフォルトのままで既に2万行近くあるので、そのままではとても重いです。
加えて使いたいライブラリなどが追加されれば、追加した分だけバンドルサイズが大きくなっていきます。

原因2:Atomic Designを細かく分けすぎる
今回のプロジェクトでは部品化する際のデザインパターンを「Atomic Design」に則り進めたのですが、部品を細かくしすぎてもあまりよくありません。Atomレベルでさえ1つの.vueファイルで3KBくらいはあるのでコンパイル時にバンドルするJSサイズが肥大化する原因になってしまうのです。よって、再利用性の少ない「Atomレベルは作成しない」などルールを決めてもよいかもしれません。

おまけ:extract設定してライブラリファイル分割する方法

webpack.mix.js
mix
   .js("resources/assets/js/app.js", "public/js") 
   .extract(["vue", "axios", "vuex"])//bootstrap.js内でimportしているライブラリー名を記述
   .version(); 

バンドルで1ファイルになるとお伝えしましたが、laravelmixではextractに記述するだけでライブラリファイル分割可能です。
extractにライブラリ指定してあげれば、指定したライブラリ群がバンドルJSファイルから外にだされ、別ファイルとして(manifest, vendor.js)分割できます。このような設定はlaravelmixのアセットコンパイル公式の各バージョン毎に記述の仕方が書いてあります。Vue.jsをLaravelで書き始める前にぜひご一読ください。

バンドルしたJSファイルサイズ目安

JSのバンドルサイズの目安は一休.comさんのスマホサイトのパフォーマンスについてのブログにも記載がありますが、
"一休"さんのスマホサイトのプロジェクト時では、1ページ300KBを上限にして、それ以下に抑えるようにコーディング意識していたりします。

私のプロジェクトではこのようなルール決めを一切行いませんでした。結果、バンドル後のJSファイルがかなり肥大化してしまい重いスマホサイトが出来上がってしまいました。( ;∀;)

解消策:より具体的な目標を立てる

プロジェクト開始前にメンバー間で、具体的なバンドルサイズ目標をたてることが効果的です。1ページ300KB以下のバンドルJSファイルと残りのリソース700KBまでに抑えるなどより具体的な数値を設定しましょう。
他には、『Googleのpage speed insights(速度計測ツール)で70点以上を獲得する』などでもよいかもしれません。大事なのは達成できなさそうだとしても「意識すること」です。そのための目標の設定は重要かと思います。

ちなみに
グーグル推奨値のリソースの合計サイズが1.6MBとの発表の記事を以前読んだことがあります。これは3G回線のモバイル端末でストレスなくアクセスできる最低の基準です。(実際には1.6MBでもまだ重い気がするので。)1.6MBは全体のリソースサイズなので、この中でJSはさらに小さく抑えるべきです。こういった基準値をプロジェクト前に調べてルール決めを行い認識合わせすることは大事です。
せっかく導入したのなら、ページが重くならないように気を付けましょう。

心得その2:ライブラリ導入の選定は早めにしよう

これもかなり悩ましかったのですが、「とりあえずVue導入して、それからあとのこと考えればいいさ」みたいに考えていたので
Vue.js導入を最優先したのですが(ダメ人間)
導入前のライブラリの選定はものすごく大事です。選定を間違えると、先ほどの話のJSのバンドルサイズの肥大化につながります。

加えて後から導入すると改修がより困難なものなどもあるので早めに検討してから導入したほうが良いでしょう。
ライブラリ選定も設計の一部だと心得てきちんと選定しましょう

初めから導入したかったライブラリ

・vuex(状態管理ライブラリ)
・vue-cli(cli)
・storybook(UIコンポーネント管理系)
・vue-router(ルーティング系)
・SPAも可能ならNuxt.js

中でもvuexは最初に導入しておけば手戻りが発生しなかったかと後悔しています。
propsでの受け渡しが親子孫までの関係のあるモジュールを作成した場合、vuexを導入した方がより簡単に人為的ミスを無くしてスマートにコードを書くことができると感じました。しかしvuexも完璧ではなく、全てvuex化してしまうと逆に重くなってしまったりするので、トレードオフを忘れずに。
vue-cliについてはあればおそらく前準備をコマンドライン入力で簡単にはじめられたことでしょう・・・参考サイト:『これでわかるはじめてのvue-cli』
storybookが初めからあればデザイナーとのコンポーネントのデザイン及び設計についてのやり取りがもっとスムーズになったことでしょう・・・参考サイト:Qiita記事『Storybook for Vue 入門』
vue-routerがあれば遅延ローディングルートを利用して各ルートコンポーネントごとに別々にチャンクして、訪れたルートの時だけロードできるようにルーティングできたでしょう・・・参考サイト:公式リファレンス『Vue Router』

おまけ:一般的にWeb関連の企業で導入されているnode_modulesのライブラリ群

株式会社LINE様のVue.jsの勉強会で行われたUIT#5イベントでは多くの企業が以下のライブラリを導入してVue.jsのWebページなどを実装していました。参考までに共有します。
・vue-router
・vue-cli
・storybook
・vue-whiteroom(storybookに変わるライブラリ)
・Nuxt(SPA化など検討する場合のVue.jsのライブラリ)

イベント情報詳細:UIT#5 わたしたちにとってのVue.js

解消策:プロジェクトスタート前にライブラリ調査・選定

vuexの導入を例に挙げたように使う可能性があるライブラリ群は早めに決めておきましょう。ただし、不要なライブラリは取り除かないとすぐに肥大化します。必要か不必要か今一度メンバーで確認し必要最低限のライブラリ選定を心がけ、同じようなライブラリが存在するか確認、もし存在すれば、より軽いサイズの代用ライブラリを導入しましょう。

又、ライブラリ自体をバンドルJSファイル外で読むこともできるので「バンドルに何を含めるのか」よく考えて選定してください。

心得その3:なんでもVue.jsでやろうとせずスコープを決めよう

今回のVue.jsの導入はLaravelでのVue.js導入だったのですが、プロジェクト開始直後私の頭の中では『新しい技術の良さを他のエンジニアに理解してもらう』ことに比重を置いていたので、Vue.jsをたくさん利用しようと考えていました。広める為にできるだけVue.jsでコーディングしようとしていたのです。その結果、本来Laravel側で事足りるものですらVue化してしまいました。JSバンドルサイズは3万行を超え、SPの初期読み込み時間はtestmysiteで(3Gで)10秒ほども出てしまうほど重いものになりました。JSのファイル数を減らせば読み込みが早くなると思っていた私ですが、1ファイルが非常に長い場合も同様にペナルティーになってしまいます。なんでもVue.jsでやろうとしてしまうと結果ページが重くなることさえありうるのです。(私の設計ミスの可能性もありますが・・・)

プロジェクトが進んでいくにつれ私の作成したVue.jsページはただ単に重い不便なページになっていました。( ;∀;)
ではどうすればよかったのでしょうか・・・

私が今回、具体的に失敗した点は、
Laravelで利用できるインクルードなどを捨てすべてVue.jsのコンポーネントに修正していたところにあります。既存の動いているコードはどんなコードであれ資産です。なんでも改修しようとするのでなく、不便さや不要箇所、エラーやバグがある場合のみ改修すればよかったのかもしれません。
既にLaravelのインクルードで実装されていたヘッダー部分の共通部品までVue化を行い、ほかにもフッターなどのすでにあるモジュールですらすべてVueのコンポーネントにしてしまったのです。プロジェクトの目的にもよるとは思うのですが、パフォーマンスの観点でいうと部品が増えればバンドルしたJSファイルのサイズも比例して大きくなるので動いているモジュールすべてをスコープに含むことは間違っていたかもしれません。
Vue.jsの実装パターンはいろいろあるということを忘れてはいけません。
ページ全体をVue化してSPA化したり、一部品だけをVue化したり。
これはすべてに言える話なのですが『大事なのは何の目的で、そのライブラリ、又はそのフレームワークを利用しているのか念頭に置いてコーディングすること』です

解消策:Vue.js適用範囲のスコープを決める

『どれがLaravel側で行うべきこと』で、『どれがVue.js側で行うべきこと』かということを意識する。これらは、おそらく「心得レベル」の話ではなく、「設計レベル」の話になってしまうかと思われます。しかし、Vue.jsをプロジェクトに導入する際は、適用範囲のスコープを決めて実装することが重要だと感じました。加えて、スコープの妥当性、適用範囲の妥当性についても考えましょう。
せっかくLaravelでVue.jsを使っているのに、Laravelの良さを捨てて何でもかんでもVue化してしまうのはもったいないこと。
Vue化する際は必ず、よりいい方法をトレードオフすることがとても重要だと強く感じました。

結論

その1:バンドルサイズを意識したコーディングを心がけないと重いサイトになる
その2:ライブラリ選定を早めにしないと手戻りが発生する
その3:Vue.jsでなんでもやろうとすると保守しにくいサイトになる

総じて
Vue.jsのプロジェクト導入を検討しているそこのアナタ!簡単に導入できるからと言って侮らないこと!

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

LaravelにESLintとPrettierを導入する時にやったことの全て

はじめに

この記事ではLaravel5.8ESLintPrettierを導入します。

LaravelのMVCモデルでは、Viewの部分は *.blade.phpファイルによって書くことが一般的です。
しかし最近では、JavaScriptの描画技術の幅やページの描画スピードの速さなどを理由として、bladeは完全に捨ててVue.jsにフロントエンドの全てを任せる構成をとるプロジェクトが多いと感じています。

今回、ESLintを導入する対象となったLaravel5.8のプロジェクトは僕がPMをしているプロジェクトです。

このプロジェクトでも、Laravelのbladeはほとんど使用しておらず、画面描画は resourses/js/に置かれているVue.jsによって行っています。

今回の記事の対象者は、LaravelでVue.jsを使っているor使いたい方全てです。

なぜESLintとPrettierを使うべきなのか

「なぜESLintPrettierを併用して使うべきなのか」ということについては、Laravelに限らず、広く議論されていることですので、ここでは触れません。
この議論の結論を言えば、「ESLintだけ導入」あるいは「Prettierだけ導入」というのは諸事情があって微妙だからどちらも使おうね、ということです。

以下ではESLintPrettierを導入するにあたり、僕が感じている利点について触れておきます。

集団開発でコードフォーマットが統一されていないのはかなりしんどい

「何を綺麗と感じるか」は人それぞれです。
しかし、一方で個々の感性が異なるために、しばしば地獄をみることになります。

  • 「あれ?末尾のセミコロンは前全部消したはずなのにまたAさんが足し直してる、、、」
  • 「キャメル?パスカル?命名規則が混在している、、、」
  • 「シングルクォーテーションとダブルクォーテーションが混在している、、、」

これを十人十色などと言ってはいけません。阿鼻叫喚です。
コードのフォーマットや変数の命名規則が理由で議論が始まったりプルリクエストが通らないとしたら、せっかくLaravelを使っているのにあなたの開発スピードは早いとは言えなくなりませんか?

  • フォーマットや変数の命名規則は開発者間で統一する
  • 開発者ごとのフォーマット調整による余計な差分はなくし、コードレビューの可読性をあげる

この重要性を理解しましょう。コードレビューは常にロジックにのみ集中するべきです。

1人で開発するからといってフォーマットが煩雑なのは許されない

僕の価値観では、コードというものは「誰が見ても美しくあるべき」です。あなたがフォーマッターを使わずに書いたコードは、他人が見ても必ず美しいと思えるでしょうか。また、開発者が感じる「美しさ」は誰が保証してくれるのでしょうか?

「ソースコードは1人で扱うから」とは言っても、いつかもう1人のメンバーが加わる場合もあります。
そう言った際に無駄なコストを払わないように、1人だとしてもフォーマットには基準を設けて整形しておくのがマナーだと僕は考えています。

ESlintPrettierは非常に優秀なフォーマッターなので、「ESLintとPrettierが共通のルールによって整形してくれたのだから信じよう」と開発者は思うのです。

いつESLintとPrettierを導入するのか

上記で「ESLintPrettierを導入しなさい」と布教したのですが、僕も最近になってようやくこれらのフォーマッターを導入したのです。

実は、ESLintPrettierを導入するまでにGitHubでのcommitは500を超えており、そこそこの規模のプロジェクトになっていました。ですから、かなりの数の *.js *.vueファイルが存在していました。

導入パターン

ESLintを全てのファイルにかける(=現存する*.jsファイルや*.vueファイルを一気に整形する)場合、非常にコンフリクトが発生しやすくなります。

対象の全てのファイルの全体に対して改行ポイントやスペース、コンマや命名規則、変数宣言や比較演算子などありとあらゆるコードを修正することを想像すれば「コンフリクトが発生しやすい」というのは火を見るより明らかです。そして、そのコンフリクトは激しくなりがちであることも理解できるでしょう。

ESLintを導入するタイミングはおおよそ次のパターンに分類できます。

  1. 今からLaravelをセットアップするあなた
    →後悔はさせないので、いますぐ導入しましょう。最もEasyです。このタイミングで気づいたあなたは偉い!

  2. すでにそこそこの規模になっているあなた
    →プロジェクト全体で大きな変更がないタイミングを伺いましょう。そこそこの努力と忍耐が必要です。

  3. かなりの規模になっていて集団開発が盛んになっているあなた
    →この時点で何かしらのフォーマッターを入れていないのはまずい状況ですが、導入のタイミングも難しいです。大量のコンフリクトを覚悟して慎重にタイミングを見計らいます。開発者全体で打ち合わせなどをする必要があるかもしれません。

僕は2番に該当していたつもりですが、それでも一気にESLintを全てのファイルにかけて本当に血の海を見ました。全ファイルが血を流して倒れました。

それでもなんとかコンフリクトが少なくなるようなタイミングを見計って行えば、ギリギリ耐えます。
あなたのフォーマッターへの想いだけを信じてください。

ESLintとPrettierを導入する

やっと本題です。しかしやることはそんなに多くありません。

今回は、プロジェクトの途中からESLintPrettierを導入します。(上記での2or3番を想定しています。1番でももちろん全く問題なし!)
ここから先はLaravelがどうこうというより、ESLintPrettier自体をよく理解していることが重要です。

パッケージをインストールする

今回必要なパッケージは以下になります。たくさんあります。
以下のパッケージは開発時にしか必要ありませんので --save-devオプションをつけてインストールしてあげます。

$ npm install babel-eslint eslint eslint-config-prettier eslint-config-standard eslint-friendly-formatter eslint-loader eslint-plugin-html eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-node eslint-plugin-prettier eslint-plugin-promise eslint-plugin-standard eslint-plugin-vue laravel-mix-eslint --save-dev

ESLintの設定ファイルを作成する

続いて、.eslintrc.jsファイルを新しく作成します。
このファイルではESLintとPrettierの整形ルールを定めます

$ cd yourLaravelProject
$ vim .eslintrc.js
.eslintrc.js
module.exports = {
  root: true,
  parserOptions: {
    parser: 'babel-eslint',
  },
  env: {
    browser: true,
  },
  extends: [
    'standard',
    'plugin:vue/recommended',
    'plugin:prettier/recommended',
    'prettier/vue',
  ],
  plugins: [
    'vue',
    'prettier', // prettierをESLintと併用します
  ],
  rules: {
    // ESLintが使用する整形ルールのうち、自分がoffにしたいルールなどを指定する
    'vue/no-v-html': 'off', // v-htmlの使用について
    'vue/prop-name-casing': 'off', // Propsの変数の命名規則について
    'no-console': 'off', // console.log()の使用について
    'no-unused-vars': 'off', // 使われていない変数について
    'camelcase': 'off', // camelcaseについて

    // この先はPrettierのルール
    "prettier/prettier": [ 
      "error",
      {
        printWidth: 120,
        tabWidth: 2,
        useTabs: false,
        singleQuote: true,
        trailingComma: 'all',
        bracketSpacing: true,
        arrowParens: 'avoid',
        semi: false,
      },
    ]
  }
}

この設定ファイルは結構厳しめにルールを設けているつもりです。
そもそもせっかくコードフォーマッターを導入するのにルールを緩くしておく必要はないからです。

しかし、もしプロジェクトの途中での導入に際してあまりルールを厳しく入れたくないという場合は、extends:[]の部分を見直すと良いと思います。

このあたりは下記サイトが参考になりました。
(参考)Vue.jsスタイルガイドとeslint-plugin-vue検証ルールのマッピング

またPrettierのルールもあなたの好みです。
Prettierのルールは公式ドキュメントを参考に変更すると良さそうです。
(参考)Options - Prettier

ファイル変更時にESLintを使用する

webpackが上記の整形ルールを使用してファイル変更をwatchしてくれるようにwebpack.mix.jsを編集します。

webpack.mix.js
const mix = require('laravel-mix')

/*
 |--------------------------------------------------------------------------
 | Mix Asset Management
 |--------------------------------------------------------------------------
 |
 | Mix provides a clean, fluent API for defining some Webpack build steps
 | for your Laravel application. By default, we are compiling the Sass
 | file for the application as well as bundling up all the JS files.
 |
 */

// ESLintに関する設定(この部分を丸ごと追記するイメージです)
if (!mix.inProduction()) { // 本番環境ではESLintは使用しない
  mix.webpackConfig({
    module: {
      rules: [
        {
          enforce: 'pre',
          exclude: /node_modules/,
          loader: 'eslint-loader',
          test: /\.(js|vue)?$/,
        },
      ],
    },
  })
}

// watchするファイルやポート番号などに関する設定(今回の内容とは関係ありません)
mix
  .js('resources/js/app.js', 'public/js')
  .sass('resources/sass/app.scss', 'public/css')
  .browserSync({ // browserSyncの設定
    files: ['resources/js/**/*', 'resources/sass/**/*', 'resources/views/**/*', 'public/css/**/*'],
    port: 3000,
    ui: {
      port: 3001,
    },
    proxy: 'localhost:8000', //php artisan serveで立ち上げた8000番をProxyする
  })

// 本番環境ではバージョン付けによるキャッシュ対策を施す(今回の内容とは関係ありません)
if (mix.inProduction()) {
  mix.version()
}

これで基本的なセットアップは全て終了です。

$ npm run watch

を唱えることによっていつも通り開発を開始しましょう。コンソールでESLintとPrettierが大量のエラーを吐いてくれるはずです。

実際に動作を確認する

あなたのコンソールを見てください。
例えば次のようになっている場合、

/Users/yourName/Laravel/resources/js/pages/A/B.js
350:1  error  Delete `····`
       prettier/prettier

これはPrettierが怒っている様子です。

このエラーが指し示すところは、
/js/pages/A/にあるファイルB.jsの350行目の1列目から無駄な空白があるから消せ」
ということです。

これらのエラーは地道に解消しても良いですが、あなたの見ているエラーの量を見てください。地獄ですよね。

でも安心してください、自動でこれらのエラーをほとんど解消してくれるESLintのコマンドがあります。

エラーを吐いているファイル名とそのディレクトリを確認して、コンソールで

// ESLintとPrettierの両方を実行する
$ eslint resources/js/pages/*/*.js --fix

を実行しましょう。
ファイル名はresources/js/pages/*/*.jsのようにワイルドカードを使用して指定できます。
これによってresources/js/pages/*/*.js に該当するファイルのエラーが自動で解消されていきます。

eslint ファイル名 --fixコマンドによってESLintとPrettierの両方が実行されることに注意してください
これはすでに上記で導入した eslint-config-prettierの恩恵を受けています。
ESLintとPrettierの併用に関する詳しい説明は以下の記事などがわかりやすいと思います。
(参考)ESLint - Prettier連携のやり方と仕組み

これでLaravelの*.jsファイルや*.vueファイルに対してESlintとPrettierを導入することができました。

どのようなエラーを吐いているかはあなたのプロジェクト次第です。

コンソールでのエラーを見ながら .eslintrc.jsファイルで整形方法を調整しましょう。特定の整形ルールをoffにすることもできますから、あなたの開発チームメンバーにとって最良の設定を記述し、幸せな開発サイクルを目指しましょう。

最後に

この設定を行ったことで、あなたのLaravelの*.jsファイルや*.vueファイルは全ての開発者間でフォーマットが統一されました。

今後の展望としては
1. git commitのときにESLintを自動で強制する
2. *.phpファイルに対しても何かしらのLintを施す

本記事ではこれらの設定はオーバーワークになりますので、別記事で紹介しようと思います。

長くなりましたが、ありがとうございました。

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

Vue.js+Blumaでダイアログを表示してみる

目的

JavaScriptのフレームワークであるVue.jsとCSSのフレームワークであるBlumaを使用してダイアログを表示してみる。

image.png

サンプル

Vue.jsにbulmadialog-componentコンポーネントを追加します。
その後、任意のタイミングで親側からコンポーネントのメソッドshowDialogを実行します。
この際、以下のプロパティを持つオブジェクトをパラメータに指定します。

  • title : ダイアログのタイトル
  • contents : ダイアログの内容。HTMLタグは無効
  • html : ダイアログの内容。HTMLのタグは有効
  • buttons : ボタン情報の配列
    • caption : ボタンのタイトル
    • callback : コールバック関数

コンポーネント側

IE11で動くようにしています。

bluma_dialog.js
/**
 * bulma + vue.jsでダイアログを表示します。
 * html: vue.jsの管理下に以下を追加します
 *   <bulmadialog-component ref="dialog"></bulmadialog-component>
 * js:Vue.jsを作成するときにコンポーネントを追加する
 *  components: {
      'bulmadialog-component': BulmaDialog,
    },        
 * js:Vue.jsの親のメソッドにて以下を実行
 *  this.$refs.dialog.showDialog({
        title:'わっふるる',
        //contents:'わっふるぼでぃ0\nsadfasfd',
        html : 'あたえたt<br>awrawtあたえたt<br>',
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('いいえ');
            }
          }
        ]
      });
 */
// eslint-disable-next-line no-unused-vars
const BulmaDialog = {
  /* eslint-disable max-len */
  template: (function() {/*
      <div v-bind:class="{ 'is-active': isShow }" class="modal">
        <div class="modal-background"></div>
          <div class="modal-card">
              <header class="modal-card-head">
                  <p class="modal-card-title">{{data.title}}</p>
              </header>
              <div >
              </div>
              <section v-if="data.html" v-html="data.html" class="modal-card-body"></section>
              <section v-else class="modal-card-body">{{data.contents}}</section>
              <footer class="modal-card-foot"  style="justify-content: flex-end;">
                  <button v-for="btnObj in data.buttons" type="button" class="button" @click="btnObj.callback(); isShow = false;">{{btnObj.caption}}</button>
              </footer>
          </div>
      </div>
      </div>
    */}).toString().match(/\/\*([^]*)\*\//)[1],
  /* eslint-enable */
  data: function() {
    return {
      isShow: false,
      data: {
        title: '',
        body: '',
        html: '',
        buttons: [],
      },
    };
  },
  methods: {
    showDialog: function(data) {
      this.isShow = true;
      this.data.title = data.title;
      this.data.contents = data.contents;
      this.data.html = data.html;
      this.data.buttons = data.buttons;
    },
  },
};

使う側

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello Bulma!</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="/phptest/codeview/js/bluma_dialog.js"></script>
  </head>
  <body>
  <div id="app">
    <bulmadialog-component ref="dialog"></bulmadialog-component>
    <button class="button is-primary" @click="test1">ダイアログA</button>
    <button class="button is-primary" @click="test2">ダイアログB</button>
  </div>
<script>
var app = new Vue({
  el: '#app',
  components: { //Scopedが使える
    'bulmadialog-component': BulmaDialog,
  },
  data: function() {
      return {
      }
  },
  methods: {
    test1 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(TEXT)', 
        contents: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('test1 はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('test2 いいえ');
            }
          }
        ]
      });
    },
    test2 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(HTML)', 
        html: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'YES',
            callback : function () {
              console.log('Yes');
            }
          },
          {
            caption : 'No',
            callback : function () {
              console.log('No');
            }
          }
        ]
      });
    }
  }
})
</script>

  </body>
</html>

参考

SafariでもエラーにならないJavascriptのヒアドキュメントの書き方
https://qiita.com/ampersand/items/c6c773ba7ae9115856d0

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

Vue.js+ bulmaでダイアログを表示してみる

目的

JavaScriptのフレームワークであるVue.jsとCSSのフレームワークである bulmaを使用してダイアログを表示してみる。

image.png

サンプル

Vue.jsにbulmadialog-componentコンポーネントを追加します。
その後、任意のタイミングで親側からコンポーネントのメソッドshowDialogを実行します。
この際、以下のプロパティを持つオブジェクトをパラメータに指定します。

  • title : ダイアログのタイトル
  • contents : ダイアログの内容。HTMLタグは無効
  • html : ダイアログの内容。HTMLのタグは有効
  • buttons : ボタン情報の配列
    • caption : ボタンのタイトル
    • callback : コールバック関数

コンポーネント側

IE11で動くようにしています。

bulma_dialog.js
/**
 * bulma + vue.jsでダイアログを表示します。
 * html: vue.jsの管理下に以下を追加します
 *   <bulmadialog-component ref="dialog"></bulmadialog-component>
 * js:Vue.jsを作成するときにコンポーネントを追加する
 *  components: {
      'bulmadialog-component': BulmaDialog,
    },        
 * js:Vue.jsの親のメソッドにて以下を実行
 *  this.$refs.dialog.showDialog({
        title:'わっふるる',
        //contents:'わっふるぼでぃ0\nsadfasfd',
        html : 'あたえたt<br>awrawtあたえたt<br>',
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('いいえ');
            }
          }
        ]
      });
 */
// eslint-disable-next-line no-unused-vars
const BulmaDialog = {
  /* eslint-disable max-len */
  template: (function() {/*
      <div v-bind:class="{ 'is-active': isShow }" class="modal">
        <div class="modal-background"></div>
          <div class="modal-card">
              <header class="modal-card-head">
                  <p class="modal-card-title">{{data.title}}</p>
              </header>
              <div >
              </div>
              <section v-if="data.html" v-html="data.html" class="modal-card-body"></section>
              <section v-else class="modal-card-body">{{data.contents}}</section>
              <footer class="modal-card-foot"  style="justify-content: flex-end;">
                  <button v-for="btnObj in data.buttons" type="button" class="button" @click="btnObj.callback(); isShow = false;">{{btnObj.caption}}</button>
              </footer>
          </div>
      </div>
      </div>
    */}).toString().match(/\/\*([^]*)\*\//)[1],
  /* eslint-enable */
  data: function() {
    return {
      isShow: false,
      data: {
        title: '',
        body: '',
        html: '',
        buttons: [],
      },
    };
  },
  methods: {
    showDialog: function(data) {
      this.isShow = true;
      this.data.title = data.title;
      this.data.contents = data.contents;
      this.data.html = data.html;
      this.data.buttons = data.buttons;
    },
  },
};

使う側

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Hello Bulma!</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
    <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="/phptest/codeview/js/bulma_dialog.js"></script>
  </head>
  <body>
  <div id="app">
    <bulmadialog-component ref="dialog"></bulmadialog-component>
    <button class="button is-primary" @click="test1">ダイアログA</button>
    <button class="button is-primary" @click="test2">ダイアログB</button>
  </div>
<script>
var app = new Vue({
  el: '#app',
  components: { //Scopedが使える
    'bulmadialog-component': BulmaDialog,
  },
  data: function() {
      return {
      }
  },
  methods: {
    test1 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(TEXT)', 
        contents: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'はい',
            callback : function () {
              console.log('test1 はい');
            }
          },
          {
            caption : 'いいえ',
            callback : function () {
              console.log('test2 いいえ');
            }
          }
        ]
      });
    },
    test2 : function () {
      this.$refs.dialog.showDialog({
        title:'確認(HTML)', 
        html: "ふんぐるい むぐるうなふ くとぅるう るるいえ うがふなぐる ふたぐん<br>Ph'nglui mglw'nafh Cthulhu R'lyeh wgah'nagl fhtagn",
        buttons : [
          {
            caption : 'YES',
            callback : function () {
              console.log('Yes');
            }
          },
          {
            caption : 'No',
            callback : function () {
              console.log('No');
            }
          }
        ]
      });
    }
  }
})
</script>

  </body>
</html>

参考

SafariでもエラーにならないJavascriptのヒアドキュメントの書き方
https://qiita.com/ampersand/items/c6c773ba7ae9115856d0

メモ

Bulma自体がIEのサポートを部分的にのみしかしていないので、テンプレートリテラルを使ってIEをあきらめた方が無難かも

Bulma uses autoprefixer to make (most) Flexbox features compatible with earlier browser versions. According to Can I use, Bulma is compatible with recent versions of:

  • Chrome
  • Edge
  • Firefox
  • Opera
  • Safari

Internet Explorer (10+) is only partially supported.

https://github.com/jgthms/bulma

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

【記事散乱から卒業】目的別に記事をリスト化できるサービスを開発してみた

「散らばった情報を自分の切り口でリスト化して、みんなにシェアできるサービス」 をリリースしました!

毎日新しい情報が、莫大に発信されていて、自分の求めている情報はすぐに埋もれてしまいます。また、自分と同じような情報を求めている人も他にいるのに、その人たちも、良質な情報を求めて、同じように莫大な情報を掻き分けて、宝探しをしています。

このような作業の重複を改善するために、Ownlistというサービスを作りました。

image.png

(このサービスの特徴となる機能を作る上で参考にした情報を添付しておいたので、興味のある方は、ご覧ください。)

こんな人に使って欲しい

  • エラー対応で使った記事を再び探すことがある人
  • 勉強の参考にした記事をストックしておきたい人
  • 「後で読む」とブックマークしたが、それらが散乱してしまっている人
  • インプットに使った記事を他の人にシェアしたい人

開発言語

  • フロント
    • JavaScript(Vue.js)
  • バックエンド
    • Python(Flask)

特徴①時系列でまとめたリストが表示される

作成したリストは時系列順に並びます。

1ヶ月前、1年前に「勉強のため」「エラー解決のため」などにまとめたリストが見返しやすい状態で表示されます。
自分の振り返りに使っても良いでしょうし、後輩や同僚に過去の参考になった記事を目的ごとにまとめてシェアできます。

04b0f7dc17873de63f206e5032739759.gif

時系列で表示するために参考にした記事リスト

特徴②まとめる目的をタイトルに付ける必要がある

「後で読むからとりあえずブックマーク!」「良さそうな記事だからとりあえずslackでシェア!」など、なんとなく良さそうだからとか、何かのランキング上位の記事だから、という理由で、ストックしたりシェアしたりする人もいると思います。

ただ、それらをシェアされた側の人や後から見返した自分は、 なぜ、これらが重要だと感じたのか がわからなくなってしまうことは、少なくありません。

なので、ブックマークする前、シェアする前に、 どんな目的でこの記事が役立つのか を明記してまとめておくことで、読むべき理由を知らせることができ、より有益な情報になります。

image.png

Vue.jsを勉強するために参考にしたページ

まとめ

流れてきた記事をなんとなくブックマークしてしまっていた人、過去見た記事と同じ内容の記事を求めてネットの世界で迷子になっていた人、勉強の記録をつていなかった人などの、情報集積をないがしろにしてきた方に 自分のため仲間のため に使っていただきたいサービスです。

ownlistはこちら

image.png

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

Vue.js + Quasarで爆速プロトタイピング(3)〜Side Drawer編〜

1.概要

Vue.js + Quasar Frameworkでプロトタイプ作成を行うことがあったので、メモがてら記録しておきます。

前回の記事 -> Vue.js + Quasarで爆速プロトタイピング(2)〜Toolbar編〜

今回はサイドメニューを作成します。

2.コーディング

2-1.フォルダ構成

/vue-sample
 ├ css/
 │ └ style.css
 ├ pages/
 │ └ main.html
 └ app.js

2-2.main.html

main.html
<!DOCTYPE html>
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width,initial-scale=1.0" />
    <link rel="shortcut icon" type="image/x-icon" href="../../favicon.ico" />
    <title>Vue.js + Quasarで爆速プロトタイピング</title>
    <!-- Material Icons -->
    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet" type="text/css" />
    <!-- animations -->
    <link href="https://cdn.jsdelivr.net/npm/animate.css@^3.5.2/animate.min.css" rel="stylesheet" />
    <!-- Finally, add Quasar's CSS -->
    <link href="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/quasar.mat.min.css" rel="stylesheet" type="text/css" />

    <link rel="stylesheet" type="text/css" href="../css/style.css" />
  </head>

  <body>
    <!-- Vue.js -->
    <script src="https://cdn.jsdelivr.net/npm/vue@latest/dist/vue.min.js"></script>

    <div id="app">
      <q-layout id="main">
        <!-- ヘッダー -->
        <q-layout-header>
          <q-toolbar color="secondary">
<!-- ヘッダーのメニューボタン押下時にドロワーの表示フラグを更新します。 -->
            <q-btn flat round dense icon="menu" @click="drawer = !drawer"></q-btn>

            <q-toolbar-title>
              {{ title }}
            </q-toolbar-title>
          </q-toolbar>
        </q-layout-header>

<!-- 今回追加START ドロワー -->
        <q-layout-drawer overlay side="left" v-model="drawer">
          <q-list link>
            <q-item>
              <q-item-side>
                <q-icon name="thumb_up"></q-icon>
              </q-item-side>
              <q-item-main>
                <q-item-tile>メニュー1</q-item-tile>
              </q-item-main>
            </q-item>
            <q-item>
              <q-item-side>
                <q-icon name="thumb_up"></q-icon>
              </q-item-side>
              <q-item-main>
                <q-item-tile>メニュー2</q-item-tile>
              </q-item-main>
            </q-item>
            <q-item>
              <q-item-side>
                <q-icon name="thumb_up"></q-icon>
              </q-item-side>
              <q-item-main>
                <q-item-tile>メニュー3</q-item-tile>
              </q-item-main>
            </q-item>
          </q-list>
        </q-layout-drawer>
<!-- 今回追加END -->

        <!-- コンテンツ -->
        <q-page-container>
          <div class="page">
            <q-input stack-label="greet" v-model="greet"></q-input>
          </div>
        </q-page-container>
      </q-layout>
    </div>

    <!-- IE support -->
    <script src="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/quasar.ie.polyfills.umd.min.js"></script>
    <!-- Add Quasar's JS -->
    <script src="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/quasar.mat.umd.min.js"></script>
    <!-- If you want to add a Quasar Language pack (other than "en-us"). -->
    <script src="https://cdn.jsdelivr.net/npm/quasar-framework@0.17.20/dist/umd/i18n.ja.umd.min.js"></script>

    <script src="../app.js"></script>
  </body>
</html>

2-3.app.js

app.js
// Quasar
Vue.use(Quasar);

var app = new Vue({
  el: "#app",
  data: function() {
    return {
      drawer: false, // <- 今回追加
      title: "Vue.js + Quasarで爆速プロトタイピング",
      greet: "Hello World!"
    }
  }
});

app.$mount("#app");

2-4.説明

Drawerのリファレンスはこちらです。
https://v0-17.quasar-framework.org/components/layout-drawer.html

プロパティoverlayをつけることで画面全体にかぶせるように表示されます。
side = "right"を指定してあげれば右から表示されます。

中身のメニューはq-listを使って実現しています。
https://v0-17.quasar-framework.org/components/lists-and-list-items.html

レイアウトは以下の図のような構成です。
この後もガンガン使っていきますので、そちらも参考にしてください。
スクリーンショット 2019-11-26 8.52.29.png

3.完成!

これまたお洒落なサイドメニューが表示されました!
動かしてもらえばわかりますが、しっかり画面外からスライドアニメーションして表示してくれます。
設定ひとつで右から出すことも可能です!

次回からは中身のコンテンツを作っていきたいと思います!

読んでいただき、ありがとうございました!

スクリーンショット 2019-11-26 8.43.49.png
d

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

ストアのmutationバンドラをthisで使いまわそうとしたらハマった

やっていること

vueを使って講義回数をカウントするTodoリストを作りました。
https://vuejs-http-e3587.firebaseapp.com/

問題点

ストアのmutationバンドラをthisで使いまわそうとしたらハマってしまいました。

store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    drawer: false,
    count: 0,
    name: "",
    todos: [],
    memo: []
  },
  getters: {
    getItemJson(state) {
      state.todo = JSON.parse(localStorage.getItem("state.todos")) || [];
    }
  },
  mutations: {
    toggleSideMenu(state) {
      state.drawer = !state.drawer
    },
    templateJson(state) {
      state.todos = JSON.parse(localStorage.getItem("state.todos")) || [];
      let setJson = JSON.stringify(state.todos);
      localStorage.setItem("state.todos", setJson);
    },
    addMemo() {
      this.templateJson();
      this.isActive = false;
    },
    addTodo(state) {
      if (state.name != "") {
        state.todos.push({
          name: state.name,
          count: state.count
        });
      }
      state.name = "";
      this.templateJson();
    },
    increment(state) {
      state.count++;
      this.templateJson();
    },
    decrement(state) {
      if (state.count > 0) {
        state.count--;
      }
      this.templateJson();
    },
    deleteItem(state, index) {
      state.todos.splice(index, 1);
      let setJson = JSON.stringify(state.todos);
      localStorage.removeItem("state.todos");
      localStorage.setItem("state.todos", setJson);
    }
  },
  actions: {
    toggleSideMenu({ commit }) {
      commit("toggleSideMenu")
    },
    templateJson({ commit }) {
      commit("templateJson")
    },
    addMemo({ commit }) {
      commit("addMemo")
    },
    addTodo({ commit }) {
      commit("addTodo")
    },
    deleteItem({ commit }, index) {
      commit("deleteItem", index)
    },
    increment({ commit }, state) {
      commit("increment", state)
    },
    decrement({ commit }, state) {
      commit("decrement", state)
    },
  }
})

調べた結果

どうやらmutationハンドラ内(vuexのメソッド)をthisを使って使いまわしすることは出来なかった。しかし、storeでミューテーション経由のアクションを定義してコンポーネントで使用する場合はいつも通りthisを使うことが出来た。
修正バージョンが下記コードです。

Count.vue
<template>
  <v-app>
    <v-content>
      <v-container>
        <v-row>
          <v-col cols="6">
            <v-text-field v-model="name" label="授業名" @keyup.enter="addTodo" required></v-text-field>
          </v-col>
        </v-row>
        <div v-for="(todo,index) in ($store.state.todos)" :key="todo.name">
          <v-card card_id max-width="344" class="mx-auto">
            <v-card-title>{{todo.name}}</v-card-title>
            <v-card-text>
              <v-text-field label="メモ" v-model="todo.memo" @input="addMemo"></v-text-field>
            </v-card-text>
            <v-card-actions>
              <v-btn @click="increment(todo)" color="primary">さぼり回数</v-btn>
              <span>{{ todo.count }}</span>
              <v-btn @click="decrement(todo)" color="error">間違い(-)</v-btn>
              <v-btn @click="deleteItem(index)">削除</v-btn>
            </v-card-actions>
          </v-card>
        </div>
      </v-container>
    </v-content>
  </v-app>
</template>

<script>
import { mapActions } from "vuex";
export default {
  data() {
    return {
      count: 0,
      name: "",
      memo: []
    };
  },
  mounted() {
    this.$store.commit("setTodos");
  },
  methods: {
    ...mapActions(["templateJson", "templateJsonz"]),
    addMemo() {
      this.templateJson();
      this.isActive = false;
    },
    addTodo() {
      if (this.name != "") {
        this.$store.state.todos.push({
          name: this.name,
          count: this.count
        });
      }
      this.name = "";
      this.templateJson();
    },
    increment(todo) {
      todo.count++;
      this.templateJson();
    },
    decrement(todo) {
      if (todo.count > 0) {
        todo.count--;
      }
      this.templateJson();
    },
    deleteItem(index) {
      this.$store.state.todos.splice(index, 1);
      this.templateJsonz();
    }
  }
};
</script>
store/index.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    drawer: false,
    todos: [],
  },
  mutations: {
    toggleSideMenu(state) {
      state.drawer = !state.drawer
    },
    setTodos(state) {
      state.todos = JSON.parse(localStorage.getItem("state.todos")) || [];
    },
    templateJson(state) {
      let setJson = JSON.stringify(state.todos);
      localStorage.setItem("state.todos", setJson);
    },
    templateJsonz(state) {
      let setJson = JSON.stringify(state.todos);
      localStorage.removeItem("state.todos");
      localStorage.setItem("state.todos", setJson);
    }
  },
  actions: {
    toggleSideMenu({ commit }) {
      commit("toggleSideMenu")
    },
    templateJson({ commit }) {
      commit("templateJson")
    },
    templateJsonz({ commit }) {
      commit("templateJsonz")
    }
  }
})

ストアで管理してみた感想

今回の成果物はコンポーネント間のやり取りが実質要らなかったので、コードが冗長になるだけだった。しかし、vuexのいい勉強になったと思う。
それでも結局、ストアのmutation内のみでバンドラを使いまわすことは出来ませんでした。良ければどなたか教えてください。
またmutation内のメソッドのことをドキュメントを参考にしてバンドラと呼んでいるのですが、あまり一般的ではないと思います。他の呼び方とかありませんでしょうか?それとも、やはりバンドラでいいのでしょうか?前者、後者どちらでもいいので良ければアドバイスお願いします。

改善前の全コード↓
https://github.com/masal9pse/courageTodo/tree/d7537dab1037441bfdf47db3db127cd60c10f85a

改善後の全コード↓
https://github.com/masal9pse/courageTodo

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

Nuxt.jsに飛びつく前に~Nuxt.jsを習得するための前提技術と、その勉強方法の紹介~

概要

Nuxt.jsは今最もイケてるゥ!最高にCooooooolなWEB開発フレームワークです。巷でNuxt.jsについての記事も増えていますね。
しかし、ネット上のNuxt.jsの記事では、Nuxt.jsを始める上で前提となる前提知識の存在が省略されているように思います。Nuxt.jsはVue.jsの発展形(=Nuxt.jsを触る人はVue.jsの経験があるという前提)なので当然と言えば当然なのですが。

本記事では、これからWEB開発者を目指す人を対象に、
・Nuxt.jsを習得するには何が必要か?
・そのための勉強方法
を紹介します。

自己紹介

Nuxt.jsとFirebaseを用いたWEBアプリ開発を担当することになりました。
Pythonはある程度触ってきたものの(機械学習やスクレイピング等)、WEB開発は全くの未経験。Progateや入門書を触って、簡単なWEBサイトを公開した経験がある程度でした。

失敗談「とりあえずNuxtアプリを作ってみたけど・・・・・・・?」

さて、Nuxt.jsは公式ドキュメントの内容が充実しており、"分かりやすい"です。
しかも驚くべきことに日本語に翻訳されています。

例えば、npxさえ事前にインストールされていれば下記の簡単なコマンドで簡単にプロジェクト(※)を始めることができます。
(※厳密には違うのですが、初心者の方は「プロジェクトを作る=アプリを作る」と読みかえてください)
Nuxt.js - ユニバーサル Vue.js アプリケーション/インストール

//任意のディレクトリで下記を実行すると、<project-name>という名前のプロジェクトが作成されます
$ npx create-nuxt-app <project-name>

//サーバーを立ち上げます。http://localhost:3000で確認できます
$ npm run dev

また、Nuxt.jsとFirebaseを組み合わせることも簡単です。
私は下記記事を参考にユーザー認証を実装しました。
『FirebaseとNuxt.jsを使ってユーザ認証関係を簡単に作ってみる+1ヶ月前の自分に教えたいリンク集
あとはTwitterでログインできる簡単なチャットなんかも作りましたね。

ここまでで、カタチがあり動くものを何となく作れました。

しかし、WEBエンジニアとして自分が殆ど成長していないことに気付きます。なぜなら、Nuxt.jsの中身を理解できているわけではないから。「このままだとググってコピペすることしかできないし、永遠にWEBエンジニアとして自立できない」と危機感を募らせました。

Nuxt.jsに飛びつく前にまず基本をちゃんと押さえよう

「Nuxt.jsは公式ドキュメントが充実していて分かりやすい」とよく言われていますが、正直当初はさっぱり分かりませんでした。
ええ、Nuxt.jsのドキュメントはとても優良なドキュメントです。
しかし但書をつけると、「(Vue.jsの経験がある人には)Nuxt.jsは公式ドキュメントが充実していて分かりやすい」んですね。なぜなら、Nuxt.jsはVue.jsを発展させて、その弱点を補ったものだからです。

なので、Nuxt.jsに飛びつく前にまず基本をきちんと押さえましょう。(自戒を込めて)

Nuxt.jsを習得するために必要な技術

さて、いよいよ本題です。WEB開発未経験者がNuxt.jsを習得するために必要な要素を列挙します。

Vue.js
「コンポーネントとは?」「テンプレートとは?」等、基本を抑えていますか? ごく簡単なもので良いので、フルスクラッチ※(後述)で一つ一つのファイルやコードの意味をきちんと理解しながらアプリを作ってみると良いです。
JavaScript ES6
Progateレベルだと少し物足りないです。特にアロー関数は頻繁に使いますね。おすすめは「MDN web docs」を読むことです。
JSON
超大事です!エンジニアにとって当たり前の知識ですが、何となくで済ませてきた人も多いのではないでしょうか。この機会にしっかりと理解しましょう。おすすめは「MDN web docs」を読むことです。
HTML
やはりProgateレベルだと少し物足りないです。おすすめは「MDN web docs」を読むことです。
CSS
CSSはProgateで十分ですね。
WEB開発の基本知識
コードを書くのと直接は関係ないですが、きちんと理解しておくと学習が捗ります。おすすめは「MDN web docs」を(略)

「こんなにやってられないよ」と思う方。安心してください!
それぞれのおすすめの教材もきちんとご紹介します。
目標(=Nuxt.jsを扱えるようになる)と目標に向けて自分に必要なカリキュラムが明確に分かっていれば、短期間で効率的に学習することができます。具体的には合わせて2週間程度で、無理なくNuxt.jsを習得するための準備学習を完了できます。

おすすめの学習教材・方法

「Vue.jsは未経験だがES6は分かる」「正直HTMLも怪しい・・・」とレベル感に差があると思うので、三つのペルソナに分けました。ご自身のレベルに応じて参考になさってください。

「Vue.jsの経験がある、基本がわかる」人向け

公式ドキュメントや『Nuxt.jsビギナーズガイド Vue.jsベースのフレームワークによるシングルページアプリケーション』がおすすめです。

「Vue.js未経験、Vueだけ分からない」人向け

『Vue.js&Nuxt.js超入門』
正直、時間がない方はこれ一冊でも良いかもしれません。
Nuxt.jsを習得するためのVue.jsやJavaScriptを網羅しています。
特に素晴らしいのですが、フルスクラッチで(=一枚のHTMLファイルから手作業で)Vue.jsのアプリを作っていくので、Vue.jsの構造やファイルの中身を一つ一つきちんと理解できます。
僕は本書がきっかけで急に理解が深まりました(掌田さんの本は片っ端から揃えようと思います)。

「Progateは終わったけど・・・」「正直WEB開発の基本も自信ない」「JSONって何?」人向け

「MDN web docs」を読みましょう!
「MDN web docs」はGoogleやMicroSoft等、世界のトップレベルの人たちが中心に作成しているサイトです。つまり情報の信頼性がこの上なく高いので、正確で丁寧な理解ができます。「でも難しいんでしょ?」と思われた方、そんなことはありません。むしろ、文章量が紙幅に制限されないので懇切丁寧に解説が記述されており、とても分かりやすいです。しかも完全無料。(ほぼ)日本語対応。あらゆる点で書籍や大学の情報系の講義より遥かに優れた教材です。
何とWEB入門JavaScriptの第一歩などの体系的なカリキュラムまで備えています。

ただ、プログラミングの知識が全くのゼロの状態で読むと少し辛いと思うので、プログラミング初心者の方はまずProgateから始めるのが良いでしょう。

終わりに

さあ、これで貴方もNuxt.js公式ドキュメントが「分かりやすい」と感じられるようになります。
自分のレベルに合わせて、無理なく学習を進めてください。
良いエンジニアライフを〜

参考文献

Nuxt.js 公式ドキュメント
MDN web docs
『Nuxt.jsビギナーズガイド Vue.jsベースのフレームワークによるシングルページアプリケーション』
『Vue.js&Nuxt.js超入門』

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