2018年5月14日月曜日

Gulp導入手順書とファーストインプレッション

この文書作成時のソフトウェアバージョンは以下です。

  • Windows 10 Home バージョン1709
  • Chocolatey v0.10.8
  • Node.js v10.0.0
  • npm 5.6.0
  • Gulp.js CLI version 3.9.1

Gulp is 何

GulpはNode.jsのモジュールとして提供されるタスクランナー。だと思う。MavenとかGradleのWebフロントで使われる版。
GRUNTっていうタスクランナーが居たんだけど、不満があったので解消のために作られたのがGulp。
似たようなやつにGRUNTとかBroccoliとかがいる。
流れの早いWebフロントにおいて、この手のタスクランナーへの依存は負の遺産になるから依存を減らしてnpmでシェルスクリプトをごりごり書こうという向きもあるようだ。

導入手順書

Node.jsのインストール

Windows環境ではChocolateyを使うのがお手軽。

PowerShell
> cinst nodejs.install

インストール確認は以下

PowerShell
> node -v

Gulpのインストール

ここからnpmを使う。npmはNode.jsのパッケージマネージャでNode.jsに含まれている。
グローバル環境にGulpをインストールする方法もあるようだが、ローカルとグローバルで多重管理するのは嫌な予感しかしないのでローカルにのみインストールする。
まずは対象プロジェクトのルートディレクトリをnpm管理下に置く。

PowerShell
> npm init

初期情報は適当に入力する。
package.jsonやらnode_modulesディレクトリやらができる。VCS管理下に置いている場合はnode_modulesディレクトリは管理対象外にしておく。

次にGulpをインストールする

PowerShell
> npm install gulp --save-dev

package.jsonのdevDependenciesにGulpのバージョン情報が記載される。
グローバルにはGulpをインストールしないので、このままではgulpコマンドは使えない。
ので、npmコマンドから実行できるようにする。
package.jsonのscriptsに以下を書き加える。

package.json
{
    ...
    "scripts": {
        "gulp": "gulp"
    },
    ...
}

以上で> npm run gulp タスク名コマンドでタスクを走らせられる。コマンド長ぇな。

タスクを書く

ルート直下におもむろにgulpfile.jsを新規作成する。

gulpfile.js
var gulp = require('gulp');

gulp.task('hello', function() {
    console.log('Hello Gulp!!');
});

これで> npm run gulp helloするとfunctionの中身が実行される。

所感

ファイルのコピーとかjsonのマージとかやってみたけど、まあ、ビルドスクリプトって感じ。
まだ使っていないけど、ファイルを監視するwatchとか使い始めたら本番じゃないだろうか?
jsで書ける点とタスク間の依存関係を少なくできそうな雰囲気があるのは良い感じ。でもやっぱり使い倒してみないと本領や欠点はわからないね。

2018年5月9日水曜日

ChromeExtensionとWebExtensionsのコードを共通化したい(追記)

この文書作成時のソフトウェアバージョンは以下です。

  • Google Chrome 66.0.3359.139 (64bit)
  • Firefox Quantum 59.0.3 (64 ビット)

結論から言うと、manifestをプラットフォームごとに分岐させ、js上はchrome.runtime.idの値で分岐させれば多重管理は防げる。

ChromeExtensionとWebExtensionsには互換性がある、しかし

ブラウザ拡張に用いるAPIはブラウザごとに違う。
OperaとChromeで採用されているのはextension API
Firefoxで採用されているのはWebExtensions API
MDNによれば、

WebExtension API で開発する拡張機能は、ブラウザー間で互換性が維持されるように設計されており、大半は Google Chrome や Opera でサポートされている extension API とコード互換性があります。これらのブラウザー向けに書かれた拡張機能はほとんどの場合、少しの変更を加えるだけで Firefox でも動くようになります。1

とのことなので、ひとつのコードベースでChromeもFirefoxもサポートしたいところ。
しかし、非互換API2があったり、互換とされるAPIであっても微妙に動きが異なったりする。

例えば、EventPageでコンテキストメニューに項目を追加するにはChromeExtensionでは以下のコードが推奨されている3
が、これをFirefoxで動かすとブラウザを閉じたときにコンテキストメニューはなくなり、再度インストールするまでコンテキストメニューに項目が追加されなくなる。(しかもパッケージ化していない拡張機能はブラウザが閉じるまでしか有効にならないのでテストできない! なんてこった)

background.js
chrome.runtime.onInstalled.addListener(function(){
    chrome.contextMenus.create({
        ...
    });
});

UserAgentからブラウザを判別して処理を分岐する方法

とりあえず思いついたのがUserAgent文字列をパースしてブラウザを判別する方法。
パーサは勿論既存のものを使う。
今回はこちらを拝借https://github.com/faisalman/ua-parser-js
で、分岐したい部分を愚直に分岐する。

background.js
var parser = new UAParser();
if(parser.getResult().browser.name === 'Chrome'){
    chrome.runtime.onInstalled.addListener(createContextMenus);
}
else{
    createContextMenus();
}

(今回はChromeとFirefoxのことしか考えていない)

他にいい方法があれば教えてください

ください!!


ここから追記
教えていただきました! ありがとうございます!

chrome.runtime.id

Chromeではchrome.runtime.idの値は拡張機能一覧に表示されるIDと一致する。
image.png (20.3 kB)

Firefoxではmanifestに記載したIDと一致する

manifest.json
{
    ...
    "applications" :{
        "gecko" : {
            "id" : "test@example.com"
        }
    },
    ...
}

Chromeではmanifestにapplicationsキーが書かれていると

Unrecognized manifest key 'applications'.

とエラーを吐かれてしまうので、なんとかする必要がある。

ビルド時にmanifestを分岐する

結局分岐させた。
firefox用のmanifestを記載したjsonを作っておき、ビルド時にマージする。今回はGulpを使ってみた。

manifest-firefox.json
{
    "applications" :{
        "gecko" : {
            "id" : "test@example.com"
        }
    }
}
gulpfile.js
var gulp = require('gulp');
var mergeJson = require('gulp-merge-json');
var rimraf = require('rimraf')

gulp.task('clean', function(callback){
    rimraf('release/firefox', callback)
});

gulp.task('releaseForFirefox', ['clean'], function(){
    gulp.src(['manifest.json', 'manifest-firefox.json'])
    .pipe(mergeJson({
        fileName: 'manifest.json',
    }))
    .pipe(gulp.dest('release/firefox'));

    // ソースのコピーとかアーカイブとか
    ...
});

gulp.task('releaseForChrome', ['clean'], function(){
    gulp.src('manifest.json').pipe(gulp.dest('release/chrome'));

    // ソースのコピーとかアーカイブとか
    ...
});

結局manifestは分岐することになったけど多重管理を防ぐことはできたので満足。
jsのソースも一本化できた。

background.js
if(chrome.runtime.id === 'test@example.com'){
    createContextMenus();
}
else{
    chrome.runtime.onInstalled.addListener(createContextMenus);
}