Webpack 入門 その3 : Babel による ECMAScript のトランスパイル・TypeScript のトランスパイル
Webpack を勉強しながら Browserslist の効果を確認するシリーズ企画第3弾。
今回は初めに ECMAScript を Babel でトランスパイルしつつ、Browserslist の効果を確認。さらにその後、TypeScript もトランスパイルできるようにしてみる。
目次
- 使用するツールと適用順
- 必要なパッケージをインストールする
- ECMAScript をトランスパイルしてみる
- TypeScript をトランスパイルする
- SCSS のトランスパイルコードと合わせてまとめ
- 以上
使用するツールと適用順
ECMAScript のトランスパイルに使用するのは、おなじみ Babel である。
コアである @babel/core
と、Polyfill を提供する core-js
、ターゲットブラウザなどを判定する @babel/preset-env
、そしてそれを Webpack で読み込むための babel-loader
が必要になる。
続いて TypeScript をトランスパイルするにあたって必要なのは、typescript
本体と、ts-loader
となる。
前回 SCSS をトランスパイルする際、CSS ファイルも sass-loader
にかませていたのに気付いただろうか。
webpack.config.js
{
// ↓ この正規表現は .sass・.scss・.css ファイルに該当する
test: (/\.(sa|sc|c)ss$/u),
// test: [ (/\.sass$/u), (/\.scss$/u), (/\.css$/u) ] と書くのと同じ
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
]
}
素の CSS は当然 SCSS 記法として認識させても素通りするだけで、その後の PostCSS がうまく効くというワケだ。
ECMAScript と TypeScript を混在させることは少ないだろうが、今回の素振り環境では、混在させてもうまく動くよう、似たようなことをやろうと思う。すなわち、
ts-loader
を使って TypeScript をトランスパイルする- 素の ECMAScript は素通り
- トランスパイル処理自体は
typescript
パッケージが行う
babel-loader
を通じて Babel が ECMAScript をトランスパイルする- トランスパイル処理は
@babel/core
が担う - どの Polyfill を適用するかは、
@babel/preset-env
が Browserslist 設定を参照しながら決める - 実際に適用される Polyfill は
core-js
のコードが利用される
- トランスパイル処理は
このような処理順となるワケだ。
必要なパッケージをインストールする
というワケでパッケージのインストール。
# ECMAScript のトランスパイルに必要なモノたち
$ npm install -D @babel/core @babel/preset-env core-js babel-loader
# TypeScript をトランスパイルする際はコレも追加する
$ npm install -D typescript ts-loader
package.json
: インストールされたバージョン
"devDependencies": {
"@babel/core": "7.10.5",
"@babel/preset-env": "7.10.4",
"babel-loader": "8.1.0",
"core-js": "3.6.5",
"ts-loader": "8.0.1",
"typescript": "3.9.7",
"webpack": "4.44.0",
"webpack-cli": "3.3.12"
}
ECMAScript をトランスパイルしてみる
まずは ECMAScript のトランスパイルから試してみよう。
src/js/main.js
- Polyfill が必要になりそうな
Promise
やMap
なんかを使ったコードを適当に入れてみる
- Polyfill が必要になりそうな
const wait = (ms) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
wait(1000)
.then(() => {
console.log('Test');
});
.babelrc
- 新規で設定ファイルを作る
// .babelrc は JSON だがコメントが書ける
{
"presets": [
[
"@babel/preset-env",
{
// usage にすると Browserslist の指定に応じて Polyfill を入れられる
"useBuiltIns": "usage",
// Polyfill のために使用する core-js のバージョンを指定する
"corejs": 3
}
]
]
}
.browserslistrc
- 前回 SCSS 用に用意したモノと全く同じで良い
- Polyfill を適用させるため、古い IE を対象に入れてみる
> 5%
last 2 versions
Firefox ESR
dead
# IE に対応させる
ie >= 6
webpack.config.js
- 前回構築した SCSS に関する設定部分は全て省略
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/js/main.js',
output: {
path : path.resolve(__dirname, 'dist/'),
filename: 'js/[name].js'
},
module: {
rules: [
{
test: (/\.js$/u),
use: [
'babel-loader' // オプションは .babelrc で指定する
],
exclude: (/node_modules/u)
},
]
}
};
コレでビルドしてみよう。すると ./dist/js/main.js
の中に Promise 関連の Polyfill が含まれていることが何となく確認できると思う。
別ファイルのインポートやエクスポートにも、Webpack がデフォルトで対応している。
src/js/another.js
const another = 'another.js';
module.exports = another;
src/js/main.js
const another = require('./another');
console.log(another);
こんな風に2ファイル用意しても、うまくビルドできる。
TypeScript をトランスパイルする
続いて、TypeScript をトランスパイルできるようにする。
src/ts/main.ts
- エントリポイントのディレクトリと拡張子を変更している
- 前述のコードをベースに、TypeScript 記法を追加している
- JS ファイルと TS ファイルをインポートしている
import child from './child'; // TypeScript ファイル
import * as another from '../js/another'; // ECMAScript ファイル
const wait = (ms: number = 1000) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, ms);
});
};
const waitMs: number = 2000;
wait(waitMs)
.then(() => {
console.log(child);
console.log(another);
});
src/ts/child.ts
- TypeScript の
export
- TypeScript の
const child: string = 'child.ts';
export default child;
src/js/another.js
- Node.js の
module.exports
- Node.js の
const another = 'another.js';
module.exports = another;
tsconfig.json
- 新規設定ファイル
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"moduleResolution": "node",
// JavaScript ファイルを読み込むために指定する
"allowJs": true,
// VSCode 上の「入力ファイルを上書きすることになるため、src/another.js を書き込めません。」エラーを回避するために宣言のみ記載・Webpack がファイル出力するため使用されない
"outDir": "",
// 以下テキトーに設定…
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"esModuleInterop": true
},
"include": ["src/"],
"exclude": ["node_modules/"]
}
webpack.config.js
- 色々設定が増えているのでよくよく注意…
const path = require('path');
module.exports = {
mode: 'production',
entry: './src/ts/main.ts', // エントリポイントを TypeScript に直す
output: {
path : path.resolve(__dirname, 'dist/'),
filename: 'js/[name].js'
},
resolve: {
// デフォルトでは拡張子 .ts を取得できないため指定する (import の解決などに使用する)
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: [(/\.ts$/u), (/\.js$/u)],
use: [
'babel-loader', // オプションは .babelrc で指定する
'ts-loader' // tsconfig.json も参照
],
exclude: (/node_modules/u)
},
]
}
};
resolve
セクションで .ts
ファイルを解釈できるようにしておく。test
部分は (/\.(j|t)s$/u)
と表現しても良いが、.ts
と .js
を対象にしている。
…ふぅ…。設定ファイル多すぎない?w
コレでビルドしてみると、ちゃんとコードが動くことが確認できる。
$ npm start
> practice-browserslist@ start /Users/Neo/practice-browserslist
> npm run build && node ./dist/js/main.js
> practice-browserslist@ build /Users/Neo/practice-browserslist
> webpack
Hash: 8cef69703bd99dbae1b4
Version: webpack 4.44.0
Time: 1929ms
Built at: 2020-07-28 14:24:52
Asset Size Chunks Chunk Names
js/main.js 18.7 KiB 0 [emitted] main
Entrypoint main = js/main.js
[40] ./src/js/another.js 68 bytes {0} [built]
[42] (webpack)/buildin/global.js 472 bytes {0} [built]
[78] ./src/ts/main.ts + 1 modules 760 bytes {0} [built]
| ./src/ts/main.ts 695 bytes [built]
| ./src/ts/child.ts 45 bytes [built]
+ 76 hidden modules
# Promise による待機のあと、以下が出力される
child.ts
another.js
できたできた。
SCSS のトランスパイルコードと合わせてまとめ
前回の設定もまとめたモノを再掲しよう。
.browserslistrc
(Browserslist の設定).babelrc
(ECMAScript のトランスパイル設定、および Browserslist 参照用)postcss.config.js
(Autoprefixer の設定、および Browserslist 参照用)tsconfig.json
(TypeScript のトランスパイル設定)
これらは省略する。
src/ts/main.js
import '../css/other.css'; // CSS
import '../scss/main.scss'; // SCSS
import * as another from '../js/another'; // ECMAScript
import child from './child'; // TypeScript
webpack.config.js
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
mode: 'production', // モード指定
entry: './src/ts/main.ts', // エントリポイント
// 出力先パス・ファイル名
output: {
path : path.resolve(__dirname, 'dist/'),
filename: 'js/[name].js'
},
plugins: [
new MiniCssExtractPlugin({
filename: '/css/[name].css'
})
],
resolve: {
// デフォルトでは拡張子 .ts を取得できないため指定する (import の解決などに使用する)
extensions: ['.ts', '.js']
},
module: {
rules: [
// ECMAScript をトランスパイルする
{
test: [(/\.ts$/u), (/\.js$/u)],
use: [
'babel-loader', // オプションは .babelrc で指定する
'ts-loader' // tsconfig.json も参照
],
exclude: (/node_modules/u)
},
// SCSS を CSS ファイルとして出力する
{
test: (/\.(sa|sc|c)ss$/u),
use: [
// Loader は最後のモノから順に適用される
// sass-loader で SCSS から CSS に変換 → postcss-loader (PostCSS) で Autoprefixer を適用し CSS 圧縮 → css-loader で @import などを解決 → mini-css-extract-plugin で CSS ファイルとして書き出す
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader', // Autoprefixer の指定は postcss.config.js で行う
'sass-loader'
]
}
]
}
};
こうなった。
以上
設定多すぎてしんどい!Webpack の設定は自分で書くもんじゃない気がする…。
Browserslist の効果はなかなか面白い。一つの設定ファイルで Autoprefixer (CSS) にも Babel (JS) にも効果が出るので、CSS と JS のブラウザサポートの範囲を統一できる。
つらみはあったけど、コレで Browserslist および Webpack の扱いはおけおけですわ。