- TypeScript
JavaScriptからTypeScriptへ移行する手順|段階的な導入
JavaScriptからTypeScriptへの移行とは、動いているコードを止めずに、型チェックの範囲を少しずつ広げていく作業です。 一度に全部を書き換える必要はなく、tsconfig.json の allowJs と checkJs、ファイル先頭の // @ts-check、そして strict を順番に切り替えれば、毎回「エラー0件」の状態に戻しながら進められます。
この記事では、4ファイルの小さな JavaScript プロジェクトを題材に、5つの段階を実際に進め、各段階の tsconfig.json 全文と tsc が出したエラー件数・エラー全文をそのまま載せます。 TypeScript 7 で strict の既定値が変わった影響も、5.9・6.0・7.0 の3つの版で同じ設定を実行して比べました。
確認環境:Ubuntu 24.04 / Node.js 22.22.2 / TypeScript 7.0.2(npx tsc --version の表示は Version 7.0.2)。比較用に TypeScript 6.0.3 と 5.9.3 も使用。
JSからTSへの移行5段階の早見表
| 段階 | やること | tsconfig の要点 | 今回のエラー件数 | 見つかるもの |
|---|---|---|---|---|
| 1 | TypeScript を入れて JS を読ませる | allowJs: true、checkJs: false、strict: false |
0件 | なし(新しく書く .ts だけ検査) |
| 2 | 1ファイルだけ検査する | 設定は同じ。JS の先頭に // @ts-check |
3件 | タイプミス、引数の数の違い(TS2339・TS2554・TS2551) |
| 3 | 全 JS を検査する | checkJs: true |
0件(段階2で直したため) | 全ファイルの同種の誤り |
| 4 | 拡張子を .ts に変える | allowJs と checkJs を外す |
8件 | クラスのプロパティ未宣言、省略できる引数(TS2339・TS2554) |
| 5 | strict を有効にする | strict: true |
16件 → 型を書いて5件 → 0件 | 型なしの引数、undefined の可能性(TS7006・TS18048) |
| 一時的に通す方法 | 書く場所 | 効く範囲 |
|---|---|---|
// @ts-check |
JS ファイルの先頭 | そのファイルだけ検査する |
// @ts-nocheck |
JS / TS ファイルの先頭 | そのファイルだけ検査しない |
// @ts-expect-error |
エラーが出る行の直前 | 次の1行。エラーが消えたら TS2578 で知らせる |
// @ts-ignore |
エラーが出る行の直前 | 次の1行。エラーが消えても何も言わない |
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JavaScriptからTypeScriptへ移行する手順|段階的な導入」
題材にする小さなJavaScriptプロジェクト
在庫のある商品をカートに入れて合計を表示する、4ファイルのプログラムです。package.json に "type": "module" を書いた ES モジュール形式です。
{
"name": "stock-cart",
"private": true,
"type": "module",
"scripts": {
"start": "node src/main.js",
"typecheck": "tsc --noEmit"
}
}
// src/items.js
export const items = [
{ id: 1, name: "ノート", price: 300, stock: 12 },
{ id: 2, name: "ペン", price: 120, stock: 40 },
{ id: 3, name: "付箋", price: 250 },
];
export function findItem(id) {
return items.find((item) => item.id === id);
}
// src/cart.js
import { findItem } from "./items.js";
export class Cart {
constructor(owner) {
this.owner = owner;
this.lines = [];
}
add(id, quantity) {
const item = findItem(id);
if (item.stock < quantity) {
throw new Error("在庫が足りません: " + item.name);
}
this.lines.push({ name: item.name, price: item.price, quantity: quantity || 1 });
return this;
}
total() {
let sum = 0;
for (const line of this.lines) {
sum += line.price * line.quantity;
}
return sum;
}
}
// src/format.js
export function formatYen(amount) {
return amount.toLocaleString("ja-JP") + "円";
}
export function formatLine(line, width) {
const name = line.name.padEnd(width || 6, " ");
return name + " x" + line.quantity + " " + formatYen(line.price * line.quantity);
}
const settings = { taxRate: 0.1, currency: "JPY" };
export function withTax(amount) {
return Math.round(amount * (1 + settings.taxrate));
}
// src/main.js
import { Cart } from "./cart.js";
import { formatLine, formatYen, withTax } from "./format.js";
const cart = new Cart("taro");
cart.add(1, 2).add(2);
cart.note = "包装あり";
for (const line of cart.lines) {
console.log(formatLine(line));
}
console.log("小計: " + formatYen(cart.total()));
console.log("税込: " + formatYen(withTax(cart.total(), 0.08)));
console.log("件数: " + cart.lines.lenght);
node src/main.js の実行結果です。
ノート x2 600円
ペン x1 120円
小計: 720円
税込: NaN円
件数: undefined
エラーにならずに最後まで動きますが、税込が NaN、件数が undefined になっています。 JavaScript は存在しないプロパティを読んでも止まらないため、こうした誤りが表示まで届きます。移行を進めると、どの段階でこれが見つかるかを見ていきます。
段階1:TypeScriptを入れてallowJsでJSを読ませる
最初の段階では、既存のコードには一切手を入れません。TypeScript を開発用に入れ、tsconfig.json を1つ置くだけです。
npm i -D typescript@latest
npx tsc --version
Version 7.0.2
{
"compilerOptions": {
"target": "es2023",
"module": "nodenext",
"allowJs": true,
"checkJs": false,
"strict": false,
"noEmit": true
},
"include": ["src"]
}
| オプション | この段階での役割 |
|---|---|
allowJs: true |
.js ファイルを tsc の対象に含める。.ts から .js を import したときに型が推論される |
checkJs: false |
.js の中身は検査しない(既定値。意図を示すために書いている) |
strict: false |
TypeScript 6.0 以降は既定が true なので、最初は明示的に切る |
noEmit: true |
ファイルを出力せず、型チェックだけ行う |
npx tsc を実行すると、何も表示されず終了コード 0 で終わります(エラー0件)。
この段階の価値は、新しく書くファイルを最初から .ts にできることです。試しに、既存の items.js を読み込む src/tax.ts を追加します。最後の1行はわざと型を間違えています。
// src/tax.ts
import { items } from "./items.js";
export function stockValue(): number {
return items.reduce((sum, item) => sum + item.price * (item.stock ?? 0), 0);
}
const first: string = items[0].price;
src/tax.ts(7,7): error TS2322: Type 'number' is not assignable to type 'string'.
items.js には型を1つも書いていませんが、tsc は配列の中身から price が number だと推論し、.ts 側の誤りを指摘しました。確認できたら最後の1行は消しておきます。
段階2:@ts-checkで1ファイルずつ検査する
次に、検査する JS ファイルを1つだけ選び、先頭に // @ts-check を書きます。tsconfig.json は段階1のままです。呼び出し側である main.js から始めると、他のファイルの使い方の誤りがまとめて見つかります。
// @ts-check
import { Cart } from "./cart.js";
import { formatLine, formatYen, withTax } from "./format.js";
// (以下は同じ)
npx tsc の出力です(3件)。
src/main.js(7,6): error TS2339: Property 'note' does not exist on type 'Cart'.
src/main.js(13,54): error TS2554: Expected 0-1 arguments, but got 2.
src/main.js(14,33): error TS2551: Property 'lenght' does not exist on type 'any[]'. Did you mean 'length'?
| エラー | 意味 | 直し方 |
|---|---|---|
TS2339 note |
Cart のコンストラクタで作っていないプロパティに代入している |
使わない行だったので削除(必要ならコンストラクタで this.note = "" と初期化) |
| TS2554 | withTax は引数1つなのに2つ渡している |
2つ目の 0.08 を外す |
TS2551 lenght |
length のタイプミス。「件数: undefined」の原因 |
length に直す |
「Expected 0-1 arguments」と表示されるのは、JS ファイルでは引数がすべて省略可能として扱われるためです。
3か所を直しても、税込はまだ NaN のままです。原因は format.js の settings.taxrate(正しくは taxRate)ですが、format.js に // @ts-check を足しても件数は3件のまま変わりませんでした。strict: false の JS ファイルでは、オブジェクトリテラルに無いプロパティの読み取りが指摘されないためです。このタイプミスは、後で strict を有効にした時点で次のように検出されます。
src/format.js(13,44): error TS2551: Property 'taxrate' does not exist on type '{ taxRate: number; currency: string; }'. Did you mean 'taxRate'?
ここでは先に taxRate へ直しておきます。直したあとの実行結果です。
ノート x2 600円
ペン x1 120円
小計: 720円
税込: 792円
件数: 2
すぐに直せない行は@ts-expect-errorで印を付ける
直し方をあとで決めたい行は、直前に // @ts-expect-error を書くと、その1行のエラーだけを抑えられます。理由を同じ行に書いておくと、あとで探しやすくなります。
// @ts-expect-error あとで Cart に coupon を定義する
cart.coupon = "AUTUMN";
// @ts-expect-error
console.log(cart.lines.length);
src/main.js(16,1): error TS2578: Unused '@ts-expect-error' directive.
1つ目はエラーを抑えられ、エラーのない行に付けた2つ目は TS2578 で「不要になった」と知らせてくれます。 修正が済んだ印が自動で見つかるので、移行中の目印には @ts-ignore より @ts-expect-error が向いています。
段階3:checkJsで全JSファイルを検査する
ファイル単位の検査に慣れたら、checkJs を true にして全 JS を対象にします。各ファイルの // @ts-check は不要になるので消します。
{
"compilerOptions": {
"target": "es2023",
"module": "nodenext",
"allowJs": true,
"checkJs": true,
"strict": false,
"noEmit": true
},
"include": ["src"]
}
段階2で直してあるため、npx tsc はエラー0件で終わります。まだ手を付けたくないファイルがあれば、そのファイルの先頭に // @ts-nocheck を書くと検査から外せます。
TypeScript 7でstrictが既定になった影響
段階3の tsconfig.json から "strict": false の行を消すと、結果は版によって変わります。同じプロジェクトを3つの版で実行しました。
| TypeScript の版 | strict を書かない場合のエラー件数 |
|---|---|
| 5.9.3 | 0件 |
| 6.0.3 | 14件 |
| 7.0.2 | 14件 |
TypeScript 6.0 から strict の既定値が true になったためです(TypeScript 6.0 のリリースノートに「strict is now true by default」とあります)。7.0.2 の出力全文です。
src/cart.js(4,15): error TS7006: Parameter 'owner' implicitly has an 'any' type.
src/cart.js(6,5): error TS7008: Member 'lines' implicitly has an 'any[]' type.
src/cart.js(9,7): error TS7006: Parameter 'id' implicitly has an 'any' type.
src/cart.js(9,11): error TS7006: Parameter 'quantity' implicitly has an 'any' type.
src/cart.js(11,9): error TS18048: 'item' is possibly 'undefined'.
src/cart.js(11,9): error TS18048: 'item.stock' is possibly 'undefined'.
src/cart.js(12,38): error TS18048: 'item' is possibly 'undefined'.
src/cart.js(14,29): error TS18048: 'item' is possibly 'undefined'.
src/cart.js(14,47): error TS18048: 'item' is possibly 'undefined'.
src/format.js(1,27): error TS7006: Parameter 'amount' implicitly has an 'any' type.
src/format.js(5,28): error TS7006: Parameter 'line' implicitly has an 'any' type.
src/format.js(5,34): error TS7006: Parameter 'width' implicitly has an 'any' type.
src/format.js(12,25): error TS7006: Parameter 'amount' implicitly has an 'any' type.
src/items.js(7,26): error TS7006: Parameter 'id' implicitly has an 'any' type.
移行の途中では "strict": false を明示しておき、段階5で true に切り替えると、エラーの増え方を自分で調整できます。なお、cart.js の先頭に // @ts-nocheck を書くと、14件のうち cart.js の9件が消えて5件になりました。
件数を数えるコマンドは次のとおりです。
npx tsc | grep -c "error TS" # 総数
npx tsc | grep -oE "TS[0-9]+" | sort | uniq -c # エラー番号ごとの件数
14
5 TS18048
8 TS7006
1 TS7008
段階4:拡張子を.tsに変える
いよいよファイル名を変えます。Git を使っている場合は git mv src/cart.js src/cart.ts のように改名すると、履歴がつながります。import 文の "./cart.js" は書き換えません。 module: nodenext では、.js と書いた import を tsc が同名の .ts に結び付けます。
まず cart.js だけを cart.ts に変えた結果です(7件)。
src/cart.ts(5,10): error TS2339: Property 'owner' does not exist on type 'Cart'.
src/cart.ts(6,10): error TS2339: Property 'lines' does not exist on type 'Cart'.
src/cart.ts(14,10): error TS2339: Property 'lines' does not exist on type 'Cart'.
src/cart.ts(20,29): error TS2339: Property 'lines' does not exist on type 'Cart'.
src/main.js(5,16): error TS2554: Expected 2 arguments, but got 1.
src/main.js(7,25): error TS2339: Property 'lines' does not exist on type 'Cart'.
src/main.js(12,27): error TS2339: Property 'lines' does not exist on type 'Cart'.
残りの3ファイルも .ts に変えると8件になりました(main.ts(8,15) の TS2554 が1件増えます)。原因は2種類だけです。
| エラー | .js のとき | .ts にすると | 直し方 |
|---|---|---|---|
TS2339 owner / lines |
コンストラクタ内の this.x = ... がプロパティの宣言として扱われる |
クラス本体での宣言が必要 | クラスの先頭に owner; lines = []; を書く |
| TS2554 Expected 2 arguments | 引数はすべて省略可能 | 省略できる引数は ? か既定値が必要 |
add(id, quantity?)、formatLine(line, width?) |
直した cart.ts です。strict: false のままなので、型注釈はまだ書いていません。
// src/cart.ts
import { findItem } from "./items.js";
export class Cart {
owner;
lines = [];
constructor(owner) {
this.owner = owner;
}
add(id, quantity?) {
const item = findItem(id);
if (item.stock < quantity) {
throw new Error("在庫が足りません: " + item.name);
}
this.lines.push({ name: item.name, price: item.price, quantity: quantity || 1 });
return this;
}
total() {
let sum = 0;
for (const line of this.lines) {
sum += line.price * line.quantity;
}
return sum;
}
}
format.ts は formatLine(line, width?) の1か所だけ変えます。
.tsにしたあとの実行方法
.js が無くなったので、node src/main.js は使えません。試しに node src/main.ts を実行すると、import 先の ./cart.js が見つからず止まります。
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/home/taro/stock-cart/src/cart.js' imported from /home/taro/stock-cart/src/main.ts
(パスの表示は環境に合わせて置き換えています。)
そこで、tsconfig.json から allowJs・checkJs・noEmit を外し、dist に JavaScript を出力する設定にします。
{
"compilerOptions": {
"target": "es2023",
"module": "nodenext",
"strict": false,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
npx tsc && node dist/main.js
ノート x2 600円
ペン x1 120円
小計: 720円
税込: 792円
件数: 2
package.json の scripts も "build": "tsc"、"start": "node dist/main.js" に変えておきます。型チェックだけ行うときは npx tsc --noEmit です。
改名直後によく出るエラーの早見表
別の検証用ファイルで、JavaScript でよく見る書き方を .ts にして tsc 7.0.2(strict: false)にかけた結果です。
| JS での書き方 | .ts にしたときのエラー全文 | 直し方 |
|---|---|---|
const option = {}; option.width = 10; |
error TS2339: Property 'width' does not exist on type '{}'. |
const option = { width: 10 }; と最初から入れる。または型を付ける |
クラスで this.lines = [] をコンストラクタだけに書く |
error TS2339: Property 'lines' does not exist on type 'Cart'. |
クラス本体でプロパティを宣言する |
function greet(name, suffix) を greet("taro") と呼ぶ |
error TS2554: Expected 2 arguments, but got 1. |
suffix? とするか既定値を付ける |
const path = require("node:path"); |
error TS2591: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig. |
import path from "node:path"; に書き換え、@types/node を入れて "types": ["node"] を追加 |
module.exports = { double }; |
error TS2591: Cannot find name 'module'. …(以下 require と同文) |
export { double }; に書き換える |
let count = 0; count = "多数"; |
error TS2322: Type 'string' is not assignable to type 'number'. |
変数を分ける。または let count: number | string |
同じファイルで確かめた、エラーにならなかった書き方もあります。
- JSDoc の型は .ts では使われません。
/** @param {number} n */を付けた関数にdouble("3")と文字列を渡すと、.js(checkJs: true)ではerror TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.が出ましたが、.ts にすると何も出ませんでした。改名したら、JSDoc の型を引数の型注釈に書き移します。 - 関数にプロパティを足す書き方(
function counter() {}のあとにcounter.total = 0;)は .ts でもそのまま通りました。
段階5:strictを有効にして型を書く
最後に "strict": false を "strict": true に変えます(TypeScript 6.0 以降は行ごと消しても同じです)。
{
"compilerOptions": {
"target": "es2023",
"module": "nodenext",
"strict": true,
"rootDir": "src",
"outDir": "dist"
},
"include": ["src"]
}
npx tsc --noEmit の出力です(16件)。
src/cart.ts(7,15): error TS7006: Parameter 'owner' implicitly has an 'any' type.
src/cart.ts(11,7): error TS7006: Parameter 'id' implicitly has an 'any' type.
src/cart.ts(11,11): error TS7006: Parameter 'quantity' implicitly has an 'any' type.
src/cart.ts(13,9): error TS18048: 'item' is possibly 'undefined'.
src/cart.ts(13,9): error TS18048: 'item.stock' is possibly 'undefined'.
src/cart.ts(14,38): error TS18048: 'item' is possibly 'undefined'.
src/cart.ts(16,21): error TS2345: Argument of type '{ name: string; price: number; quantity: any; }' is not assignable to parameter of type 'never'.
src/cart.ts(16,29): error TS18048: 'item' is possibly 'undefined'.
src/cart.ts(16,47): error TS18048: 'item' is possibly 'undefined'.
src/cart.ts(23,19): error TS2339: Property 'price' does not exist on type 'never'.
src/cart.ts(23,32): error TS2339: Property 'quantity' does not exist on type 'never'.
src/format.ts(1,27): error TS7006: Parameter 'amount' implicitly has an 'any' type.
src/format.ts(5,28): error TS7006: Parameter 'line' implicitly has an 'any' type.
src/format.ts(5,34): error TS7006: Parameter 'width' implicitly has an 'any' type.
src/format.ts(12,25): error TS7006: Parameter 'amount' implicitly has an 'any' type.
src/items.ts(7,26): error TS7006: Parameter 'id' implicitly has an 'any' type.
一度に多すぎるときはフラグを1つずつ有効にする
strict は複数のフラグをまとめたものです。件数が多いときは、strict: false のまま中身を1つずつ有効にすると、作業を小さく区切れます。コマンドラインで試すと tsconfig.json を書き換えずに件数だけ確かめられます。
npx tsc --noEmit --strict false --noImplicitAny true | grep -c "error TS"
npx tsc --noEmit --strict false --strictNullChecks true | grep -c "error TS"
| 有効にしたフラグ | 件数 | 内訳 |
|---|---|---|
noImplicitAny のみ |
9件 | TS7006 が8件、TS7008 が1件 |
strictNullChecks のみ |
8件 | TS18048 が5件、TS2339 が2件、TS2345 が1件 |
strict: true |
16件 | TS7006 が8件、TS18048 が5件、TS2339 が2件、TS2345 が1件 |
noImplicitAny のみのときに出る TS7008 の全文は次のとおりです。
src/cart.ts(5,3): error TS7008: Member 'lines' implicitly has an 'any[]' type.
strictNullChecks を有効にすると、型注釈のない lines = [] は never[] と推論され、TS2345 と TS2339 に変わります。どちらも lines に型を書けば消えるので、先に noImplicitAny のエラー(引数とプロパティの型)を直すのが近道です。
引数とプロパティに型を書く(noImplicitAnyの解消)
データの形を type で定義し、引数に型注釈を付けます。
// src/items.ts
export type Item = {
id: number;
name: string;
price: number;
stock?: number;
};
export const items: Item[] = [
{ id: 1, name: "ノート", price: 300, stock: 12 },
{ id: 2, name: "ペン", price: 120, stock: 40 },
{ id: 3, name: "付箋", price: 250 },
];
export function findItem(id: number) {
return items.find((item) => item.id === id);
}
// src/format.ts
import type { Line } from "./cart.js";
export function formatYen(amount: number) {
return amount.toLocaleString("ja-JP") + "円";
}
export function formatLine(line: Line, width?: number) {
const name = line.name.padEnd(width || 6, " ");
return name + " x" + line.quantity + " " + formatYen(line.price * line.quantity);
}
const settings = { taxRate: 0.1, currency: "JPY" };
export function withTax(amount: number) {
return Math.round(amount * (1 + settings.taxRate));
}
cart.ts は、Line 型を定義して owner: string;、lines: Line[] = [];、constructor(owner: string)、add(id: number, quantity = 1) に変えます(完成形は次の節に載せます)。この時点で noImplicitAny のみの検査は0件になり、strict: true では5件が残りました。
src/cart.ts(15,9): error TS18048: 'item' is possibly 'undefined'.
src/cart.ts(15,9): error TS18048: 'item.stock' is possibly 'undefined'.
src/cart.ts(16,38): error TS18048: 'item' is possibly 'undefined'.
src/cart.ts(18,29): error TS18048: 'item' is possibly 'undefined'.
src/cart.ts(18,47): error TS18048: 'item' is possibly 'undefined'.
undefinedの可能性に対応する(strictNullChecksの解消)
findItem は、該当する商品が無いと undefined を返します。また stock は省略できるプロパティです。直し方をすぐ決められないときは、段階2と同じく // @ts-expect-error で印を付ければ、先に strict: true を確定できます。
const item = findItem(id);
// @ts-expect-error TODO: 商品が見つからない場合の処理を決める
if (item.stock < quantity) {
// @ts-expect-error TODO: 同上
throw new Error("在庫が足りません: " + item.name);
}
// @ts-expect-error TODO: 同上
this.lines.push({ name: item.name, price: item.price, quantity });
この状態で npx tsc --noEmit は0件になりました。残っている印の数は grep -rc "@ts-expect-error" src で数えられます(今回は src/cart.ts:3)。この数を0に近づけていくのが、移行の仕上げです。
印を外して正しく直した cart.ts の完成形です。
// src/cart.ts
import { findItem } from "./items.js";
export type Line = { name: string; price: number; quantity: number };
export class Cart {
owner: string;
lines: Line[] = [];
constructor(owner: string) {
this.owner = owner;
}
add(id: number, quantity = 1) {
const item = findItem(id);
if (item === undefined) {
throw new Error("商品が見つかりません: id=" + id);
}
if ((item.stock ?? 0) < quantity) {
throw new Error("在庫が足りません: " + item.name);
}
this.lines.push({ name: item.name, price: item.price, quantity });
return this;
}
total() {
let sum = 0;
for (const line of this.lines) {
sum += line.price * line.quantity;
}
return sum;
}
}
npx tsc --noEmit は0件、npx tsc && node dist/main.js の表示も段階4と同じです。さらに main.ts の最後に cart.add(3); を足して実行すると、在庫数の無い「付箋」に対して Error: 在庫が足りません: 付箋 が表示されました。元の JavaScript では undefined < undefined が false になり、そのままカートに入っていた箇所です。型エラーを直す過程で、値が無い場合の動きを決められたことになります。
移行を進める順番の決め方
| 判断すること | おすすめ | 理由 |
|---|---|---|
どのファイルから @ts-check を付けるか |
呼び出し側(入口のファイル) | 他のファイルの使い方の誤りがまとめて見つかる(今回は3件) |
| どのファイルから .ts に変えるか | 他から import されている側(今回は items や cart) |
型を定義すると、それを使う側の推論も正確になる |
| 新しく書くファイル | 段階1から .ts で書く | 既存の JS からも型が推論される |
| strict をいつ有効にするか | 全ファイルを .ts にしたあと。多ければフラグを1つずつ | 件数を小さく区切れる(今回は 9件 → 5件 → 0件) |
| すぐ直せないエラー | // @ts-expect-error と理由を書く |
直ったときに TS2578 で知らせてくれる |
| 検査から外したいファイル | 先頭に // @ts-nocheck |
生成されたコードなど、手を入れないファイル向け |
公式ハンドブックの「Migrating from JavaScript」でも、allowJs で JS を入力に含める → ファイルを .ts に改名する → エラーを取り除く → noImplicitAny や strictNullChecks などの厳しい検査を有効にする、という順番が示されています。空のオブジェクトにあとからプロパティを足す書き方や、引数の数の違いが改名後によく出るエラーとして挙げられている点も、今回の結果と一致しました。
よくある質問
Q. allowJs と checkJs の違いは何ですか?
A. allowJs は .js を tsc の対象に含める設定、checkJs はその .js の中身まで型チェックする設定です。allowJs: true・checkJs: false なら、JS は読み込まれて型の推論に使われますが、JS の中のエラーは報告されません。
Q. import の拡張子は .ts に書き換えますか?
A. module: nodenext では "./cart.js" のまま書き換えません。tsc が同名の .ts に結び付け、出力された JavaScript ではそのまま .js を読みます。今回も import 文は1文字も変えずに最後まで進められました。
Q. TypeScript を 5.9 から 6.0 や 7.0 に上げたら、急にエラーが増えました。
A. tsconfig.json に strict を書いていない場合、既定値が true に変わった影響です。今回のプロジェクトでは 5.9.3 で0件、6.0.3 と 7.0.2 で14件でした。"strict": false を明示すれば以前と同じ結果に戻るので、そこから段階5の手順で1フラグずつ有効にできます。
Q. @ts-ignore と @ts-expect-error はどちらを使いますか?
A. 移行中は @ts-expect-error が向いています。どちらも次の1行のエラーを抑えますが、@ts-expect-error はエラーが無くなったときに error TS2578: Unused '@ts-expect-error' directive. で知らせるため、消し忘れが残りません。
まとめ
- 移行は5段階:allowJs のみ → ファイル単位の
// @ts-check→checkJs: true→ .ts へ改名 →strict: true。各段階でエラー0件に戻してから次へ進む。 - 段階1は既存コードに手を入れない:tsconfig.json を置くだけで、新しいファイルを .ts で書け、JS からも型が推論される。
// @ts-checkは入口のファイルから:今回は TS2339・TS2554・TS2551 の3件で、表示がundefinedになる原因が見つかった。- .ts への改名直後は TS2339 と TS2554 が中心:クラスのプロパティ宣言と、省略できる引数の
?で解消できる。JSDoc の型は .ts では使われないので型注釈に書き移す。 - TypeScript 6.0 以降は strict が既定で有効:移行中は
"strict": falseを明示し、--noImplicitAny、--strictNullChecksの順に件数を数えながら進める。 - すぐ直せない行は
// @ts-expect-errorと理由:直ると TS2578 で知らせてくれるので、grep -rcで残数を追える。
参考・出典
- TypeScript Handbook「Migrating from JavaScript」 https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html
- TypeScript Handbook「JS Projects Utilizing TypeScript」 https://www.typescriptlang.org/docs/handbook/intro-to-js-ts.html
- Announcing TypeScript 6.0 https://devblogs.microsoft.com/typescript/announcing-typescript-6-0/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「JavaScriptからTypeScriptへ移行する手順|段階的な導入」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。