フロントエンドワークショップ フロントエンドツールチェインについて
JavaScript関連のツールチェイン
Reactに慣れてもらったあとはJavaScriptを扱う際のツールチェインについての外観を掴んでもらうパートです
create-react-appはあらゆるツールをラップしている
Trynpm run ejectで隠匿されている各種設定ファイルやラッパーなどを全部露出させられるので眺めてみよう
そもそもなんですが
create-react-appやcreate-next-appなどで始めるとラップして隠匿されている
基本的にはこれらは良い感じに更新とかもされていてベストプラクティスが詰まっているので、積極的に剥がす必要はない(はず・・・)
剥がしたくなったらまずはその剥がしたさは本当に必要なものなのか考えてみよう
と言っても、まぁ何も分からないブラックボックスになっていても辛いと思うので、フロントエンド周りの特にJSファイルのデリバリーに関わるツールについて紐解いていきたいと思います
TSで書かれたReactプロジェクトのコードをユーザーに届けるまでに必要なことを考えてみる
*.tsx?をJSにする
JSXで書かれた部分をJSで解釈可能な形に置き換える
module化されている複数のファイルや参照されているnpmモジュールなどを1つ(または複数)のファイルにする
これらをそれぞれどういうツールで実践するか
*.tsx?をJSにする→transpiler(ts)
JSXで書かれた部分をJSで解釈可能な形に置き換える→transpiler(jsx)
場合によってはユーザーのブラウザが古い場合の互換性のための対応が欲しい→transpiler(es)
module化されている複数のファイルや参照されているnpmモジュールなどを1つ(または複数)のファイルにする→bundler
transpiler(ts)としての機能を備えるツールの例
tsc
babel
esbuild
transpiler(jsx)としての機能を備えるツールの例
tsc
babel
esbuild
transpiler(es)としての機能を備えるツールの例
tsc
esbuild
babel
コラム: transpilerとpolyfill
transpilerは記法を変換する
polyfillは新たなメソッドを環境に生やす
この中でpolyfillの注入をやってくれるのはbabelのみ
bundlerとしての機能を備えるツールの例
webpack
esbuild
ツールを組み合わせて実行する
webpack
bundlerとしてwebpackを使うことになる
transpilerはloaderの形で利用する
npm script
それぞれからツールを選んで組み合わせるとユーザーに提供できるJSを得ることが出来る
1つのツールが複数の役割を担うことがある
その結果、色々なツールが乱立しているように見える
どの役割をどのツールに担わせて、どういう組み合わせの戦略を立てるか
どこが交換可能か
どこは兼務させられるか
組み合わせの例
webpackを用いて、babelを利用する
全てをesbuildで行い、npm scriptsで実行する
tscでjsにしたものをesbuildでbundleし、npm scriptsで実行する
全てをesbuildでも出来るが、tscはtypecheckerとしての役割を挟むことでbuild時に検証できる
逆にtscの型チェックをしないとbuild時は型チェックを無視できる
ツールの選択をどのように行うか
polyfillを入れるならbabelをどこかで噛ませたい
速度が欲しいのでwebpackよりesbuildを使いたい
ここまでは所謂ビルド時の出来事、その他にもCIなどでやりたいこともある
それぞれの代表例
typecheck
tsc
test
jest
linter
eslint
formatter
prettier
webpack + babelでビルドをやってみよう
npm run ejectしても良いけど、なんか色々くっついてくるので、まずは最小構成の設定を手で書いてみる
作戦紹介
webpackのloaderとしてbabel-loaderを使ってTypeScriptとJSXの変換をやる
webpackの設定とbabelの設定をやります
importしているcssを別のファイルとして書き出せるようにする
cra同様にwebpack-dev-serverを立ち上げて配信できるようにする
必要なものを入れる
webpack関連グッズ
npm install -D webpack webpack-cli webpack-dev-server css-loader style-loader
babelとpreset達
npm install -D babel-loader @babel/core @babel/preset-typescript @babel/preset-react
webpackの設定を書く①
まずはファイルを用意する
webpack.config.js
entrypointと出力先を書く
code: webpack.config.js
module.exports = {
entry: "./src/index",
output: {
path: __dirname + "/public/dist",
publicPath: "/dist",
filename: "bundle.js"
},
}
拡張子.tsxなどを扱えるようにする
code: webpack.config.js.diff
path: __dirname + "/public/dist",
publicPath: "/dist",
filename: "bundle.js"
},
+ resolve: {
+ },
}
babel-loaderを設定する
code: webpack.config.js.diff
resolve: {
},
+ module: {
+ rules: [
+ {
+ exclude: /node_modules/,
+ use: {
+ loader: 'babel-loader',
+ }
+ },
+ ]
+ }
}
babelの設定を書く
babel.config.jsに書いても良いが、簡単のためにwebpack.config.jsにそのまま書いてしまう
code: webpack.config.js.diff
exclude: /node_modules/,
use: {
loader: 'babel-loader',
+ options: {
+ presets: [
+ ['@babel/preset-react', {
+ "runtime": "automatic"
+ }],
+ '@babel/preset-typescript'
+ ]
+ }
}
},
]
webpackの設定を書く②
.cssの扱いについて
import './index.css';のようなCSSのimportはそのままJSとしては解釈できないので、webpackのloaderを用いてここも変換を噛ませる
css-loader
JSの中でimportされているCSSをJSとして扱えるように変換してくれる
style-loader
JSの中にあるCSSをDOMの中に挿入してくれる(デフォルトは<style>だが、<link>を使うことも出来る)
この2つを使って、JSの中でimportされるCSSをパースして、<style>として埋め込めるようにする
2つのloaderの設定を追加する
code: webpack.config.js
}
}
},
+ {
+ test: /\.css$/i,
+ },
]
}
}
webpackの設定を書く③
webpack-dev-serverを使う
npm exec webpack-dev-serverで立ち上げる
ちょっと手を入れて動くようにする
public.htmlの%PUBLIC_URL%がパース不能でコケているはず
%PUBLIC_URL%は無くても、今は一旦大丈夫なので全部消す
Try react-scriptsではinterpolate-html-pluginを使ってこの置き換えをやっている
成果物を読み込む<script>もプラグインで実現されているので、これも手で書いておく
minifyについて
minify: JSのサイズが小さくなるように最適化する
「難読化」とは区別されることが多い
最適化の例
変数名や関数名の省略
コメントの削除
ライセンスコメントは残す設定に出来る
モジュールの不要なコードを削るTreeShaking
不要な分岐の削除
code: js
if (process.env.NODE_ENV !== 'production') { hoge() }
がwebpackのEnvironmentPluginによってNODE_ENV=productionの環境で埋め込まれると
code: js
if ('production' !== 'production') { hoge() }
となり、この結果は自明なので、全体が削除される
その他細やかなテクニックによる圧縮の例
trueを !0に置換
,による結合
本番環境向けのbuildのときだけminifyする
webpackのmodeについて
webpackはmode=productionのときは、minifyを自動で有効にしてくれる
手元の開発時は実行時に--mode developmentを渡すようにして区別する
minifyとsourcemap
minifyや各種transpilerによって変換されたコードを元のコードにマッピングできるようにするのがsourcemap
トレースを表示する際などに利用されることで元のコードの行番号や位置を使って表示させることが出来る
webpackのdevtoolオプションや各種ツールのオプションを使ってsourcemapは各種変換のときに受け継がれるようにする
手元で開発する際は成果物にくっつけたりしておいて、本番向けにビルドするときは hidden-source-mapなどにしておいて、エラーレポートなどにだけ利用するようにする
webpackの設定を変更して振る舞いを変える
前述の通りそれぞれの役割は可換であることを確認する
babelにブラウザ互換性のための設定を入れる
preset-envを追加してサポートブラウザに合わせて出力をtranspileする
babelをtscに差し替えてみる
babel-loaderを剥がして、ts-loaderに置き換える
build時に型チェックが走るようになる
オプションでtranspileOnlyをtrueにすると型チェックは剥がせる
babelをtscに差し替える
code: webpack.config.js.diff
use: {
- loader: 'babel-loader',
- options: {
- presets: [
- ['@babel/preset-react', {
- "runtime": "automatic"
- }],
- '@babel/preset-typescript'
- ]
- }
+ loader: 'ts-loader',
}
code: tsconfig.json.diff
"isolatedModules": true,
- "noEmit": true,
"jsx": "react-jsx"
noEmitを有効にすると型チェックだけが出来る。これを外さないと成果物を生成出来ない
逆にCIで実行する際は --noEmitなどで実行するようにする
Try webpackを剥がしてesbuildにする
esbuildは現時点ではCSSのimportをサポートしていないので、importをやめたりして分離させる必要があることに注意
npm install -D esbuild
esbuild --bundle src/index.tsx
まとめ
JSのファイルをデリバリーするまでの各ステップと、各ツールがどういった役割を担うかについて紹介しました
交換可能なツールはどこか、利用したい機能は何かを検討してツールの洗濯をやっていくことになる
といっても、craに乗っかれるままいけるなら(ある程度までは)それが良いと思います