本記事は NewsPicks Advent Calendar 2019 9日目の記事です。
- 2019/12/20: VSCode 1.41.1 のリリースに伴い
editor.codeActionsOnSave
という設定項目ができたので .vscode/settings.json を更新しました。
最近TypeScriptを書く機会が多いので、備忘録としておすすめ設定をまとめておきます。
ついでに、標準では存在しないけどあったら便利だなというメソッドをメモしておきます。
2019/12 版 TypeScript おすすめ設定
ざっくりとした内容
- nodenvでnodeのバージョン指定
- eslintでlint+フォーマット
- .vscode連携
ファイルがいっぱいあって大変ですが、、しょうがないのでやりましょう。
基本的には下記の設定をした上で、プロジェクトごとに必要な設定を足す使い方を想定しています。
.node-version
任意のnodeのバージョンを書きます。nodenvを利用していると、このプロジェクト内でのnodeのバージョンがこのバージョンに固定されます。
12.13.1
npm (package.json)
eslint関連パッケージをインストールしておきます。
npm i -D \
@typescript-eslint/eslint-plugin \
@typescript-eslint/parser \
@typescript-eslint/typescript-estree \
eslint \
eslint-config-prettier \
eslint-plugin-prettier \
eslint-plugin-simple-import-sort \
prettier
package.jsonの"scripts"
に下記を追加します。
"lint": "eslint . --ext .ts --fix"
npm run lint
で eslintが起動できます。
.eslintrc
ひたすらおすすめ設定を入れるだけです。こういうところで出来るだけ工数を取られたくないですね。
importのソートはいくつか方法があるのですが、一番設定が簡単なものを採用しています。
{
"parser": "@typescript-eslint/parser",
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"prettier/@typescript-eslint"
],
"plugins": [
"@typescript-eslint",
"prettier",
"simple-import-sort"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": ["warn", { "allowExpressions": true }],
"simple-import-sort/sort": "error"
}
}
.eslintignore
typescriptが出力する型定義ファイルをeslintが見ないようにします。
*.d.ts
.prettierrc
自動フォーマットでのインデント幅を調整します。
{
"tabWidth": 4
}
.editorconfig
ファイル末尾の改行がつくようにしておきます。typescriptのファイル以外も有効です。
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
.tsconfig.json
"compilerOptions"
の"lib"
に"es2019.array"
が入ってなかったら入れておきます。flatMap()
が有効になります。
.vscode/settings.json
vscodeではプロジェクトごとの設定と個人用設定を別に持てるので、プロジェクト単位で統一したい設定はリポジトリにコミットしてしまっていいんじゃないかと思っています。というわけで以下のようにします。ファイルの保存時にeslintに統合されたprettierが発動し、コードを自動でフォーマットします。
{
"typescript.tsdk": "./node_modules/typescript/lib",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
}
}
標準では存在しないけどあると便利なメソッド
そのうちnpm化します。きっと、、、
/**
* タプルを返します
* @param args
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function tuple<T extends any[]>(...args: T): T {
return args;
}
export type Func<T, R> = (t: T) => R;
export type KeyValuesPair<K, V> = [K, V[]];
export function isTruthy<T>(x: T | undefined): x is T {
return Boolean(x);
}
declare global {
interface Array<T> {
filterIn(this: T[], others: Iterable<T>): Array<T>;
filterNotIn(this: T[], others: Iterable<T>): Array<T>;
groupBy<K>(this: T[], keySelector: Func<T, K>): KeyValuesPair<K, T>[];
groupBy<K, V>(
this: T[],
keySelector: Func<T, K>,
valueSelector: Func<T, V>
): KeyValuesPair<K, V>[];
toMapBy<K>(this: T[], keySelector: Func<T, K>): Map<K, T>;
toMapBy<K, V>(
this: T[],
keySelector: Func<T, K>,
valueSelector: Func<T, V>
): Map<K, V>;
toMap<K, V>(this: [K, V][]): Map<K, V>;
unique(this: T[]): T[];
compact(this: T[]): NonNullable<T>[];
}
interface Set<T> {
/**
* returns union set
* @param others
*/
add(this: Set<T>, others: Set<T>): Set<T>;
/**
* returns intersection set
* @param others
*/
intersect(this: Set<T>, others: Set<T>): Set<T>;
/**
* returns difference set
* @param others
*/
subtract(this: Set<T>, others: Set<T>): Set<T>;
}
}
Array.prototype.filterIn = function<T>(
this: T[],
others: Iterable<T>
): Array<T> {
const set = others instanceof Set ? (others as Set<T>) : new Set(others);
return this.filter(x => set.has(x));
};
Array.prototype.filterNotIn = function<T>(
this: T[],
others: Iterable<T>
): Array<T> {
const set = others instanceof Set ? (others as Set<T>) : new Set(others);
return this.filter(x => !set.has(x));
};
function groupBy<T, K>(
this: T[],
keySelector: Func<T, K>
): KeyValuesPair<K, T>[];
function groupBy<T, K, V>(
this: T[],
keySelector: Func<T, K>,
valueSelector: Func<T, V>
): KeyValuesPair<K, V>[];
function groupBy<T, K, V>(
this: T[],
keySelector: Func<T, K>,
valueSelector?: Func<T, V>
): KeyValuesPair<K, V | T>[] {
const map = new Map<K, (V | T)[]>();
const keys = [] as K[];
this.forEach(x => {
const key = keySelector(x); // to let result be stable order
const collection = map.get(key);
if (!collection) {
map.set(key, [valueSelector ? valueSelector(x) : x]);
keys.push(key);
} else {
collection.push(valueSelector ? valueSelector(x) : x);
}
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return keys.map(key => tuple(key, map.get(key)!));
}
Array.prototype.groupBy = groupBy;
function toMapBy<T, K>(this: T[], keySelector: Func<T, K>): Map<K, T>;
function toMapBy<T, K, V>(
this: T[],
keySelector: Func<T, K>,
valueSelector: Func<T, V>
): Map<K, V>;
function toMapBy<T, K, V>(
this: T[],
keySelector: Func<T, K>,
valueSelector?: Func<T, V>
): Map<K, V | T> {
return new Map(
this.map(x =>
tuple(keySelector(x), valueSelector ? valueSelector(x) : x)
)
);
}
Array.prototype.toMapBy = toMapBy;
Array.prototype.toMap = function<K, V>(this: [K, V][]): Map<K, V> {
return new Map(this);
};
Array.prototype.unique = function<T>(this: T[]): T[] {
const set = new Set<T>();
const result = [] as T[];
for (const x of this) {
if (set.has(x)) {
continue;
}
set.add(x);
result.push(x);
}
return result;
};
Array.prototype.compact = function<T>(this: (T | undefined)[]): T[] {
return this.filter(isTruthy);
};
Set.prototype.add = function<T>(this: Set<T>, others: Set<T>): Set<T> {
return new Set([...this, ...others]);
};
Set.prototype.intersect = function<T>(this: Set<T>, others: Set<T>): Set<T> {
return new Set(Array.from(this).filterIn(others));
};
Set.prototype.subtract = function<T>(this: Set<T>, others: Set<T>): Set<T> {
return new Set(Array.from(this).filterNotIn(others));
};
終わり
明日は @kz_morita さんの「Github Actions で FizzBuzzしてみる」です。お楽しみに。