Atom v1.19.0 の IME 問題とダウングレード方法

2017年8月9日 0 開発

Atom v1.19.0 の更新があったので反映したら、IME 入力がおかしい。例えば「にほんご」と入力した場合、変換中は一文字目だけしか表示されず、順に「に」、「ほ」、「ん」、「ご」と切り替わってゆく。変換前の文字がひとつしか見えないので、まともに入力できない状態。あまりに致命的な問題なので issue があるだろうと探したら以下をみつけた (ついでにコメントしておいた)。

beta2 で見つかっていた問題だが、残念ながら安定版でも再現する。コメントを読むと影響があるようならダウングレードしてほしいとのこと。Atom の標準機能としてはダウングレードを提供していないので手動実行する必要あり。手順は以下。

  1. Atom が起動しているなら終了しておく
  2. Web ブラウザーでダウングレードしたい旧バージョンのリリースページにアクセスする
    • リリースされたバージョンは Releases に一覧あり
    • 例えば Release 1.18.0 にアクセスする
  3. リリース用イメージを入手する
    • Windows は AtomSetup.exe
    • macOS は atom-mac.zip
  4. 旧バージョンをインストールする
    • Windows は AtomSetup.exe を実行
    • macOS は atom-mac.zip を展開した中身の Atom.app をアプリケーション フォルダーに上書き
  5. Atom を起動してバージョンを確認

この手順で v1.18.0 にダウングレードして IME 問題が再現しないことを確認できた。

ダウングレードにおける懸念としてプラグインの互換性がある。例えばプラグインが最新の Atom に依存している場合、ダウングレードによって不具合を生じるかもしれない。今回のように minor レベルの更新なら大丈夫そうだけど、major の場合は要注意である。

安定版とはいえ致命的なデグレードが発生することは今後もありえるだろうから、今回の件はダウングレード手順を調べるよい契機だと考ることにした。

  • 2017/8/15: Atom v1.19.1 にて本記事の問題が修正されていることを確認

Redmine テーマ minimalflat2 v1.3.0 リリース

2017年7月16日 0 開発 , ,

Redmine 3.4 がリリースされたので minimalflat2 も対応した。

以下、開発メモ。

Stylus 定義を標準 application.css にあわせる

minimalflat2 の CSS は Stylus で記述して application.cssresponsive.css へコンパイルしている。Stylus の代表的な機能には透過的な変数参照、Mix-In、クラスのネストがあってこれまで便利に利用してきたのだけど、本バージョンからネストは控え目にした。

ネストによってクラス定義の冗長さは軽減される。例えば

#top-menu {background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;}
#top-menu ul {margin: 0;  padding: 0;}
#top-menu li {
  float:left;
  list-style-type:none;
  margin: 0px 0px 0px 0px;
  padding: 0px 0px 0px 0px;
  white-space:nowrap;
}

のように同一 id やクラスを親としているものは

#top-menu {
  background: #3E5B76; color: #fff; height:1.8em; font-size: 0.8em; padding: 2px 2px 0px 6px;

  ul {
    margin: 0;  padding: 0;
  }

  li {
    float:left;
    list-style-type:none;
    margin: 0px 0px 0px 0px;
    padding: 0px 0px 0px 0px;
    white-space:nowrap;
  }
}

のようにネスト可能。ただしこれを徹底すると標準 application.css と定義位置が乖離してゆき、差分比較して追従するのが難しくなる。また、ときには位置の依存関係が崩れることで意図せぬ問題を引き起こす。

以上の理由からアイコン フォント用の疑似要素などを除き、標準 application.css の定義順へならうことにした。Redmine のバージョン更新があれば、最後に対応したものと最新版の application.css を差分比較して部分対応すればよい。従来もそうしていたのだが、今回の変更によりこれが更に容易となった。

Redmine v3.4 の Vagrant Box

テーマ開発における Redmine 上の動作確認は onozaty さん が提供している Vagrant Box を利用している。今回も Redmine v3.4 版が公開されたので、それを Vagrantfile へ指定するようにした。

Twitter 上で Vagrant Box リリースされないのかな?とつぶやいていたら迅速に対応していただけた。非常にありがたい。

Redmine v3.4.0 から間隔が空いたのは、これのすぐ後に致命的なバグを修正した v3.4.1 がリリースされたので、これを待っていたのかもしれない。

Redmine v3.4 プロジェクト一覧の謎

minimalflat2 では theme.js によりプロジェクト一覧をツリー上に開閉する機能がある。しかし Redmine v3.4 へ更新したら、これがうまく動作しない。そもそもプロジェクト名と説明文が横並びになったりする。

もしかして HTML の DOM 構造が大幅に変更された?標準テーマではどうだろう?と試したら、標準のほうでもそうなる。これは application.css にある

#projects-index {
  column-count: auto;
  column-width: 400px;
  -webkit-column-count: auto;
  -webkit-column-width: 400px;
  -webkit-column-gap : 0.5rem;
  -moz-column-count: auto;
  -moz-column-width: 400px;
  -moz-column-gap : 0.5rem;
}

という定義が原因だった。

複数カラムでグリッド状に表示するための定義らしいけど、responsive.css のほうは従来どおり縦一列である。表示幅が広ければグリッドで、ということなのだろう。しかしプロジェクト名に対して説明文が回り込んでしまうなど、好ましくない表示のされかたをする。また minimalflat2 としてはツリー表示によりプロジェクト一覧を整理する方針なのでグリッドにしなくても冗長さはおさえられる。

以上の理由から、この新しい定義は無効化することにした。プロジェクト一覧は表示幅にかかわらず、従来どおり常に縦一列になる。

モバイル用メニューのアイコン フォント対応強化

Redmine v3.4 ではアイコン画像を表示する DOM 要素に icon- を接頭辞とするクラスが統一的に指定されるようになった。指定されるだけで CSS に画像指定のないものも多数あるのだが、minimalflat2 としてはなるべくこれらにアイコン フォントを割り当てるようにした。

もっとも目立つのはモバイル用メニューのアイコンだろう。サイド メニューから移動されてきた項目以外はのきなみ icon- 接頭辞をもつため、かなり華やかになった。

モバイル用メニュー

簡易テスト用 HTML 更新

Vagrant なのか Redmine の設計なのか分からないが、VM のテーマ ディレクトリーと同期している場所で CSS が更新されても Redmine に反映されない。Web ブラウザーのリロード、スーパー リロードをしてもダメで、しかたなく vagrant reload している。

しかしこれは非常に時間がかかる。そのため Redmine の代表的な画面を静的 HTML として保存し、そこにコンパイルされた application.css などを読ませるようにして簡易テストできるようにしている。

今回も Redmine v3.4 用に HTML を保存し直して更新した。また従来のリポジトリー構成では

src/
├── debug_images/
├── favicon/
├── fonts/
├── images/
├── javascripts/
├── stylesheets/
├── stylus/
└── *.html

のように src/ 直下に全ファイルが並んでいて stylus のように開発で頻繁に書き換えるものと、静的でほとんど更新のないものが区別しにくかった。そこで

src/
├── assets/
│   ├── debug_images/
│   ├── favicon/
│   ├── fonts/
│   ├── images/
│   ├── stylesheets/
│   └── *.html
└── stylus/

のように静的リソースは assets/ へ置くようにした。最近の Web フロントエンドや Electron アプリ開発でもこのようにしている。Stylus がコンパイルした CSS と Source Maps は assets/stylesheets/ へ出力される。

assets/ がテーマとして動作するための構成をもったディレクトリーとなる。リリース用イメージ生成も、ここにあるものから必要なファイルをコピーすればよい。動的なファイルは Stylus のコンパイル結果ぐらいなので、cpx 用のフィルターも書きやすくなった。

gulp で uglify-es を利用する

2017年6月14日 0 開発 , ,

タスクランナーは npm-scripts 派なのだけど、akabekobeko/examples-web-app に公開している front-end-starter には gulp 版も用意してある。かつて gulp を利用していたこと、世間では現在もそれなりに Web フロントエンド開発で gulp が採用されていることから動向チェック用にメンテナンスしている。

front-end-starter は Electron も含む Web フロントエンド開発環境の変更を気が向いたときに反映しているのだが、そういえば uglify-es を gulp で利用したくなったらどうするのだろう?というのが気になったので試してみた。

gulp-uglify

gulp で uglify-js を利用する場合、gulp wrapper となる gulp-uglify を採用するのが一般的だろう。では uglify-es はどうなのか。uglify-js 本家が uglify-es を独立したパッケージとしているように gulp-uglify-es があるとか?と予想したが gulp-uglify として js/es を切り替えられるようになっていた。

README の Using a Different UglifyJS から引用する。

By default, gulp-uglify uses the version of UglifyJS installed as a dependency. It’s possible to configure the use of a different version using the “composer” entry point.

標準では dependency としてインストールされた uglify-js を使用するが、composer を利用することで js/es を分岐できるとのこと。以下は README に併記されているサンプル コード。

var uglifyjs = require('uglify-js'); // can be a git checkout
                                     // or another module (such as `uglify-es` for ES6 support)
var composer = require('gulp-uglify/composer');
var pump = require('pump');

var minify = composer(uglifyjs, console);

gulp.task('compress', function (cb) {
  // the same options as described above
  var options = {};

  pump([
      gulp.src('lib/*.js'),
      minify(options),
      gulp.dest('dist')
    ],
    cb
  );
});

gulp ストリーム (pipe) をそのまま使用せず pump で wrap している点を除けば、割りと簡単に切り替えられるようだ。

gulp タスク修正

gulp-uglify としては pump を推奨しているようだが、以下のように gulp-watchify と組み合わせていると書き換えが多くなるので、

// ...import や gulp-load-plugins の設定など

gulp.task('js', $.watchify((watchify) => {
  const time = process.hrtime()

  return gulp.src([ config.dir.js + 'App.js' ])
    .pipe($.plumber())
    .pipe(watchify({
      watch: config.isWatchify,
      basedir: './',
      debug: true,
      transform: [ 'babelify' ]
    }))
    .pipe(VinylBuffer())
    .pipe($.if(!(config.isRelease), $.sourcemaps.init({ loadMaps: true })))
    .pipe($.if(config.isRelease, $.uglify()))
    .pipe($.rename('bundle.js'))
    .pipe($.if(!(config.isRelease), $.sourcemaps.write('./')))
    .pipe($.if(config.isRelease, gulp.dest(config.dir.dist), gulp.dest(config.dir.assets)))
    .on('end', () => {
      const taskTime = PrettyHRTime(process.hrtime(time))
      $.util.log('Bundled', $.util.colors.green('bundle.js'), 'in', $.util.colors.magenta(taskTime))
    })
}))

gulp ストリームのまま移行する。

// ...import や gulp-load-plugins の設定など

// 追加
import UglifyES from 'uglify-es'
import UglifyComposer from 'gulp-uglify/composer'

gulp.task('js', $.watchify((watchify) => {
  const time = process.hrtime()

  // uglify-es で minify するための関数設定
  const minify = UglifyComposer(UglifyES, console)

  return gulp.src([ config.dir.js + 'App.js' ])
    .pipe($.plumber())
    .pipe(watchify({
      watch: config.isWatchify,
      basedir: './',
      debug: true,
      transform: [ 'babelify' ]
    }))
    .pipe(VinylBuffer())
    .pipe($.if(!(config.isRelease), $.sourcemaps.init({ loadMaps: true })))
    // uglify-es で minify
    .pipe($.if(config.isRelease, minify({})))
    .pipe($.rename('bundle.js'))
    .pipe($.if(!(config.isRelease), $.sourcemaps.write('./')))
    .pipe($.if(config.isRelease, gulp.dest(config.dir.dist), gulp.dest(config.dir.assets)))
    .on('end', () => {
      const taskTime = PrettyHRTime(process.hrtime(time))
      $.util.log('Bundled', $.util.colors.green('bundle.js'), 'in', $.util.colors.magenta(taskTime))
    })
}))

このサンプルには掲載していないが、別のリリース用イメージ生成タスクで config.isReleasetrue にしてから js タスクを実行することで、uglify-es による minify 処理が走ることを確認できた。

まとめ

Electron や Chrome (Chromium) のように先進的な環境を決め打ちで開発したり、babel-preset-env で環境を抽象化しつつ変換を最小におさえると minify 対象となるコードに ES.next 部分があらわれる。その際は minify ツール側も ES.next 対応が求められるため uglify-esbabili を利用することになるだろう。gulp に関しては本記事のように gulp-uglify の機能として uglify-es を採用するとよい。

ところで gulp-uglify の設計は gulp wrapper における問題へのよき回答にもなっており、感心した。

gulp を想定していない npm を gulp ストリームにのせる場合、vinyl-buffer などで頑張るか、そのような処理で wrap した gulp プラグインとする必要がある。前者はエンド ユーザー負担が大きすぎるので後者を選ぶことが多いのだけど、こちらだと gulp プラグインの dependency で wrap 対象 npm の更新へ追従しなければならない。

gulp-uglify/composer 方式なら自身の dependency が古びても、エンド ユーザー側から任意バージョンの npm を指定できる。つまり gulp 化の恩恵を享受しつつ、最新 npm に置き換えられるのだ。対象 npm のインターフェースが維持されていることは前提となるが、うまい方法である。自身の dependency + 同一インターフェース npm をサポートできる。

私がタスクランナーを gulp から npm-scripts へ移行した理由のひとつは gulp プラグインの陳腐化なのだけど、この設計が普及すれば、この問題に関しては概ね解決しそうである。gulp プラグインのガイドラインに含めてもよいのでは、とも思う。

この記事を書いた後に気付いたのだが babili の gulp 版として gulp-babili が提供されている。こちらは Babel/babili 本家なので、Babel ファミリーを好むなら uglify-es の代りに採用してもよいだろう。