Share
Embed
大規模改修の裏でTypeScriptとテスト導入をすすめた話
Amon Keishima
June 05, 2020
Transcript
31 slides
-
プロジェクトが抱えていた問題点 2013年にスタートしたサービスであるため… テストコードが書かれていない ・Karmaが導⼊だけされているものの、テストコードがない ・しかし、リリースの度にQAを⾏うので品質は担保できていた いろんな環境が古い ・Webpackのバージョンが古い ・Babelのバージョンが古い サブプロジェクトとメインプロジェクトのビルド環境が違う ・Browserifyでビルドしている(gruntも使ってた)
・module.exports / requireを使っており import / export ではない QA: Quality Assuranceのこと -
作戦 TypeScriptの環境を導⼊ / Vue.js の TypeScript対応 Jestの導⼊ / Babelのアップデート /
Webpackのアップデート 脱Browserify / 脱grunt など… やりたいこと(やらないといけないこと)はたくさんあるが、 ⼀気にいろいろ変えてしまうとコードの差分が⼤きくなってしまう 動作を確認するにも時間かかるし レビュワーの負担も⼤きくなってしまう 今回の作戦は なるべく⼩さく⼊れること とした -
まずはTypeScriptをコンパイルできるように ・既存のjsファイルのビルド結果を変えないように ・JavaScript → TypeScript に変換したものや 新規で書いた TypeScript のコードだけが正しくトランスパイルできればOK ・ビルド時間の増加について⼀旦スルー
・Vueのファイルについても⼀旦考えない 以下の⽅針でまずははじめてみる -
まずはTypeScriptをコンパイルできるように 1. TypeScriptのビルド環境を整えつつ、1つだけ⼩さめのファイルをTS化した ・ビルド⽣成物を⾒て、そのファイル以外の差分がないことを確認した 2. tsconfig の allowJS オプションを有効にした ・TS→JSの参照の場合、jsDocがちゃんと書かれていれば
型チェックをしてくれる /** * @param {Number} id */ export function getArticle(id) { // 略 } import { getArticle } from 'foo' export function doSomething(id: string) { const article = getArticle(id) } foo.js bar.ts -
まずはTypeScriptをコンパイルできるように 3. tsconfig の target を es2019 にした → ts-loader→babel-loaderを通すときに、tsでトランスパイルをしてほしくなかった
→ spread-arrays や rest-spread は、今まで通りbabelでpolyfillを⼊れてほしい → tsのトランスパイルで差し込まれるpolyfillで挙動が変わるかもしれない みたいな⼼配をしたくなかった // spread-arrays const nums = [ 1, 2 ]; const newNums = [ 0, ...nums ]; console.log(newNums); // [ 0, 1, 2 ]; // rest-spread let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 }; console.log(z); // { a: 3, b: 4 } -
VueのSFCファイルをTS対応にする ・Vueの型定義を追加 ・まずはVue.extendを使う形に書き換えてみた(1ファイルだけ) → 差分が出るので、ここは⾃分で動作確認。問題ないことを確認 declare module '*.vue' { import
Vue from 'vue'; export default Vue; } export default Vue.extend({ props: { propA: { type: Number } } }) export default { props: { propA: { type: Number } } } -
VueのSFCファイルをTS対応にする import { Vue, Component, Prop } from 'vue-property-decorator' @Component
export default class FooComponent extends Vue { @Prop(Number) readonly propA: number | undefined } export default { props: { propA: { type: Number } } } "vue-class-decorator" や "vue-class-component" があるが、導⼊を⾒送った ・書き換え量が多いこと ・Vue3のRFCではrejectされていること → これは特に⻑く続いているプロジェクトとして気にした また⼤規模に書き換える必要がありそうなら、ここでやることはないと判断 Example: vue-class-component -
ビルド時間の短縮 TypeScriptを導⼊してから、ビルドに倍以上の時間がかかるように "fork-ts-checker-webpack-plugin" を導⼊して、型チェックのプロセスを分離した Before / 117s After / 41s
ts-loader fork-ts... ts-loader ts js ts js type-check & transpile type-check transpile -
ビルド環境のシンプル化 ts-loaderでJavaScript / TypeScript 両⽅ビルドするようにした → 数秒ビルド時間が伸びる程度・環境のシンプルさを優先 module.exports = {
module: { rules: [ { test: /\.js$/, use: babelLoader, }, { test: /\.ts$/, loader: [ babelLoader, tsLoader ], } ] } } module.exports = { module: { rules: [ { test: /\.(js|ts)x?$/, loader: [ babelLoader, tsLoader ], } ] } } Before / 41s After / 45s -
テストの導⼊ QAをしてもらえるとはいえ、しょうもないエラーを出すのは申し訳ない ⼀定のクオリティーを担保するためにテストを導⼊した ・KarmaからJestに移⾏した ・windowオブジェクトを使うテストが書きやすい → location.href を変更するようなコードとか ・jest.resetModules() が便利だった
→ コード中の変数でキャッシュしているコードなど ・新しく書くコード(特にユーテリティ系とか)を中⼼にテストを追加した -
TODO<T>: any で "攻めながらanyを使う" js → ts に拡張⼦を変えただけでビルドが通らなくなるケースはあるはず そんなときにTODO型を使うと ・とりあえずビルドを通すことができる
・やるべき型をメモしておけるので、anyより便利 // 内部的にはany でエラーを回避しつつ、本来当てたい型をメモしておける const foo: TODO<Article> = getArticles(); const foo = getArticles(); // なんかエラー出るんだけど!!! ↓ -
Vueの型解決エラーはComputedの返り値を書け ・VueのSFCをTS化する最中に、どうしても this.hogehoge の型が解決できないときがある ・こういうときはcomputedの返り値を明⽰的に書くことでほとんど解決した computed: { getOpacity(): number {
return this.isActive ? 1 : 0; } } 注釈: 循環参照の都合上こうなってしまうようです。詳しくは公式ドキュメント参照 → https://jp.vuejs.org/v2/guide/typescript.html#戻り値の型にアノテーションをつける