SvelteのESLintおすすめ設定

Takumi Shimizu
nextbeat-engineering
23 min readApr 30, 2024

0.はじめに

最近ESLintの有用性を実感している清水琢巳です。
私は、株式会社ネクストビートで「おもてなしHR」という地方創生に関わる、宿泊業界に特化した転職支援プロダクトの開発をしています。

現在おもてなしHRでは、Svelteを採用して開発しています。
その開発する中で、SvelteのESLint設定について調査をしました。
今回はその結果を共有します。

※ 本記事はeslint-plugin-svelte[1]を参考にし、私が翻訳した内容を記載しています。そのため言い回しが異なっている場合がありますが、ご了承ください。

目次

本記事の構成は以下のとおりです。

1. SvelteのESLintの導入方法
2. plugin:svelte/recommendedの設定内容
3. 個別に追加したおすすめの設定
4. 私のベスト3
5. まとめ
6. 参考文献
7. 告知

1. SvelteのESLintの導入方法

1.1. インストール

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

npm install --save-dev eslint eslint-plugin-svelte svelte

1.2. recommended設定

以下の設定を.eslintrc.jsonに追加します。これによってplugin:svclte/recommendedの設定が適用されます。

{
"extends": ["plugin:svelte/recommended"]
}

2. plugin:svelte/recommendedの設定内容

plugin:svclte/recommendedは基本的な設定が多く含まれているため、これをベースに設定を追加していくと良いでしょう。
以下にplugin:svclte/recommendedで設定されている内容を示します。

3. 個別に追加した設定

ここからは、私が個別に追加した設定を紹介します。 私個人としてはルールはガチガチにしておいて、後からもっと柔軟にしたいなと思ったときに変更するほうが好きなので、「普通そんな書き方しないよ!」というものも含めて設定してあります。

3.1 svelte/no-export-load-in-svelte-module-in-kit-pages

<script context="module">内で予期せずエクスポートされたload関数を報告してくれます。 load関数は、ページ用のjsファイルまたはtsファイルでのみ使用されるため、コンポーネントで使用する必要はありません。

<script context="module">
/* eslint svelte/no-export-load-in-svelte-module-in-kit-pages: "error" */
/* ✓ GOOD */
export function foo() {}
/* ✗ BAD */
export function load() {}
// export const load = () => {}
</script>

3.2 svelte/no-reactive-reassign

このルールは、バインドや再割り当てによって引き起こされる意図しない動作を防いでくれます。 Svelteのリアクティブ変数は、割り当てすると再割り当てされた変数がリアクティブ変数として機能しなくなります。

<script>
/* eslint svelte/no-reactive-reassign: "error" */
let value = 0;
$: reactiveValue = value * 2;

 function handleClick() {
/* ✓ GOOD */
value++;
/* ✗ BAD */
reactiveValue = value * 3;
reactiveValue++;
}
</script>

<!-- ✓ GOOD -->
<input type="number" bind:value />

<!-- ✗ BAD -->
<input type="number" bind:value={reactiveValue} />

3.3 svelte/no-store-async

このルールは、Svelteストア内でのasync/awaitの使用をすべて報告してくれます。自動購読解除機能($:)で問題が発生するためです。

/* eslint svelte/no-store-async: "error" */
import { writable } from 'svelte/store';
/* ✓ GOOD */
const w1 = writable(false, () => {});
/* ✗ BAD */
const w2 = writable(false, async () => {});

3.4 svelte/require-store-reactive-access

このルールは、ストア自体をオペランド(非演算子)として使用することを禁止します。ストアの値へのアクセスには、$接頭辞かget関数を使用しなければなりません。

/* eslint svelte/require-store-reactive-access: "error" */
<script>
import { writable, get } from 'svelte/store';
const storeValue = writable('world');
/* ✓ GOOD */
$: message = `Hello ${$storeValue}`;
/* ✗ BAD */
$: message = `Hello ${storeValue}`;
</script>

<!-- ✓ GOOD -->
<p>{$storeValue}</p>
<p>{get(storeValue)}</p>

<!-- ✗ BAD -->
<p>{storeValue}</p>

3.5 svelte/no-target-blank

このルールは、rel=”noopener noreferrer”なしでtarget=”_blank”属性を使用することを禁止しています。 「target=”_blank”」によって開かれたページは対策をしていないと、 JavaScriptを使って親ページ(元ページ)の操作ができてしまうためです。

target=”_blank”を使って新しいリンクを開いた場合、新しいタブからwindow.openerというオブジェクトを用いてリンク元の操作ができてしまいます。 リンク先のサイトが悪意のあるサイトや、ハッキングされているサイトだった場合に、リンク先から自分の見ていたリンク元ページにアクセス・操作される可能性があります。また、フィッシング詐欺に利用されるリスクもあるのです。 openerを否定するnoをつけたnoopenerを使うことで、元ページは操作されません。これにより、サイトのページを不正に改ざんされるのを防ぐことが可能です。[2]

/* eslint svelte/no-target-blank: "error" */
<!-- ✓ GOOD -->
<a href="http://example.com" target="_blank" rel="noopener noreferrer">link</a>

<!-- ✗ BAD -->
<a href="http://example.com" target="_blank">link</a>

3.6 svelte/block-lang

このルールは、すべてのSvelteコンポーネントがscriptstyleに同じ言語を設定することを強制します。 こちらのルールはなくても問題ないですが、コードに統一感を持たせるために設定しています。

/* eslint svelte/block-lang: ["error", { "script": "ts", "style": "scss" }] */
<!-- ✓ GOOD -->
<script lang="ts">
</script>

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

<!-- ✗ BAD -->
<script>
</script>

<style>
</style>

3.7 svelte/button-has-type

このルールは、ボタンのtype属性にtypeが使われていないか、無効なtypeが使われている場合に警告を出します。 <button>はデフォルトでsubmitボタンとして動作するため、type属性を指定しないと意図しない動作を引き起こす可能性があります。 また明示的にtype属性を指定することで、ボタンの動作を明確にできるメリットもあります。

/* eslint svelte/button-has-type: "error" */
<!-- ✓ GOOD -->
<button type="button">Hello World</button>

<!-- ✗ BAD -->
<button>Hello World</button>
<button type="">Hello World</button>
<button type="foo">Hello World</button>

3.8 svelte/no-ignored-unsubscribe

このルールは、subscribe()の呼び出しによって返されたサブスクライバーが、変数やプロパティに代入されていないか、関数に渡されていない場合に警告を出します。 ストアは不要になったら、常にサブスクライブを解除すべきです。でなければサブスクリプションが有効なままとなり、メモリリークの原因となります。

<script>
/* eslint svelte/no-ignored-unsubscribe: "error" */
import myStore from './my-stores';

/* ✓ GOOD */
const unsubscribe = myStore.subscribe(() => {});

/* ✗ BAD */
myStore.subscribe(() => {});
</script>

3.9 svelte/no-immutable-reactive-statements

このルールは、リアクティブ・ステートメントで参照されるすべての変数が不変である場合に警告を出します。 リアクティブ変数は、変数が変更されるたびに再評価されるため、不変な変数を使用すると、無駄な再評価が発生します。

<script>
/* eslint svelte/no-immutable-reactive-statements: "error" */
import myStore from './my-stores';
import myVar from './my-variables';
let mutableVar = 'hello';
export let prop;

/* ✓ GOOD */
$: computed1 = mutableVar + ' ' + mutableVar;
$: computed2 = fn1(mutableVar);
$: console.log(mutableVar);
$: console.log(computed1);
$: console.log($myStore);
$: console.log(prop);

const immutableVar = 'hello';

/* ✗ BAD */
$: computed3 = fn1(immutableVar);
$: computed4 = fn2();
$: console.log(immutableVar);
$: console.log(myVar);

/** ignore */
$: console.log(unknown);

function fn1(v) {
return v + ' ' + v;
}
function fn2() {
return mutableVar + ' ' + mutableVar;
}
</script>
<input bind:value={mutableVar} />

3.10 svelte/no-reactive-functions

このルールは、関数がリアクティブ・ステートメントで定義されるたびに警告を出します。 関数は実行されるたびに、必要な最新の値にアクセスしているので、リアクティブに定義する必要はありません。 リアクティブ文の中で関数を再定義することは、CPUの無駄遣いにしかなりません。

<script>
/* eslint svelte/no-reactive-functions: "error" */

/* ✓ GOOD */
const arrowFn = () => {
/* ... */
};
/* ✗ BAD */
$: arrowFn = () => {
/* ... */
};
</script>

3.11 svelte/no-reactive-literals

このルールは、リアクティブ・ステートメント内での静的で不変な値の代入を報告します。 こちらもリアクティブ変数は、変数が変更されるたびに再評価されるため、不変な値を使用すると、無駄な再評価が発生します。

<script>
/* eslint svelte/no-reactive-literals: "error" */
/* ✓ GOOD */
let foo = 'bar';

/* ✗ BAD */
$: foo = 'bar';
</script>

3.12 svelte/no-unused-class-name

このルールは、HTMLテンプレートで使用されていないクラスを報告します。 svelte-checkは、<style>ブロックにテンプレートで使用されていないクラスが含まれている場合、css-unused-selectorを生成しますが、このルールはその逆で、<style>ブロックで参照されていないクラスがテンプレートに含まれているケースを報告します。

svelte/valid-compileで未使用のstyleを警告しているため、相性が良いです。不要なコードは削除していきましょう。

/* eslint svelte/no-unused-class-name: "error" */

<!-- ✓ GOOD -->
<div class="first-class">Hello</div>
<div class="second-class third-class">Hello</div>

<!-- ✗ BAD -->
<div class="fifth-class">Hello</div>
<style>
.first-class, .second-class {
color: red;
}

.third-class {
font-size: 16px;
}
</style>

3.13 svelte/require-each-key

このルールはキーのない{#each} ブロックを報告します。 Svelteのデフォルトでは、{#each}ブロックの値を変更すると、ブロックの末尾の項目が追加・削除され、変更された値が更新されます[3] そのため、キーを指定することで、変更された値のみが更新されるようになります。

/* eslint svelte/require-each-key: "error" */
<!-- ✓ GOOD -->
{#each things as thing (thing.id)}
<Thing name={thing.name} />
{/each}

<!-- ✗ BAD -->
{#each things as thing}
<Thing name={thing.name} />
{/each}

3.14 svelte/valid-each-key

このルールは{#each}ブロックのキーが{#each}ブロックで、定義された変数名ではない場合に報告します。 {#each}ブロック内でキーは一意である必要があります。そのため、変数名をキーとして使用することが推奨されています。

<script>
/* eslint svelte/valid-each-key: "error" */

let things = [
{ id: 1, name: 'apple' },
{ id: 2, name: 'banana' },
{ id: 3, name: 'carrot' },
];
let foo = 42;
</script>

<!-- ✓ GOOD -->
{#each things as thing (thing.id)}
<Thing name={thing.name} />
{/each}

<!-- ✗ BAD -->
{#each things as thing (foo)}
<Thing name={thing.name} />
{/each}

3.15 svelte/html-self-closing

このルールは、HTML要素が自己終了しているかどうかを報告します。中身がない場合は自己終了タグを使うことで、コードがスッキリします。

/* eslint svelte/html-self-closing: "error" */
<!-- ✓ GOOD -->
<div />
<p>Hello</p>
<img />
<svelte:head />

<!-- ✗ BAD -->
<div></div>
<p> </p>
<img>
<svelte:body></svelte:body>

3.16 svelte/indent

このルールは.svelteで一貫したインデントスタイルを強制します。デフォルトのスタイルは2スペースです。 やっぱりインデントって大事ですよね。

<script>
/* eslint svelte/indent: "error" */
function click() {}
</script>

<!-- ✓ GOOD -->
<button
type="button"
on:click={click}
class="my-button primally"
>
CLICK ME!
</button>

<!-- ✗ BAD -->
<button
type="button"
on:click={click}
class="my-button primally"
>
CLICK ME!
</button>

私は以下のように設定しています。

{
"svelte/indent": [
"error",
{
"indent": 2,
"ignoredNodes": [],
"switchCase": 1,
"alignAttributesVertically": true
}
]
}

3.17 svelte/max-attributes-per-line

このルールは、可読性を向上させるために、一行あたりの属性/ディレクティブの最大数を制限します。

<script>
/* eslint svelte/max-attributes-per-line: "error" */
</script>

<!-- ✓ GOOD -->
<input
type="text"
bind:value={text}
{maxlength}
{...attrs}
readonly
size="20"
/>

<!-- ✗ BAD -->
<input type="text" bind:value={text} {maxlength} {...attrs} readonly />

こちらデフォルトでは1に設定されていますが、私は行が増えすぎるのも嫌なので2に設定しました。 ここは個人の好みに合わせて設定してください。

{
"svelte/max-attributes-per-line": [
"error",
{
"singleline": 2,
"multiline": 1
}
]
}

3.18 svelte/mustache-spacing

このルールは、マスタッシュ内(中括弧内)のスペースを統一します。

/* eslint svelte/mustache-spacing: "error" */
<!-- ✓ GOOD -->
{name}
<input {id} {...attrs} />
{@html page}
{#if c1}...{:else if c2}...{:else}...{/if}
{#each list as item}...{/each}

<!-- ✗ BAD -->
{ name }
<input { id } { ...attrs } />
{ @html page }
{ #if c1 }...{ :else if c2 }...{ :else }...{ /if }
{ #each list as item }...{ /each }

3.19 svelte/shorthand-attribute & svelte/shorthand-directive

このルールは、ディレクティブで省略構文を使うことを強制します。 Svelteは短く書けるのが魅力なので、コードがスッキリさせるためにも設定しておきましょう。

/* eslint svelte/shorthand-attribute: "error" */
<!-- ✓ GOOD -->
<button {disabled}>...</button>

<!-- ✗ BAD -->
<button disabled={disabled}>...</button>

<script>
/* eslint svelte/shorthand-directive: "error" */
let value = 'hello!'
let active = true
let color = 'red'
</script>

<!-- ✓ GOOD -->
<input bind:value>
<div class:active>...</div>
<div style:color>...</div>

<!-- ✗ BAD -->
<input bind:value={value}>
<div class:active={active}>...</div>
<div style:color={color}>...</div>

3.20 svelte/sort-attributes

このルールは属性の順序を強制します。 デフォルトの順序は以下です。

  • this
  • bind:this
  • id
  • name
  • slot
  • --style-props(同じグループ内ではアルファベット順)
  • style, style:
  • class
  • class:(同じグループ内ではアルファベット順)
  • 他(同じグループ内ではアルファベット順)
  • bind:, bind:this, on:
  • use:(同じグループ内ではアルファベット順)
  • transition:
  • in:
  • out:
  • animate:
  • let:(同じグループ内ではアルファベット順)

私は特に順序にこだわりはないですが、揃ってる方が好きなので、デフォルトのまま使用しています。

4. 私の中のベスト3

第3位 svelte/sort-attributes

第3位は、svelte/sort-attributesです。理由は、チーム開発には欠かせないルールだと考えたからです。 チーム開発では、コードの統一感が重要です。ですが、属性の順序は個人の好みによって異なることが多いです。 そのため属性の順序を統一することで、コードの見通しが良くなり、コードの品質を保つことができます。 また深く考えずとも、pre-commit時に自動で並び替えてくれるので、手間はかかりません。

第2位 svelte/no-unused-class-name

第2位は、svelte/no-unused-class-nameです。styleが割り当てられていないクラスを報告してくれるため、コードの見通しが良くなります。 またsvelte-checkにあるcss-unused-selectorとも相性が良いです。 styleやクラスは不要なものが実装されていても気づきにくいため、このルールを設定しておくことで、コードの品質を保つことができます。

第1位 svelte/require-each-key

第1位は、svelte/require-each-keyです。理由は、Svelteのベストプラクティスに則っているためです。 Svelteは学習コストが低く、扱いやすい点が魅力ですが、新しいフレームワークのため、ベストプラクティスを把握することが難しいです。 SvelteのESLint内には、ベストプラクティスをサポートしてくれるルール多く含まれていますが、その中でもrequire-each-keyは実装者が予期していない挙動を防いでくれるため、第1位に選びました。

5. まとめ

今回はSvelteのESLint設定について調査とまとめを行いました。 SvelteのESLintはかなりルールが豊富で驚きました。 特にベストプラクティスを助けてくれるようなルールが多いので、細かく観て設定しておくことは、コード品質の担保と開発スピードの向上に繋がるはずです。 初期設定が大変なイメージでしたが、Svelteの仕様を理解するきっかけにもなり、かなり勉強になりました。 ぜひ、SvelteのESLint設定を導入してみてください。

6. 参考文献

7. 告知

We are hiring! 本記事をご覧いただき、ネクストビートの技術や組織についてもっと話を聞いてみたいと思われたかた、カジュアルにお話しませんか?

・今後のキャリアについて悩んでいる ・記事だけでなく、より詳しい内容について知りたい ・実際に働いている人の声を聴いてみたい

など、まだ転職を決められていないかたでも、ネクストビートに少しでもご興味をお持ちいただけましたら、ぜひカジュアルにお話しましょう!

🔽 申し込みはこちら
https://hrmos.co/pages/nextbeat/jobs/1000008

また、ネクストビートについてはこちらもご覧ください。

🔽 エントランスブック
https://note.nextbeat.co.jp/n/nd6f64ba9b8dc

--

--