import.meta.url を esbuild で CJS に変換する


メモ

ES2020 の機能で import.meta というのがあります。

import.meta はモジュールのメターデータを含むオブジェクトです。ブラウザや Node.js では import.meta.url というプロパティが生えています。

これは import.meta.url が評価されたモジュールのファイルの URL を表すプロパティです。

Node.js では、ESM 環境下で createRequire をしたり __filename__dirname に相当するものを取得する用途等でよく使われます。

import module from "node:module";
const require = module.createRequire(import.meta.url);
import path from "node:path";
import url from "node:url";

const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dir(__filename);

こんな感じで import.meta.url を使うプログラムを、esbuild を使って CJS に変換すると、実は空オブジェクトになります。

変換前のコードがこちら。ただ import.meta.urlconsole.log します。

console.log(import.meta.url)

変換後のコードがこちら。import.meta{} になって、{}urlconsole.log しています。

var import_meta = {};
console.log(import_meta.url);

これでは困っちゃうので、なんとかしましょう。

なんとかする方法の一つとして、defineinject があります。

こんな感じで import.meta.url を CJS 環境で再現するコードを用意しておきます。

import-meta-url.js
export var import_meta_url = require("url").pathToFileURL(__filename);

で、esbuild の defineimport.meta.urlimport_meta_url に置き換え、injectimport-meta-url.js を読むことで import_meta_url という変数を import.meta.url 相当のものに置き換えます。

import { build } from "esbuild";
import path from "path";

/** @type {import('esbuild').BuildOptions} */
const options = {
  entryPoints: ["./src/index.js"],
  minify: true,
  bundle: true,
  outfile: "./dist/index.cjs",
  format: "cjs",
  define: {
    "import.meta.url": "import_meta_url",
  },
  inject: [
    // このへんのパスは各々いい感じで
    path.join(process.cwd(), "import-meta-url.js"),
  ],
};

build(options).catch((err) => {
  process.stderr.write(err.stderr);
  process.exit(1);
});

これで良い感じになる。