Babelとtscの比較

Babel と tsc どっちがいいのか気になったので調べて見たメモ
結論から言うと基本的には tsc で良い

確認環境

Env Ver
@babel/cli 7.14.5
@babel/core 7.14.6
@babel/preset-env 7.14.7
@babel/preset-typescript 7.14.5
typescript 4.3.2

Babel とは何か?

What is Babel? より Babel とは JavaScript のコンパイラと説明されている

Babel がしてくれること

  • 構文の変換
  • corejs を使った Polyfill
  • ソースコードの変換(codemods
  • その他色々

基本的には ES6+を ES6 にしてくれると考えれば良さそうです
でもそれって別に tsc でもいいいよねって思う

Babel の導入

基本はこれ

npm i -D @babel/core @babel/cli @babel/preset-env

Babel の設定

.babelrc を作ってその中に JSON 書いていく
こんな感じ

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": {
          "edge": "17",
          "ie": "11",
          "safari": "11.1"
        },
        "useBuiltIns": "usage",
        "corejs": 3
      }
    ]
  ]
}

Babel の機能について

コンポーネント

@babel/cli

Babel の CLI、これがないと始まらない

@babel/core

Babel 本体、CLI がよしなにしてくれる

@babel/preset-env

構文の変換や Polyfill を設定できる

@babel/preset-typescript

TS をトランスパイルしてくれる

Babel と Browserlist

Babel は Browserlist の設定を認識して自動で Polyfill を挿入してくれます
なお、Babel 7.4.0 以前では設定方法が異なる可能性があります

サンプルコード

IE11 をターゲットにした設定のサンプルです
以下のコマンドを流せば Polyfill された JS が出ることを確認できます
npx babel src -d dest --extensions ".ts"

.babelrc

{
  "presets": [
    ["@babel/preset-env", { "corejs": 3, "useBuiltIns": "usage" }],
    ["@babel/preset-typescript"]
  ]
}
.browserlist

ie 11

Babel と tsc でトランスパイルしてみる

割と違うコードが出てきます

元のソース

const sp = new URLSearchParams('?aaa=bbb&ccc');
console.log(sp);
const prm = new Promise((res) => res(true));
console.log(prm);
[...Array(10)].forEach((_, i) => console.log(i));

console.log(globalThis.Date());

export {};

Babel

.babelrc

{
  "presets": [["@babel/preset-env"], ["@babel/preset-typescript"]]
}

ビルド結果

"use strict";

Object.defineProperty(exports, "__esModule", {
    value: true,
});

function _toConsumableArray(arr) {
    return (
        _arrayWithoutHoles(arr) ||
        _iterableToArray(arr) ||
        _unsupportedIterableToArray(arr) ||
        _nonIterableSpread()
    );
}

function _nonIterableSpread() {
    throw new TypeError(
        "Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
    );
}

function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))
        return _arrayLikeToArray(o, minLen);
}

function _iterableToArray(iter) {
    if (
        (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null) ||
        iter["@@iterator"] != null
    )
        return Array.from(iter);
}

function _arrayWithoutHoles(arr) {
    if (Array.isArray(arr)) return _arrayLikeToArray(arr);
}

function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;
    for (var i = 0, arr2 = new Array(len); i < len; i++) {
        arr2[i] = arr[i];
    }
    return arr2;
}

var sp = new URLSearchParams("?aaa=bbb&ccc");
console.log(sp);
var prm = new Promise(function (res) {
    return res(true);
});
console.log(prm);

_toConsumableArray(Array(10)).forEach(function (_, i) {
    return console.log(i);
});

console.log(globalThis.Date());

tsc

tsconfig.json

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "allowJs": true,
    "checkJs": true,
    "sourceMap": true,
    "outDir": "./dist",
    "strict": true,
    "noImplicitAny": true,
    "moduleResolution": "node",
    "isolatedModules": true,
    "baseUrl": ".",
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"]
}

ビルド結果

"use strict";
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
        to[j] = from[i];
    return to;
};
Object.defineProperty(exports, "__esModule", { value: true });
var sp = new URLSearchParams('?aaa=bbb&ccc');
console.log(sp);
var prm = new Promise(function (res) { return res(true); });
console.log(prm);
__spreadArray([], Array(10)).forEach(function (_, i) { return console.log(i); });
console.log(globalThis.Date());
//# sourceMappingURL=index.js.map

結論

tsc で問題ない

tsc の方が綺麗なコードが出てきてるので、Polyfill 要らなければ tsc で問題ないです
因みに ES6+を ES5 にするのもできるので JS のトランスパイルにも使えます

core-js を使うなら Babel

ちょいちょい触ってて Babel の利点は core-js があれば .browserlist を使えるので、そこでターゲットを指定してやれば Polyfill を勝手に差し込んでくれるところですね

但し CRA では Babel に対する .browserlist はほぼ無価値

あとは Create React App はビルドに Babel を採用しているので、FW 側でビルドパイプラインがあるときには採用したほうが楽です
(態々書き換える意味もないので)
ただ react-scripts 4.0.3 には core-js が入っていないので、基本的に .browserlist を書いたところで Polyfill は入らないため、あんまり存在感はないです