Motomichi Works Blog

モトミチワークスブログです。その日学習したことについて書いている日記みたいなものです。

webpackでビルドする前にeslintで.vueと.jsの構文チェックをする | webpack4.x+babel7+vue.js 2.x 環境構築 2019年3月版 ステップ0004

参考にさせて頂いたページ

webpack.config.jsについて

eslintについて

eslint-loaderのoptionsについて

色々

以前書いた記事

グローバルにインストール

npm install -g eslint

プロジェクトにインストール

まず以下の4つのモジュールをインストールします。

npm install --save-dev eslint eslint-loader babel-eslint eslint-config-vue eslint-plugin-vue

eslint-config-standard - npm」を入れます。依存するモジュールが4つあるのでまとめてインストールします。

npm install --save-dev eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard

ここまでやった段階でのpackage.json

{
  "name": "webpack_4_vue_introduction",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "watch": "webpack -d --watch",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.4.3",
    "@babel/core": "^7.4.3",
    "@babel/preset-env": "^7.4.3",
    "@vue/test-utils": "^1.0.0-beta.29",
    "babel-core": "^7.0.0-bridge.0",
    "babel-eslint": "^10.0.1",
    "babel-jest": "^24.7.1",
    "babel-loader": "^8.0.5",
    "css-loader": "^2.1.1",
    "eslint": "^5.16.0",
    "eslint-config-standard": "^12.0.0",
    "eslint-config-vue": "^2.0.2",
    "eslint-loader": "^2.1.2",
    "eslint-plugin-import": "^2.17.1",
    "eslint-plugin-node": "^8.0.1",
    "eslint-plugin-promise": "^4.1.1",
    "eslint-plugin-standard": "^4.0.0",
    "eslint-plugin-vue": "^5.2.2",
    "jest": "^24.7.1",
    "jest-serializer-vue": "^2.0.2",
    "node-sass": "^4.11.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "vue-jest": "^3.0.4",
    "vue-loader": "^15.7.0",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.30.0",
    "webpack-cli": "^3.3.0"
  },
  "dependencies": {
    "vue": "^2.6.10",
    "vuex": "^3.1.0"
  }
}

webpack.config.jsの編集

rulesに以下のeslint-loaderをrulesの.vueと.jsの二か所に追記します。

          {
            loader: 'eslint-loader',
          },

ここまでやった段階でのwebpack.config.js

以下のようになりました。

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  // entry point
  entry: {
    'javascripts/first_page': './src/javascripts/entry_points/first_page.js',
  },
  // 出力するパスは絶対パスで書きます
  output: {
    path: `${__dirname}/webroot/packed`,
    filename: (arg) => {
      return '[name].bundle.js'
    },
  },
  // webpack4はlordersではなくなりました
  module: {
    rules: [
      // 拡張子.vueのファイルに対する設定
      {
        test: /\.vue$/,
        use: [
          {
            loader: "vue-loader",
            options: {
              loaders: {
                js: 'babel-loader',
              },
            },
          },
          {
            loader: 'eslint-loader',
          },
        ]
      },
      // 拡張子.jsのファイルに対する設定
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'eslint-loader',
          },
        ]
      },
      // 拡張子.scssのファイルに対する設定(.vueの中にscssを書きたい場合もここに設定します。)
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'sass-loader',
          },
        ]
      },
    ]
  },
  // デフォルトの設定値だけでは足りないことについて解決します
  resolve: {
    // モジュールを読み込むときに検索するディレクトリの設定
    modules: [path.join(__dirname, 'src'), 'node_modules'],
    // importするときに省略できる拡張子の設定
    extensions: ['.js', '.vue'],
    alias: {
      // importのファイルパスを相対パスで書かないようにsrcのrootを設定
      '@': path.join(__dirname, 'src'),
      // 例えばmain.js内で `import Vue from 'vue';` と記述したときの`vue`が表すファイルパスを指定
      'vue$': 'vue/dist/vue.esm.js'
    },
  },
  // プラグインを列挙
  plugins: [
    new VueLoaderPlugin()
  ],
}

.eslintrc.jsを空ファイルで作成

.eslintrcは以前は拡張子がありませんでしたが、今は拡張子を付けることが推奨されています。

.eslintrcは色々な拡張子で作成できます。

jsonの例をよく見かける気がしますが、jsonは記述ルールが厳しいので、.jsにしようと思います。

windowsの場合

copy nul .eslintrc.js

macの場合

touch .eslintrc.js

.eslintrc.jsの記述内容

以下の通り記述します。

module.exports = {
  "extends": [
    // eslint-plugin-vue(.vueファイルのtemplateとscriptのlint)をextend
    "plugin:vue/recommended",
    // eslint-config-standardをextend
    "standard",
  ],
  "parserOptions": {
    // ecmaVersionを指定
    "ecmaVersion": 6,
    // type="module"をサポート
    "sourceType": "module",
    // parserを指定
    "parser": "babel-eslint",
  },
  "env": {
    // browserが持っているオブジェクトをサポート
    "browser": true,
    // ES2015以降に追加された組み込みオブジェクトをサポート
    "es6": true
  },
  "globals": {},
  "rules": {
    "semi": ["error", "always"],
    "comma-dangle": ["error", "always-multiline"],
  }
}

一度ビルドしてみる

webpack -d

以下のような感じでとても多くの構文エラーが出ました。

何行目の何文字目がエラーなのか書いてあります。

   1:22  error    Extra semicolon                                                               semi
   6:1   error    Import in body of module; reorder to top                                      import/first
   6:70  error    Extra semicolon                                                               semi
   8:33  error    Extra semicolon                                                               semi
  10:1   error    Do not use 'new' for side effects                                             no-new
  14:3   warning  The "components" property should be above the "template" property on line 13  vue/order-in-components
  15:3   error    Extra semicolon                                                               semi
  16:1   error    Too many blank lines at the end of file. Max of 0 allowed                     no-multiple-empty-lines

構文を修正する

以下はルールの一例です。コツコツ修正するか、後述するautofixのoptionを追加してから再度ビルドします。

  • 'mapGetters' is defined but never used: 変数を定義しているが使われていない
  • 'mapActions' is defined but never used: 変数を定義しているが使われていない
  • Extra semicolon: 余計なセミコロンがある
  • The "components" property should be above the "template" property on line 13: 記述する順番が規約に沿ってない
  • Expected 1 line break after opening tag ('<button>'), but no line breaks found: 改行してない
  • Missing trailing comma: カンマが無い
  • Missing space before function parentheses: (の前に半角スペースが無い
  • Expected a space before '/>', but not found: /の前に半角スペースが無い
  • Property name "app" is not PascalCase: 一文字目は大文字にする
  • Do not use 'new' for side effects: newする場合は変数に入れる

自動修正の設定をする

webpack.config.jsに以下のようにoptionsを設定することで、ビルドの度に自動修正することができるので、自動修正したい場合は設定します。

webpack.config.jsを二か所修正しておきます。

          {
            loader: 'eslint-loader',
            options: {
              fix: true,
            },
          },

ここまでやった段階でのwebpack.config.js

以下のようになりました。

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  // entry point
  entry: {
    'javascripts/first_page': './src/javascripts/entry_points/first_page.js',
  },
  // 出力するパスは絶対パスで書きます
  output: {
    path: `${__dirname}/webroot/packed`,
    filename: (arg) => {
      return '[name].bundle.js'
    },
  },
  // webpack4はlordersではなくなりました
  module: {
    rules: [
      // 拡張子.vueのファイルに対する設定
      {
        test: /\.vue$/,
        use: [
          {
            loader: "vue-loader",
            options: {
              loaders: {
                js: 'babel-loader',
              },
            },
          },
          {
            loader: 'eslint-loader',
            options: {
              fix: true,
            },
          },
        ]
      },
      // 拡張子.jsのファイルに対する設定
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          },
          {
            loader: 'eslint-loader',
            options: {
              fix: true,
            },
          },
        ]
      },
      // 拡張子.scssのファイルに対する設定(.vueの中にscssを書きたい場合もここに設定します。)
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'sass-loader',
          },
        ]
      },
    ]
  },
  // デフォルトの設定値だけでは足りないことについて解決します
  resolve: {
    // モジュールを読み込むときに検索するディレクトリの設定
    modules: [path.join(__dirname, 'src'), 'node_modules'],
    // importするときに省略できる拡張子の設定
    extensions: ['.js', '.vue'],
    alias: {
      // importのファイルパスを相対パスで書かないようにsrcのrootを設定
      '@': path.join(__dirname, 'src'),
      // 例えばmain.js内で `import Vue from 'vue';` と記述したときの`vue`が表すファイルパスを指定
      'vue$': 'vue/dist/vue.esm.js'
    },
  },
  // プラグインを列挙
  plugins: [
    new VueLoaderPlugin()
  ],
}

もう一度ビルドする

自動修正の設定をした場合、今回の構文エラーのほとんどはこれで直ります。

webpack -d

Do not use 'new' for side effects を無視する

.eslintrc.jsを編集します。

以下のようにrulesに追記しました。

  "rules": {
    〜略〜
    "no-new": "off",
  }

出力されるWarningメッセージについて

ESLint v5.0.0 変更点まとめ - Qiita

eslint実行時に以下のWarningメッセージが出力されています。
これはeslint5から表示されるようになったようです。
parserOptions.ecmaFeatures.experimentalObjectRestSpreadが非推奨でparserOptions.ecmaVersionを使用してくださいということらしいですが解決の仕方がわからず一旦このまま使っています。

(node:16602) [ESLINT_LEGACY_OBJECT_REST_SPREAD] DeprecationWarning: The 'parserOptions.ecmaFeatures.experimentalObjectRestSpread' option is deprecated. Use 'parserOptions.ecmaVersion' instead. (found in "vue")

またそのうちわかったら。

おわりに

.vueファイルの中にscssを書けるようにする | webpack4.x+babel7+vue.js 2.x 環境構築 2019年3月版 ステップ0003

参考にさせていただいたページ

TextField.vueを編集する

src/javascripts/vue_applications/common/components/TextField.vueのtemplate に以下の通り追記します。

    <div class="mod-error-messages">
      <ul class="mod-error-messages__list">
        <li class="mod-error-messages__item">
          sample message
        </li>
      </ul>
    </div>

ファイル末尾に以下の通り追記します。

<style lang="scss" scoped>
  .mod-error-messages {
    .mod-error-messages__list {
      box-sizing: border-box;
    }
    .mod-error-messages__item {
      color: #ff0000;
    }
  }
</style>

TextField.vueは以下のようになりました。

<template>
  <section>
    <label>TextField: </label>
    <input
      :value="value"
      type="text"
      @input="onInput"
    >
    <div class="mod-error-messages">
      <ul class="mod-error-messages__list">
        <li class="mod-error-messages__item">
          sample message
        </li>
      </ul>
    </div>
    <div class="test-synced-text">
      value: {{ value }}
    </div>
  </section>
</template>

<script>
import { mapState, mapGetters, mapActions, mapMutations } from 'vuex';

export default {
  name: 'TextField',
  computed: {
    ...mapState('textFieldUnit/textField', [
      'value',
    ]),
  },
  methods: {
    ...mapMutations('textFieldUnit/textField', [
      'setState',
    ]),
    onInput(e) {
      this.setState({
        key: 'value',
        value: e.target.value,
      });
    },
  },
}
</script>

<style lang="scss" scoped>
  .mod-error-messages {
    .mod-error-messages__list {
      box-sizing: border-box;
      border-radius: 4px;
    }
    .mod-error-messages__item {
      color: #ff0000;
    }
  }
</style>

一度ビルドを実行してみる

以下のコマンドでビルドを実行してみます。

webpack -d

以下のようなエラーが出ました。

ERROR in ./src/vue_applications/components/TextField.vue?vue&type=style&index=0&id=273a995e&lang=scss&scoped=true& (./node_modules/vue-loader/lib??vue-loader-options!./src/vue_applications/components/TextField.vue?vue&type=style&index=0&id=273a995e&lang=scss&scoped=true&) 40:0
Module parse failed: Unexpected token (40:0)
You may need an appropriate loader to handle this file type.
|
|
> .mod-error-message {
|   color: red;
| }
 @ ./src/vue_applications/components/TextField.vue?vue&type=style&index=0&id=273a995e&lang=scss&scoped=true& 1:0-160 1:176-179 1:181-338 1:181-338
 @ ./src/vue_applications/components/TextField.vue
 @ ./node_modules/babel-loader/lib!./node_modules/vue-loader/lib??vue-loader-options!./src/vue_applications/components/TextFieldUnit.vue?vue&type=script&lang=js&
 @ ./src/vue_applications/components/TextFieldUnit.vue?vue&type=script&lang=js&
 @ ./src/vue_applications/components/TextFieldUnit.vue
 @ ./node_modules/babel-loader/lib!./node_modules/vue-loader/lib??vue-loader-options!./src/vue_applications/App.vue?vue&type=script&lang=js&
 @ ./src/vue_applications/App.vue?vue&type=script&lang=js&
 @ ./src/vue_applications/App.vue
 @ ./src/index.js

node_modulesを追加する

.vueにscssを記述できるように以下の4つのモジュールを追加します。

npm install --save-dev style-loader css-loader sass-loader node-sass

ここまでやった段階でのpackage.json

{
  "name": "webpack_4_vue_introduction",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "watch": "webpack -d --watch",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.4.0",
    "@babel/preset-env": "^7.4.2",
    "@vue/test-utils": "^1.0.0-beta.29",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^24.5.0",
    "babel-loader": "^8.0.5",
    "css-loader": "^2.1.1",
    "jest": "^24.5.0",
    "jest-serializer-vue": "^2.0.2",
    "node-sass": "^4.11.0",
    "sass-loader": "^7.1.0",
    "style-loader": "^0.23.1",
    "vue-jest": "^3.0.4",
    "vue-loader": "^15.7.0",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0"
  },
  "dependencies": {
    "vue": "^2.6.10",
    "vuex": "^3.1.0"
  }
}

webpack.config.jsを編集する

rulesに渡している配列に以下の行を追加します。

.vueの中にscssを書くのですが、test: /\.scss$/,のルールを追加することになります。

      // 拡張子.scssのファイルに対する設定(.vueの中にscssを書きたい場合もここに設定します。)
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'sass-loader',
          },
        ]
      },

ここまでやった段階でのwebpack.config.js

以下のようになっています。

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  // entry point
  entry: {
    'javascripts/first_page': './src/javascripts/entry_points/first_page.js',
  },
  // 出力するパスは絶対パスで書きます
  output: {
    path: `${__dirname}/webroot/packed`,
    filename: (arg) => {
      return '[name].bundle.js'
    },
  },
  // webpack4はlordersではなくなりました
  module: {
    rules: [
      // 拡張子.vueのファイルに対する設定
      {
        test: /\.vue$/,
        use: [
          {
            loader: "vue-loader",
            options: {
              loaders: {
                js: 'babel-loader',
              },
            },
          },
        ]
      },
      // 拡張子.jsのファイルに対する設定
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          },
        ]
      },
      // 拡張子.scssのファイルに対する設定(.vueの中にscssを書きたい場合もここに設定します。)
      {
        test: /\.scss$/,
        use: [
          {
            loader: 'style-loader',
          },
          {
            loader: 'css-loader',
          },
          {
            loader: 'sass-loader',
          },
        ]
      },
    ]
  },
  // デフォルトの設定値だけでは足りないことについて解決します
  resolve: {
    // モジュールを読み込むときに検索するディレクトリの設定
    modules: [path.join(__dirname, 'src'), 'node_modules'],
    // importするときに省略できる拡張子の設定
    extensions: ['.js', '.vue'],
    alias: {
      // importのファイルパスを相対パスで書かないようにsrcのrootを設定
      '@': path.join(__dirname, 'src'),
      // 例えばmain.js内で `import Vue from 'vue';` と記述したときの`vue`が表すファイルパスを指定
      'vue$': 'vue/dist/vue.esm.js'
    },
  },
  // プラグインを列挙
  plugins: [
    new VueLoaderPlugin()
  ],
}

もう一度ビルドを実行してみる

以下のコマンドでビルドを実行してみます。

webpack -d

今度はビルド成功したと思います。

first_page.htmlをブラウザで開いてみる

スタイルが適用されて赤い文字になっているのがわかります。

また、開発者ツールなどで確認してみると、セレクタが追加されてscopedな状態になっていることがわかります。

おわりに

今回のポイントは.vueにstyleを書くために、.scssのルールを追加するところだと思います。

変数やmixinなど複数のコンポーネントで共通して使用する部分をsass-resources-loaderで読み込むことができるのでそのうちこの記事に追記するかもしれません。

プリプロセッサの使用 · vue-loader

次回はeslintで.vueと.jsの構文チェックをしようと思います。

続きはこちら

motomichi-works.hatenablog.com

vue-test-utils + jestを追加する | webpack4.x+babel7+vue.js 2.x 環境構築 2019年3月版 ステップ0002

参考にさせて頂いたページ

テストを実行する環境の構築

specファイルの書き方

はじめに

Windowsでもできます。

MacUbuntuなどでもできると思います。

これから何回かに分けてnpm initするところから、以下のようなことをできる環境を構築していきます。

  • webpack4 + babel7 を使ってビルド
  • vue-test-utils + jest を使ってテスト実行
  • eslintを使って .vue と .js の構文チェック

今日の環境

  • Windows10 Home
  • node v8.11.1
  • npm 5.8.0

前提

以下の記事の手順で基本的なビルド環境の構築をしたところまでが前提です。

続きとしてテストを実行できるようにしていきます。

node_modulesを追加する

以下の5つのモジュールを追加する必要があるようです。

Additional Dependencies

vue-test-utils jest babel-jest (for ES2015+ features in tests) vue-jest (for handling *.vue files in tests) jest-serializer-vue (for snapshot tests)

以下のコマンドでインストールしました。

npm install --save-dev @vue/test-utils jest babel-jest vue-jest jest-serializer-vue

package.jsonを編集

package.jsonのscriptsのtestを編集してjestを実行するようにします。

  "scripts": {
    "watch": "webpack -d --watch",
    "test": "jest"
  },

jest.config.jsの作成

windowsの場合

copy nul jest.config.js

macの場合

touch jest.config.js

jest.config.jsの編集

以下のように記述します。

module.exports = {
  // テスト対象の拡張子
  "moduleFileExtensions": [
    "js",
    "vue",
  ],
  // specファイル中の"^@/(.*)$"にマッチする文字列を"<rootDir>/src/$1"のパスに置き換えてテストを実行
  "moduleNameMapper": {
    "^@/(.*)$": "<rootDir>/src/$1",
  },
  // transformerを設定
  "transform": {
    "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
    ".*\\.(vue)$": "<rootDir>/node_modules/vue-jest",
  },
  "snapshotSerializers": [
    "<rootDir>/node_modules/jest-serializer-vue",
  ],
}

ここまでやった段階でのpackage.json

以下のようになっています。

{
  "name": "webpack_4_vue_introduction",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "watch": "webpack -d --watch",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.4.0",
    "@babel/preset-env": "^7.4.2",
    "@vue/test-utils": "^1.0.0-beta.29",
    "babel-jest": "^24.5.0",
    "babel-loader": "^8.0.5",
    "jest": "^24.5.0",
    "jest-serializer-vue": "^2.0.2",
    "vue-jest": "^3.0.4",
    "vue-loader": "^15.7.0",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0"
  },
  "dependencies": {
    "vue": "^2.6.10",
    "vuex": "^3.1.0"
  }
}

testディレクトリを作成する

mkdir test

空ファイルを二つ作成する

windowsの場合

以下のコマンドで作成できると思います。

copy nul test\TextFieldUnit.spec.js
copy nul test\TextField.spec.js

macの場合

以下のコマンドで作成できると思います。

touch test/TextFieldUnit.spec.js
touch test/TextField.spec.js

./test/TextFieldUnit.spec.jsの作成とその記述内容

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
// modules
import textFieldUnit from '@/javascripts/vue_applications/common/modules/textFieldUnit.js'
// components
import TextFieldUnit from '@/javascripts/vue_applications/common/components/TextFieldUnit.vue'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('TextFieldUnit.vue', () => {
  let store;

  beforeEach(() => {
    // 本来のthis.$storeに入るものと同様のオブジェクト構造を作成します。
    store = new Vuex.Store({
      state: {},
      getters: {},
      mutations: {},
      actions: {},
      modules: {
        textFieldUnit,
      },
    })
  })

  it('mutationsの動作確認', () => {
    const state = store.state.textFieldUnit;
    const key = 'count';

    textFieldUnit.mutations.setState(state, {
      key,
      value: state[key] + 1,
    });

    expect(state[key]).toBe(1);
  });

  it('ボタンのclickイベントが発火したとき', () => {
    const wrapper = shallowMount(TextFieldUnit, { store, localVue })
    const button = wrapper.find('button')

    // 初期値の検証
    expect(button.text()).toBe('count: 0')

    // イベント発火
    button.trigger('click')

    // 変更後の値の検証
    expect(button.text()).toBe('count: 1')
  });
})

./test/TextField.spec.jsの作成とその記述内容

import { shallowMount, createLocalVue } from '@vue/test-utils'
import Vuex from 'vuex'
// modules
import textFieldUnit from '@/javascripts/vue_applications/common/modules/textFieldUnit.js'
import textField from '@/javascripts/vue_applications/common/modules/textField.js'
// components
import TextField from '@/javascripts/vue_applications/common/components/TextField.vue'

const localVue = createLocalVue()

localVue.use(Vuex)

describe('TextField.vue', () => {
  let store;

  beforeEach(() => {
    // 本来のthis.$storeに入るものと同様のオブジェクト構造を作成します。
    store = new Vuex.Store({
      state: {},
      getters: {},
      mutations: {},
      actions: {},
      modules: {
        textFieldUnit,
      },
    });
  });

  it('mutationsの動作確認', () => {
    const state = store.state.textFieldUnit.textField;
    const key = 'value';

    textFieldUnit.mutations.setState(state, {
      key,
      value: 'editedValue',
    });

    expect(state[key]).toBe('editedValue');
  });

  it('テキストフィールドのinputがイベント発火したとき', () => {
    const wrapper = shallowMount(TextField, { store, localVue });
    const input = wrapper.find('input');
    const syncedText = wrapper.find('.test-synced-text');

    // 初期値の検証
    expect(input.element.value).toBe('defaultValue');
    expect(syncedText.text()).toBe('value: defaultValue');

    // イベント発火
    input.element.value = 'editedValue';
    input.trigger('input');

    // 変更後の値の検証
    expect(input.element.value).toBe('editedValue');
    expect(syncedText.text()).toBe('value: editedValue');
  });
});

testを実行してみる

package.jsonのscriptsに定義してあるので、以下のコマンドで実行できます。

npm run test

scoped packagesになったバージョンの@babel/babel-coreを使っているため以下のようなメッセージが出てテスト実行に失敗します。

  ● Test suite failed to run

    Cannot find module 'babel-core'

      at Object.<anonymous> (node_modules/vue-jest/lib/compilers/babel-compiler.js:1:15)

babel-bridge(@babel/coreへの橋渡し)

GitHub - babel/babel-bridge: A placeholder package that bridges babel-core to @babel/core.

さきほどのエラーでは Cannot find module 'babel-core' でした。
'babel-core' を探しにいったときに '@babel/core' へ橋渡しをできるようにします。

npm install --save-dev babel-core@^7.0.0-bridge.0 @babel/core

もう一度testを実行してみる

もう一度testを実行してみます。

npm run test

以下のように4つ成功したかと思います。

Test Suites: 2 passed, 2 total
Tests:       4 passed, 4 total

ここまでやった段階のpackage.jsonの記述内容

{
  "name": "webpack_4_vue_introduction",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "watch": "webpack -d --watch",
    "test": "jest"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@babel/cli": "^7.2.3",
    "@babel/core": "^7.4.0",
    "@babel/preset-env": "^7.4.2",
    "@vue/test-utils": "^1.0.0-beta.29",
    "babel-core": "^7.0.0-bridge.0",
    "babel-jest": "^24.5.0",
    "babel-loader": "^8.0.5",
    "jest": "^24.5.0",
    "jest-serializer-vue": "^2.0.2",
    "vue-jest": "^3.0.4",
    "vue-loader": "^15.7.0",
    "vue-template-compiler": "^2.6.10",
    "webpack": "^4.29.6",
    "webpack-cli": "^3.3.0"
  },
  "dependencies": {
    "vue": "^2.6.10",
    "vuex": "^3.1.0"
  }
}

おまけ

以下のような感じでvmのcomputedやmethodsにもアクセスできます。

wrapper.vm.hoge

おわりに

testディレクトリに入っている.spec.jsファイルはもっとディレクトリ階層を深くしてもテストを実行できます。

以下のようなテストが実行できるようになりました。

  • Vueコンポーネントのテスト
  • Vuexのstoreのテスト
  • Vueとは関係なく定義した関数のテスト

今日はここまで。

次回は.vueの中にstyleを書けるようにしていこうと思います。

続きはこちら

motomichi-works.hatenablog.com

Vuexを使用する基本的なビルド環境を構築する | webpack4.x+babel7+vue.js 2.x 環境構築 2019年3月版 ステップ0001

はじめに

Windowsでもできます。

MacUbuntuなどでもできると思います。

これから何回かに分けてnpm initするところから、以下のようなことをできる環境を構築していきます。

  • webpack4 + babel7 を使ってビルド
  • vue-test-utils + jest を使ってテスト実行
  • eslintを使って .vue と .js の構文チェック

今日の環境

  • Windows10 Home
  • node v8.11.1
  • npm 5.8.0

プロジェクトディレクトリを作成

mkdir webpack_4_vue_introduction

移動してnpm init する

cd webpack_4_vue_introduction
npm init -y

グローバルにインストール

npm install -g webpack webpack-cli babel

プロジェクトにインストール

npm install --save-dev webpack webpack-cli vue-loader vue-template-compiler babel-loader @babel/core @babel/cli @babel/preset-env
npm install --save vue vuex

今回webpack_4_vue_introductionディレクトリ内に作成するディレクトリ構造とファイル

./
├── package-lock.json
├── package.json
├── src
│   └── javascripts
│       ├── entry_points
│       │   └── first_page.js
│       └── vue_applications
│           ├── common
│           │   ├── components
│           │   │   ├── TextField.vue
│           │   │   └── TextFieldUnit.vue
│           │   └── modules
│           │       ├── textField.js
│           │       └── textFieldUnit.js
│           └── pages
│               └── first_page
│                   ├── App.vue
│                   └── store.js
├── webpack.config.js
└── webroot
    └── first_page.html

ディレクトリと空のファイルを作成する

windowsの場合

windowsの場合は以下のコマンドでできると思います。

mkdir src\javascripts\entry_points
mkdir src\javascripts\vue_applications\pages\first_page
mkdir src\javascripts\vue_applications\common\components
mkdir src\javascripts\vue_applications\common\modules
mkdir webroot
copy nul .babelrc
copy nul webpack.config.js
copy nul src\javascripts\entry_points\first_page.js
copy nul src\javascripts\vue_applications\pages\first_page\App.vue
copy nul src\javascripts\vue_applications\pages\first_page\store.js
copy nul src\javascripts\vue_applications\common\components\TextFieldUnit.vue
copy nul src\javascripts\vue_applications\common\components\TextField.vue
copy nul src\javascripts\vue_applications\common\modules\textFieldUnit.js
copy nul src\javascripts\vue_applications\common\modules\textField.js
copy nul webroot\first_page.html

macの場合

mkdir -p src/javascripts/entry_points
mkdir -p src/javascripts/vue_applications/pages/first_page
mkdir -p src/javascripts/vue_applications/common/components
mkdir -p src/javascripts/vue_applications/common/modules
mkdir webroot
touch .babelrc
touch webpack.config.js
touch src/javascripts/entry_points/first_page.js
touch src/javascripts/vue_applications/pages/first_page/App.vue
touch src/javascripts/vue_applications/pages/first_page/store.js
touch src/javascripts/vue_applications/common/components/TextFieldUnit.vue
touch src/javascripts/vue_applications/common/components/TextField.vue
touch src/javascripts/vue_applications/common/modules/textFieldUnit.js
touch src/javascripts/vue_applications/common/modules/textField.js
touch webroot/first_page.html

.babelrcの編集

以下の通り記述します。

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": [
          "last 2 versions",
          "ie >= 11"
        ],
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}

webpack.config.jsの編集

以下の通り記述します。

const path = require('path');
const VueLoaderPlugin = require('vue-loader/lib/plugin');

module.exports = {
  // entry point
  entry: {
    'javascripts/first_page': './src/javascripts/entry_points/first_page.js',
  },
  // 出力するパスは絶対パスで書きます
  output: {
    path: `${__dirname}/webroot/packed`,
    filename: (arg) => {
      return '[name].bundle.js'
    },
  },
  // webpack4はlordersではなくなりました
  module: {
    rules: [
      // 拡張子.vueのファイルに対する設定
      {
        test: /\.vue$/,
        use: [
          {
            loader: "vue-loader",
            options: {
              loaders: {
                js: 'babel-loader',
              },
            },
          },
        ]
      },
      // 拡張子.jsのファイルに対する設定
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: 'babel-loader',
          },
        ]
      },
    ]
  },
  // デフォルトの設定値だけでは足りないことについて解決します
  resolve: {
    // モジュールを読み込むときに検索するディレクトリの設定
    modules: [path.join(__dirname, 'src'), 'node_modules'],
    // importするときに省略できる拡張子の設定
    extensions: ['.js', '.vue'],
    alias: {
      // importのファイルパスを相対パスで書かないようにsrcのrootを設定
      '@': path.join(__dirname, 'src'),
      // 例えばmain.js内で `import Vue from 'vue';` と記述したときの`vue`が表すファイルパスを指定
      'vue$': 'vue/dist/vue.esm.js'
    },
  },
  // プラグインを列挙
  plugins: [
    new VueLoaderPlugin()
  ],
}

first_page.jsの編集

src/javascripts/entry_points/first_page.jsに以下の通り記述します。

import Vue from 'vue';
import Vuex from 'vuex'
import store from '@/javascripts/vue_applications/pages/first_page/store'
Vue.use(Vuex)

import App from '@/javascripts/vue_applications/pages/first_page/App';

Vue.config.productionTip = false;

new Vue({
  el: '#app',
  store,
  template: '<App/>',
  components: { App },
});

App.vueの編集

src/javascripts/vue_applications/pages/first_page/App.vueに以下の通り記述します。

<template>
  <div id="app">
    <TextFieldUnit/>
  </div>
</template>

<script>
import TextFieldUnit from '@/javascripts/vue_applications/common/components/TextFieldUnit'
export default {
  name: 'app',
  components: {
    TextFieldUnit,
  },
}
</script>

store.jsの編集

src/javascripts/vue_applications/pages/first_page/store.jsに以下の通り記述します。

import Vue from 'vue'
import Vuex from 'vuex'

import textFieldUnit from '@/javascripts/vue_applications/common/modules/textFieldUnit'

Vue.use(Vuex)

const state = () => {

}

const getters = {

}

const mutations = {

}

const actions = {

}

export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
  modules: {
    textFieldUnit,
  },
})

TextFieldUnit.vueの編集

src/javascripts/vue_applications/common/components/TextFieldUnit.vueに以下の通り記述します。

<template>
  <section>
    <h1>TextFieldUnit</h1>
    <button @click="increment">count: {{ count }}</button>
    <TextField />
  </section>
</template>

<script>
import { mapState, mapMutations } from 'vuex';

import TextField from '@/javascripts/vue_applications/common/components/TextField';
export default {
  name: 'TextFieldUnit',
  components: {
    TextField,
  },
  computed: {
    ...mapState('textFieldUnit', [
      'count',
    ]),
  },
  methods: {
    ...mapMutations('textFieldUnit', [
      'setState'
    ]),
    increment() {
      this.setState({
        key: 'count',
        value: this.count + 1,
      });
    },
  }
}
</script>

TextField.vueの編集

src/javascripts/vue_applications/common/components/TextField.vueに以下の通り記述します。

<template>
  <section>
    <label>TextField: </label>
    <input
      :value="value"
      type="text"
      @input="onInput"
    >
    <div class="test-synced-text">
      value: {{ value }}
    </div>
  </section>
</template>

<script>
import { mapState, mapMutations } from 'vuex';

export default {
  name: 'TextField',
  computed: {
    ...mapState('textFieldUnit/textField', [
      'value',
    ]),
  },
  methods: {
    ...mapMutations('textFieldUnit/textField', [
      'setState',
    ]),
    onInput(e) {
      this.setState({
        key: 'value',
        value: e.target.value,
      });
    },
  },
}
</script>

textFieldUnit.jsの編集

src/javascripts/vue_applications/common/modules/textFieldUnit.jsに以下の通り記述します。

import textField from '@/javascripts/vue_applications/common/modules/textField'

const state = () => {
  return {
    count: 0,
  }
}

const getters = {

}

const actions = {

}

const mutations = {
  setState(state, payload) {
    state[payload.key] = payload.value;
  },
}

export default {
  // strictとnamespacedをそれぞれtrueにしておきます。
  strict: true,
  namespaced: true,
  state,
  getters,
  actions,
  mutations,
  modules: {
    textField,
  },
}

textField.jsの編集

src/javascripts/vue_applications/common/modules/textField.jsに以下の通り記述します。

const state = () => {
  return {
    value: 'defaultValue',
  };
}

const getters = {

}

const actions = {

}

const mutations = {
  setState(state, payload) {
    state[payload.key] = payload.value;
  },
}

export default {
  // strictとnamespacedをそれぞれtrueにしておきます。
  strict: true,
  namespaced: true,
  state,
  getters,
  actions,
  mutations,
}

first_page.htmlの編集

webroot/first_page.htmlに以下の通り記述します。

<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<title>vue-tutorial</title>
</head>
<body>
<div id="app">
</div>

<script type="text/javascript" src="packed/javascripts/first_page.bundle.js"></script>
</body>
</html>

package.jsonのscriptsに追記する

以下のような感じになるようにwatchを追記しました。

  "scripts": {
    "watch": "webpack -d --watch",
    "test": "echo \"Error: no test specified\" && exit 1"
  },

watchしてトランスパイルしてみる

npm run watch

2020年3月14日追記

2020年3月14日にここまでの手順をやり直してみたところ、以下のようなエラーが出ました。

Can't resolve 'core-js/modules/es6.array.iterator'

core.jsのバージョンに関するエラーのようです。

以下のコマンドでインストールします。

npm install --save-dev core-js@3

もう一度トランスパイルしてみたらエラーは解消されていました。

npm run watch

ここまでやった段階のpackage.jsonの内容

{
  "name": "vuex_design",
  "version": "1.0.0",
  "description": "vuexを使用する際のディレクトリ構造やコンポーネント設計のベースを作成します。",
  "main": "index.js",
  "scripts": {
    "watch": "webpack -d --watch",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/motomichi-works/vuex_design.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/motomichi-works/vuex_design/issues"
  },
  "homepage": "https://github.com/motomichi-works/vuex_design#readme",
  "devDependencies": {
    "@babel/cli": "^7.8.4",
    "@babel/core": "^7.8.7",
    "@babel/preset-env": "^7.8.7",
    "babel-loader": "^8.0.6",
    "core-js": "^3.6.4",
    "vue-loader": "^15.9.0",
    "vue-template-compiler": "^2.6.11",
    "webpack": "^4.42.0",
    "webpack-cli": "^3.3.11"
  },
  "dependencies": {
    "vue": "^2.6.11",
    "vuex": "^3.1.3"
  }
}

ページを見てみる

webroot/first_page.htmlをブラウザで開いて確認します。

  • JSエラーが出ないこと
  • ボタンをクリックするとカウントアップすること
  • テキストフィールドを編集するとその下のテキストも一緒に変化すること

今日はここまで。

次回はVuexで作っているアプリケーションをvue-test-utils + jestでテストできるようにしていきます。

続きはこちら

motomichi-works.hatenablog.com

IE9でも動くようにES6のシンタックスを使用していないことをeslintでチェックする

参考にさせて頂いたページ

はじめに

業務でIE9に対応したJSを書くことになりました。
babelは導入されていないのですが、つい癖でうっかりES6のシンタックスで書いてしまいます。
それがそのままマージされてしまうとIE9で動かなくなるので、せめてマージされないようにeslintでエラーを出してもらおうと思います。

ES6のシンタックスを使用していないことをチェックする

eslintをグローバルにインストールする

npm install -g eslint

.eslintrc.jsonを作成する

以下の通り書きました。

{
  "env": {
    "es6": false,
    "browser": true
  }
}

以下は引用ですが、公式ページによると最近は拡張子を付けた方が良いらしい。

Deprecated - use .eslintrc, which can be either JSON or YAML.

ES6シンタックスを使っていないファイルにeslintを実行してみる

ES6を使わない記述で以下の通りファイルを作成します。

var hoge = {
  example: function() {
    console.log('example');
  }
}

構文チェックを実行します。

eslint sample.js

エラーは出ませんでした。

ES6シンタックスを使っているファイルにeslintを実行してみる

ES6を使った記述で以下の通りファイルを作成します。

const hoge = {
  example() {
    console.log('example');
  }
}

構文チェックを実行します。

eslint sample_es_6.js

以下のようにエラーが出ました。

  2:1  error  Parsing error: The keyword 'const' is reserved

✖ 1 problem (1 error, 0 warnings)

constをvarに書き換えて再度実行してみます。

以下のように3行目がエラーになりました。 意図通りの挙動です。

  3:10  error  Parsing error: Unexpected token (

✖ 1 problem (1 error, 0 warnings)

任意のディレクトリ内の複数のJSファイルの構文をチェックする

例えばjavascriptsというディレクトリにJSファイルを複数入れて、以下のコマンドを実行します。

eslint javascripts/**.js

.eslintrc.jsonを任意のファイル名にしたい

例えば以下のようにファイル名を指定して実行できます。

eslint -c .eslintrc_for_ie_9.json javascripts/**.js

おわりに

これであとはCircleCIで実行するだけかしら。

なんだか本当にこれでいいのか心配だけど。

gitその0002 マージ済みのブランチをローカルからまとめて削除する

参考にさせて頂いたページ

【 xargs 】コマンド――コマンドラインを作成して実行する:Linux基本コマンドTips(176) - @IT

マージ済みのブランチをローカルからまとめて削除するコマンド

git branch の実行結果を一つずつ引数にしてくれます。
このときmasterブランチなども一緒に削除されてしまうと思うので、originからcheckoutし直すと良いと思います。

git branch | xargs git branch -d