Open7

JavaScript界隈

kwikwi

最近はプリプロセスという名でコンパイルして使うことが多く、開発中はdevサーバに繋がったブラウザで動的に更新しながら作るスタイルが増えた。
TypeScriptがよく使われるようになり、HTMLページのDOM操作からJSXが広まり、JavaScriptはコンパイルするものになった。HTML, JavaScript はまとめてソースコードとして扱うようになってきた。CSSも、適用順序を制御したり、ポリフィルを気にしたりと大変なことが増えてきたので、CSS的なソースコード断片とライブラリから、ブラウザで実用的なCSSへとコンパイルするようになってきた。CSSの適用範囲をコントロールするために HTML と CSS をまとめてソースコードとして扱うようになってきた。つまり HTML, JavaScript, CSS は全部まとめてコンパイルするようになり、結局ブラウザとほぼ同じことをやるようになった。

kwikwi

package A 内で pnpm link B とすると、A の package.json が更新されて、local B がリンクされる。

pnpm add B と同じ向きの依存方向となる。

kwikwi

Nodejsのstream.Readableを使う際、なるべくバージョン間で一貫して使えるようにstream-reader packageを活用することになる。一方、Nodeのstreamとは別にW3C標準化されたReadableStreamがあり、これも合わせて使いたいことがある。

ReadableStream(browser/w3c) を Readable(readable-stream) に変換して使うには、次のようにするとよい。

import { Readable } from 'readable-stream';

const browserStreamReader = browserStream.reader();
const proxy=new Readable({
  async read(size){
    const { done, value } = await browserStreamReader.read();
    if(!done){
      this.push(value);
    }else{
      this.push(null);
    }
  }
});

Nodejsのstream APIでは、Readable._read functionを作ることになっているけれども、実はここは async function をとることもできる。ReadableStream(w3c) のfunction呼び出しは async interface しかないので、こうする。

readable-web-to-node-streamというのもある。呼び出し元から、純粋に一時的なラッパーとして使うのみなら大丈夫かもしれない。しかしながら呼び出し元でもreadable-streamを import して Readable インターフェースを使う場合、関連するライブラリバージョンを合わせこんでいかないといけない。依存性解決が大変になってしまったので、結局上記のように書くほうが簡単だった。

さらに@types/readable-streamもNode.jsで実際に実装されている定義と合致していないこともあり、TypeScriptコンパイルエラーを引き起こして解決が困難になることもある。これも上記のように書くと回避できる。

kwikwi

Access-Control-Expose-Headers は「ブラウザ」が「ブラウザ上で実行されているスクリプト」に見せる HTTP ヘッダを、「サーバ」が指定している。

kwikwi

Test 用のツールがたくさんある。時にそれらを組み合わせて使ったりする。

  • mocha describe(), it() を実装していて、テストを記述するフレームワーク。mocha にはプラグイン機構はない。expect() などは chai で fill するのが一般的の模様。シンプルなテストランナーが付属する。
  • jasmine describe(), it() 等を実装していて、テストを記述するフレームワーク。プラグイン機構もなく、mocha と立ち位置は近い。expect() も fill されて使える。
  • playwright ブラウザーをプログラムから制御できる。playwright 自体にもテストランナーはあるが、@playwright/test に切り出されている。playwright の期待する config object を export する module を用意する。
  • cypress ブラウザ上で動作するランナー。UI component をマウントする。Protractor からの移行を期待している。
  • karma ブラウザ対応のテストランナー。ブラウザでの実行環境がセットアップされた上で、テストケースを走らせる。plugin 機構を持っていて、mocha や jasmine などを外側から呼び出して組み合わせられるようになっている。テスト環境を構築するには、ブラウザを起動させる launcher、ブラウザ上でJavaScriptが実行できるように形式変換(typescript, esbuild)するpreprocessorといったプラグインが標準的に必要になる。JavaScriptをHTMLにロードして、実行結果を回収する部分はkarmaがやってくれる。JSTDに限界を感じて作られたが、現在は非推奨になった。
  • Web Test Runner ブラウザ対応のテストランナー。puppeteer, playwright, selenium, webdriverio を呼び出す。mocha のランナー実装がある。
  • jasmine-browser-runner jasmine wrapper になって、ブラウザ対応のテストランナーとなる。
  • jest describe(), it() 等を実装していて、テストを記述するフレームワーク。ランナー周辺も充実している。jasmine, mocha からの移行を期待している(AVA, Automattic Expect.js, Jasmine, Mocha, proxyquire, Should.js, Tape)。
  • vitest describe(), it() 等を実装していて、テストを記述するフレームワーク。ランナーは、ブラウザ対応モードもある。Jest からの移行を期待している。
  • Jest Preview ブラウザ対応のテストランナー
kwikwi

以下の二つはブラウザの実行ファイル自体をダウンロードして管理してくれる。それぞれ別物。

  • puppeteer : node_modules/puppeteer/ 以下にブラウザインストール用のスクリプトがあるので、実行する。
  • playwright : ライブラリとして使う分には playwright-core だけでいい。node_modules/.bin に playwright-core コマンドがあり、playwright-core install chromium でブラウザのバイナリが取得できる。

ダウンロードしたバイナリの位置は次のようにして指定できる。

process.env.CHROME_BIN = require("puppeteer").executablePath();
process.env.CHROME_BIN = require("playwright-core").chromium.executablePath();
kwikwi

karma でブラウザにプロクシを設定したいときの設定例。平文通信でクロスドメインなテスト環境を作ることができる。セキュアなコンテキストを作ろうとなると、これだけでは済まなくて、ブラウザ組み込みの認証局ツリーにある証明書が必要になる。
ちなみに localhostセキュアなコンテキストに指定されている。

const conf = karma.config.parseConfig(null, {
    logLevel: karma.constants.LOG_DEBUG,
    singleRun: true,
    autoWatch: false,
    files: [specfile],
    preprocessors: { [specfile]: ["webpack"] },
    plugins: [
        "karma-mocha",
        "karma-webpack",
        "karma-chrome-launcher",
        "karma-firefox-launcher",
        "karma-webkit-launcher",
    ],
    customLaunchers: {
        ChromeHeadless_with_proxy: {
            base: "ChromeHeadless",
            flags: [
                "--proxy-server=localhost:9875",
                "--proxy-bypass-list=<-loopback>"
            ]
        },
        Firefox_with_proxy: {
            base: "FirefoxHeadless",
            prefs: {
                "network.proxy.type": 1,
                "network.proxy.http": "localhost",
                "network.proxy.http_port": 9875
            }
        },
        Webkit_with_proxy: {
            base: "WebkitHeadless",
            flags: ["--proxy=http://localhost:9875"]
        }
    },
    browsers: ["ChromeHeadless_with_proxy", "Firefox_with_proxy", "Webkit_with_proxy"],
}, { throwErrors: true });