かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

今更のバベる。 Babel 7を試してみたメモ

BabelやWebpackとかを業務で使う機会がなくて、なんとなくな感じだったので自分の手動かして設定しながら試してみました。Babel 7になっていて検索で出てくる情報だとうまく行かなかったり試行錯誤したのでメモ。

Babelとは

babel + webpack の構成で合わせて説明されている場合が多いので、まずbabelとwebpackを切り分けて理解する必要がありました。
雑な理解👇

babel

Babel is a JavaScript compiler

Babelはjavascriptコンパイラ。新しい書き方を古い書き方に変換できる。

webpack

webpack is a static module bundler for modern JavaScript applications.

webpackはモジュールバンドラ。別々のファイルに分かれてあるものの依存関係を見つけて1つのファイルにまとめる。

ざっくり言えば、
ES2015とかES2017とかで書かれたJavaScriptのファイルをbabelでES2015に変換して、
webpackで1つのJavaScriptファイルにまとめる。
という使われ方が多いと言うことですね。

本記事ではbabelについてだけ。

babelのインストール

babel7からインストールするパッケージの指定方法が@babel/〜に変更になっていました。

$ npm install --save-dev @babel/core @babel/cli @babel/preset-env

※ babel7より前の記事ではbabel-cliとかbabel-preset-envと書かれているので、babel7の時は置き換えて読む必要があります。

設定ファイルbabel.config.jsの作成

$ touch babel.config.js

設定ファイル

// babel.config.js
const presets =  [
  ["@babel/preset-env"]
];
module.exports = { presets }

単純に@babel/preset-envを使うだけの設定。
※ これもbabel7より前の記事では["env"]とか["preset-env"]とか書かれていて、そう指定するとエラーになってしまうので注意が必要。

対象ブラウザの指定

babelで変換する対象はbabel.config.jstargetsパラメーターで指定します。
cf. @babel/preset-env · Babel

ブラウザのバージョンを直接指定する場合はオブジェクト形式で

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    "targets": {
      "chrome": "58",
      "ie": "11" 
    }
  }]
];

Example environments: chrome, opera, edge, firefox, safari, ie, ios, android, node, electron.

electronとかも指定できるようです。

browserslist で指定

browserslistのクエリを使って、>=1% in JP(日本で使われている1%以上)のような指定も可能です。
browserslistはtargetsに直接渡せばOK。複数のクエリを組み合わせる時は配列形式で指定。(1つでも配列にしておいた方が楽かも)

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    "targets": [">0.25% in JP", "not ie <= 10", "not op_mini all"]
  }]
];

どんなブラウザが対象になるかは👇のサービスで確認ができます。便利!!

targets.browsers での指定は廃止予定

検索していると

{
  "targets": {
    "browsers": ["last 2 version"]
  }
}

のような指定を見かけることもありましたが、この方法はbabel 7では有効だけど将来的に廃止予定なようです。

targets.browsers
A query to select browsers (ex: last 2 versions, > 5%, safari tp) using browserslist.
Note, browsers' results are overridden by explicit items from targets.

Note: this will be removed in later version in favor of just setting "targets" to a query directly.

出典: @babel/preset-env · Babel

参考にする記事がtargets.browsers: ...の方法で書かれている場合は、targets: ...の形式に置き換えて参考にするのが良さそうです。

動作テスト

構成

/
 |- package.json
 |- babel.config.js
 |- /src
      |- index.js

package.jsonにバベるためのnpm scriptsを書きます

"scripts": {
  "babel": "./node_modules/.bin/babel src --out-dir dist/js"
}
// src/index.js
import { foo, bar } from './test.js'
console.log( foo() );

const countup = (x) => x + 1;
console.log( countup(1) )

Promise.resolve().then(res => console.log("Promise end") );

バベる

$ npm run babel

👇

// dist/js/index.js
var _test = require("./test.js");

console.log((0, _test.foo)());

var countup = function countup(x) {
  return x + 1;
};

console.log(countup(1));
Promise.resolve().then(function (res) {
  return console.log("Promise end");
});

アロー関数や変数定義のconstimport文などが変換されました!

@babel/polyfill

  • polyfill (ポリフィル) ... 壁の穴埋め剤 Polyfilla を動詞化。転じて、古いブラウザやまだ実装されていない新しい機能など足りない機能の穴を埋めることっぽい。

PromiseWeakMap, Array.from, Object.assign, Array.prototype.includesなど文法ではなく、機能を対応していないブラウザ用に変換
つまり、コードを直接変換して対応させる機能って事かな。

インストール

$ npm install --save @babel/polyfill

設定 useBuiltIns: "usage"

babel.config.jsuseBuiltIns: "usage"オプションを追加すると、必要なものだけ自動的にプラグインをインストールして変換してくれる

Now luckily for us, we're using the env preset which has a "useBuiltIns" option that when set to "usage" will practically apply the last optimization mentioned above where you only include the polyfills you need. cf. Usage Guide · Babel
@babel/polyfill · Babel

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    "targets": [">0.25% in JP", "not ie <= 10", "not op_mini all"],
    // 必要なpolyfillプラグインを自動的に使う
    "useBuiltIns": "usage",
  }]
];
動作テスト
// src/index.js
Promise.resolve().then(res => console.log("Promise end") );

👇 バベる $ npm run babel

// dist/js/index.js
require("core-js/modules/es6.promise");
Promise.resolve().then(function (res) {
  return console.log("Promise end");
});

おそらくPromiseを定義してあるcore-js/modules/es6.promiseプラグインが読み込まれました。

async/await

既に結構使われている印象があるES2017のasync/awaitでも試してみます。

// src/async-test.js
const asyncSampleFunc = () => {
  return new Promise((resolve, reject) => {
    setTimeout(resolve, 100, "resolve");
  });
};

async function getAsyncVal() {
  const val = await asyncSampleFunc();
  return `Complete Async: ${val}`
}

getAsyncVal()
.then((res) => console.log(res))
.catch((err) => console.log(err));

👇 バベる $ npm run babel

// dist/js/async-test.js
require("regenerator-runtime/runtime");
require("core-js/modules/es6.promise");

function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }

var asyncSampleFunc = function asyncSampleFunc() {
  return new Promise(function (resolve, reject) {
    setTimeout(resolve, 100, "resolve");
  });
};

function getAsyncVal() {
  return _getAsyncVal.apply(this, arguments);
}

function _getAsyncVal() {
  _getAsyncVal = _asyncToGenerator(
  /*#__PURE__*/
  regeneratorRuntime.mark(function _callee() {
    var val;
    return regeneratorRuntime.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            _context.next = 2;
            return asyncSampleFunc();

          case 2:
            val = _context.sent;
            return _context.abrupt("return", "Complete Async: ".concat(val));

          case 4:
          case "end":
            return _context.stop();
        }
      }
    }, _callee, this);
  }));
  return _getAsyncVal.apply(this, arguments);
}

getAsyncVal().then(function (res) {
  return console.log(res);
}).catch(function (err) {
  return console.log(err);
});

すっごい色々追加されてますが、変換できたっぽいです!

最近、実際にasync/awaitを使ったプロダクトを作ってる会社の人に、「async/awaitはbabelでも変換できないよ」って言われてたので、出来ないものかと思っていたのですが、ふつーに変換できてしまったっぽい.... (出来ない時代があったのですかね?

import文がrequireに変換されるのを防ぐ

デフォルトではimport文がCommonJSの?requireに変換されるようです。
検索すると、babelでiomport文のままにする方法が特に説明なく書かれているケースが多かったのですが、webpackとかで1つにまとめる時にimport文の方がいろいろ都合が良いのでしょうか?

設定 modules オプション

babel.config.jsmodules: falseを指定するとimport文がそのままになるようです。

modules
"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false, defaults to "auto".
Enable transformation of ES6 module syntax to another module type.
Setting this to false will not transform modules.
Also note that cjs is just an alias for commonjs.

// babel.config.js
const presets =  [
  ["@babel/preset-env", {
    // import文をそのままにする
    "modules": false,
    "targets": [">0.25% in JP", "not ie <= 10", "not op_mini all"],
    // 必要なpolyfillプラグインを自動的に使う
    "useBuiltIns": "usage",
  }]
];
動作テスト
// src/index.js
import { foo, bar } from './test.js'
console.log( foo() );

const countup = (x) => x + 1;
console.log( countup(1) )

Promise.resolve().then(res => console.log("Promise end") );

👇バベる npm run babel

// dist/js/index.js
import "core-js/modules/es6.promise";
import { foo, bar } from './test.js';
console.log(foo());

var countup = function countup(x) {
  return x + 1;
};

console.log(countup(1));
Promise.resolve().then(function (res) {
  return console.log("Promise end");
});

import文がそのまま出力されました!

 

これで、なんとなくですが新しい書き方のJavaScriptをバベってブラウザ対応させることができるようになった気がします。
と、qiita投稿したことなかったので、qiitaに本記事のざっくりしたものを書いてみました。
babel 7 触ってみたメモ - Qiita

次はwebpackの理解に進みたいと思います。


[WIP] babel.config.jsと.babelrcの使い方の違いについて。

babel7からbabel.config.jsがプロジェクトのrootにあれば、これを見つけて設定を読み込むっぽい。
以前の.babelrcとは別アプローチで、.babelrcも引き続き使えるみたいだけど、ちょっと英語ちからが足りなくて違いを理解できてない。
cf. Configure Babel · Babel

/
 |- package.json
 |- babel.config.js
 |- /src
      |- .babelrc
      |- index.js

こんな構成で試してみたら、index.jsは同じディレクトリにある.babelrcの設定で変換されたので、全体の変換の設定をbabel.config.jsで行って、特別に別な指定を行いたい部分に.babelrcを使うのでしょうか?

ドキュメント( Config Files · Babel )には「babel.config.jsで指定できるから、それぞれのディレクトリに.babelrc作る必要はなくなる」みたいなことも書かれてあるっぽいので....ぬーん....
詳しい人に教えていただきたい。よろしくおねがいします。


[参考]

シャングリ・ラ 上 (角川文庫)

シャングリ・ラ 上 (角川文庫)

JavaScript (ES2015) 今更のPromise (3) 非同期処理を順番に実行するチョットマニアックなメモ

前回までのあらすじ

非同期処理を平行に実行する場合はPromise.allPromise.raceを使えばよいのですが、非同期処理で受け取った値を使って次の処理を行いたいなど順番(同期的)に実行したい場合のメモ

Promise~thenでのメソッドチェーン

たぶん一番正当なやり方。
Promiseオブジェクトを返す関数を実行してresolveされた時に実行されるthenメソッド内で、Promiseオブジェクトを返す関数をreturnすることで、Promise.prototype.thenのメソッドチェーンができる。
エラーになるとcatchに処理が流れる。

実行される関数

function myFunc(a, b = 0) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(a) {
        let res = (a + b) * 2
        console.log(`(${a} + ${b}) * 2 = ${res}`)
        resolve( res )
      } else {
        reject(`Error a is ${a}`)
      }
    }, 10)
  })
}

function onReject(error) {
  console.log('>> onReject', error)
}

Promise.prototype.thenのメソッドチェーン

myFunc(1).then((res1) => {
  // res1 => 2
  return myFunc(2, res1)
})
.then((res2) => {
  // res2 => 8
  return myFunc(3, res2)
})
.then((res3) => {
  // res3 => 22
  console.log('> Promaise Complete', res3)
})
.catch(onReject)
// => (1 + 0) * 2 = 2
// => (2 + 2) * 2 = 8
// => (3 + 8) * 2 = 22
// => > Promaise Complete 22

エラーになる場合

myFunc(1).then((res1) => {
  // res1 => 2
  return myFunc(0, res1) // => reject になる
})
.then((res2) => {
  return myFunc(3, res2)
})
.then((res3) => {
  console.log('> Promaise Complete', res3)
})
.catch(onReject)
// => (1 + 0) * 2 = 2
// => >> onReject error a is 0

エラーが発生するとそれ以降のthenはスキップされる。
わかりやすいですね!

promise.then(successCallback, failureCallback) の場合

Promiseを使う - JavaScript | MDNに書かれているpromise.then(successCallback, failureCallback)で同期的に実行させようとする場合。

myFunc(1).then((res1) => {
  myFunc(2, res1).then((res2) => {
    myFunc(3, res2).then((res3) => {
      console.log('> Promaise Complete', res3)
    }, (error) => console.log('Error 3', error))
  }, (error) => console.log('Error 2', error))
}, (error) => console.log('Error 1', error))
// => (1 + 0) * 2 = 2
// => (2 + 2) * 2 = 8
// => (3 + 8) * 2 = 22
// => > Promaise Complete 22

// Errorになる場合
myFunc(1).then((res1) => {
  myFunc(0, res1).then((res2) => {
    myFunc(3, res2).then((res3) => {
      console.log('> Promaise Complete', res3)
    }, (error) => console.log('Error 3', error))
  }, (error) => console.log('Error 2', error))
}, (error) => console.log('Error 1', error))
// => (1 + 0) * 2 = 2
// => Error 2 Error a is 0

エラーの場合は先のthenをメソッドチェーンした場合と同じで、エラーになるとそれ以降の処理には流れず、エラーの時点で処理は完了になります。

thenの第一引数内で次のPromiseオブジェクトを返す関数を実行する必要があるので、同期処理が多くなるとコードのようにネストして見通しがあまり良くない感じです。ただ、エラーの処理をそれぞれ別に設定できるのはメリットかも知れません。

promise.then(successCallback, failureCallback) でthenのメソッドチェーンにした場合

ここからチョットマニアックかも。

myFunc(1)
.then(
  (res1) => myFunc(0, res1),
  (error) => console.log('Error 1', error)
)
.then(
  (res2) => myFunc(3, res2),
  (error) => console.log('Error 2', error)
)
.then(
  (res3) => console.log('> Promaise Complete', res3),
  (error) => console.log('Error 3', error)
)
.catch(onReject)
// => (1 + 0) * 2 = 2
// => Error 2 Error a is 0
// => > Promaise Complete undefined

上の例では、

  1. 1つめのthen: successCallback => muFunc(0, res1) (res1は2)
  2. 2つめのthen: failureCallback => console.log('Error 2', error)
  3. 3つめのthen: successCallback => console.log('> Promaise Complete', res3) (res3はundefined)

thenの第二引数にfailureCallbackが渡されていると、エラーの時にcatchに流れるのではなく、failureCallbackが呼ばれ、メソッドチェーンしてる次のthenに処理が流れるようです。

次のthenに流れた時に渡される引数について

先の例ではエラーになった時のコールバックではreturn console.log('Error 2')が実行されundefinedが返されているので、エラーで次のthenに渡される引数が「常にundefined」なのか「コールバック関数で返された値」がなのかが判断しきれません。

そこで、明示的に値を返して試してみます。

myFunc(0)
.then(
  (res1) => myFunc(2, res1),
  (error) => {
    console.log('Error 1')
    return 'Foo'
})
.then(
  (res2) => console.log('> Promise Complete:', res2),
  (error) => console.log('Error2', error)
)
// => Error 1
// => > Promise Complete: Foo

どうやら、エラーコールバックで返される(returnされる)値が次のthen内のsuccessCallbackに渡され実行されるようです。

Promise.reject()を返せば次のthenはエラーコールバックの方が呼ばれる

明示的にreturn Promise.reject()すると、次のthenの処理はエラーコールバックfailureCallbackの方に処理が流れました。

myFunc(0).then(
  (res1) => myFunc(2, res1),
  (error) => {
    console.log('Error 1')
    // Promise.reject を返す
    return Promise.reject('Reject 1')
})
.then(
  (res2) => console.log('> Promise Complete:', res2),
  (error) => console.log('Error2', error)
)
// => Error 1
// => Error2 Reject 1

 
promise.then(successCallback, failureCallback).then(...)の形式でメソッドチェーンをした場合は、

  • エラーの場合はcatchではなく、第二引数のfailureCallbackが実行される
  • 先の処理の成功失敗に関わらず、次のthenが呼ばれる
  • 次のthenのコールバックに渡される値は、先のコールバック内でreturnされた値
  • 次のthenでエラーに処理を流したい場合は、return Promise.reject("エラーコールバックに渡す引数")とする

という感じっぽいです。

先の非同期処理が成功しようが失敗しようが処理を待って次の処理を実行することができるので、値があれば値を使って、無ければ無いなりで必須な共通処理とか使い方はあるかもしれません。

myFunc(0)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(res1)
  },
  (error1) => {
    console.log('Error 1: default val 1')
    return 1
  }
)
.then(
  (res2) => {
    // 前のthenでPromise.rejectされないので、必ずココが実行される
    console.log('Common callback: res2 is', res2)
    return myFunc(res2)
  },
  (error2) => {
    console.log('Error 2:', error2)
    return Promise.reject('ERROR 2')
  }
)
.then(
  (res3) => {
    console.log('Promise complete:', res3)
  },
  (error) => {
    console.log('Error3', error)
  }
)
// => Error 1: default val 1
// => Common callback: res2 is 1
// => (1 + 0) * 2 = 2
// => Promise complete: 2

途中からthen~catchのメソッドチェーンにした場合

TASK

function myFunc2(a, b = 0) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(!a) {
        reject(`Error a is ${a}`)
      }
      let res = (a + b) * 2
      console.log(`(${a} + ${b}) * 2 = ${res}`)
      resolve( res )
    }, 10)
  })
}

1. 成功した場合は、そのまま次のthenが実行される

myFunc2(1)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(2, res1)
  },
  (error1) => {
    console.log('Error 1')
    return 2
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (1 + 0) * 2 = 2
// => Success 1: res1 is  2
// => (2 + 2) * 2 = 8
// => Success 2: res1 is  8

2. 失敗し、コールバック関数内で単に値をreturnした場合も、そのまま次のthenが実行される

myFunc2(0)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(2, res1)
  },
  (error1) => {
    console.log('Error 1')
    return 2
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (0 + 0) * 2 = 0
// => Error 1
// => Success 2: res2 is  1 ... 先のエラー関数でreturnされた値が渡される

3. 失敗し、コールバック関数内でPromise.reject()を返した場合、catchに処理が流れる

myFunc2(0)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    return myFunc(2, res1)
  },
  (error1) => {
    console.log('Error 1')
    return Promise.reject("Reject!")
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (0 + 0) * 2 = 0
// => Error 1
// => Error catch Reject! ... 先のコールバック関数で呼ばれたPromise.rejectに渡された値が引数で渡される

4. 成功したコールバック関数内でPromise.rejectが返された場合もcatchに処理が流れる

myFunc2(1)
.then(
  (res1) => {
    console.log('Success 1: res1 is ', res1)
    // 成功したけど、reject
    return Promise.reject(`res 1 is ${res1}`)
  },
  (error1) => {
    console.log('Error 1')
    return Promise.reject("Reject!")
  }
)
.then(
  (res2) => {
    console.log('Success 2: res2 is ', res2)
  }
)
.catch(
  (error) => {
    console.log("Error catch", error)
  }
)
// => (1 + 0) * 2 = 2
// => Success 1: res1 is  2
// => Error catch res 1 is 2

途中からthen~catchのメソッドチェーンを繋ぐことは問題ないようなので、successCallback, failureCallbackを使った場合の特性を知っていれば、特定のエラー処理だけ分けたいとかって場合などに使えるかもです。(あまりお行儀はよくなさそうだけど...)
 
さっさとawait /asyncに行けばいいのに色々と気になって試してしまいました。(学習が遅い言い訳


これから学ぶ JavaScript

これから学ぶ JavaScript

JavaScript (ES2015) 今更のPromise (2) Promiseが返された後の処理のメモ

前回のあらすじ

通常のPromiseを返す関数で明示的にresolve()reject()returnしてないと関数の終わりまで実行されるのでは?と思い調べてみました。

明示的に resolve/reject がreturnされていない場合

function myFunc(a, b = 0) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if(!a) {
        reject( `Error a is ${a}` )
      }
      let res = (a + b) * 2
      console.log(`(${a} + ${b}) * 2 = ${res}`)
      resolve( res )
    }, 10)
  })
}

myFunc(0)
.then((res1) => console.log(res1))
.then((res2) => console.log('CHAIN!!', res2))
.catch((error) => console.log(error))
// => (0 + 0) * 2 = 0
// => Error a is 0

やはりrejectが呼ばれた後も、関数の中は最後まで実行されていました。
恐らくresolve( res )も実行されているけど、元の呼び出し元では先にrejectが返っているので、再度thenに処理が流れたり、次のthenが呼び出されることもなく無視するようになってるっぽい。
ただresolve, rejectしてもその後の処理が無駄に実行されるので、Promiseオブジェクトを返す関数内ではそれ以上処理が必要ないならreturn resolve()return reject()とした方が良さそう。

並列処理 Promise.all, Promise.race は処理が返っても残りの処理は実行され続けている。

Promise.allでエラーが返ったり、Promise.raceで最初の値が返されても、呼び出していた処理は引き続き実行されているようです。
task

const task1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('> TASK-1')
      resolve('task1')
    }, 20)
  })
}

const task2 = () => {
  return new Promise((resolve, rejecy) => {
    setTimeout(() => {
      console.log('> TASK-2')
      resolve('task2')
    }, 5)
  })
}

const task3 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('> TASK-3')
      reject('REJECT TASK3')
    }, 10)
  })
}

Promise.all

Promise.all([task1(), task2(), task3()])
.then((res) => {
  console.log('>>> Promise.all', res)
})
.catch((error) => {
  console.log('>>> Promise.all ERROR')
})
// >> TASK-2
// >> TASK-3
// >>> Promise.all ERROR
// >> TASK-1

task3rejectされcatchに処理が流れているが、呼び出されたtask1はそのまま実行されている

Promise.race

Promise.race([task1(), task2(), task3()])
.then((res) => {
  console.log('>>> Promise.race', res)
})
.catch((error) => {
  console.log('>>> Promise.race ERROR')
})
// >> TASK-2
// >>> Promise.race task2
// >> TASK-3
// >> TASK-1

task2の処理が完了しても呼び出されていたtask1, task3は引き続き実行はされている

APIとかでエラーがあればエラーを返して処理をまるっと終了させたいような場合はprocess.exit(1)とかでプロセスを終了させてしまうのが良いのかな? (API作ったこと無いから正解かどうか判断しかねるけど)