最小構成のサンプルから始めて、React のビルドや Babel プラグインの利用も出来るようにしていく。
当初はts-loader
とbabel-loader
を組み合わせてビルドする予定だったのだが、Babel プラグインが動かなかったので断念した。この資料の内容に沿って設定してもダメだった。
そのため、babel-loader
のみでビルドすることにした。
使用しているライブラリのバージョン。
- webpack@4.30.0
- webpack-cli@3.3.2
- babel-loader@8.0.5
- @babel/core@7.4.4
- @babel/preset-env@7.4.4
- @babel/preset-typescript@7.3.3
- typescript@3.4.5
- react@16.8.6
- react-dom@16.8.6
- @types/react@16.8.17
- @types/react-dom@16.8.4
- @babel/preset-react@7.0.0
- babel-plugin-react-remove-properties@0.3.0
対象ファイルの作成
以下の内容のsrc/index.ts
を作成する。TypeScript の拡張子は.ts
なので注意する。
const value: number = 1; console.log(value);
: number
という JavaScript にはない記法を使っているので、JavaScript ファイルとして実行することは出来ない。
$ node src/index.ts > SyntaxError: Missing initializer in const declaration
まずはこのシンプルな TypeScript ファイルを、JavaScript 実行エンジンが処理できる形にビルドする。
必要なライブラリをインストールする
$ yarn init -y
でプロジェクトを開始して、必要なライブラリをインストールしていく。
まずは webpack。
$ yarn add -D webpack webpack-cli
次に、babel-loader
とそれを使用するためのライブラリ。
ここで入れている@babel/preset-typescript
が、TypeScript のビルドを担う。
$ yarn add -D babel-loader @babel/core @babel/preset-env @babel/preset-typescript
設定ファイルを作成してビルドする
webpack.config.js
を作成。
const path = require('path'); module.exports = { entry: { main: './src/index.ts', }, output: { filename: '[name].js', path: path.resolve(__dirname, 'dest'), }, module: { rules: [ {test: /\.ts$/, use: ['babel-loader']} ], }, }
src/index.ts
をエントリポイントとして、dest/main.js
を出力する。
.ts
ファイルに対してbable-loader
を使用する。
これで、src/index.ts
に対してbabel-loader
を実行した結果がdest/main.js
として出力されるようになったので、あとは Babel の設定ファイルを書けばよい。
以下の内容でbabel.config.js
を作成する。
const presets = [ [ '@babel/preset-env', { targets: { ie: '11', safari: '7', }, }, ], ['@babel/preset-typescript'], ]; const config = { presets, }; module.exports = config;
@babel/preset-env
の設定内容は何でもいいのだが、今回はこのような内容にした。
ビルドのためのnpm scripts
を定義して、それを実行する。
diff --git a/package.json b/package.json index 4204881..963f549 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,9 @@ "version": "1.0.0", "main": "index.js", "license": "MIT", + "scripts": { + "build": "webpack --mode=production" + }, "devDependencies": { "@babel/core": "^7.4.4", "@babel/preset-env": "^7.4.4",
$ yarn build
これで、dest/main.js
が生成される。
JavaScript としてビルドされたので、JavaScript 実行エンジンが処理できるようになった。
$ node dest/main.js 1
型チェックも行うようにする
これでビルドは出来るようになったのだが、型チェックは行われない。
babel-loader
はあくまでもファイルの変換だけを行い、型については見ていないためである。
例えば、src/index.ts
を以下の内容にする。
const value: number = 'foo'; console.log(value);
number
であるべきvalue
に文字列を代入しているが、問題なくビルドできてしまう。
$ yarn build $ node dest/main.js foo
これでは TypeScript を利用する意味がないので、型チェックを行うための設定も追加する。
まず、typescript
をインストールする。
$ yarn add -D typescript
次に、TypeScript の設定ファイルであるtsconfig.json
を作成する。
公式ブログの内容を参考にした。
{ "compilerOptions": { "target": "esnext", "moduleResolution": "node", "noEmit": true, "strict": true, "isolatedModules": true, "esModuleInterop": true }, "include": [ "src" ] }
これで$ yarn tsc
を実行すると、型チェックが実行される。
src/index.ts:1:1 - error TS1208: Cannot compile namespaces when the '--isolatedModules' flag is provided. src/index.ts:1:7 - error TS2322: Type '"foo"' is not assignable to type 'number'.
エラーが2つ出た。TS2322
はvalue
に数値を代入するようにすれば直る。
TS1208
は、value
をエクスポートすることで直る。
const value: number = 1; console.log(value); export default value;
例えばビルドの前にtsc
を実行することで、型が合っていないのにビルドされるのを防げる。
diff --git a/package.json b/package.json index b183a11..5f112b0 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "main": "index.js", "license": "MIT", "scripts": { - "build": "webpack --mode=production" + "build": "tsc && webpack --mode=production" }, "devDependencies": { "@babel/core": "^7.4.4",
React + TypeScript
次に、React をビルドできるようにする。
まずはindex.html
を作成する。
このファイルがdest/main.js
を読み込み、React で作ったアプリを表示する。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>TypeScript</title> </head> <body> <div id="app"></div> <script src="./dest/main.js"></script> </body> </html>
React をインストール。
$ yarn add react react-dom
jsx
を扱うファイルは拡張子を.tsx
にするので、src/index.tsx
を作成する。
import React from 'react'; import ReactDOM from 'react-dom'; const App = () => <div>Hello!</div>; ReactDOM.render( <App />, document.querySelector('#app') );
webpack.config.js
を、.tsx
に対応させる。
diff --git a/webpack.config.js b/webpack.config.js index f8b09f6..be31412 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -2,7 +2,7 @@ const path = require('path'); module.exports = { entry: { - main: './src/index.ts', + main: './src/index.tsx', }, output: { filename: '[name].js', @@ -10,7 +10,7 @@ module.exports = { }, module: { rules: [ - {test: /\.ts$/, use: ['babel-loader']} + {test: /\.*(ts|tsx)$/, use: ['babel-loader']} ], }, }
tsconfig.json
にも、jsx
オプションを追加する。
diff --git a/tsconfig.json b/tsconfig.json index 5f7b294..4a611ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "noEmit": true, "strict": true, "isolatedModules": true, - "esModuleInterop": true + "esModuleInterop": true, + "jsx": "react" }, "include": [ "src"
React の型定義ファイルをインストールする。
$ yarn add -D @types/react @types/react-dom
これで、$ yarn tsc
は通るにようになった。
だが Babel の対応が終わっていないので、ビルドは出来ない。
@babel/preset-react
をインストールしてbabel.config.js
を追加すれば、ビルドできるようになる。
$ yarn add -D @babel/preset-react
diff --git a/babel.config.js b/babel.config.js index 27ad3e1..14bec9b 100644 --- a/babel.config.js +++ b/babel.config.js @@ -9,6 +9,12 @@ const presets = [ }, ], ['@babel/preset-typescript'], + [ + '@babel/preset-react', + { + development: process.env.NODE_ENV === 'development', + }, + ], ]; const config = {
$ yarn build
したあとに$ open index.html
するとページが開き、Hello!
と表示されているはず。
import/export
通常、React のコンポーネントは別ファイルに記述してそれをexport/import
するのが一般的なので、ここでもそれに合わせる。
src/App.tsx
として、別ファイルに切り出す。
import React from 'react'; const App = () => <div>Hello!</div>; export default App;
diff --git a/src/index.tsx b/src/index.tsx index c31e3ed..3da2e7a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; -const App = () => <div>Hello!</div>; +import App from './App'; ReactDOM.render( <App />,
だがこれでビルドしようとするとModule not found: Error: Can't resolve './App'
となり、App.tsx
をインポートできないので、webpack.config.js
を編集する。
diff --git a/webpack.config.js b/webpack.config.js index be31412..219f7d9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,6 +8,9 @@ module.exports = { filename: '[name].js', path: path.resolve(__dirname, 'dest'), }, + resolve: { + extensions: ['.ts', '.js', '.tsx', 'jsx'], + }, module: { rules: [ {test: /\.*(ts|tsx)$/, use: ['babel-loader']}
これでビルドできる。
Babel プラグインの利用
最後に、Babel プラグインの利用を試す。
サンプルとして利用するプラグインは、babel-plugin-react-remove-properties
。
React の要素から、任意の属性を取り除くことが出来る。
例えばdata-test
は、デフォルトで取り除いてくれる。
テストコードのためにdata-test
という属性を持たせたいが、プロダクションではこの属性は不要なので削除したい、というケースなどで使う。
早速、属性を追加する。
diff --git a/src/App.tsx b/src/App.tsx index de582d9..38cd448 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ import React from 'react'; -const App = () => <div>Hello!</div>; +const App = () => <div data-test="text">Hello!</div>; export default App;
これをビルドすると当然、そのままdata-test
がレンダリングされてしまう。
<div id="app"><div data-test="text">Hello!</div></div>
babel-plugin-react-remove-properties
を使えばこれを除去できる。
$ yarn add -D babel-plugin-react-remove-properties
diff --git a/babel.config.js b/babel.config.js index 14bec9b..1175e1f 100644 --- a/babel.config.js +++ b/babel.config.js @@ -17,8 +17,15 @@ const presets = [ ], ]; +const plugins = []; + +if (process.env.NODE_ENV !== 'test') { + plugins.push('react-remove-properties'); +} + const config = { presets, + plugins, }; module.exports = config;
これでビルドしたものをブラウザで確認すると、data-test
が除去されているのが分かる。
<div id="app"><div>Hello!</div></div>