2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[TypeScript環境構築] VSCodeとWSLを活用した効率的な開発環境の構築

Posted at

はじめに

この記事では、TypeScript学習のための実践的な開発環境の構築手順を紹介します。
学習環境としての構築ですが、実際の開発にも耐え得る環境を構築しているのでぜひ活用してみてください。

本記事ではWSLとVSCodeのセットアップは解説していません。
WSLとVSCodeセットアップ方法はこちら をご覧ください。


この記事でわかること

・WSLとVSCodeを組み合わせた環境でのTypeScriptプロジェクトセットアップ手順
・TypeScriptからJavaScriptへのトランスパイルの流れとその手法
・TypeScriptとJavaScriptの実行方法
・ESLintとPrettierの導入と統合


目次

  1. npmのインストールとプロジェクト初期化
  2. TypeScriptのセットアップ
  3. ts-nodeの導入から学ぶトランスパイルと実行の仕組み
  4. 継続的トランスパイル
  5. ESLintとPrettierの導入と統合
  6. スクリプト設定と便利コマンドの活用

1. nvmのインストールとプロジェクト初期化

TypeScriptを導入する前段階として、nvmのインストールとnpmの初期化を実行します。
nvmNode Version Managerの略で、node.jsのバージョン管理を行ないます。
npmnode package managerの略で、nodeプロジェクトの依存関係を管理してくれるパッケージマネージャーです。
TypeScriptは JavaScript のスーパーセットであるため、実行には JavaScript のランタイムであるNode.js(あるいはブラウザ等)が必要になります。
また、nvmはあくまでnode.jsのバージョン管理であるため、依存関係は専用のパッケージマネージャーが必要です。
今回はnpmで依存関係を管理します。
node.jsのパッケージマネージャーはnpm以外にもありますが、本記事では取り扱いません。

  1. nvmのインストール

    sudo apt install nvm
    node -v  # node.jsのバージョンが表示される。筆者の環境は v22.12.0
    npm -v  # npmのバージョンが表示される。筆者の環境は 11.0.0
    
    • nvmをインストールすると、npmも一緒にインストールされます。
  2. プロジェクト初期化

    npm init -y  # package.jsonが自動生成される
    
    • 作成された package.json"type""module" に変更します。typeはモジュールのインポート形式を指定しており、JavaScript modules(ESM)とCommonJSがあります。
    • CommonJSnode.jsが開発された初期の頃の古い規格であり、現在はESMが主流となっています。CommonJSを指定する場合、トランスパイル後のJavaScriptのバージョンをES5(2025/1/18現在の最新はES2022)という古いバージョンに指定する必要があり、実質的にESM一択と言えます。

2. TypeScriptのセットアップ

TypeScriptでの開発を円滑にできるようにするための様々な準備をします。
このセクションでは、必要な準備を進めながら、なぜそれが必要なのかも理解できるように、解説に加えて適宜”想定通りではない結果”となる操作も実行します。
想定通りではない結果やエラーを通して、設定や操作の必要性を理解しつつ環境を構築していきましょう。

  1. TypeScriptのインストール

    npm install typescript --save-dev  # TypeScriptが開発環境の依存関係としてインストールされる
    
    • --save-devオプションを指定することで、TypeScriptを開発環境専用の依存関係としてインストールします。TypeScriptは開発中にのみ使用され、本番環境ではトランスパイル済みのJavaScriptが実行されるため、この設定が推奨されます。
  2. ディレクトリ作成

    mkdir src dist
    
    • ソースファイル格納先のsrc/ディレクトリと、トランスパイルファイル出力先のdist/ディレクトリを作成。
  3. index.tsの作成

    echo "console.log(\"complete npx tsc\");" > src/index.ts  # src配下にindex.tsを作成
    # トランスパイルして実行するとコンソールに complete npx tsc と表示されるスクリプト
    
    • TypeScriptファイルを作成し、トランスパイルを試す準備を整えます。
  4. index.tsのトランスパイル

    npx tsc  # トランスパイルを実行するコマンド。エラーが発生する
    
    • トランスパイル対象もオプションも指定していないため、tscが何をすればいいのかわからないためエラーが発生します。
    • エラーを回避するには、対象を引数で渡すか、後述のtsconfig.jsonの設定が必要になります。
    npx tsc src/index.ts  # エラーなくindex.tsがトランスパイルされるが、src/配下に出力される
    
    • トランスパイル対象を引数として渡すことで正常にトランスパイルされます。
    • トランスパイル出力先としてdist/を作成していましたが、srcに出力されています。これは、出力先が指定されていないためです。
    • 対象と出力先を毎回指定すれば正しくトランスパイルされますが、毎回は面倒です。tsconfig.jsonを作成することでこの問題をまとめて解決できます。
  5. index.jsの実行
    tsconfig.jsonの作成の前に、せっかくトランスパイルしたので、一度index.jsを実行してみましょう。

    node src/index.js  # complete npx tsc がコンソールに表示される
    
    • node <ファイルパス>.js でJavaScriptファイルを実行できます。
  6. tsconfig.jsonの作成と設定

    npx tsc --init  # TypeScriptプロジェクトの初期化。tsconfig.jsonが自動生成される
    
    • 自動生成されたtsconfig.jsonには、基本的な設定と、コメントアウトされたその他設定が既にに記述されています。
    • 好みやチームの事情に応じてカスタマイズすればよいですが、最初はよくわからないと思うので筆者の設定を公開しておきます。必要最低限の設定のみとなっているため、必要に応じて追加や修正をするとよいでしょう。

    設定例

    {
      "compilerOptions": {
        "target": "ESNext",
        "module": "NodeNext",
        "rootDir": "./src",
        "outDir": "./dist"
      },
      "include": ["src/**/*"]
    }
    
    • compilerOptions:トランスパイルにかかわる設定を記述するセクション
    • target:トランスパイル結果をESMCommonJSのどちらに準拠するかとそのバージョンを記述する。つまり出力された.jsESM or CommonJSとそのバージョン。ESNextは最新のESMを指定。つまりESM latest。
    • module:トランスパイルにどのモジュールシステムを使用するかを指定。Node.jsESM解決方式に合わせた NodeNextを指定。これも意味的にはESM latest。
    • rootDir:トランスパイル対象ファイルのルートディレクトリを指定する。なくてもトランスパイルは可能だが、ルートディレクトリを認識できないので、dist/src/<ファイル名>.jsになったりする。
    • outDir:トランスパイル出力先を指定する。指定しないとトランスパイル元と同じ階層に出力される。
    • include:トランスパイル対象を指定する。指定しないとtsconfig.jsonと同階層以下の.ts,.js,.mjs等のJavaScriptやTypeScript系のあらゆるファイルを対象にしようとする。
  7. tsconfig.json作成後、もう一度トランスパイルと実行をしてみる

    npx tsc  # dist/index.js が作成される
    node dist/index.js  # complete npx tsc がコンソールに表示される
    
    • トランスパイル対象もオプションも指定せずにトランスパイルが成功します。
    • トランスパイルファイルもdist/配下に生成されています。

3. ts-nodeの導入から学ぶトランスパイルと実行の仕組み

このセクションでは、tscを実行せずとも直接.tsを実行できるts-nodeを導入し、いくつかの設定変更やエラーを通してトランスパイルと実行についての理解を深めていきます。
環境構築手順というよりは学習目的が強いので、とにかく構築を進めたい人は飛ばしていただいても大丈夫です。

  1. トランスパイル操作をせず直接実行できるように ts-node の導入

    npm install ts-node --save-dev
    npx ts-node src/index.ts  # TypeError: Unknown file extension ".ts" が発生してエラーとなる
    
    • npx ts-node <ファイル名>.tsで直接tsファイルを実行できます。ただし、今回はエラーが発生しています。
    • このエラーは.tsファイルをnode.jsが認識できないという意味のエラーです。
    • node src/index.tsでもまったく同じエラーが発生します。
    • package.jontypemoduleからcommonjsに変更するとエラーが解消されます。次項で試してみましょう。
  2. package.jsonのtypeをcommonjsに変更し、ts-nodeを試してみる

       "keywords": [],
       "author": "",
       "license": "ISC",
       "type": "commonjs",
       "description": "",
       "devDependencies": {
         "globals": "^15.14.0",
         "ts-node": "^10.9.2",
         "typescript": "^5.7.3"
       }
    
    npx ts-node src/index.ts  # エラーなく実行できる
    
    • commonjsで実行できて、moduleESM)で実行できない理由は、ts-nodeの実行方式とフックがESMに対応していないからです。
    • 次項ではts-nodeのフックおよび実行方式と、tscによるトランスパイルと実行の違いについて解説します。
  3. ts-nodeのフックとnodeによる実行の仕組み
    nodeによる実行とts-nodeによるフックについて解説していきます。
    まずは、commonjsにおける実行の仕組みです。

    commonjsにおける実行の仕組み

    node index.js ⇒ require('index.js') ⇒ index.js を実行
    ts-node index.ts ⇒ require('index.ts') ⇒ (フックで ts-node がトランスパイル) ⇒ require('index.js' 相当のコード) ⇒ index.js を実行
    
    • フックとは、ある処理が行われる前または後に処理を差し込んで実行する仕組みのことです。
    • ts-nodecommonjsのモジュール読み込み処理であるrequire()の前にトランスパイルするフックを持っています。
    • node.jsが実行のためにrequire()を実行する際に、ts-nodeはそのファイルをトランスパイルします。これにより、.tsファイルを指定しても最終的にnode.jsが実行するファイルは.jsとなります。(正確にはフックの後の処理はオンザフライと呼ばれる方式で、トランスパイル後のソースを直接node.jsに渡しています。)
    • トランスパイルしたソースをnode.jsに直接渡しているため、dist/ディレクトリにファイルが出力されません。気になる方はdist/index.jsを削除して試してみてください。

    次に、ESMにおける実行の仕組みを説明します。

    ESMにおける実行の仕組み

    node index.js ⇒ import index.js ⇒ index.js を実行
    ts-node index.ts ⇒ import index.ts ⇒ (フックが適用されないためエラー; Node.js が .ts を認識しない)
    
    • ts-nodeのフックはrequire()に対するフックであるため、importには反応しません。
    • 結果的に、node.jsには.tsがそのまま渡されるため、認識できずにエラーとなります。
  4. ESMにおける.tsファイルの直接実行
    ESMにおいて、ts-nodeはそのままでは.tsファイルを直接実行できません。しかし、ある方法でESMでもts-nodeによる直接実行が可能になります。

    node --loader ts-node/esm src/index.ts  # 直接実行が成功する。ただし、ExperimentalWarningが表示される
    
    • この方法なら.tsの直接実行が可能です。ただし、警告文が表示されます。これは、このコマンドが実験段階のコマンドであり、正式リリースされたものではないからです。
    • --loadernode.jsのモジュール読み込みに使うローダーを指定します。
    • ts-node/esmESMに対応したts-nodeのフックをローダーとして提供します。
    • node.jsのローダーとして、ESMに対応したts-nodeのフックを指定することにより、.tsの直接実行を実現します。

このように、node.jsのローダー指定とts-nodeのフックを組み合わせることで.tsファイルを直接実行できます。
しかし、この方式はまだ正式リリースではないため、予期しない動作や将来的に削除されるリスクがあります。
次節では、リスクがなく安定でありながら利便性の高いトランスパイルと実行の方式として、tsc --watchによる継続的トランスパイルの方法を解説します。
(次節に進む前にpackage.jsontypemoduleに戻しておきましょう)


4. 継続的トランスパイル

tscによるトランスパイルは確実で安定していますが、ファイルを変更するたびに実行するのは少々手間です。
前節ではts-nodeによる.tsの直接実行方法を紹介しましたが、そのままではCommonJSでしか実行できませんでした。ESMでも実行する方法も紹介しましたが、実験的機能であり正式採用するにはリスクがあります。
ここでは、トランスパイルを自動かつ継続的に実行する方法を紹介します。

  1. ターミナルの分割
    ターミナルの右端にある”+”マークをクリックします。
    ターミナルが複製され、右端にターミナルを選択するウィンドウが追加されます。

    • 右端のターミナル名を 右クリック ⇒ 名前の変更 で名前を変更できます。
    • ゴミ箱マークをクリックすれば対象のターミナルを終了できます。
  2. 継続的トランスパイルの起動

    npx tsc --watch  # 継続的トランスパイルが常駐する
    
    • 継続的トランスパイルを実行する--watchオプションでtscを常駐します。
    • 常駐するため、--watchを実行中のターミナルではほかの操作ができません。複製したターミナルで実行することで元のターミナルで操作を継続できます。(コマンド末尾に&をつけてバックグラウンド実行したり等、ほかにもいろいろ方法はあります。あくまで一例です)
    • ctrl + cで終了できます。
  3. .tsファイルを修正し、自動コンパイルされていることを確認

    echo "console.log(\"watch test\");" > src/watch_test.ts  # src配下に--watch確認用の.tsファイルを作成
    node dist/watch_test.js  # watch test
    
    • すぐに自動トランスパイルされ、dist/watch_test.jsが生成される。
    • .ts作成後、即座に実行すると間に合わずにError: Cannot find moduleとなる場合がある。エラーを認識した瞬間にはもうトランスパイルされているはずなので、もう一度実行しよう。

5. ESLintとPrettierの導入と統合

前節までの準備で、TypeScriptの実行環境は整いました。
このままでも開発は可能ですが、せっかくなら安全で読みやすいコードを書けるようにさらに環境を整えていきましょう。
このセクションでは、linterとしてTypeScriptの文法や型をチェックし自動修正してくれるESLintと、フォーマットチェックと自動修正をしてくれるPrettierの導入を行ないます。

  1. ESLintのインストールと初期化

    npm install eslint --save-dev  # ESLintのインストール
    npx eslint --init  # ESLintの初期化。いくつかの質問が表示され、回答に合わせて初期化内容が変化する
    
    • 初期化時の質問と回答例:
      • 質問:How would you like to use ESLint?

        • 回答:To check syntax and find problems
          • 補足:eslintを構文チェック以外の種々の問題確認にも適用
      • 質問:What type of modules does your project use?

        • 回答:JavaScript modules (import/export)
          • 補足:モジュールのインポート形式の選択。moduleはESMを指す
      • 質問:Which framework does your project use?

        • 回答:None of these
          • 補足:React、vue.js、プレーンなTypeScriptのいずれかを選択できる。
          • 補足:今回は純粋なTypeScriptの環境を構築するのでNone of theseを選択
      • 質問:Does your project use TypeScript?

        • 回答:Yes
          • 補足:TypeScriptの環境なのでyes
      • 質問:Where does your code run?

        • 回答:Nodeのみ
          • 補足:今回は純粋なTypeScript環境で、ブラウザ実行する予定はないためNodeのみとする
          • 補足:ブラウザのチェックは外しておく(スペースキーでオンオフ切り替え可能)
      • 質問:The config that you've selected requires the following dependencies:
        eslint, globals, @eslint/js, typescript-eslint
        ? Would you like to install them now? ‣ No / Yes

        • 回答:Yes
          • 補足:依存関係としてeslint, globals, @eslint/js, typescript-eslintをインストールが必要だが、インストールしてよいか聞かれている。インストールが必要なのでyes。
      • 質問:Which package manager do you want to use?

        • 回答:npm
          • 補足:使用しているパッケージマネージャーを選択。今回はnpmを使用しているので、npm

    質問にすべて答えるとeslintの設定ファイルであるeslint.config.mjsが自動生成されます。

  2. Prettierの導入

    npm install prettier --save-dev  # Prettierのインストール
    touch .prettierrc  # 設定ファイルの作成。Prettierの設定ファイルは自動生成されない
    
    • Prettierの設定ファイルは自動生成されないため、手動で作成する必要があります。

    .prettierrcを下記のように修正

    {
      "semi": true,
      "singleQuote": true,
      "tabWidth": 2,
      "trailingComma": "all"
    }
    
    • "semi": true:文末にセミコロンを強制
    • "singleQuote": true:ダブルクオートとシングルクォートのうち、シングルクォートを強制
    • "tabWidth": 2:インデントを2byteに強制
    • "trailingComma": "all":可能なすべての場所にカンマを付けることを強制
  3. 統合用パッケージの導入

    npm install eslint-config-prettier eslint-plugin-prettier --save-dev
    
    • eslint-config-prettierESLintPrettierのルール競合を回避する設定を提供します。ESLintはフォーマッターの機能もあるため、そのままだとPrettierと競合が発生します。
    • eslint-plugin-prettierESLintPrettierのルールを適用するためのプラグインです。ESLintのフォーマッター機能をPrettierに置き換えます。
  4. ESLintとPrettierの統合設定
    ESLintPrettierを統合するために、eslint.config.mjsの設定を修正します。
    ここでは、自動生成されたeslint.config.mjsの内容と、修正後の内容を載せるのみに留めます。
    各設定の意味と意図についてはすべてコメントで補足してありますので、皆さんの理解の助けになれば幸いです。
    正直、ここはかなりの鬼門で相当な試行錯誤をしました。公式リファレンスを確認しながら試行錯誤を重ねた結果となるので、最適解であると断言はできないです。より効率的な設定や高機能な設定をご存じの方はコメントで教えていただけると大変助かります。

    • 自動生成されたeslint.config.mjs

      import globals from "globals";
      import pluginJs from "@eslint/js";
      import tseslint from "typescript-eslint";
      
      
      /** @type {import('eslint').Linter.Config[]} */
      export default [
        {files: ["**/*.{js,mjs,cjs,ts}"]},
        {languageOptions: { globals: globals.node }},
        pluginJs.configs.recommended,
        ...tseslint.configs.recommended,
      ];
      
    • 修正後のeslint.config.mjs

      import globals from "globals"; // node用のglobal変数をインポート
      import eslint from "@eslint/js" // js用のeslint。ベースとなる。
      import tseslint from "typescript-eslint"; // TypeScript用のeslint。拡張設定。
      import tsParser from "@typescript-eslint/parser"; // TypeScriptのAST解析用のparser。静的解析をサポート。
      import prettierConfig from "eslint-config-prettier";  // ESLint のスタイルルールとの競合を無効化するパッケージ。
      import prettier from "eslint-plugin-prettier"; // Prettier を ESLint のルールとして利用するためのプラグイン。
      
      
      /** @type {import('eslint').Linter.Config[]} */
      export default [
        {
          ignores: [
            "**/dist/**/*", // distディレクトリをlint対象から除外
            "**/eslint.config.*", // この設定ファイル自身をlint対象から除外
          ],
        },
        {
          files: ["src/**/*.ts"], // この設定ファイルを適用する対象をsrc配下に限定。この設定をしてもlint対象から除外されるわけではなく、ルール適用外になるだけ。除外はignoresで。
        },
        ...tseslint.config( // flat configでは設定を配列で列挙する。tseslint.config(...)は配列を返すが、export default [...]で配列形式で記述しているため、入れ子になる。よってスプレッド構文で展開。
          eslint.configs.recommended, // tseslintの設定として、eslintの推奨設定を適用。
          tseslint.configs.recommended, // tseslintの設定として、tseslintの推奨設定で設定を拡張
          tseslint.configs.strict, // eslintの型認識を厳格化。
        ),
        {
          plugins: {
            prettier, // Prettier を ESLint のルールとして利用するためのプラグイン。
          },
        },
        {
          languageOptions: {
            globals: globals.node, // nodeのデフォルト。nodeのグローバル変数をインポート。
            ecmaVersion: 15, // ECMA Scriptのバージョン。tsconfig.jsonと合わせておく。
            sourceType: "module", // ESMかCommonJSか。ESMの場合はmodule。
            parser: tsParser, // AST解析で使用するparserを設定する。TypeScriptなのでtsParserを指定。
          },
          rules: {
            "prettier/prettier": "error", // prettierルールの違反をエラーとして扱う。
            ...prettierConfig.rules, // prettierと競合するeslintルールの無効化をする設定の展開。{rules: {...}}のJSON形式であるため、スプレッド構文でrulesまで展開する。
          },
        },
      ];
      
      
  5. ESLintとPrettierのリアルタイム解析の導入

    • VSCodeの拡張機能で、ESLintを検索し、ESLintをインストールします。
    • VSCodeの拡張機能で、Prettierを検索し、Prettier – Code formatterインストールします。

    ESlintとPrettierは、本来はコマンドラインで実行することで解析や修正を行なうパッケージです。
    しかし、型やスタイルが正しいかをいちいちコマンドで解析するのは手間です。
    拡張機能を導入することで、解析をリアルタイムに反映することができます。


6. スクリプト設定と便利コマンドの活用

package.jsonにはscriptsという、スクリプトを記述するセクションがあります。
記述したスクリプトはnpm run <スクリプト名> で実行することができます。
スクリプトはLinuxのコマンドエイリアスに近いイメージです。
ここでは、筆者が設定したスクリプトを紹介します。

  1. package.jsonの設定

      "scripts": {
        "build": "tsc",
        "start": "tsc && node dist/index.js",
        "run": "node $npm_config_file",
        "tsnode": "ts-node $npm_config_file",
        "tsnode:esm": "node --loader ts-node/esm $npm_config_file",
        "watch": "tsc --watch",
        "lint": "eslint .",
        "lint:fix": "eslint . --fix",
        "format:check": "prettier src/ --check",
        "format": "prettier src/ --write"
      }
    
    • build:トランスパイルを実行します。npx tscのほうが短いですが、npm runの形式で統一することができます
    • start:トランスパイルとindex.jsを実行します。基本的なアプリケーション設計ではメインとなるファイルから必要なモジュールが呼び出されるため、起点となるファイルを指定します。
    • run:任意の.jsnodeで実行します。$npm_config_file"は引数で渡したファイルを読み込みます。
      • 実行例:npm run run index.jscomplete npx tscが出力
    • tsnodets-nodeで指定した.tsファイルを実行します。ただし、ts-node導入済でtypecommonjsの場合のみ成功します。詳しくは3章 ts-nodeの導入から学ぶトランスパイルと実行の仕組み を参照ください
    • tsnode:esmESM環境で.tsファイルを実行します。ただし、実験的機能であるため警告が表示されます。詳しくは3章 ts-nodeの導入から学ぶトランスパイルと実行の仕組み を参照ください
    • watchtsc --watchを常駐します。
    • lintESLintによる静的解析を実行します。ただし、拡張機能でリアルタイム解析されているため、基本的には実行することはありません。
    • lint:fix:ESLintによる静的解析を実行し、さらに自動修正を実行します。
    • format:checkPrettierによるフォーマットチェックを実行します。こちらもリアルタイムで解析されているため、基本的には実行することはありません。
    • formatPrettierによるフォーマット自動修正を実行します。
  2. 実行例

    • ESLintチェック:
      npm run lint  # src/watch_test.tsにエラーが検知される。
      # watch_test.tsはダブルクォートを使用しているため、シングルクォートのみの設定に違反
      
    • ESlintの自動修正:
      npm run lint:fix  # src/watch_test.tsが自動修正される
      # watch_test.tsがシングルクォートに自動修正されている
      

まとめ

この記事では、TypeScriptの学習用の開発環境構築方法を解説しました。この環境を基にTypeScriptを活用した開発を楽しんでください!


2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?