Skip to content

added an endpoint without side effect#463

Merged
niryuu merged 7 commits into
geolonia:masterfrom
niryuu:add-non-side-effect-endpoint
Mar 13, 2026
Merged

added an endpoint without side effect#463
niryuu merged 7 commits into
geolonia:masterfrom
niryuu:add-non-side-effect-endpoint

Conversation

@niryuu

@niryuu niryuu commented Mar 2, 2026

Copy link
Copy Markdown
Contributor

React ラッパー向け embed 改修(Phase 1)

@geolonia/react-embed を作るための前提となる改修です。

tl;dr

変更点を示します。

  • embed.tsは読み込んだ瞬間に副作用が色々走るため、importして使いやすいようにはなっていません。これはreactなどで利用する際の障壁になります。
  • このため、embed-core.tsで副作用のない新しいエンドポイントを作りました。
  • geolonia-map.ts, parse-atts.ts, embed.ts, types.ts はその際に型定義を整理したものになります。
  • いくつかのe2eテストのバグ修正を含みます。
  • simplestyle.tsの変更が多いのは、部品化の際にreactなどで使えることを見越して削除機能を追加したことによります。

背景

現状の embed.ts は window.geolonia の設定や DOM スキャン(renderGeoloniaMap())などの副作用を持つため、React コンポーネントからそのまま import すると SSR
時にクラッシュしたり、マップが二重初期化されるなどの問題が起きます。React ラッパーはクラスだけを import できる副作用なしのエントリポイントを必要としています。

変更内容

src/embed-core.ts を新規追加

副作用なしのエントリポイント。renderGeoloniaMap() も window.geolonia の設定もしません。PMTiles プロトコル登録(全利用者に必要なため残す)だけを行い、GeoloniaMap・SimpleStyle
等のクラスと型をエクスポートします。@geolonia/react-embed はこちらを import します。

src/types.ts を新規追加・循環依存の解消

  • EmbedAttributes・EmbedPlugin 型を embed.ts からここに移動しました。これにより parse-atts.ts が embed.ts を import していた循環依存が解消されます。

  • SimpleStyle.remove() を追加

React の useEffect クリーンアップで必要です。addTo() で追加したレイヤーとソースをまとめて削除します。これがないとコンポーネントの再レンダリング時にレイヤーが重複してエラーになります。

useEffect(() => {
const ss = new SimpleStyle(geojson).addTo(map);
return () => ss.remove();
}, [map, geojson]);

ビルド・パッケージ設定

  • webpack.config.js: embed-core.js のビルドを追加(配列形式に変更)
  • package.json: exports フィールドを追加。@geolonia/embed/core で embed-core.js を参照できるようになります

e2e テスト整備

  • dev tileserver 廃止に伴い、npm start に MAP_PLATFORM_STAGE=v1 を追加
  • external-style.spec.ts を新規追加(外部スタイル URL でマップが動作することを確認)
  • docs/webpack.config.js を配列エクスポート対応に修正

動作確認

dist/embed.js # 従来どおり(後方互換性あり)
dist/embed-core.js # 新規追加

両バンドルのビルド成功・ユニットテスト 51 件・e2e テスト 24 件(Chromium / Firefox / WebKit)すべてパスを確認しています。

Summary by CodeRabbit

リリースノート

  • 新機能

    • 新しいコアライブラリエントリーポイントを追加し、プログラマティック利用をサポート
    • マップレイヤーのクリーンアップ機能を実装
  • バグ修正

    • ネットワークエラーのコンソール記録をフィルタリング
    • マップ初期化の検証ロジックを改善
  • その他

    • ビルド構成を最適化し、複数エントリーポイントに対応

@coderabbitai

coderabbitai Bot commented Mar 2, 2026

Copy link
Copy Markdown
📝 Walkthrough

ウォークスルー

このプルリクエストは、ウェブパック設定を2つの独立したエントリーポイント(embed.ts と embed-core.ts)をサポートする構造に再構築し、TypeScript型定義を共有型モジュールに集約し、SimpleStyleクラスにイベントハンドラー追跡と削除機能を追加し、package.jsonにエクスポートフィールドを導入し、E2Eテストを更新します。

変更内容

コホート / ファイル(s) 概要
ウェブパック・ビルド設定
webpack.config.js, docs/webpack.config.js
単一設定を embed.js と embed-core.js の2つのビルド出力をサポートする配列ベースの設定に変更。sharedConfig を導入して共通ルール(TypeScript、SVG、CSS)を統合し、ウェブパック設定をモジュール化。
パッケージ設定
package.json
新しい「exports」フィールドを追加して、デフォルトエントリ(".")と "./core" エントリを定義。「start」スクリプトに MAP_PLATFORM_STAGE=v1 プレフィックスを追加。
型定義・コアモジュール
src/types.ts, src/embed-core.ts, src/embed.ts, src/lib/parse-atts.ts
EmbedAttributes と EmbedPlugin 型を新しい src/types.ts に抽出して共有化。src/embed-core.ts を新規作成し、PMTiles プロトコル登録と API コンポーネント・型の再エクスポートを実装。インポート参照を更新。
機能拡張
src/lib/simplestyle.ts
イベントハンドラー追跡用の _eventHandlers プライベートフィールドを追加。インラインハンドラーを名前付き関数に置き換え。マップリソースとイベントリスナーをクリーンアップする remove() メソッドを導入。
E2E テスト更新
e2e/basic.spec.ts, e2e/map.spec.ts
コンソールエラーフィルタリングを実装してネットワークリソースエラーを除外。マップ読み込み待機ロジックを簡略化し、インスタンス作成とキャンバス可視性の確認に変更。

推定コードレビュー工数

🎯 3 (中程度) | ⏱️ ~25 分

🐰 兎のつぶやき

二つのウェブパック、美しく分かれて
型たちは新しい家を見つけ
イベントもきちんと、追跡・整理
テストも確かに、ネットワーク除いて
変わりゆく embed、さらに輝く ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed このPRのタイトル「added an endpoint without side effect」は、新しいside-effect-freeなエントリポイント(embed-core.ts)の追加を説明していますが、PR全体のスコープに対して部分的です。タイトルは主要な変更の一部を捉えていますが、同等に重要なSimpleStyle.remove()メソッドの追加や型の整理などは説明していません。
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/webpack.config.js`:
- Around line 7-9: The output override spreads embedConfig.output
(embedConfig.output) which carries clean: true and then sets path: __dirname
(docs/), causing the docs directory to be wiped on build; explicitly override
the inherited clean flag by setting clean: false in the output object where you
spread embedConfig.output (the same block that sets path: __dirname and
filename: 'embed') so that clean: false replaces the inherited clean: true and
prevents deletion of docs/.

In `@e2e/external-style.spec.ts`:
- Around line 60-63: The code inside page.evaluate assumes
document.querySelector('#map canvas.maplibregl-canvas') returns a non-null
HTMLCanvasElement and calls getContext directly; add a null guard for the canvas
variable (inside the page.evaluate callback) and return false early if canvas is
null so getContext isn't invoked on null; update the canvas declaration and
control flow around canvas/getContext in the page.evaluate block that computes
isNotBlank.

In `@src/lib/simplestyle.ts`:
- Around line 352-374: The remove() implementation currently assumes this.map is
set and will throw if called before addTo() or called twice; add a guard at the
start of remove() (e.g., if (!this.map) return) to make the method idempotent
and safe to call when uninitialized, and after successfully removing
layers/sources clear the stored map reference (set this.map = undefined/null) so
subsequent calls are no-ops; refer to the remove() method, this.map field, and
the addTo() entry point when making the change.
- Around line 352-374: The remove() method currently removes layers/sources but
does not unregister event handlers added by setPopup() and setCluster(), causing
duplicate handlers and leaks when re-adding; capture the handler functions
passed to this.map.on(...) in instance properties (e.g.,
this._popupClickHandler, this._popupEnterHandler, this._popupLeaveHandler,
this._clusterClickHandler, this._clusterEnterHandler, this._clusterLeaveHandler)
when setPopup() and setCluster() register 'click', 'mouseenter', and
'mouseleave' listeners, and then call this.map.off(event, layerOrType, handler)
for each stored handler in remove() to fully clean them up before removing
layers/sources.

In `@src/types.ts`:
- Around line 3-34: EmbedAttributes currently types every attribute as string
but code in parse-atts.ts (e.g., defaults like lat: 0, lng: 0, zoom: 0, maxZoom:
20) and util.ts (calls parseFloat(atts.lat)) use numeric defaults, causing a
type mismatch; update the EmbedAttributes type so numeric keys accept string |
number (at least for lat, lng, zoom, bearing, pitch, minZoom, maxZoom and any
other numeric-like keys) or change the defaults to strings to match the exported
type, then run type-checks to ensure parse-atts.ts and util.ts compile without
assertions — refer to the EmbedAttributes type, parse-atts.ts defaults, and
util.ts parseFloat usage when making the change.

In `@webpack.config.js`:
- Around line 47-64: The two multi-compiler configs (embedConfig and
embedCoreConfig) race because output.clean is true for embedConfig and execution
order isn't guaranteed; fix by making execution deterministic: add unique name
properties (e.g., embedCoreConfig.name = 'embed-core', embedConfig.name =
'embed') and set embedConfig.dependencies = ['embed-core'] so embed-core builds
first (or alternatively set both outputs to clean: false and use an explicit
cleaning step), ensuring embedCoreConfig artifacts aren't deleted by
embedConfig's clean step.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d9abfa7 and 57d67e6.

📒 Files selected for processing (12)
  • docs/e2e/external-style.html
  • docs/webpack.config.js
  • e2e/basic.spec.ts
  • e2e/external-style.spec.ts
  • e2e/map.spec.ts
  • package.json
  • src/embed-core.ts
  • src/embed.ts
  • src/lib/parse-atts.ts
  • src/lib/simplestyle.ts
  • src/types.ts
  • webpack.config.js

Comment thread docs/webpack.config.js Outdated
Comment thread e2e/external-style.spec.ts Outdated
Comment thread src/lib/simplestyle.ts
Comment thread src/types.ts
Comment thread webpack.config.js
@yuiseki
yuiseki self-requested a review March 5, 2026 06:10
@bougan1160

Copy link
Copy Markdown
Contributor

@niryuu
#466e2e/external-style.spec.ts‎ の扱いを修正したので、お手数ですが変更点を取り込んでいただければと思います。
あと不足しているテストがあれば適宜追加していただければと。
よろしくお願いします。

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/simplestyle.ts (1)

274-286: ⚠️ Potential issue | 🟠 Major

クリーンアップ後も古い async ハンドラが map を更新できます

setPopup()setCluster() の async ハンドラで、await sanitizeDescription() / await getClusterExpansionZoom() の後に世代確認がないため、remove() 直前に発火したハンドラがクリーンアップ後も Popup.addTo()map.easeTo() を実行できます。また、表示中の popup を remove() で閉じていません。

💡 修正イメージ
 export class SimpleStyle {
+  private _mountSeq = 0;
+  private _activePopup?: maplibregl.Popup;
   private _eventHandlers: { event: string; layer: string; handler: (...args) => void }[] = [];
 
   addTo(map) {
     this.map = map;
+    this._mountSeq += 1;
 
     const features = this.geojson.features;
     ...
   }
 
   async setPopup(map, source) {
+    const mountSeq = this._mountSeq;
     const clickHandler = async (e) => {
       ...
       if (description) {
         const sanitizedDescription = await sanitizeDescription(description);
+        if (mountSeq !== this._mountSeq || this.map !== map) return;
+        this._activePopup?.remove();
+        this._activePopup = new maplibregl.Popup()
           .setLngLat(center)
           .setHTML(sanitizedDescription)
           .addTo(map);
       }
     };
   }
 
   setCluster() {
+    const mountSeq = this._mountSeq;
+    const map = this.map;
     ...
     const clusterClickHandler = async (e) => {
       ...
       const zoom = await map
         .getSource(`${this.options.id}-points`)
         .getClusterExpansionZoom(clusterId);
+      if (mountSeq !== this._mountSeq || this.map !== map) return;
 
       map.easeTo({
         center: features[0].geometry.coordinates,
         zoom: zoom,
       });
     };
   }
 
   remove() {
     if (!this.map) return this;
+    this._mountSeq += 1;
+    this._activePopup?.remove();
+    this._activePopup = undefined;
     ...
+    this.map = undefined;
     return this;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/simplestyle.ts` around lines 274 - 286, The async handlers
(clickHandler used by setPopup() and the handler used by setCluster()) call
await sanitizeDescription() / await getClusterExpansionZoom() but don't verify
the map/instance hasn't changed afterward and they never close the displayed
popup on cleanup; fix by capturing the current map instance or an incrementing
generation/token (e.g., store currentMap or handlerGeneration before awaiting)
and compare it immediately after each await to bail out if different, and ensure
any created maplibregl.Popup() is kept in a variable that is removed/closed in
the component cleanup (call popup.remove() and avoid calling popup.addTo(map) or
map.easeTo() if the generation check fails); apply these checks around
sanitizeDescription, getClusterExpansionZoom, Popup.addTo, and map.easeTo to
prevent stale handlers from modifying the map.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/simplestyle.ts`:
- Around line 290-298: The mouse cursor can remain set to 'pointer' if remove()
is called before mouseleave fires; update the remove() method to explicitly
reset the canvas cursor to default by calling this.map.getCanvas().style.cursor
= '' at the end of the method, and do the same for any other cleanup paths;
ensure this change addresses both the basic feature handlers (mouseEnterHandler
/ mouseLeaveHandler) and the cluster handlers so the cursor is always cleared
when the layer/interaction is removed.

---

Outside diff comments:
In `@src/lib/simplestyle.ts`:
- Around line 274-286: The async handlers (clickHandler used by setPopup() and
the handler used by setCluster()) call await sanitizeDescription() / await
getClusterExpansionZoom() but don't verify the map/instance hasn't changed
afterward and they never close the displayed popup on cleanup; fix by capturing
the current map instance or an incrementing generation/token (e.g., store
currentMap or handlerGeneration before awaiting) and compare it immediately
after each await to bail out if different, and ensure any created
maplibregl.Popup() is kept in a variable that is removed/closed in the component
cleanup (call popup.remove() and avoid calling popup.addTo(map) or map.easeTo()
if the generation check fails); apply these checks around sanitizeDescription,
getClusterExpansionZoom, Popup.addTo, and map.easeTo to prevent stale handlers
from modifying the map.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c5a95967-bd11-4f40-bfac-1e8f8f449bc6

📥 Commits

Reviewing files that changed from the base of the PR and between 57d67e6 and 546b906.

📒 Files selected for processing (2)
  • docs/webpack.config.js
  • src/lib/simplestyle.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/webpack.config.js

Comment thread src/lib/simplestyle.ts
@niryuu

niryuu commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

@niryuu #466e2e/external-style.spec.ts‎ の扱いを修正したので、お手数ですが変更点を取り込んでいただければと思います。 あと不足しているテストがあれば適宜追加していただければと。 よろしくお願いします。

こちらはPRから外しました。

@bougan1160
bougan1160 self-requested a review March 13, 2026 01:50

@bougan1160 bougan1160 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

良いと思います!

@niryuu
niryuu merged commit 3e62985 into geolonia:master Mar 13, 2026
10 checks passed
@yuiseki yuiseki added Priority: High The issue has high priority enhancement New feature or request labels Mar 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request Priority: High The issue has high priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants