トップ 追記

2019-10-16 [長年日記]

_ JavaScriptのcallbackをstub/mock化できるようにしたいのでbind(this)する

まとめ

JavaScript の callback メソッドを testable にしたい。

  • testable にするには独立して呼べることが大事。つまり独立メソッドにし、名前をつけることが必要。
  • 独立メソッドを callback として渡すにはリファレンスにする
  • リファレンスとして渡されたものは実行時に this が決定するのでそのまま実行すると同じ class 内でも this を見失う
  • リファレンスを bind(this) しておこう

this.method(val, function(err, res) {
  ..
})

みたいなコードはクロージャの中だけをテストすることができないからやめたい。じゃあこれを独立した callback メソッドにするとどうなるか。

this.method(val, this.callback)

これは callback メソッドのリファレンスを渡している形。これで必要な引数は自動的に渡るようになる。

このために method の中身は以下のようになっている。

method (val, cb) {
  ..
  cb(err, res)
  ..
}

これをそのまま実行すると callback は this を見失う。this は lexical scope ではなく dynamic に実行時に bind される。this を共有するには this を与える必要がある。

つまり正解はこう。

this.method(val, this.callback.bind(this))

arrow function を挟んで以下のようにすることもできるが、冗長だし、間違えそうなので避けたい。引数の数が少なく、名前も短いうちは読みやすいとは思うけど。

this.method(val, (err, res) => {
  this.callback(err, res)
})
Tags: JavaScript

2019-10-09 [長年日記]

_ JavaScript Standardだけだと足りなかったのであれこれ

対象バージョン

このエントリは以下のバージョンを基準にしています。未来において違うことを言っている可能性はおおいにあります。

  • Node.js 12.10.0
  • ESLint 6.5.1
  • Standard 14.3.1
  • Jest 24.9.0
  • Power-Assert 1.6.1
  • eslint-config-standard 14.1.0
  • eslint-plugin-jsdoc 15.10.0

これまで

先月に Flycheck を入れてみて すこぶる快適なのだけど、やはり JavaScript についてはいろいろ不満は残っていて、

  • describe, it が undef で怒られる
  • そのくせ console は ok で browser 向けだとまずそう
  • 現実と戦う際に env: { jquery: true } とか欲しかったり
  • 結局 .eslintrc.js が欲しくなる

ということが分かってきた。多くのエディタでリアルタイムに近いレベルでチェックを走らせるとか、ほんとの基本となる基準としてはよいけど、十分かと言われるとそうでもない。

結局ESLintの設定を見直した

ということで ESLint の設定でしばらく悩んでいたんだけど、こんな感じになった。

module.exports = {
  extends: [
    'standard',
    'plugin:jsdoc/recommended'
  ],
  env: {
    browser: true,
    node: true
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'development' ? 'off' : 'error',
    'no-debugger': process.env.NODE_ENV === 'development' ? 'off' : 'error',
    'jsdoc/require-param-description': 'off',
    'jsdoc/require-returns-description': 'off'
  }
}

端折っている部分もあるし、すべてにおいてこれと同じではないけど、だいぶ減らせたのはありがたい。

基本的な考え方

  • エディタ編集時のチェックで怒られなさすぎるのは良くない
  • でもじゃあエディタ編集時のチェックを全部「ESLint + .eslintrc + エディタの設定」に寄せるのはコストと合わない
    • describe, it についてはファイルローカルの /* global describe, it */ で回避
  • プロジェクトローカルではこれまで通り ESLint を入れて(ブラウザ向けかどうかなど考慮しつつ)設定を追加

JSDoc 周りは ESLint 4 以下の時代の方が自分の意図にはしっくりきてたので、今後の eslint-plugin-jsdoc の進化に期待。2019-10 現在まさに breaking changes をモリモリ入れながら開発が進んでいる感じ。*1

特に痛いのは require-jsdoc してるのに function definition に対しては lint が有効だけど method definition に対しては効かない点。これはだいぶ痛い。

describe, it については ESLint を使うなら globals の設定で ok だし、standard でもコマンドラインオプションを渡せばよいのも分かっているんだけど、上に書いた通り、まず(現在メンバーの使っている)

すべてのエディタでカジュアルに動かせること

を優先した結果、

/* global describe, it */

あたりが落としどころかなと考えている。

// eslint-disable-line

と同じような考え方だと思ってもらえればよいです。

*1 semver のおかげでバージョンがすごいことになってる


2019-10-05 [長年日記]

_ GitHub Actionsでpkgを使ったNode.js実行バイナリをWindowsを含めてmatrixビルドする

目的

pkgでNode.jsスクリプトを実行バイナリにする - あーありがち(2019-10-04) において、各プラットフォーム上で pkg を叩けば Node.js スクリプトを Windows, macOS, Linux それぞれで動く単体のバイナリにできることは分かった。

今日はこの pkg を使って各 platform 上で build する処理を GitHub Actions で自動化してみたときのメモ。これが完成すれば基本的には Windows 上のちょっとしたツールを、いつもの macOS で、いつもの JavaScript で書いて git push すればオッケーと言える。便利。

※ 現時点では公開できる完成リポジトリはないので、日本語だけ。

対象

CircleCI など YAML で CI の設定をした経験のある人向け。細かい説明は一切しません。

参考

GitHub ActionsでElectronアプリのクロスプラットフォームバイナリを配布する | Web Scratch

メモ

About GitHub Actions - GitHub Help

matrixビルド

複数の環境を指定していくつもビルドを実行することをmatrixビルドと呼ぶらしい。これは GitHub Actions 独特のものではない。

  • strategy で matrix を指定したうえで runs-on は ${{ matrix.os }} で
  • pkg の target にはこの matrix.os をそのまま使うことはできないので、「matrix.os を加工する action」を加える必要がある
環境の課題
  • step の run に複数行与える書き方は Windows 標準の shell の cmd では正しく動かない*1
  • run の中の文字列は shell の世界。bash だったり cmd だったりするので env の展開方法はそれぞれ異なる
  • うっかり yarn <command> と書くと Windows では動かない。しっかり yarn run <command> と書く。
YAMLの中の条件判別などのSyntax大事

Contexts and expression syntax for GitHub Actions - GitHub Help

何が大事って、この部分は shell でも JavaScript でもない独自の文法の世界ってこと。リファレンス見ないと分からないし、オフラインの linter などもまず存在しない世界。

Actionの書き方
  • uses で action を利用する際の書き方は二種類。GitHub上のactionとそのバージョンの指定か、同一repos内のディレクトリのいずれか。外部のリポジトリを指す場合はバージョンを明記しろと言われるが、ローカルのファイルの場合はむしろ書いたらダメ。
    • <owner/repos@version>
    • ./.github/actions/<name>
  • uses で action を利用する場合、inputs / outputs ともに決められた方法でやりとりする
  • action を JavaScript で書く場合、npm install は実行されないので node_modules も含める。Linux / macOS ではなんとなく動く場合もあるけど、Windows だとちゃんとエラーになる。

.github/ ディレクトリの中に直接 action をを置いてしまってもよいが、uses の書き方がやや異なるので注意が必要。

共通で使いそうなものは独立させて Creating a JavaScript action - GitHub Help に書いてある通り zeit/ncc を使って node_modules 込みでパッケージにしてしまった方がテストもしやすい(入出力は環境変数っぽい)のでよさそう。

※ 急に zeit さんと親しくなってしまった。

役に立つAction

release にできあがった file を upload する方法に go binary を使っている記事も見かけたけど、action があるのでそっちを使う方が手軽でよさげ。

matrix buildしてるYAMLの例の一部

outputs の読み取り方が結構独特。

run-on: ${{ matrix.os }}

strategy:
  matrix:
    os: [macos-latest, windows-latest, ubuntu-latest]

steps:
  - uses: <owner/repos@version>
    id: <action>
    with:
      os: ${{ matrix.os }}
  - name: package executable
    env:
      <NAME>: ${{ steps.<action>.outputs.<name> }}
    run: command $<NAME>
    if: matrix.os == 'windows-latest'
  - name: package executable
    env:
      <NAME>: ${{ steps.<action>.outputs.<name> }}
    run: command %<NAME>%
    if: matrix.os != 'windows-latest'

ここで env, if の部分は完全に GitHub Actions 独自の Syntax で、if の部分は $ で囲まなくてもよい。ただし、参照できるオブジェクトは決まっている。

run の中身は shell で実行されるので、shell に応じて環境変数の展開方法などを変える必要がある。

*1 少なくとも 2019-10-05 時点で


2019-10-04 [長年日記]

_ pkgでNode.jsスクリプトを実行バイナリにする

Rubyスクリプトをcross-platformなstandaloneバイナリにしようとして諦めた - あーありがち(2019-10-03)

で Ruby を諦めたので、次は Node.js.

zeit/pkgですごく簡単にできる

zeit/pkg: Package your Node.js project into an executable

試したのは 4.4.0

基本的な使い方は

pkg [-t target] [-o output] <source>

で、必要な stub を自動でダウンロードして依存モジュールも含めて -o で指定した名前の実行バイナリを生成してくれる。

ネットワークが弱い場合はこのダウンロードでイライラするけど、そこが済めば*1実にあっけなく終わる。本当にあっという間。

ただし、通常ではできあがったバイナリはすごくでかい。どれだけ簡単なコードでも 30MB 以上になってしまう。もしかしたら V8 の option でサイズの削減はできるかもしれないけど、そこはまだ試していない。

でもcross-compileはできない

README には cross-compile できると書かれているんだけど、これは間違いだった。macOS 上で Windows ターゲットのコンパイルを試してみて、確かにコンパイル自体は動作はするんだけど、実際にできあがったバイナリを実行することはできなかった。Windows 上で Windows ターゲットのバイナリを作ったら普通に実行できたので、CI のプラットフォームを Windows にして pkg を実行する形でないとダメっぽい。

続きは GitHub Actionsでpkgを使ったNode.js実行バイナリをWindowsを含めてmatrixビルドする - あーありがち(2019-10-05)

localのファイルのrequireはそのままでは相対パスを解決できない

バイナリはできるけど実行すると以下のように怒られる。

Error: Cannot find module 'xxx
Require stack:
- /snapshot/src/index.js
1) If you want to compile the package/file into executable, please pay attention to compilation warnings and specify a literal in 'require' call. 2) If you don't want to compile the package/file into executable and want to 'require' it from filesystem (likely plugin), specify an absolute path in 'require' call using process.cwd() or process.execPath.
   at Function.Module._resolveFilename (internal/modules/cjs/loader.js:608:15)
   at Function.Module._resolveFilename (pkg/prelude/bootstrap.js:1287:46)
   at Function.Module._load (internal/modules/cjs/loader.js:524:27)
   at Module.require (internal/modules/cjs/loader.js:664:19)
   at Module.require (pkg/prelude/bootstrap.js:1166:31)
   at require (internal/modules/cjs/helpers.js:16:16)
   at Object.<anonymous> (/snapshot/src/index.js:0:0)
   at Module._compile (pkg/prelude/bootstrap.js:1261:22)
   at Object.Module._extensions..js (internal/modules/cjs/loader.js:768:10)
   at Module.load (internal/modules/cjs/loader.js:626:32)

解決策としては process.cwd() とか process.execPath ではなく、

https://github.com/zeit/pkg#config

を参考に、package.json に

"pkg": {
  "scripts": "src/**/*.js"
}

みたいなやつを足しておく必要あり。

Tags: Node

*1 環境変数を指定しておくとダウンロードした stub をキャッシュしてくれる