c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

esbuild で大量の file を生成すると webpack --watch が SIGBUS で落ちる

實は esbuild は關係が無い。だうやら大量の file を生成すると webpack --watch が SIGBUS で落ちるやうだ。esbuild が tsc よりとても速いから一氣に webpack に更新を食はせて、落ちる頻度が随分と上がる、といふ事があった。bundle せず大量の TypeScript file を大量の JavaScript file に build してゐる理由は別途であるので、ここではさういふものだと思って欲しい。

似た issue を見附けた。

github.com

この issue を讀み、再生成する file 數を減らせば改善すると假說を立て、少なくとも見かけ上は改善した。斷言せず留保してゐるのは、原因を探ってゐないからだ。

まづ esbuild は glob を解釋しないから、node-glob を使ふ wrapper script を書く。src/ から dist/ へ build するとしよう。esbuild の watch 機能を使ふと、require() 等省略するが、かうなる。

const isWatch = process.argv.includes("--watch");

function isEntryPoint(filename) {
  return true; // 省略
}

function build(entryPoints) {
  esbuild.build({
    entryPoints,
    format: "cjs",
    logLevel: "warning",
    outbase: path.join(__dirname, "src"),
    outdir: path.join(__dirname, "dist"),
    watch: isWatch,
  });
}

glob(path.join(__dirname, "src/**/*.{ts,tsx}"))
  .then(filenames => filenames.filter(isEntryPoint))
  .then(build);

これでは file を更新する度に全ての JavaScript file が再生成され、webpack --watch が SIGBUS で落ちる。これを減らす爲に、自前で watch する。

function watch(dirname, callback) {
  let queue = [];
  fs.watch(path.join(__dirname, dirname), { recursive: true }, (eventType, filename) => {
    queue.push(path.join(__dirname, dirname, filename));
  });
  const loop = async () => {
    if (queue.length === 0) {
      setTimeout(loop, 1000);
      return;
    }
    const filenames = Array.from(new Set(queue));
    queue = [];
    const existFilenames = (
      await Promise.all(
        filenames.map(async filename => {
          try {
            await fs.promises.access(filename);
          } catch (err) {
            return undefined;
          }
          return filename;
        })
      )
    ).filter(filename => filename !== undefined);
    await callback(existFilenames);
    setImmediate(loop);
  };
  setImmediate(loop);
}

1 秒 (1,000 ms) 以内の file 更新は纏めてある。file 削除は考慮してゐない、clean & build でなんとかする。そもそも手元での watch は CI での build に影響しない。

この watch 函數から esbuild を呼び出す。

function build(entryPoints) {
  esbuild.build({
    entryPoints,
    format: "cjs",
    logLevel: "warning",
    outbase: path.join(__dirname, "src"),
    outdir: path.join(__dirname, "dist"),
  });
}

glob(path.join(__dirname, "src/**/*.{ts,tsx}"))
  .then(filenames => filenames.filter(isEntryPoint))
  .then(build)
  .then(() => {
    if (isWatch) {
      watch("src", filenames => {
        const entryPoints = filenames.filter(isEntryPoint);
        if (entryPoints.length === 0) {
          return;
        }
        build(entryPoints);
      });
    }
  });

何とかなった。