TypeScriptのインストール
TypeScriptのインストール方法はグローバルインストールとローカルインストールの2種類あります。
グローバルインストール方法は以下です。
$ npm install -g typescript
環境を汚したくない場合はローカルインストールをします。node_modules
が作成されその中にインストールされます。こっちだとpackage.json
やpackage-lock.json
にTypeScript情報が追記されて、移植性が高いのでおすすめ。
$ npm install --save typescript # または $ npm install --save-dev typescript
--saveと--save-devの違いはnpmの--save, --save-dev, --save-optionalの違い - how to code something
を参照。今回私はChrome拡張を作るつもりなので--save-dev
でインストールします。
ローカルインストールの場合、tsc
コマンドを相対パスで指定する必要があります。毎回それだと面倒くさいので、package.json
(package-lock.jsonではない)のscriptsフィールドにショートカットを記述します。
{ "name": "sample", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "tsc": "tsc" }, ... }
こうすることで、npm run
コマンドでローカルインストールしたnode_modules内のtsc
を実行できるようになります。
$ tsc # グローバルインストールの場合 $ npm run tsc # ローカルインストールの場合
参考URL
tsconfig.jsonの作成
次にtsconfig.json
の雛形を作成します。これはTypeScriptからJavaScriptにトランスパイルする際の設定ファイルです。
# グローバルインストールの場合 $ tsc --init # ローカルインストールの場合 $ ./node_modules/.bin/tsc --init # npm run tsc はオプション引数を渡すことができないので、以下だとエラーが発生する $ npm run tsc --init
tsconfig.jsonの代表的なプロパティは以下のような感じです。
compilerOptions.target
生成するJavaScriptのECMAScriptバージョン。
compilerOptions.module
モジュールの形式。
compilerOptions.strict
厳密な型変換をするかどうか(暗黙的な型変換を許さないかどうか)。TrueでOK
compilerOptions.esModuleInterop
CommonJS形式で書かれた外部ライブラリのモジュールを妥当に扱えるようにするらしいです。よくわからないからそのままで。
compilerOptions.outDir
tsc
をしてコンパイルしたとき、出力ファイルはtsconfig.js
と同じ場所に出力されます。別フォルダに出力したいときはこのプロパティで指定します。
dstフォルダに出力したいときは以下のようにします。
{ "compilerOptions": { "outDir": "dst", /* Redirect output structure to the directory. */ ... }
Include
tsc
コマンドでコンパイルするファイルをフォルダで指定します。
srcフォルダ内のファイルを対象としたいときは以下のようにします。
{ "compilerOptions": { ... }, "include": [ "src/**/*" ] }
allowJsとcheckJs
tsc
のコンパイル対象にJavaScriptファイルを含めたいときには、これらの項目を設定します。既存のJavaScriptファイルをTypeScript内でimportしたいときはこの設定が必要です。
{ "compilerOptions": { ..., "allowJs": true, "checkJs": true } }
まとめ
tsconfig.json
は、例えば私の場合は以下のようになりました。
{ "compilerOptions": { "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "allowJs": true, /* Allow javascript files to be compiled. */ "checkJs": true, "outDir": "dst", /* Redirect output structure to the directory. */ "strict": true, /* Enable all strict type-checking options. */ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ }, "include": [ "src/**/*" ] }
ビルドツールのインストール
実際のアプリ開発では直接tsc
コマンドを打つことはなく、ビルドツールと連携して使うことが多いようです。
私の場合はwebpackを使いたいのでそれに関するものをインストールします。
webpackのインストール
webpackはTypeScriptの自動コンパイル、複数のソースコードの結合、ソースコード更新時の自動リロードなどをしてくれます。一部tscと機能が被っていますね。
$ npm install --save-dev typescript ts-loader webpack webpack-cli webpack-dev-server webpack-merge
ざっくりとですが、各モジュールについて私の理解で書いておきます。
- typescript
- TypeScriptファイル(.ts)をコンパイルするために必要。
- ts-loader
- よくわからないけど、TypeScriptと連携するために必要なwebpack用のloader。
- webpack
- webpackを使って.tsファイルをコンパイルするために必要。
- webpack-cli
- 開発中に、ファイルを保存したらコンパイルも自動でされるみたいな便利なことをするために必要。
- webpack-dev-server
- 開発用WEBサーバ。
- webpack-merge
- webpack.config.jsを、開発用と本番用にファイルを分割するために必要。
ts-loaderはnode_modules内にtypescriptがあることを前提としているので、typescriptをローカルインストールする必要があるらしいです。
webpack.config.js
webpackの設定ファイルです。設定方法は参考URLのTypeScriptチュートリアル① -環境構築編- - Qiitaが詳しいのでそちらを参照。
const path = require('path'); module.exports = { entry: { content_scripts: './src/content_scripts.ts' }, output: { path: path.join(__dirname,'dst'), filename: '[name].js' // [name]は、entryのプロパティ名(content_scripts) }, optimization: { minimize: true }, resolve: { extensions:['.ts','.js'] }, devServer: { contentBase: path.join(__dirname,'dst') }, module: { rules: [ { test:/\.ts$/, use: { loader:'ts-loader' } } ] } }
出力フォルダの指定などがtsconfig.json
と被ってますね。webpackを通じてコンパイルするときはwebpack.config.js
の設定でされるのできちんと設定しておきます。
webpack.config.jsを開発用と本番用にファイルを分ける
webpackで開発用/本番用の設定を分ける - Qiitaが詳しいのでそちらを参照。
例えば、開発用は出力ファイルを圧縮せずに、本番用は出力ファイルを圧縮する。といった切り替えをしたいです。このようにしたいとき、公式の推奨によるとwebpack.config.jsを以下のようにファイル分割します。
- webpack.common.js
- 共通設定。開発用と本番用の両方に適用したい設定を記述します。
- webpack.dev.js
- 開発用設定
- webpack.prod.js
- 本番用設定
例えば私は以下のようになりました。
webpack.common.js
const path = require('path'); module.exports = { entry: { // ビルドの起点となるファイルの指定 content_scripts: './src/content_scripts.ts' }, output: { // ビルド結果の出力場所 path: path.join(__dirname,'dst'), filename: '[name].js' // [name]は、entryのプロパティ名(content_scripts) }, resolve: { // モジュールとして扱いたいファイルの拡張子を指定する extensions:['.ts','.js'] }, devServer: { // webpack-dev-serverの公開フォルダ contentBase: path.join(__dirname,'dst') }, module: { rules: [ { // 拡張子が.tsで終わるファイルに対して、TypeScriptコンパイラを適用する test:/\.ts$/, use: { loader:'ts-loader' } } ] } }
webpack.dev.js
// webpack-merge ver.5.0.3以降は { merge } = ... という書き方になる const { merge } = require('webpack-merge') const common = require('./webpack.common.js') // 汎用設定をインポート // common設定とマージする module.exports = merge(common, { mode: 'development', // 開発モード devtool: 'inline-source-map', // 開発用ソースマップ optimization: { minimize: false // 出力JSファイルを圧縮しない } })
webpack.prod.js
// webpack-merge ver.5.0.3以降は { merge } = ... という書き方になる const { merge } = require('webpack-merge') const common = require('./webpack.common.js') // 汎用設定をインポート // common設定とマージする module.exports = merge(common, { mode: 'production', // 本番モード optimization: { minimize: true // 出力JSファイルを圧縮する } })
package.jsonを編集してwebpackのコマンドを簡単に使えるようにする
package.jsonのscriptsフィールドに、以下のプロパティを追加します。
"scripts": { "tsc": "tsc", "build": "webpack --config webpack.prod.js", "build-dev": "webpack-cli -w --config webpack.dev.js", "server": "webpack-dev-server --config webpack.dev.js" },
これにより、
npm run build
で、webpackをProduction(本番環境)モードで起動して、ビルドする- .tsファイルを本番用にビルドする用のコマンド
npm run build-dev
で、webpack-cliをDevelopment(開発)モードかつwatchモード(ファイルを保存すると自動ビルドされる)で起動する- 普段の開発中はこちらを使う
npm run server
で、webpack-dev-serverを開発モードで起動する- webpack-dev-serverは、開発用WEBサーバーを起動するコマンド。localhost:8080にアクセスできる
ができるようになりました。webpack-dev-serverはビルド処理もしてくれるため、基本的にnpm run server
だけ使っておけば問題ないらしいですが、webpack-dev-serverは、ファイルを変更したときはdstフォルダに出力ファイルは更新されず、メモリ上に保存されるようです。
私の場合はChrome拡張を作りたかったので、Chrome拡張は出力ファイルをChromeにアップロードする必要があるので、出力ファイルが更新されないと困ります。なので代わりにwebpack-cli
を使い、npm run build-dev
を中心に使って開発を進めることになります。
参考URL
- TypeScriptチュートリアル① -環境構築編- - Qiita
- 非常に参考にした
- npmの--save, --save-dev, --save-optionalの違い - how to code something
- https://www.nogson.blog/entry/2018/02/01/005525
- webpack-dev-serverとは何か
- webpackで開発用/本番用の設定を分ける - Qiita
- webpackの設定ファイルを開発用と本番用に分ける推奨のやり方
@types
のインストール
@types
は型定義ファイルで、例えばChrome拡張を作るときは@types/chrome
をインストールしておかないと、chrome.runtime.onMessage.addListener
などをコードに書いてコンパイルしたらTS2304: Cannot find name 'chrome'.
みたいなコンパイルエラーが出ます。なのでプロジェクトに必要な型定義ファイルを適宜インストールする必要があります。
例えばChrome拡張の場合は、@types/chrome
をインストールします。
$ npm install --save-dev @types/chrome
Reactのインストール
今回、Chrome拡張で設定画面を作りたいのですが、せっかくなので練習も兼ねてReactで作ることにしました。
$ npm install --save-dev react react-dom $ npm install --save-dev @types/react-dom @types/react-dom
「実践TypeScript」という本にはparcelを使うと便利とありますが、私の場合はwebpackを使用していますので、今回はparcelは不要なのでインストールしません。parcelは設定ファイルなしで即React環境を構築できるのがメリットな反面、複雑な設定ができないというデメリットがあるようです。
Reactを使う場合、.tsxファイルを使いますので、これのビルドを許可するように設定ファイルを書き換えます。
tsconfig.json
"compilerOptions": { "target": "ES5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ "jsx": "react", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ ...
jsx
を'react'にします。また、必須ではないですがmodule
をes2015にしました。理由は
最新版TypeScript+webpack 5の環境構築まとめ(React, Vue.js, Three.jsのサンプル付き) - ICS MEDIA
の「TypeScriptの設定ファイル: tsconfig.json」の章を読んでそうしました。
webpack.common.js
entry: { content_scripts: './src/content_scripts.ts', background: './src/background.ts', index: './src/index.ts', app:'./src/react/app.tsx' // ★これを追加 }, output: { // モジュールバンドルを行った結果を出力する場所やファイル名の指定 // "__dirname"はこのファイルが存在するディレクトリを表すnode.jsで定義済みの定数 path: path.join(__dirname,'dst/js'), filename: '[name].js' // [name]は、entryのプロパティ名(content_scripts) }, // モジュールとして扱いたいファイルの拡張子を指定する // 例えば「import Foo from './foo'」という記述に対して"foo.ts"という名前のファイルをモジュールとして探す // デフォルトは['.js', '.json'] resolve: { extensions:['.ts','.tsx', '.js'] // ★tsxを追加 }, devServer: { // webpack-dev-serverの公開フォルダ contentBase: path.join(__dirname,'dst') }, // モジュールに適用するルールの設定(ここではローダーの設定を行う事が多い) module: { rules: [ { // 拡張子が.tsで終わるファイルに対して、TypeScriptコンパイラを適用する test:/\.(ts|tsx)$/, // ★tsxを追加 use: { loader:'ts-loader' } } ] }
フォルダ構成は次のようになっています(一部省略しています)。
root/ ├dst/ │ ├js/ │ │ └ app.js │ └ index.html ├src/ │ └ react/ │ └ app.tsx ├package.json ├tsconfig.json ├ webpack.common.js
ReactでDOMを構築するためのファイルはsrc/react/app.tsx
です。これをnpm run build-test
等でコンパイルすると、dst/js/app.js
が作成されます。index.html
はdst
フォルダ側に作成し、app.js
を読み込むようにします。
index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <title>ReactTest</title> </head> <body> <div id="app"></div> あああああああああああああああああああああああああああ <!-- Load our React component. --> <script src="/js/app.js"></script> </body> </html>
app.tsx
import * as React from 'react'; import { render } from 'react-dom'; render(<div>Hello World!!!!!</div>, document.getElementById('app'));
コンパイル後にWebサーバーを立ち上げindex.htmlにアクセスし、「Hello World!!!!!」が表示されていたら成功です。
参考URL
- 最新版TypeScript+webpack 5の環境構築まとめ(React, Vue.js, Three.jsのサンプル付き) - ICS MEDIA
- webpack+TypeScript+Reactの最小構成の解説なので、何がどう必要なのかがわかりやすい
- 170330_webpack/tutorial-typescript-react at master · ics-creative/170330_webpack · GitHub
- 上記の参考URLを読みすすめたらわかるが、最小構成のフォルダ例。読み進めた後に見るとなるほどとなる
index.htmlをsrcフォルダに入れたい
「index.htmlファイルがdstにあってそれを直接編集するのおかしくねぇ?srcにあるべきじゃん」と思っていて、そういう方法がないか調べました。
React & TypeScriptのプロジェクト作成 - TypeScript Deep Dive 日本語版にその方法が載っていたのでそれを踏襲します。
clean-webpack-plugin
とhtml-wabpack-plugin
を使うので、それらをインストールします。
$ npm install --save-dev clean-webpack-plugin html-webpack-plugin
その後、webpack.common.js(webpack.config.js)
を以下のように編集します(編集箇所を★でマークしています)。
const path = require('path'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); // ★追加 const HtmlWebpackPlugin = require('html-webpack-plugin'); // ★追加 module.exports = { // モジュールバンドルを行う起点となるファイルの指定 // 指定できる値としては、ファイル名の文字列や、それを並べた配列やオブジェクト // 下記はオブジェクトとして指定した例 entry: { content_scripts: './src/content_scripts.ts', background: './src/background.ts', index: './src/index.ts', app:'./src/react/app.tsx' }, // ★pluginsプロパティを追加 plugins: [ new CleanWebpackPlugin({ cleanAfterEveryBuildPatterns: ['dst'] }), new HtmlWebpackPlugin({ template: 'src/templates/index.html' }), ], output: { // モジュールバンドルを行った結果を出力する場所やファイル名の指定 // "__dirname"はこのファイルが存在するディレクトリを表すnode.jsで定義済みの定数 path: path.join(__dirname,'dst'), // ★出力フォルダをdstに修正 filename: 'js/[name].js' // ★jsファイルの出力は 'js/[name].js'に修正 }, // モジュールとして扱いたいファイルの拡張子を指定する // 例えば「import Foo from './foo'」という記述に対して"foo.ts"という名前のファイルをモジュールとして探す // デフォルトは['.js', '.json'] resolve: { extensions:['.ts','.tsx', '.js'] }, devServer: { // webpack-dev-serverの公開フォルダ contentBase: path.join(__dirname,'dst') }, // モジュールに適用するルールの設定(ここではローダーの設定を行う事が多い) module: { rules: [ { // 拡張子が.tsで終わるファイルに対して、TypeScriptコンパイラを適用する test:/\.(ts|tsx)$/, use: { loader:'ts-loader' } } ] } }
フォルダ構成は以下のようになっています(一部省略)。
root/ ├dst/ │ └js/ │ └ app.js ├src/ │ ├ react/ │ │ └ app.tsx │ └ templates/ │ └ index.html ├package.json ├tsconfig.json ├ webpack.common.js
これでnpm run build-test
等してビルドすると、src/templates/index.html
をビルドしたものがdst/
直下に作成されます。
これで、ソースファイルはsrcフォルダにまとめることができました。
その他の参考
- 実践TypeScript ~ BFFとNext.js&Nuxt.jsの型定義~
- 最新版TypeScript+webpack 5の環境構築まとめ(React, Vue.js, Three.jsのサンプル付き) - ICS MEDIA
- TypeScript Deep Dive 日本語版について - TypeScript Deep Dive 日本語版
- とほほのReact入門 - とほほのWWW入門
- とほほのWWW入門のReact記事。色んな意味ですごい
- React – ユーザインターフェース構築のための JavaScript ライブラリ
- React公式。公式ドキュメントが豊富なので、まずはこちらでチュートリアルしたほうが早いかも
編集履歴
日時 | 編集内容 |
---|---|
2022/1/9 | webpack-mergeをver.5.0.3以降の書き方に修正 |
2020/3/19 | tsconfig.jsonの作成方法を修正 |
2020/1/6 | React公式の参考URLを追加 |
2019/12/31 | 「index.htmlをsrcフォルダに入れたい」の項目を追加 |
2019/12/29 | Reactのインストールの項目を追加 |
2019/12/15 16:00頃 | @typesに関する記述を追加 |
2019/12/15 04:41頃 | 初版 |