dshimizu/blog/alpha

とりとめのないITブログ

macOS(Intel Chip) へ TypeScript の実行環境を構築するまでにやったことや調べたことの覚え書き

はじめに

今年は Typescript を勉強してみようと思い、まずは環境構築をするところから何にもわからん状態だったので調べたことの覚え書きです。

TypeScript

マイクロソフトによって開発されている Javascript 用のプログラミング言語。 静的型付けの機能を有しているのが特徴と言われていて作成したTypeScriptのコードをコンパイルすることでJavaScriptへ変換され、 AltJS と呼ばれたりもしますが最近はAltJSという単語をあまり聞かなくなったような気もします。 全然わかっていないし詳しいサイト等はたくさんあるのでここではあまり書きません。

実行環境構築

TypeScript をインストールして実行環境を構築してみます。

環境

% sw_vers
ProductName:    macOS
ProductVersion: 12.6.2
BuildVersion:   21G320

Node.jsのインストール

TypeScript のインストールは npm からインストールするため、Node.js が必要になります。 また、TypeScript コンパイラ自体が TypeScript で動くため、そこでサーバーサイドJS が必要なため Node.js が利用されるようです。

Node.js のインストール方法はいろいろあって、 nodebrew とか nvm とか volta とかがあるようですが、どれが良いか詳しくわからないのと、とりあえず TypeScript を動かせる状態にしたいので、ここでは Homebrew でインストールします。

偶数バージョンが LTS なので、現時点で最新 LTS のバージョン 18 系をインストールします。

% brew install node@18

インストールされました。

% node -v
v18.13.0

コマンドがないとか言われる場合はシンボリックリンクを貼ります。

% brew link node@18

同時に npm の cli ツールもインストールされます。

% npm -v
8.19.3
% npx -v
8.19.3

TypeScript のインストール

npm コマンドが使えるようになったので、 TypeScript をインストールします。

まず適当に作業ディレクトリを作成して移動します。

% mkdir -p ~/workspace/typescript-practice

npm の初期化を行います。 package.json が生成されます。

% npm init --yes
Wrote to /path/to/workspace/typescript-practice/package.json:

{
  "name": "typescript-practice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

作業ディレクトリ配下でインストールコマンドを実行します。

% npm install typescript --save-dev

インストールされました。 以下のようなファイル、ディレクトリ構成になりました。

% tree -L 2
.
├── node_modules
│   └── typescript
├── package-lock.json
└── package.json

package.json の中身を確認してみると、 typescriptdevDependencies のところに追記されています。 devDependencies は、プログラムの実行に必要なパッケージではなく、開発のために必要なパッケージであることを表すもののようです。 npm install --production といったコマンドを実行した場合、devDependencies のパッケージはインストールされない、などの細かい制御に使われるもののようです。

{
  "name": "typescript-practice",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "typescript": "^4.9.5"
  }
}

これで tcs というコマンドが利用可能になります。 npm install 実行時に -g or --global オプションを付与していないので、npm install 実行したディレクトリ配下に node_modules ができており、その配下にパッケージがインストールされていました。 コマンド類も以下のように node_modules 配下にあります。

% ls node_modules/typescript/bin/
tsc     tsserver

このままだとパスが通っていないので、これを実行するために npx というコマンドを組み合わせてコマンドを実行します。 npx は npm インストール時に一緒にインストールされたプログラムで、node_modules 配下のコマンドを実行してくれるコマンドラインツールのようです。

以下のようになります。

% npx tsc --version
Version 4.9.4

TypeScript の設定

プロジェクトディレクトリ直下で tsc --init コマンドを実行し、 tsconfig.json を生成します。 tsconfig.json は、中身に記載されている通り、TypeScriptのコンパイラオプションが記載されています。

% npx tsc --init

Created a new tsconfig.json with:
                                                                                                                     TS
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig

初期状態の中身は下記のような状態になっていました。

{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "es2016",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "commonjs",                                /* Specify what module code is generated. */
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "resolveJsonModule": true,                        /* Enable importing .json files. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
    // "outDir": "./",                                   /* Specify an output folder for all emitted files. */
    // "removeComments": true,                           /* Disable emitting comments. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
    // "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

    /* Type Checking */
    "strict": true,                                      /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true                                 /* Skip type checking all .d.ts files. */
  }
}

実行環境に合わせた tsconfig のパッケージが公開されているので、それをインストールします。

今回は Node.js 18 用の tsconfig インストールします。

% npm install --save-dev @tsconfig/node18

package.json は下記のようになりました。

{
  "name": "typescript-practice",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@tsconfig/node18": "^1.0.1",
    "typescript": "^4.9.5"
  },
  "description": ""
}

tsconfig.json に以下を追記します。

{
  "extends": "@tsconfig/node18/tsconfig.json"
  "compilerOptions": {
    :
  }
}

これで node_modules/@tsconfig/node18/tsconfig.json が読み込まれるようになります。 node_modules/@tsconfig/node18/tsconfig.json は下記のようになっています。

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "Node 18",

  "compilerOptions": {
    "lib": ["es2022"],
    "module": "commonjs",
    "target": "es2022",

    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "moduleResolution": "node"
  }
}

下記を参考に、tsconfig.json に追加の修正をします。

とりあえず下記のようにしてみました。

{
  "extends": "@tsconfig/node18/tsconfig.json",
  "compilerOptions": {
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "moduleResolution": "node",
    "baseUrl": "src",
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true
  },
  "include": ["src/**/*"],
  "exclude": ["dist", "node_modules"],
  "compileOnSave": false
}

JavaScript(ECMAScript) 規格

ここで出てきた es2022 というのは、 JavaScript(ECMAScript) 規格の 2022 年版です。ES2022 として2022年6月22日に正式承認されました。 ECMAScript(エクマスクリプト)というのは、Ecma International という団体のもとで標準化された JavaScript の規格になります。 ECMAScript 仕様は、Ecma International で ECMA-262 という規格番号で標準化されてるようです。

サンプル TypeScript ファイル作成・コンパイル・Node.js での実行

サンプルコードを作って動かしてみます。 プロジェクトディレクトリ直下にいることを確認します。

% pwd
/path/to/workspace/typescript-practice

Node.js の型定義用のパッケージをインストールします。 これがないと、作成したコードを TypeScript でコンパイルする時に、 Console.log() とかを呼び出そうとしたらそこでエラーになります。

% npm install --save-dev @types/node@18

package.json は下記のようになりました。

{
  "name": "typescript-practice",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@tsconfig/node18": "^1.0.1",
    "@types/node": "^18.11.18",
    "typescript": "^4.9.5"
  },
  "description": ""
}

src/ 配下に index.ts というファイルを作成します。 tsconfig.json"rootDir": "./src" と指定しているので、 src/ 配下に .ts のコードを配置すれば自動で TypeScript のコンパイル対象となります。

  • src/index.ts
const message: string = "Hello World!!";

console.log(message);

コンパイルします。

% npx tsc 

tsconfig.json""outDir": "./dist" と指定しているので、コンパイルしたコードが ./dist 配下に生成されます。 index.js, index.js.map というファイルが生成されました。

% ls dist/
index.js    index.js.map

それぞれ中身は下記のようになっています。

dist/index.jsコンパイルによって生成された JavaScript コードです。

  • dist/index.js
"use strict";
const message = "Hello World!!";
console.log(message);
//# sourceMappingURL=index.js.map%

.map 拡張子の方はソースマップファイルというものらしく、コンパイル前とコンパイル後のファイルの対応が記載されたファイルのようです。

  • dist/index.js.map
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,MAAM,OAAO,GAAW,eAAe,CAAC;AAExC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC"}%

Node.js で実行してみます。

% node dist/index.js
Hello World!!

正常に実行できました。

まとめ

macOS 上で TypeScript コードを作成してそれを Node.js で動かしてみるところまでの過程をまとめました。 似たようなコマンドがいくつか登場して、慣れないとどれが何をやっているのかわかりませんでしたが、各ツールについてもちょっと理解できました。

参考