できない.dev

@babel/preset-env を入れても構文が変換されない

preset-env は targets が無ければ Browserslist の設定を読み、その対象が最新ブラウザだけなら何も変換しない。
変換されないときはまず targets と browserslist の指定を確認する。

公開: 更新:

症状できない
npx browserslist
chrome 154

2026-10-04 に node:20(Node.js 20.20.2 / npm 10.8.2) で実際に打って取った出力。検証環境の詳細

要約

@babel/preset-env を入れたのにアロー関数やクラスがそのまま残るのは、多くの場合「変換する必要が無い」と判断されているためだ。
preset-env は targets が指定されていなければ Browserslist の設定を読み、その対象が最新ブラウザだけなら出力は入力とほぼ同じになる。

まず実際の対象を表示して確かめる。

npx browserslist

実行例

実際に上記の手順を node:20 で動かすと、対象が last 1 chrome version のときは chrome 154 だけが返り、アロー関数もクラスもそのまま出力に残る。
対象を ie 11 に変えると両方が function と var を使った書き方に書き換わり、targets に Chrome 49 を指定した場合は、ie 11 の設定が残っていても書き換わるのは const から var への置き換えだけになる。

$ npx browserslist
chrome 154
$ npx babel src/index.js
"use strict";
 
const greet = name => `hello ${name}`;
class Greeter {
  say() {
    return greet("babel");
  }
}
console.log(new Greeter().say());
$ npx browserslist
ie 11
$ npx babel src/index.js
"use strict";
 
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
var greet = function greet(name) {
  return "hello ".concat(name);
};
var Greeter = /*#__PURE__*/function () {
  function Greeter() {
    _classCallCheck(this, Greeter);
  }
  return _createClass(Greeter, [{
    key: "say",
    value: function say() {
      return greet("babel");
    }
  }]);
}();
console.log(new Greeter().say());
$ npx babel src/index.js
"use strict";
 
var greet = name => `hello ${name}`;
class Greeter {
  say() {
    return greet("babel");
  }
}
console.log(new Greeter().say());

— 2026-10-04 時点の出力

検証環境Node.js 20.20.2 / npm 10.8.2 / Python 3.11.2 / 2026-10-04 検証(ほか 1 件)
検証日
実行環境
node:20 Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

解決策

1. 実際の対象ブラウザを表示する

設定を変える前に、いま何を対象にしているかを確認する。

npx browserslist
npx browserslist "defaults"

出力が Chrome の最新版だけであれば、変換が起きないのは正常な動作である。

2. browserslist で対象を明示する

Browserslist の設定は Babel だけでなく Autoprefixer とも共有されるため、ファイルとして持つのが扱いやすい。

# .browserslistrc
> 0.5%
last 2 versions
not dead

package.json に書く場合は次のようになる。

{
  "browserslist": ["> 0.5%", "last 2 versions", "not dead"]
}

3. preset のオプションに targets を直接書く

ツールごとに対象を変えたいときは、preset のオプションとして指定する。
この場合、Browserslist の設定は読まれない。

// babel.config.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "targets": { "chrome": "80", "ie": "11" }
      }
    ]
  ]
}

Node.js 向けのビルドでは { "node": "current" } を指定すると、実行中のバージョンに合わせた最小限の変換になる。

4. API の polyfill は useBuiltIns で分けて考える

構文の変換と、Promise などの実装の追加は別の仕組みだ。useBuiltIns と corejs を設定すると、対象環境に足りない機能だけが読み込まれる。

// babel.config.json
{
  "presets": [
    [
      "@babel/preset-env",
      {
        "useBuiltIns": "usage",
        "corejs": "3.38"
      }
    ]
  ]
}

usage はソース中で使われた機能だけを import に展開する。corejs のバージョンは、実際に依存へ入れた core-js に合わせる。

よくある原因

  1. Browserslist の対象が新しすぎる: 公式ドキュメントは「By default @babel/preset-env will use browserslist config sources unless either the targets or ignoreBrowserslistConfig options are set」と説明している。package.json に "browserslist": ["last 1 chrome version"] のような指定があれば、変換はほとんど発生しない。
  2. targets の書き場所が違う: targets は preset のオプションとして渡す必要がある。babel.config.json のトップレベルに書いても、preset 側のオプションとしては読まれない。
  3. ignoreBrowserslistConfig: true を付けている: Browserslist を無視する設定にしたまま targets を書かないと、対象が定まらず期待した変換にならない。
  4. 構文ではなく API の話である: Promise や Array.prototype.includes は構文ではなくランタイムの機能なので、preset-env の構文変換では追加されない。
    polyfill の設定が別に要る。

この手順で直った?