Vue3からはfilterが廃止されます。
何も考えずアップデートすると今までのコードが突如として動かなくなる悲しい場面に遭遇するわけです。
filter依存コードを手動で変換していくのはしんどいです。
特に、突然composition APIに移行するのはなおさらしんどいです。
そこで一旦options APIのままVue3に対応したコードを生成するeslint pluginを作りました。
npmに何か公開するのも、なんならqiitaに投稿するのも人生初です。
対象
- composition APIへの過渡期として、filterを利用したVue2以前のコードを自動的にVue3に移行したい
- Options APIを使用している
- Global filterを1ファイルで登録している
-
const { filterX } = this.$options.filters
とかいう使い方をして いない
何が起きるの?怖いんだけど
2フェイズに分かれています。
フェイズ1
- global filterをexportに変換
- 既存のfilterの名前をそのまま使えます
- filterと同名のでimportされた関数は
filterName as _filterName
みたいに名前を変更しています
before
import Vue from 'vue';
import def, { numeric } from '~/utils/Strings';
import dollar, { yen, euro } from '~/utils/Money';
Vue.filter('stringDefaultFilter', def);
Vue.filter('percent', (value: string) => `${value}%`);
Vue.filter(
'numeric',
(value: string) => `${numeric(value)}`,
);
Vue.filter('dollar', dollar);
Vue.filter('yen', yen);
Vue.filter('eur', euro);
Vue.filter('func', function(xxx: string) {
return `xxx${xxx}xxx`;
});
after
import def, { numeric as _numeric } from '~/utils/Strings';
import dollar, { yen, euro } from '~/utils/Money';
export const stringDefaultFilter = def;
export const percent = (value: string) => `${value}%`;
export const numeric = (value: string) => `${_numeric(value)}`;
export { dollar };
export { yen };
export const eur = euro;
export function func(xxx: string) {
return `xxx${xxx}xxx`;
}
フェイズ2
- フェイズ1でexportした関数をimport
- パイプによるfilterを関数呼び出しに変換
-
(this.)$options.filters
も変換 - メソッドとしてマッピング
- local filterにも対応
before
<template>
<div>
<p>{{ x.y.z | localFilterA }}</p>
<p>{{ id[0] | localFilterB }}</p>
<p>{{ $options.filters.filterA(prop) | localFilterC }}</p>
<p>{{ prop | localFilterA | filterB }}</p>
<p>{{ methodFilter(prop) }}</p>
<p :id="id | idFilter">XXX</p>
<p :id="$options.filters.idFilter2(id) | localIdFilter">XXX</p>
<p :id="$options.filters.idFilter3(id)">XXX</p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
filters: {
localFilterA(value: string): string {
return value.toUpperCase();
},
localFilterB(value: number): string {
return value.toString();
},
localFilterC(obj: { [key: string]: string }): string {
const texts = Object.entries(obj).reduce((acc: string[], [k, v]) => {
acc.push(`${k}: ${v}`);
return acc;
}, []);
return texts.join('\n');
},
localIdFilter(id: string): string {
return id.slice(0, 5);
},
},
methods: {
methodFilter(x: string): string {
return this.$options.filters.importedFilter(x);
},
},
})
</script>
after
<template>
<div>
<p>{{ localFilterA(x.y.z) }}</p>
<p>{{ localFilterB(id[0]) }}</p>
<p>{{ localFilterC(filterA(prop)) }}</p>
<p>{{ filterB(localFilterA(prop)) }}</p>
<p>{{ methodFilter(prop) }}</p>
<p :id="idFilter(id)">XXX</p>
<p :id="localIdFilter(idFilter2(id))">XXX</p>
<p :id="idFilter3(id)">XXX</p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import { filterA, filterB, idFilter, idFilter2, idFilter3, importedFilter } from 'path/to/filters.ts';
export default Vue.extend({
methods: {
methodFilter(x: string): string {
return this.importedFilter(x);
},
filterA,
filterB,
idFilter,
idFilter2,
idFilter3,
importedFilter,
localFilterA(value: string): string {
return value.toUpperCase();
},
localFilterB(value: number): string {
return value.toString();
},
localFilterC(obj: { [key: string]: string }): string {
const texts = Object.entries(obj).reduce((acc: string[], [k, v]) => {
acc.push(`${k}: ${v}`);
return acc;
}, []);
return texts.join('\n');
},
localIdFilter(id: string): string {
return id.slice(0, 5);
},
},
})
</script>
使い方
上記対象に当てはまった方には、使い方です。(そうじゃない方は諦めてPR送るかコード読んでいじるかしてください)
インストール
$ npm install -D eslint-plugin-destroy-vue-filter
準備
eslint設定ファイルを2個作成することをおすすめします。
- フェイズ1用
ファイル名は好きに(以下、.eslintrc.convert-filterとします)
.eslintrc.convert-filter
{
"plugins": ["destroy-vue-filter"],
"rules": {
"destroy-vue-filter/convert-filter": 1,
"destroy-vue-filter/resolve-callee": 1
}
}
- フェイズ2用
ファイル名は好きに(以下、.eslintrc.destroy-filterとします)
.eslintrc.destroy-filter
{
"plugins": ["destroy-vue-filter"],
"rules": {
"destroy-vue-filter/destroy-filter": ["error", "<global filterを登録しているファイル>"]
}
}
実行
以下を実行してください
eslint -c .eslintrc.convert-filter --fix <global filterを登録しているファイル>
eslint -c .eslintrc.destroy-filter --fix **/*.vue
なぜeslint?
- Vue/typescriptをパースしてVue/typescriptに書き戻せるツールが他に見つからなかった(あったら教えて下さい)
- linterのくせに基本一回しか使わないけどまあいいよね
- 複数箇所でglobal filterを登録していたらお手上げです。無理
-
const { filterX } = this.$options.filters
みたいな使い方も変数のスコープをめっちゃ見なきゃならないので無理 - 正直会社でのユースケース(+ 私が思いつく適当に複雑なパターン)に合わせました
- 僕は適当なんだ。すまない
- ASTを頑張っていじくってきた経緯からgitのlogはグダグダです。見るな
- 正直動かなかったら言ってください。僕はフロントエンドエンジニャー歴がとても短いです