imageminのクロスプラットフォームなインストール

npmからimageminをインストールするとimagemin-optipngやimagemin-jpegtranも一緒にインストールされると思うのだけど、 インストールされるoptipngやjpegtranのバイナリは当然ながらコマンドを実行したOSのものがインストールされる。

これを別のOSのバイナリをインストールさせるようにしたい。というのも、Electronの各OS向けアーカイブを一つのOSで作りたいので。

optipng-binやjpegtran-binを調べてみると、内部で使用しているbin-wrapperというモジュールでバイナリをダウンロードしているみたいだった。 OSやCPUのアーキテクチャを判定してダウンロードするモジュールを変えているのはbin-wrapperが内部で使用しているos-filter-objというモジュールだった。 os-filter-objは直接process.platformprocess.archを見て判定しているので、これらを偽装する必要がある。

http://stackoverflow.com/questions/30405397/how-do-you-mock-process-platform-in-jasmine-tests

を見ると、Object.definePropertyで既存のプロパティを上書きしてしまえばいいみたいなので、以下のようなものを書いた。

function ProcessFaker() {
  this._originalProcess = process;
  this._originalDescripters = {};
}

ProcessFaker.prototype.fake = function fake(propertyName, fakeValue) {
  var originalDescripter =
    Object.getOwnPropertyDescriptor(this._originalProcess, propertyName);

  this._originalDescripters[propertyName] = originalDescripter;
  Object.defineProperty(process, propertyName, fakeValue);
};

ProcessFaker.prototype.unfake = function unfake(propertyName) {
  var originalDescripter =
    this._originalDescripters[propertyName];

  Object.defineProperty(process, propertyName, originalDescripter);
  this._originalDescripters[propertyName] = null;
};

module.exports = ProcessFaker;

うーん。で、パッケージ化するコードの簡易なやつは以下のような感じ。 実際に使ったコードはもっと長いので省略した感じで書いてるけど、代わりに動かないかもしれない。

'use strict';

var co = require('co'),
    electronPackager = require('electron-packager'),
    glob = require('glob'),
    rimraf = require('rimraf'),
    thunkify = require('thunkify'),
    ProcessFaker = require('./process-faker');

co(function*() {
  var processFaker = new ProcessFaker();

  // electron-packagerでパッケージ化する
  console.log(yield thunkify(electronPackager).call(electronPackager, {
    // options...
  }));

  try {
    let vendorDirs;

    // process.archとprocess.platformを偽装する
    processFaker.fake('arch', { value: 'x64' });
    processFaker.fake('platform', { value: 'win32' });

    // バイナリがインストールされているディレクトリを
    // electron-packagerが出力したディレクトリから探す
    vendorDirs = yield thunkify(glob).call(glob, `/path/to/**/vendor`);

    // バイナリがインストールされているディレクトリを削除する
    yield vendorDirs.map(
      (vendorDir) => thunkify(rimraf).call(rimraf, vendorDir)
    );

    // optipng-binやjpegtran-binを直接requireしてdownload()を実行する
    yield vendorDirs.map(function(vendorDir) {
      var pluginModule = require(`${vendorDirPath}/../lib`);

      return co(function*() {
        return yield thunkify(pluginModule.download.bind(pluginModule));
      });
    });
  } finally {
    // 一応戻しておく
    processFaker.unfake('arch');
    processFaker.unfake('platform');
  }
}).then(function() {
  console.log('done');
}).catch(function(err) {
  console.error(err);
});

とても無理矢理な感じがする( ˘ω˘)


もっと良い方法があったりするのかなー?