読者です 読者をやめる 読者になる 読者になる

この日記は私的なものであり所属会社の見解とは無関係です。 GitHub: takahashikzn

素のless-v2.0をRhinoで動かす

Javascript

lessの2.0がリリースされました。

http://github.com/less/less.js/blob/master/CHANGELOG.md


世間的にはSass/SCSSが主流だと思いますが、動作にはRubyが必要です。

一方でlessは全部JSで記述されているため、(僕にとっては)使い勝手が良い。
JSPのように、初回のHTTPリクエストが来た時にJVMプロセス内でlessを動かしてコンパイルするサーブレットを動かしています。

less-2.0は内部実装が大幅に変わった

less-1.7(一つ前のリリース)までは、RhinoでEnvJSをロードしておけば*1、概ね問題なくlessを動かすことができていました。

これまではバージョンアップを重ねても大きな互換性問題は出なかったので、今回も何も考えずにアップデート。すると動かなくなる。どうやら何かが変わったようです。

ソースを見てみると、Promiseが必要になったっぽい。
こんな感じ。

var PromiseConstructor = typeof Promise === 'undefined' ? require('promise') : Promise,
    contexts = require("./contexts"),
    Parser = require('./parser/parser'),
    PluginManager = require('./plugin-manager');

もちろん互換性のために、Promiseがない環境では自前で定義するようにもなっています。
less-2.0.0.jsの9053行目はこんな感じ。

module.exports = Promise;
function Promise(fn) {
  if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new')
  if (typeof fn !== 'function') throw new TypeError('not a function')

  ...

}

しかしウチは運用上の理由によりグローバルスコープをロックしている*2ため、勝手にグローバル関数を宣言することはできません。

RhinoにもEnvJSにもPromiseはありませんので、グローバルスコープをロックする前に自前で定義する必要があります。

とりあえず動くようにする

今回はPromiseの実装として、使い慣れているjQuery.Deferredを使いました。

ついでに、EnvJSの仮想DOM上にscriptタグが一つ以上存在しなければless-2.0.0.jsの788行目でエラーになるので、無理やり追加しています。document.currentScriptがそれ。

(function() {
    window.Promise = window.Promise || function(func) {

        return $.Deferred(function(dfr) {
            func(function(x) {
                dfr.resolve(x);
            }, function(x) {
                dfr.reject(x);
            });
        }).promise();
    };

    if(!document.currentScript) {
        document.currentScript = (function() {
            var scriptElem = document.createElement('script');
            return scriptElem;
        }());
    }
}());

これで元通りに動くようになりました。

*1:おそらくless-rhino.jsを使えばEnvJSは不要だが、興味が無いので調べてない

*2:ScriptableObject#sealObject