トップ 最新 追記

2017-02-19 [長年日記]

_ JavaScript復習2017 - importとuse strictとメソッド定義 -

背景

Ecma 262 + JScript でユニットテストを書いて から 10 年以上が経ち、途中 Rails 3 + CoffeeScript いいじゃないか、Underscore.js いいじゃないか期を経て現在は ES2015 が標準で当たり前ですよね、な時代になった。

ES2015 自体はなんでもかんでも式になってくれないと CoffeeScript の代わりにはならないよ、return 書くの面倒くさいよという気持ち以外はまぁそんなもんかなという程度の理解だったんだけど、Mithril を試した時 のように無理やり Browserfiy で CoffeeScript を挟んだりせずにそろそろ 素直に ES2015 をフルに使って書きつつ、それ以外に本当に必要なライブラリのセットをまとめる作業を行った方が UI 側の実装に弾みがつくかなと思って触ってみたらどうも ES2015 がフル実装されている処理系はないっぽいので、結局ややこしさが残っているのだなということが分かってきた。

ということでそろそろ少なくとも ES2015 でよく分からんなと思っていた部分を整理していこうと思う。これ以上時間が経つと情報が見つからなくなるかもしんないし。

これを書いている時点での自分の理解をまとめると Node.js と Browserify を適当に使っている範囲のもので、Ecma 262 3rd 以降に加わったものはちゃんと追えておらず、以下のような状態。

  • Webフロントエンドのツールチェインはブラウザなしに動く Node.js であれこれ処理するのが基本になった
  • 伝統的な JS にはライブラリを読み込む機能はなく、ホスト側でまかなっていた(<script>)が、XHR + eval などで動的に読み込むこともできる
  • Node.js 登場以降 module と require の概念が持ち込まれて function 以外のスコープが増えた
    • Browserify は Node.js のこのメリットをブラウザの世界に持ち込んだもの
    • ついでに Node.js 上でしか動かない機能以外は組み込めるようになった
  • Babel は先行案の JS の文法を使ったコードを変換して Ecma 5 相当にしてくれる
    • ので、先行案の文法*1 を使ったコードが Ecma 5 相当と互換性のあるブラウザで動作するようになる*2

ここから、もうちょっとつっこんだ部分の理解を深めたいと思う。

ちなみに、以降の確認は主に以下の環境で行っている。

  • Node.js v6.9.5
  • Yarn 0.19.1
  • Babel 6.23.1

import / export

import とかよく見るじゃないですか、React とか Babel 前提になったコードで。でもなんとなく見よう見まねの域を出なくて気持ち悪いと思っていたのでした。

2017-02 時点で結論から言うとまず Babel を使えと。そういう状況らしい。

ES Modules(ES2015) import -> Babel -> CommonJS require

結局 require に書き換えて読み込むことになる。

安定して動かせる import は今のところこの手の変換以外ないみたい。

単純な import
import './export' -> require('./export')
無名で(defaultで)定義して import 側で名前の割り当て

export default で名前のない function や class を export できるので、import 側で名前を割り当てて利用する。

※ 以降、<name> となっている部分は <> 含めて実際の名前に読み替えplz

import.js

import <name> from './export'

<name>()

export.js

export default function() {
  ...
}

を babel で変換すると

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

exports.default = function () {
  ...
};

てな感じで exports.default に function が入る。class の場合はもうちょっと複雑になるけど

export default class {
  method {
    ...
  }
}

var _class = function() {
  ...

  _createClass(_class, [{
    key: <method>,
    value: function <method>() {
      ...
    }
  }]);

  return _class;
}();

exports.default = _class;

てな感じで、やはり exports.default に class が入る。

名前付きで定義したものを export / import

{ } で囲む。

※ as は割愛

import.js

import { <name> } from './export'

<name>()

export.js

function <name>() {
  ...
}

export { <name> }

class もまったく同様。

'use strict'

Strict モード - JavaScript | MDN

Ecmascript 5 で追加された機能で Babel で変換したコードには現時点では自動で追加される。

  1. 意図せぬグローバル汚染を防止
  2. 代入の失敗は例外に
  3. 削除できないプロパティの削除をエラーに
  4. オブジェクトリテラルのプロパティ重複もエラーに
  5. 関数の引き数名の重複もエラーに
  6. 8進数表記の禁止

基本的には予期せぬエラーが起きにくくなる改善なんだけど、一つ問題があって strict モードでの動作の指定をグローバルに行おうとした場合、strict モードでの動作を意図したコードと strict モードでの動作を意図していないコードを混ぜると、先に決まったモードで動くのでお互いに意図せぬ動作をする場合があること。

したがってどんなコードを混ぜ込んでいるか分からない、伝統的な JS のコピペベースのライブラリの利用を行った場合、この指定は function 単位で行うことが望ましい。でないとどこで何が起きるかよく分からなくなってしまう。

いや、面倒だよねそれ。

というところまで分かったところで Babel に戻ると、Babel は es2015 の preset で Ecmascript 5 に変換する際に自動的に 'use strict' を必ずファイルの先頭に置くので、自動的にすべて strict モードで動作するように変換される。

ということで Ecmascript 5 を前提にするなら Babel 使えという話に帰ってくるのであった。

class / メソッド定義

クラス - JavaScript | MDN

  • class定義は事前に済んでいないと呼び出せない
    • なんか PHP 3 までの制限のような?
  • constructor, extends, super なんか PHP というか Java っぽい?
  • name: function() {} が 単に name() {} で書けるようになった
    • オブジェクトリテラルでもclass式でも同様
    • やった! function 地獄からの解放?
    • でも割とブラウザの対応状況はよくない(まぁ Babel 通すならどっちでもいいけど)

メソッドとグローバルな関数の定義はベツモノで function を書かずに済むようになったわけではないうえに => (arrow function)もまたベツモノ。

いったんここまで。

Tags: JavaScript

*1 ESnext と呼ばれる

*2 例えば IE9 以降とか。


2017-02-25 [長年日記]

_ 意図通りのURLにユーザーを誘導したい

HTMLでは

<link rel="canonical">

を書いてあげればよい。

JavaScriptでは

location.host

を見て意図通りの URL でなかったら location を書き換えてあげればよい。

サーバ側アプリでは

基本的には HTTP_HOST を見る。ところが構成によってここで取得できる値は変わる。*1

例えば Heroku の場合はもともと *.herokuapp.com の名前を持っている。これに DNS の ANAME や CNAME で hostname を設定し、これをユーザーに見せる URL として設定することになるのだが、ここに CDN が加わると設定方法にバリエーションができる。

※ 以下はアプリケーションサーバが Heroku にある前提で書いていくが、別にどんなサーバでも構わない。単にデフォルトの名前が分かりやすく決まっているので参考に挙げやすいだけである。

a) CDN側にだけDNSを設定する
  1. Heroku 側では別な endpoint を定義せず
  2. CDN 側で endpoint を定義する(例えば example.com で Origin を *.herokuapp.com に)

形になっている場合、当然だが Heroku 側で拾える HTTP_HOST はデフォルトのものだけになる。

+-----+
| DNS |
+-----+
   ↓
+-----+    +-----+
| CDN | → | App |
+-----+    +-----+

App で拾えるのはデフォルトの *.herokuapp.com だけ。なぜなから CDN から *.herokuapp.com でアクセスされるから。

これの対処は後述。

b) CDNにもAppにもDNSを設定する
      +-----+
      | DNS |
      +-----+
         ↓
   +----------+
  ↓          ↓
+-----+    +-----+
| CDN | → | App |
+-----+    +-----+

App の endpoint も DNS で設定して

  • example.com ( Heroku )
  • cdn.example.com ( CDN )

のように設定できるなら App 側で Canonical(この場合は *.herokuapp.com ではなく example.com)かどうかを HTTP_HOST で取得することができる。

CDN経由かどうか

ということで今度は CDN 経由かどうかを知りたい場合。上の a) のパターン。

  • Via ヘッダを見れば経由サーバがあるかどうかは分かる
  • Heroku のようなマルチテナント PaaS や Load Balancer 経由のアクセスの場合は常に Via 値があるので、意図したサーバを経由したかどうかは「Viaヘッダがあるかどうか」だけでは判別できない
  • CDN や Load Balancer 経由かどうかは「Via ヘッダに入る文字列」での判別になる
    • 多段の場合は複数の情報が文字列で入る

ということで例えば Rack であれば

env['HTTP_VIA'].include?(特徴的な文字列)

で判別できる。

*1 Rails 4 までだと直接 HTTP_HOST を見ずに request.host を使えばいいと思う。5 以降はこのメソッドなくなったそうなんだけど、使ってないので分からない。