◆ モジュール内で値を保持したり インスタンスをエクスポートすると
  ◆ import するだけでモジュール間で値を共有できる
  ◆ 困るところもあるのでモジュール内に変動する値を持ちたくない気もある
◆ ユニークインスタンスを管理するモジュールを使ってインスタンスを共有

困るところ

値を保持する機能があるモジュールを作る時 クラスをエクスポートしたり オブジェクトを返す関数をエクスポートすることが多いと思います

簡単に書くとこういう感じです

export class Foo {
values = []
add(value) {
this.values.push(value)
}
show() {
console.log(this.values)
}
}

export const bar = () => {
return {
values: [],
add(value) {
this.values.push(value)
},
show() {
console.log(this.values)
}
}
}

しかし インスタンスをいくつも作ることがなく 1 つが前提の場合はエクスポートするのがインスタンスだったり モジュールのグローバル変数に値を保持するモジュールもあります

export default new class {
values = []
add(value) {
this.values.push(value)
}
show() {
console.log(this.values)
}
}
let values = []

export function add(value) {
values.push(value)
}

export function show() {
console.log(values)
}

複数作らないのが前提なら この作りの方がインスタンスをあちこちのモジュールから参照したいときに楽に済みます
上の最後の例だと import して show メソッドを呼び出すだけで 別メソッドで追加した value を表示できます
しかし Foo クラスをエクスポートした場合は そのインスタンスをどこかで作って 参照するモジュールすべてに何らかの形で渡す必要があります

関数渡しだと毎回実行のたびに渡す必要があり 渡すためには呼び出し箇所全てでその変数を参照できる必要があります
それにモジュール側の作り方にも影響しますし 外部から関数を呼び出す必要が出てきます

app みたいなモジュールを作ってそこをアプリ中で使う値置き場にすることもできます
これなら そのモジュールをインポートすれば済みますが 管理しづらそうなのと 値置き場のモジュールを作る必要があります

そう考えると 複数のインスタンスが不要なモジュールなら モジュール自体が値を保持して モジュールの関数呼び出しで値を更新するという方が楽です
しかし 後になってやっぱり複数インスタンスを作れたほうがいいとか 状態をクリアしたいとか思うこともあります
最初の例のように エクスポートするのはクラスや関数で その呼び出しで新たに作られるオブジェクト内にのみ値を保持して モジュール内には値を保持しないほうがいいのかなとも思います
でもそうすると上で書いたような 複数モジュールでインスタンスの共有が面倒になります

unique new

モジュール内に値を保持しない作りで モジュール間のインスタンス共有を楽にしたいなと考えていると ユニークなインスタンスを作る機能で十分のような気がしました
もともとインスタンスはひとつでいいものの前提ですし 共有する部分は 1 つだけで 複数作っても全部を共有することはまずないと思います

ということで ユニークインスタンスを管理するモジュールを作りました

簡単な使い方はこうです

import unew from "./unew.js"

unew(Date)

これで Date 型のインスタンスを作って返します
このあとで何度 「unew(Date)」 を行っても最初に作ったインスタンスが返ってきます

Date ならどこでも参照できますが インスタンスを作りたいクラスは なにかのモジュールからインポートする場合が多いと思います
unew とそのモジュールの 2 つもインポートしないといけなくなるのを防ぐため 名前をつけて事前に登録しておくことができるようにしています

unew.register("date", Date)
unew("date")

この場合 Date を参照できなくても date という文字列だけでインスタンスを取得できます
インポートが unew だけで済むモジュールも増えます

インスタンスは register だけでは作られず unew の呼び出し時に作られます

インスタンスの作成時にコンストラクタに引数を渡す場合は 3 つめ以降の引数に指定します

unew.register("date", Date, "2020-01-01 00:00:00")
unew("date")

名前は Map のキーなので文字列以外に 数値やシンボルやオブジェクトなども使えます
null は特殊でコンストラクタ自身をキーにします

unew.register(null, Date, "2020-01-01 00:00:00")
unew(Date)



unew.register(Date, Date, "2020-01-01 00:00:00")
unew(Date)

は同じ意味です

register 済みのキーを指定した場合に unew の 2 つめ以降の引数は無意味です
しかし 未登録の場合はその場で register 処理を行い このときの引数として使われます

unew(Date, "2020-01-01 00:00:00")

register を解除するには unregister でキーを指定します

unew.unregister("date")

全部を消すには

unew.clear()

で初期状態に戻せます

このモジュールの用途的に このモジュール自体がモジュール内に値を保持して変更する作りになっています
完全に新規の unew オブジェクトを作るには unew.unew を呼び出します

const unew2 = unew.unew()

これで複数の独立したインスタンスが作れます

登録するコンストラクタはクラス定義である必要はなくアロー関数にすることもできます
アロー関数みたいな new できない関数は単純に関数実行します

unew.register("date1", () => new Date())
unew("date1")

インスタンス作成後に メソッド呼び出したりプロパティ設定もしたい場合に使えます

foo.js がエクスポートする Foo を main.js と module1.js で参照する例です

//// entrypoint.js
import unew from "./unew.js"
import Foo from "./foo.js"

unew.regsiter("foo", Foo, 1, 2, 3)

import("./main")

//// main.js
import unew from "./unew.js"
import module1 from "./module1.js"

function fn() {
const foo = unew("foo")
foo.method()
}

//// module1.js
import unew from "./unew.js"

console.log(unew("foo").getSomething())

unew.js

unew.js のソースコードはこんなのです

const _unew = () => {
const unew = (key, ...register_options) => {
if (unew.instances.has(key)) {
return unew.instances.get(key)
}
const def = unew.registry.get(key)
if (!def) {
if (typeof key !== "function") {
throw new Error("not registered")
}
// register and recall
unew.register(key, key, ...register_options)
return unew(key)
}
const { type, options } = def
const instance = type.prototype ? new type(...options) : type(...options)
unew.instances.set(key, instance)
return instance
}
unew.registry = new Map()
unew.instances = new Map()
unew.register = (key, type, ...options) => {
if (typeof type !== "function") {
throw new Error("type argument must be an function")
}
if (key == null) key = type
if (unew.registry.has(key)) {
throw new Error("already registered")
}
unew.registry.set(key, { type, options })
}
unew.unregister = (key) => {
unew.registry.delete(key)
unew.instances.delete(key)
}
unew.clear = () => {
unew.registry.clear()
unew.instances.clear()
}
unew.unew = _unew
return unew
}

export default _unew()

グローバル置き場と比べて

起動直後にインスタンスを作って app モジュールなどに全部配置してしまうのでも あまり変わらないかもしれません
それでも ただのオブジェクトのプロパティにあれこれインスタンスが入っていて好きに書き換えていけるのはグローバル変数みたいな問題も出てきそうです
unregister や clear で途中で書き換えできなくはないですが 関数呼び出しなのでどこでその処理が行われているかを特定しやすいです
あと register だけではインスタンスを作らないので 必要ない場合には作られません
作るだけで外部との通信が発生するようなインスタンスもありますし 必要になってから作るほうが良さそうです