はじめに
株式会社じげんの橋濱です。
結婚相談所の比較サービス「結婚相談所比較ネット」を開発しています。
Vue.js & TypeScript でチャットボット風のフォーム作り、これを機に JS 周りの保守性を高めるため ESLint & Jest を導入しました。
導入するにあたっていくつか詰まってしまったところがあったので、手順や設定ファイルの状態を残しておきたいと思います。
IE 対応のため、今回導入した Vue.js は2系なのでその点あらかじめご承知おきください。
まずは Vue.js を使えるように
それでは、はじめに Vue.js から導入していきます。
Laravel Mix をバージョン6へ
当サービスでは IE 対応が必要だったため、Laravel Mix Polyfill 最新版の前提として Laravel Mix 6 へアップグレードしました。
また、Vue.js 3 系を使いたい場合も Laravel Mix 6 が入っていることが前提となります。
npm i laravel-mix@^6.0
NPM Scripts ( npm run
コマンド)も Laravel Mix 6 での記法に合わせて書き換えましょう。
参照: Upgrade to Mix 6 | Laravel Mix Documentation
"scripts": {
"development": "mix",
"watch": "mix watch",
"watch-poll": "mix watch -- --watch-options-poll=1000",
"hot": "mix watch --hot",
"production": "mix --production"
},
webpack.mix.js
内で path
を使っている場合、明示的にロードしないと path is not defined
エラーが出るようになったため、以下を冒頭に加えます。
参照: path is not defined when using mix.webpackConfig · Issue #503 · laravel-mix/laravel-mix
const path = require('path');
Sass を使っている場合
Sass を使っている場合、この時点でもまだ Laravel Mix が起動できないかもしれません。
その場合、一度 sass
sass-loader
をアンインストールしてから npm run dev
を実行し、Laravel Mix から自動インストールします。
参照: [6.0.0-beta.14] [webpack-cli] Invalid configuration object. · Issue #2633 · laravel-mix/laravel-mix
npm un sass sass-loader # アンインストール
npm run dev # sass sass-loader が Laravel Mix により自動でインストールされる
導入ライブラリの状況によって必要な対応
これに加えて、プロダクトで導入しているライブラリの状況によるとは思いますが、私の場合は以下を対応する必要がありました。
Laravel Mix 6 を入れる前
# Stylelint 周りのライブラリのアップグレード
npm i stylelint@^14.0.1 stylelint-order@^5.0.0
Laravel Mix 6 を入れた後
アセットトランスパイル時のキャッシュを作るために hard-source-webpack-plugin
を入れていたのですが、Laravel Mix 6 に含まれる Webpack 5 の Persistent Caching で対応されたためお役御免となりました。
# webpack: command not found を解消
npm i stylelint-webpack-plugin@^3.1.0
# Cannot read property 'tap' of undefined を解消
npm un hard-source-webpack-plugin
また、stylelint-webpack-plugin
のアップグレードによりオプションの記法が変わったため postcss-scss
を導入して対応しました。
npm i postcss-scss@^4.0.2
new StylelintPlugin({
+ customSyntax: 'postcss-scss'
- syntax: 'scss'
}),
Vue.js をトランスパイルするための記述を追加
mix.vue({ version: 2 }); // Vue.js 2系の場合は version オプションを指定
最後に、もし vue
が package.json
になければインストールします。
(上述した sass
sass-loader
のように、もしかしたら Laravel Mix 起動時に自動でインストールしてくれるかも)
※ Laravel 6 から Vue.js がデフォルトでインストールされなくなったため
npm i vue vue-loader vue-template-compiler
これで Vue.js の導入が完了です。
TypeScript で Vue.js を書くために
TypeScript のライブラリを導入します。
npm i typescript ts-loader
{
"include": ["resources/ts/**/*"],
"compilerOptions": {
"target": "es2021",
"lib": ["dom", "es2021"],
"moduleResolution": "node",
"sourceMap": true,
"strict": true,
"noUnusedLocals": true,
"experimentalDecorators": true,
"baseUrl": "./",
"paths": {
"@": ["resources/ts"],
"@/*": ["resources/ts/*"]
}
}
}
.vue
ファイル用の型定義
Vue ファイル内の import
で子コンポーネントを読み込めるように、型定義ファイルを作成します。
declare module '*.vue' { // .vue ファイルの型定義
import Vue from 'vue';
export default Vue;
}
"paths": {
// たとえば上のように resources/ts/@types/ 以下へ型定義ファイルを設置する場合、
// Laravel Mix がそれを検知できるようにしておく
"*": ["resources/ts/@types/*"],
// ...
}
ここまでで TypeScript の導入は完了です。
ESLint に構文をチェックさせる
以下を導入します。
npm i @typescript-eslint/parser eslint eslint-plugin-vue vue-eslint-parser
{
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:vue/recommended"
],
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": 2021,
"sourceType": "module"
}
}
アセットのビルド中に構文チェックをしたい場合は eslint-webpack-plugin
も導入します。
npm i eslint-webpack-plugin
// ...
const EslintPlugin = require('eslint-webpack-plugin');
const webpackConfig = {
// ...
plugins: [
new EslintPlugin[{
extensions: ['vue', 'ts', 'js'],
exclude: ['node_modules', 'vendor']
}]
]
};
mix.webpackConfig(webpackConfig);
最後に、NPM Scripts から実行できるようにします。
// webpack.mix.js もついでに構文チェック
"lint:ts": "eslint --ext .vue,.ts,.js --fix resources/{vue,ts}/ webpack.mix.js",
ESLint のルール設定
これで ESLint の導入自体は完了ですが、参考までに比較ネットではこんな感じでルール設定しました。
"rules": {
"semi": ["error", "always"], // セミコロン必須。PHP の文法と統一
"semi-spacing": ["error", { "after": true, "before": false }], // セミコロンは分の直後
"semi-style": ["error", "last"], // セミコロンはいつも行末へ
"no-extra-semi": "error", // 二重で付いてたり等の、不要なセミコロンを禁止
"no-unexpected-multiline": "error", // 紛らわしい複数行コードの禁止
"no-unreachable": "error", // 到達不能コードの禁止
"operator-linebreak": ["error", "before"], // 改行は演算子の前で行う
"quotes": ["error", "single", { "avoidEscape": true }], // 基本はシングルクォーテーション
"max-len": ["error", { "code": 120, "tabWidth": 2 }], // PHPCS に合わせて120文字を最長の文字数に
"indent": ["error", 2], // インデントを2に
"comma-dangle": ["error", "always-multiline"], // 複数行の場合はいつもトレイリングカンマを付ける
"curly": "error", // 波括弧なしの if 文を禁止
"no-undef": "off", // eslint-plugin-vue により props のデフォルト値が必須なため undefined を使えるよう対応
"vue/html-indent": ["error", 2, { "baseIndent": 1 }], // <template> 内のベースインデントレベルを1に
"vue/script-indent": ["error", 2, { "baseIndent": 1, "switchCase": 1 }] // <script> 内のベースインデントレベルを1に
},
"overrides": [ // indent ルールと vue/script-indent ルールが衝突するため、ここで回避
{
"files": ["*.vue"],
"rules": {
"indent": "off"
}
}
]
Jest でコンポーネントテストを始めたい
関連ライブラリを導入します。
参照: vue-test-utils | Vue Test Utils
npm i jest vue-jest @vue/test-utils
module.exports = {
testEnvironment: 'jsdom',
moduleNameMapper: { // テストファイル内で import 時のエイリアス(ここでは `@/` )を使うのに必要
'^@/(.*)$': '<rootDir>/resources/$1',
},
moduleFileExtensions: ['vue', 'ts', 'js', 'json'],
transform: {
'^.+\\.vue$': 'vue-jest',
'^.+\\.ts$': 'ts-jest',
'^.+\\.js$': 'babel-jest',
},
coverageDirectory: '<rootDir>/resources/coverage', // ルートには PHPUnit の tests/ があり混同しそうなため、resources/ 下へ
transformIgnorePatterns: [ // @vue/test-utils を使うために必要
'node_modules/(?!@ngrx|(?!deck.gl)|ng-dynamic)',
],
collectCoverage: true,
collectCoverageFrom: [ // どの階層のファイルからカバレッジを計算するか
'<rootDir>/resources/vue/components/**/*.vue',
],
};
コンポーネントファイルをテストしたい場合、 import
export
などの ES Modules 構文が Jest で解釈できないため、上で書いたように babel-jest
を使います。
Babel の設定ファイルも追加します。
ちなみに Babel 自体は Laravel Mix の依存ライブラリなので、あえてインストールする必要はありません。
module.exports = {
presets: [
[
'@babel/preset-env',
{
targets: { node: 'current' },
},
],
],
};
NPM Scripts に登録します。
ちなみに test
のコマンドだけは npm run test
でなく npm test
でも実行できます。
Vuex を使っている場合の書き方
コンポーネント中に Vuex を使っている場合、テスト用の Vue クラスに Vuex プラグインを追加して、wrapper 作成時に含める必要があります。
Wrapper
は、マウントされたコンポーネントと仮想 DOM 、またはコンポーネントと仮想 DOM をテストするメソッドを含むオブジェクトです。
import Vuex from 'vuex';
import { mount, createLocalVue } from '@vue/test-utils';
import store from '../../../../../../store/hoge';
// ...
const localVue = createLocalVue(); // テスト用の Vue コンポーネントを作る
localVue.use(Vuex);
let wrapper: any;
beforeEach(() => { // 各テストごとに wrapper を作る
wrapper = mount(AnswerHoge, { localVue, store });
});
describe('AnswerHoge', () => {
test('回答後、回答がグローバルステートと一致しているか', () => {
wrapper.vm.processAnswer(3); // コンポーネントのメソッドやデータは wrapper.vm から生やす形で参照
expect(wrapper.vm.$store.state.answer.hoge).toBe(3);
});
});
まとめ
当サービスはこれまで Blade &バニラ JS なフロントエンド構成だったため、今回の導入で一気にモダン化しました。
キャッチアップを頑張っていきます!