◆ define するならクラスはエクポート不要
  ◆ グローバルに登録されるので get で取得できる
◆ define しないと使う側で登録の手間が増える
  ◆ 複数箇所で登録するとエラーなので共通のラッパーモジュールが必要になる
◆ define までモジュール内でやったほうが良いと思う

普段 Custom Element のモジュールをつくるときはこういう感じで書いています

customElements.define("foo-bar", class extends HTMLElement {
//
})

必要に応じて import したり関数定義を追加しますが メインの Custom Element 部分は customElements.define に直接書くだけです
クラスのエクスポートはしません
React や Vue とは違って Custom Element はグローバルに登録するしかないので define 関数で登録すればエクスポートするものはありません
使う側は import するだけです

継承したり static メソッドの呼び出しをしたいことはありますが customElements.get で取得できるので クラスをエクスポートしなくても困りません

import "./foo-bar.js"

customElements.define("ex-foo-bar", class extends customElements.get("foo-bar") {
//
})

共通処理を持たせただけの createElement しないベースクラスの場合はエクスポートします

//// base.js
export default class extends HTMLElement {
//
}

//// foo-bar.js
import Base from "./base.js"
customElements.define("foo-bar", class extends Base {
//
})

これで特に困ってなかったのですが クラスをエクスポートしたり define しない書き方をしているものも見かけたので少し考えてみました

クラスのエクスポートが必要かについては customElements.define をしているならなくてもいいと思います
一度 define したものは上書きや削除などの変更はできないので import に成功していれば確実に customElements.get で取得できるはずです
import 構文で一緒に受け取りたいとか そのほうがインポートする側のコード量が少し減るとかあるかもしれませんが どっちじゃないと困るとかは無いので好みの問題です


モジュール内で define を実行すべきかという点では やっぱりしておいたほうが良いと思います
foo-bar 要素があって 動きを変えたい時に define していなければ

//// foo-bar.js
export default class extends HTMLElement { /* */ }

//// ex-foo-bar.js
import FooBar from "./foo-bar.js"

customElements.define("foo-bar", class extends FooBar { /* */ })

のように同じ名前で継承したものを使ったりできます
しかし Custom Element の定義はグローバルなので継承したものじゃない元のを想定して使ってるライブラリもありえます

また foo-bar 要素を拡張する気がなくて 2 箇所で foo-bar 要素を使う場合

//// foo-bar-user1.js
import FooBar from "./foo-bar.js"
customElements.define("foo-bar", FooBar)

// something

//// foo-bar-user2.js
import FooBar from "./foo-bar.js"
customElements.define("foo-bar", FooBar)

// something

のように 使う側が define をしないといけなくなり手間です
さらに 1 つのページでこの 2 ファイルの両方を import した場合 foo-bar を二重に登録するのでエラーになります
登録内容は同じですが無視されずエラーが起きます

これを防ごうとすると foo-bar を登録するラッパーが必要で

//// foo-bar-wrapper.js
import FooBar from "./foo-bar.js"
customElements.define("foo-bar", FooBar)

これを 2 箇所から import する必要があります
2 箇所目で import されてもモジュールがインポート済みなので define 処理は実行されず エラーは起きません

使うときにこういうのを作らないといけないのは手間ですし foo-bar 要素を提供する側としても 2 種類のファイルを作るのは手間です

少し楽にするために モジュールでは URL のクエリパラメータを取得できるので 「define=false」 があれば define しないとすることもできます

const should_define = new URL(import.meta.url).searchParams.get("define") !== "false"

if (should_define) {
customElements.define(...)
}

こういうことをする場合 クエリパラメータのありなしは別の URL 扱いです
ありなしの両方を import した場合は 2 回実行されます


ただ この仕組みが役に立つのは foo-bar という名前を使ってしまっては困るときで 実際には異なるライブラリで同じ名前を使ってるようなケースくらいでしょう
基本的にライブラリ名を含めるなど重複しないようにしてそうですし やっぱり余計なことはせずシンプルにモジュール内で define する作りでいいと思います