LoginSignup
2
3

More than 5 years have passed since last update.

Modernizr を JavaScript から挿入して処理するフローを実装する。

Last updated at Posted at 2015-09-03

昨日、「プロミス処理のラッパーをつくる」という記事を書いた動機付けが、今日の記事。

tl;dr

結論から言うと、JavaScript 側から Modernizr 挿入をコントロールして、更に Modernizr.load 機能で必要な互換処理スクリプトも挿入するフローを実装したかった。

scripts =
  # 一番最初に挿入される `<script>` の配列
  modernizr: [ '/js/vendor/modernizr.custom.js' ]
  # `Modernizr.load` で必要に応じて挿入される `<script>`
  polyfills: [
    test: (M) -> M.json
    nope: '//cdnjs.cloudflare.com/ajax/libs/json3/3.3.2/json3.min.js'
  ,
    test: (M) -> M.ie8compat
    yep: '//cdnjs.cloudflare.com/ajax/libs/aight/1.2.2/aight.min.js'
  ,
     test: -> !global.console
     yep: '//cdnjs.cloudflare.com/ajax/libs/loglevel/1.4.0/loglevel.min.js'
  ]

# Modernizr の挿入
scriptsInjected scripts.modernizr
.then ->
  # `Modernizr.load` の実行
  modernized scripts.polyfills
.then ->
  # メイン処理

この為に 2 つ関数を作成した。

scriptsInjected 内部の loaded 関数、modernized 内部の completed 関数を Promise 化するのに、promisify 関数を使っている。

※ ちゃんと動作してなかったので、修正しました。

// Underscore.js、jQuery と Modernizr が必要。

/**
 * scriptInjected
 */
var scriptsInjected = function (urls) {
  var selector = 'script', fjs = document.getElementsByTagName(selector)[0];
  var loaded = promisify(function (resolve, reject, url) {
    var js = document.createElement(selector); js.src = url;
    if (js.addEventListener) {
      js.addEventListener('load', resolve, false);
      js.addEventListener('error', reject, false);
      fjs.parentNode.insertBefore(js, fjs);
    } else {
      // MSIE8 以下が `onload` 拾えないので。
      $.getScript(url).then(resolve, reject);
    }
  });
  return all(_.map(urls, loaded));
};

/**
 * modernized
 */
 var modernized = function (tests) {
  var M = window.Modernizr, len = tests.length;
  var completed = promisify(function (resolve, reject) {   
    var loads = _.map(tests, function (test, idx) {
      test.test = test.test(M);
      if (idx === len - 1) test.complete = resolve;
      return test;
    });
    M.load(loads);
  });
  return completed();
};

ちなみに上記関数で使用している promisifiedall 関数は説明していないが、promisify を用いて値を Promise にして返す関数と、複数の Promise 同期する $.when のショートハンド。

// Underscore.js が必要。

/**
 * promisified
 */
var promisified = promisify(function (resolve, reject, val) {
  if (_.isObject(val) && val instanceof Error) return reject(val);
  resolve(val);
});

/**
 * all
 * これは単に `$.when` のラッパー
 */
var all = function () {
  var args = _.toArray(arguments), promises = _.isArray(args[0]) ? args[0] : args;
  return $.when.apply(undefined, promises);
};

関心の分離

一連の実装動機は、Underscore.jsjQuery だけで、ES5 に満たない環境でも動作するスクリプトを書くというところから来た。

gulp.js でモジュール化して、細かい処理を沢山書いていて、Promise 系のモジュールと、クライアントサイド JS のモジュールを分けていた。上記の scriptsInjectedmodernized 関数も、クライアントサイド JS のモジュールとして書いていたが、機能的に Promise が必要になった。

上記関数内部で、直接 Deferred オブジェクトを生成する処理を書いた方が単純なんだけど、すごく冗長で、後で Promise 回りを修正するかも知れないと思うと、Promise モジュールとして一箇所に集約したかった。

後、Modernizr はタスクランナーでモジュール化する時、単純に require だと動作しなかったり、この手の手法と相性が悪くて、自分なりの落としどころを見つけたかったんだけど、この手法はしっくりきた。これで JS の処理フローは全部 JS に定義できる。

すっきり。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3