Open14

Chrome拡張のバンドラ周りなんもわからんから解決したいスクラップ

Sachiko TajimaSachiko Tajima

そもそも

https://github.com/Jonghakseo/chrome-extension-boilerplate-react-vite のテンプレートをもとに作ってる

目的

chrome.scripting で特定のページで実行する関数orファイルを作るとき、その関数外に宣言されているものを import したりしていると存在していないとなっちゃうので、特定の関数orファイルに関連のものをまとめる(バンドル?)する必要がある。引数を渡したいから関数のほうがいいかな?

なんもわからん技術

この何もわからんやつを理解して目的を達成する方法を知る

  • turbo: build のとき使ってそう、モノレポまわりこいつがやってそう
  • esbuild: page/ じゃない共有ディレクトリのビルドの設定が書かれてるんだけどどこから実行されてるの
  • vite: page/ 下のそれぞれ一つのファイルに吐き出されるやつビルドしてる
  • rollup: vite がラップしてるバンドラ?的な?
Sachiko TajimaSachiko Tajima

vite

何度も公式ドキュメント読んでるしなんどもわからないと言っている。
これは /pages 以下のビルドで使われていて、 root の dist/ に置く単一のファイルにバンドルするために使われている。(例: background.js popup.js )

  • 基本ライブラリモードを使っている
  • 設定は各パッケージの vite.config.mts

https://vite.dev/guide/build.html#library-mode

rollup

vite のバンドルの設定を細かくするときに rollupOptions から触るやつ。
だいたいは vite がやってくれるからやりたいことが出てきたら覚えれば良さそう。

Sachiko TajimaSachiko Tajima

esbuild

これもビルドツールらしい
rollup と比較してるけど早いらしい

Major features:

  • Extreme speed without needing a cache
  • JavaScript, CSS, TypeScript, and JSX built-in
  • A straightforward API for CLI, JS, and Go
  • Bundles ESM and CommonJS modules
  • Bundles CSS including CSS modules
  • Tree shaking, minification, and source maps
  • Local server, watch mode, and plugins

https://esbuild.github.io/

え、Go って golang ?

使い方

./node_modules/.bin/esbuild app.jsx --bundle --outfile=out.js で node_modules にも依存しない自己完結した js ファイルが出力される。すごい。

// package.json に登録する場合
{
  "scripts": {
    "build": "esbuild app.jsx --bundle --outfile=out.js"
  }
}

これを↓と書くこともできる。

// build.mjs
import * as esbuild from 'esbuild'

await esbuild.build({
  entryPoints: ['app.jsx'],
  bundle: true,
  outfile: 'out.js',
})

これは package.json に script.build: "node build.mjs" とでも登録すれば実行できるらしい。

https://esbuild.github.io/api/#build

Sachiko TajimaSachiko Tajima

今やりたいこと

  • Chrome拡張の chrome.scripting にて関数orファイルを差し込むためのパッケージの実装をする
  • 実行時に指定したもの以外指定したページで呼び込まれないため、関数orファイルは外部依存せずバンドルされている必要がある。
  • 関数は、他の vite でバンドルされているパッケージから実行時に呼ばれる

一旦こんなかんじで↓

- packages
    - content-script
        - package.json
        - esbuild.mjs
        - dist/
        - src/
            - shared/
            - checks/

呼び出すがわからはこれで使うイメージ

// この @extension ってどこに書かれてる?
import { getImages } from '@extension/content-script/shared'
import { checkAlt } from '@extension/content-script/check/'
Sachiko TajimaSachiko Tajima
// content-script/package.json
{
  "exports": {
    "./shared": "./dist/shared/index.js",
    "./checks": "./dist/checks/index.js"
  }
}

とかければ良さそうなので、 src 下のファイルが dist/checks/index.js にバンドルされてればいい?

Sachiko TajimaSachiko Tajima
import esbuild from 'esbuild';

/**
 * @type { import('esbuild').BuildOptions }
 */
const buildOptions = {
  entryPoints: ['./src/shared/index.js', './src/checks/index.ts'],
  tsconfig: './tsconfig.json',
  bundle: true,
  target: 'es6',
  outdir: './dist',
  sourcemap: true,
};

await esbuild.build(buildOptions);

一旦これでできるか試してみます

Sachiko TajimaSachiko Tajima

出力されたファイルは即時関数になってて、外から import できなくなってる。

// dist/checks/index.ts
"use strict";
(() => {
  // src/shared/getImages.ts
  var getImages = () => {
    console.log("getImages");
  };

  // src/checks/checkHoge.ts
  var checkHoge = () => {
    const h = "hoge huga";
    console.log(h);
    getImages();
  };
})();
//# sourceMappingURL=index.js.map

-> build.mjs にて、 buildOptios に format: 'esm' で解決

// src/shared/getImages.ts
var getImages = () => {
  console.log("getImages");
};

// src/checks/checkHoge.ts
var checkHoge = () => {
  const h = "hoge huga";
  console.log(h);
  getImages();
};
export {
  checkHoge
};
//# sourceMappingURL=index.js.map

Sachiko TajimaSachiko Tajima

package.json の exports にパスを各方法はまだ vscode が対応していなくて、ビルドは通るがエラーが出る状態になっている。そのため変更する

モジュール '@extension/content-scripts/checks' またはそれに対応する型宣言が見つかりません。ts(2307)

import 側の tsconfig.json の paths に各方法を試したが解決できない

Sachiko TajimaSachiko Tajima

むむむ、
dist/checks/index.jsdist/shared/index.js にバンドルされるようにはなったが、 chrome.scripting は関数かファイルのみで、ファイルは引数を渡せない。
関数にすると、こういうのは getImages がなくてエラーになっている。

// libs/shared/getImages.ts
var getImages = () => {
  console.log("getImages");
};

// libs/checks/checkHoge.ts
var checkHoge = () => {
  const h = "hoge huga";
  console.log(h);
  getImages();
};
export {
  checkHoge
};
//# sourceMappingURL=index.js.map
(() => {
  const h = "hoge huga";
  console.log(h);
  getImages();
})()
Sachiko TajimaSachiko Tajima

考え直してファイルでインポートする形にシテク。

作戦

  1. root/ にファイルを出力する。 この場合は content-script/checks/checksHoge.js
  2. 引数で渡したいものは storage にいれる。chrome.storage は使えそうだった。
Sachiko TajimaSachiko Tajima

できた!設定書く

- dist
- packages
    - content-script
        - ibs
            - checks
                - checkHoge.ts

こういった配置で、content-script に書いたファイルはファイルごとにバンドルされて dist 下に置かれるようにした。

// /packages/content-script/esbuid.mjd

import esbuild from 'esbuild';

/**
 * @type { import('esbuild').BuildOptions }
 */
const buildOptions = {
  entryPoints: ['./libs/**/*.ts'],
  tsconfig: './tsconfig.json',
  bundle: true,
  target: 'es6',
  outdir: './../../dist',
  sourcemap: true,
};

await esbuild.build(buildOptions);

// /packages/content-script/tsconfig.json

{
  "extends": "@extension/tsconfig/utils",
  "compilerOptions": {
    "baseUrl": ".",
    "outDir": "./../../dist",
    "types": ["chrome"]
  },
  "include": ["libs"]
}

これで拡張機能のページ内で以下のように呼び出せる。

    await chrome.scripting
      .executeScript({
        target: { tabId: pageInfo.tabId },
        files: ['/checks/checkHoge.js'],
      })
      .catch(err => {
        console.error(err);
      });

Sachiko TajimaSachiko Tajima

今の設定だとHRが効かないので見てみる!

Hot-reloading for JavaScript is not currently implemented by esbuild. It's possible to transparently implement hot-reloading for CSS because CSS is stateless, but JavaScript is stateful so you cannot transparently implement hot-reloading for JavaScript like you can for CSS.

え。。。。?