フロントエンドの移り変わりが激しすぎてついていけない。 というサーバサイドエンジニア向けのフロントエンドの概要第3回目です。 より深い話題については他を当たってください。。
前回は CSS について見ました。
今回は ECMAScript について見ていきましょう。
- ECMAScript 小史
- ECMAScript 2015
- Bable とは
- Bable プラグインをインストールする
- Bable タスクを定義する
- Bable タスクの実行
- Modules のトランスパイラ
- Browserify の導入
- Browserify による変換
- まとめ
ECMAScript 小史
最初に、ECMAScript の歴史を簡単に振り返ってみましょう。
- 1995年 Netscape Navigator 2.0 に JavaScript 1.0 (旧名LiveScript)が搭載される
- 1996年 MS が IE3 に JScript 1.0 (JavaScript1.0互換) を 搭載
- ブラウザ戦争の最中、ベンダー間で言語仕様の独自拡張が行われ互換性が極めて低い暗黒期
- Netscape Navigator 社が ECMA に Javascript の標準化を依頼
- ECMAとは、ECMAインターナショナルという情報通信システムの分野における国際的な標準化団体
- 1997年7月、ECMAScript の標準規格 ECMA-262 リリース
- 標準仕様がまとまらない時期を経つつ、2009年12月に ECMAScript 5 (ES5) リリース
- ES5 は IE9以上、iOS7以上、その他のモダンブラウザであればほぼ対応している
- 2015年6月、ECMAScript 2015 (ES6) リリース
- 各ブラウザの対応状況はこちら
- 次期仕様 ECMAScript next (ES.next) は現在策定中
- 今後は1年毎のサイクルでリリースされる仕様に、機能ごとに策定完了となったプロポーザルが含まれる形となる
ということで、現在はブラウザ側での ECMAScript 2015 の対応が進んできている状況となっています。
ECMAScript 2015
モダンブラウザであれば概ね対応が進んでいます。
結構大きな変更があるので、簡単に概要を見ていきましょう。
let と const よる変数宣言
let
と const
よる変数宣言が追加されました。これらはいずれもブロックスコープが有効で、悪名高き var
とはおさらばできます。
let
は通常の変数宣言、const
は定数宣言となります。
const PI = 3.14; let name = "Thome";
Arrow Functions
function
の代わりに =>
で関数定義することができます。
const arr = [1, 2, 3, 4, 5]; const squares = arr.map(x => x * x); let plus = (x, y) => x + y; // let plus = (x, y) => { return x + y; };
Java のラムダ式と似てますね。
Promise
ようやく Promise
オブジェクトが標準で提供されるようになりました。
コールバック地獄の処方箋として外部ライブラリを使うことなく、Promise
できます(Java で言うと CompletableFuture
)。
var promise = function() { return new Promise((resolve, reject) => { $.getJSON('http://www.api.com/123') .done(json => resolve(json)) .fail((xhr, status, err) => reject(status + err.message)); }); } promise.then(results => results.forEach(/* ・・・*/)) .catch(err => console.log("error:", err));
Promise
以外にも Map
Set
WeakMap
WeakSet
や、Symbol
などの新しいオブジェクトが追加されています。
Template strings
変数や式が ${}
で扱えます。
let name = 'Tiger'; console.log(`My name is ${name}.`);
classによるクラス構文
class
が使えるようになりました。 extends
や super
も想像通りに使えます。
class Person { constructor(name, age) { this.name = name; this.age = age; } incrementAge() { this.age += 1; } } class Personal extends Person { constructor(name, age, gender) { super(name, age); this.gender = gender; } } let p = new Personal('Tiger', 16, 'Female');
Modules
lib/math.js
で export します。
export function sum (x, y) { return x + y } export const pi = 3.141593
別ファイルで以下のように import して使うことができます。
import { sum, pi } from "lib/math" console.log("2π = " + sum(pi, pi))
別名を付けて import することもできます(モジュール内の全てが対象になります)。
import * as math from "lib/math" console.log("2π = " + math.sum(math.pi, math.pi))
Generator function
function*
によりジェネレーター関数を定義します。
処理を抜け出したり、後から再帰したりできる関数です。
function* idMaker(){ var index = 0; while(true) yield index++; } var gen = idMaker(); console.log(gen.next().value); // 0 console.log(gen.next().value); // 1
next()
の呼び出しにより、最初のyield演算子か、ほかのジェネレーター関数に委任する yield*
に達するまで実行して値が返却されます。
for...of による イテレーション
let iterable = [10, 20, 30]; for (let value of iterable) { console.log(value); }
レスト引数(Rest Parameter)
function f (x, y, ...a) { return (x + y) * a.length }
デフォルト引数
function hello(name = 'world') { return hello + name; }
この他にもいろいろありますが、ここでの説明は省略します。
Bable とは
ECMAScript の新しい仕様を先取りできるトランスパイラです。
ECMAScript2015 (ES6) や 次期仕様 ECMAScript next (ES.next)で書かれたソースコードを一般的なブラウザがサポートしている ECMAScript5 形式に変換するツールです。
元々は 6to5 という名前で、ES6 から ES5 への変換ツールでしたが、それにとどまらないトランスパイラとして Babel という名前に変わりました。
Bable プラグインをインストールする
早速 Bable の利用方法を見てみましょう。
前回まで同じようにインストールしていきます。
$ npm install gulp-babel --save-dev $ npm install babel-preset-es2015 --save-dev
以前は Babel 本体のみで使えましたが、Bable 6 からは各種の変換が個別プラグインとして提供されるようになったため、利用したい機能を babel-preset-es2015
のようにインストールする必要があります。
package.json
に gulp-babel
の依存が追記されます。
{ // ・・・ "devDependencies": { "babel-preset-es2015": "^6.9.0", "gulp": "^3.9.1", "gulp-autoprefixer": "^3.1.0", "gulp-babel": "^6.1.2", "gulp-sass": "^2.2.0" } }
Babel のプラグインは transform-es2015-block-scoping
のように個別機能毎に用意されますが、
babel-preset-es2015
や babel-preset-stage-3
、React の場合は babel-preset-react
のようにパッケージで指定するのが楽です。
Bable タスクを定義する
gulp-babel
プラグインを使ったトランスパイラタスクを gulpfile.js
に定義します。
var gulp = require('gulp'); var babel = require('gulp-babel'); gulp.task('babel', function() { gulp.src('./src/app/js/**/*.js') .pipe(babel({presets: ['es2015']})) .pipe(gulp.dest('dist')) });
前回のSCSSと大体同じ流れです。
presets
にて利用するパッケージを指定します。
Bable タスクの実行
試しにsrc/app/js/add.js
として以下を作成します。
let plus = (x, y) => x + y; console.log(`2 plus 3 is ${plus(2, 3)}.`);
では gulp で babel タスクを実行してみましょう。
./node_modules/.bin/gulp babel
ES6 の Arrow Functions が通常の関数定義に、let
が通常の var
に変換されました。
"use strict"; var plus = function plus(x, y) { return x + y; }; console.log("2 plus 3 is " + plus(2, 3) + ".");
Modules のトランスパイラ
Babel によるトランスパイラで ES6 の import/export がどのように変換されるか見てみましょう。
src/app/js/math.js
で以下のようにモジュールを export します。
export function sum (x, y) { return x + y } export const PI = 3.141593
src/app/js/app.js
で、export したモジュールを読み込んで使用します。
import * as math from "./math.js" console.log("2π = " + math.sum(math.PI, math.PI));
これを Babel で トランスパイル してみましょう。
$ ./node_modules/.bin/gulp babel
export 側は以下のように変換されます。
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sum = sum; function sum(x, y) { return x + y; } var PI = exports.PI = 3.141593;
import 側は以下のように変換されます。
"use strict"; var _math = require("./math.js"); var math = _interopRequireWildcard(_math); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } console.log("2π = " + math.sum(math.PI, math.PI));
色々と追加されていますが、注意すべきは2行目の require
です。
ES6 の import
は commonjs の require
に変換されます。
node で利用する分には問題ないですが、通常のブラウザでは require
によるモジュール読み込みはサポートされていません。ではどうするかというと、Browserify や Webpack の出番となります。
Browserify の導入
Browserify は、require
によるモジュールの読み込みを、事前に依存関係を解決して1つの js ファイルとして出力することで対処します。
各種のモジュールが合わさった1つの js ファイルを HTML から <script src="bundle.js"></script>
のように読み込むことでブラウザで動作させることができるようになります。
Java で例えると、jar に固めるイメージでしょうか。
Babel と browserify を組み合わせて gulp で処理するには、いくつかのパッケージを入れる必要があります。
まずはインストールしましょう。
$ sudo npm install browserify --save-dev $ npm install babelify --save-dev $ npm install vinyl-source-stream --save-dev
package.json
に 依存が追記されます。
{ // ・・・ "devDependencies": { "babel-preset-es2015": "^6.9.0", "babelify": "^7.3.0", "browserify": "^13.0.1", "gulp": "^3.9.1", "gulp-autoprefixer": "^3.1.0", "gulp-babel": "^6.1.2", "gulp-sass": "^2.2.0", "vinyl-source-stream": "^1.1.0" } }
browserify の変換処理の中で、Babel のトランスパイルを行うために babelify というプラグインを使います。
gulp は ファイルオブジェクトを vinyl
というオブジェクトとして扱ってストリーム形式で処理を連結していきます。しかし browserify の結果は vinyl
ではないため、この変換のために vinyl-source-stream
プラグインを使います。
Browserify による変換
browserify
babelify
を使ったタスクを gulpfile.js
に定義します。
最小限の定義にしています。
var gulp = require('gulp'); var browserify = require('browserify'); var babelify = require("babelify"); var source = require('vinyl-source-stream'); gulp.task('babelify', function() { browserify('./src/app/js/app.js') .transform(babelify, {presets: ['es2015']}) .bundle() .pipe(source('bundle.js')) .pipe(gulp.dest('dist')) });
では先ほどの export の例と
export function sum (x, y) { return x + y } export const PI = 3.141593
import の例をもとにして
import * as math from "./math.js" console.log("2π = " + math.sum(math.PI, math.PI));
実行してみましょう。
$ ./node_modules/.bin/gulp babel
bundle.js
が以下のように作成されます(長いですがそのまま全部のせます)。
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ "use strict"; var _math = require("./math.js"); var math = _interopRequireWildcard(_math); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } console.log("2π = " + math.sum(math.PI, math.PI)); },{"./math.js":2}],2:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.sum = sum; function sum(x, y) { return x + y; } var PI = exports.PI = 3.141593; },{}]},{},[1]);
モジュールが1つのファイルとして結合されて出力されました。
このファイルは、ブラウザ上で実行できる形式となっています。
まとめ
今回は、ECMAScript の Babel によるビルド処理を見てきました。
次回は gulp のその他便利なプラグインについて見ていきます。
- 作者:Aaron Frost
- 出版社/メーカー: O'Reilly Media
- 発売日: 2017/04/25
- メディア: ペーパーバック