From d441eb288fc3fa743e72a26bfd82861dcec7ad1a Mon Sep 17 00:00:00 2001 From: Masashi Katsumata Date: Sat, 7 Nov 2026 16:24:22 -0800 Subject: [PATCH] Isolate demo Mapbox registry from external renderers --- .../basic/scripts/vite-maplibre-registry.mjs | 20 +++++++++------- .../scripts/vite-maplibre-registry.test.mjs | 24 ++++++++++++++----- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/examples/basic/scripts/vite-maplibre-registry.mjs b/examples/basic/scripts/vite-maplibre-registry.mjs index b7e4f1a..b60e791 100644 --- a/examples/basic/scripts/vite-maplibre-registry.mjs +++ b/examples/basic/scripts/vite-maplibre-registry.mjs @@ -3,13 +3,15 @@ import { readFile } from 'node:fs/promises'; // Demo only: both renderers register built-in constructors (Object, Set, Error) // under the same private property. MapLibre's Set registration makes Mapbox look // up a class that is absent from its own registry, breaking all vector tiles. -// Keep the wire format ($name) intact; only isolate MapLibre's constructor key. +// Keep the wire format ($name) intact; isolate each renderer's constructor key. // Never apply this in the SDK package builds or modify installed dependencies. -const registryKey = '_mapconductorDemoMapLibreRegistryKey'; -const maplibreModule = /[/\\]maplibre-gl[/\\].*\.[cm]?js$/; +// Longdo loads its own external MapLibre; isolate Mapbox too so foreign +// constructor registrations cannot bypass its built-in serialization handling. +const rendererModule = /(?:maplibre-gl|mapbox-gl).*\.[cm]?js$/; -function rewrite(code) { - return code.replaceAll('_classRegistryKey', registryKey); +function rewrite(code, id) { + const renderer = id.includes('mapbox-gl') ? 'Mapbox' : 'MapLibre'; + return code.replaceAll('_classRegistryKey', `_mapconductorDemo${renderer}RegistryKey`); } /** @returns {import('vite').Plugin} */ @@ -24,8 +26,8 @@ export function isolateMapLibreRegistry() { plugins: [{ name: 'demo-isolate-maplibre-registry', setup(build) { - build.onLoad({ filter: maplibreModule }, async ({ path }) => ({ - contents: rewrite(await readFile(path, 'utf8')), + build.onLoad({ filter: rendererModule }, async ({ path }) => ({ + contents: rewrite(await readFile(path, 'utf8'), path), loader: 'js', })); }, @@ -35,8 +37,8 @@ export function isolateMapLibreRegistry() { }; }, transform(code, id) { - if (!maplibreModule.test(id.split('?')[0]) || !code.includes('_classRegistryKey')) return; - return { code: rewrite(code), map: null }; + if (!rendererModule.test(id.split('?')[0]) || !code.includes('_classRegistryKey')) return; + return { code: rewrite(code, id), map: null }; }, }; } diff --git a/examples/basic/scripts/vite-maplibre-registry.test.mjs b/examples/basic/scripts/vite-maplibre-registry.test.mjs index bba9252..5e04618 100644 --- a/examples/basic/scripts/vite-maplibre-registry.test.mjs +++ b/examples/basic/scripts/vite-maplibre-registry.test.mjs @@ -12,29 +12,32 @@ const maplibre = await readFile(maplibrePath, 'utf8'); const mapbox = await readFile(require.resolve('mapbox-gl/dist/mapbox-gl-dev.js'), 'utf8'); const plugin = isolateMapLibreRegistry(); -function serializeWithBothRenderers(maplibreCode, order) { +function serializeWithBothRenderers(maplibreCode, order, mapboxCode = mapbox) { // Exercise the installed libraries' actual registration/serialization code. // Separate registries, shared built-in constructors, just like a browser page. const registerStart = maplibreCode.indexOf('function register(name, klass, options = {})'); assert.ok(registerStart >= 0, 'MapLibre registration code changed; review the workaround'); const registerEnd = maplibreCode.indexOf('register("Object", Object);', registerStart); assert.ok(registerEnd > registerStart); - const serializeStart = mapbox.indexOf('function serialize(input, transferables)'); - const serializeEnd = mapbox.indexOf('function deserialize(input)', serializeStart); + const serializeStart = mapboxCode.indexOf('function serialize(input, transferables)'); + const serializeEnd = mapboxCode.indexOf('function deserialize(input)', serializeStart); assert.ok(serializeStart >= 0 && serializeEnd > serializeStart); const registerMaplibre = `(() => { const registry = {}; ${maplibreCode.slice(registerStart, registerEnd)} register('Object', Object); register('Set', Set); register('Error', Error); })();`; - const registerMapbox = `Object.defineProperty(Object, '_classRegistryKey', {value: 'Object'});`; + const registerStartBox = mapboxCode.indexOf('function register(klass, name, options = {})'); + const registerEndBox = mapboxCode.indexOf('register(Object, "Object");', registerStartBox); + assert.ok(registerStartBox >= 0 && registerEndBox > registerStartBox); + const registerMapbox = `${mapboxCode.slice(registerStartBox, registerEndBox)} register(Object, 'Object');`; return runInNewContext(` class ImageBitmap {} class ImageData {} class Headers {} const assert = () => {}; // Mapbox's production assertions are compiled out. const registry = {Object: {omit: []}}; const isArrayBuffer = value => value instanceof ArrayBuffer; ${order === 'mapbox-first' ? registerMapbox + registerMaplibre : registerMaplibre + registerMapbox} - ${mapbox.slice(serializeStart, serializeEnd)} + ${mapboxCode.slice(serializeStart, serializeEnd)} JSON.stringify(serialize({tiles: new Set([1, 2])})); `); } @@ -50,7 +53,16 @@ test('production transform permits both load orders without changing the wire fo tiles: {$name: 'Set', 1: 1, 2: 2}, }); } - assert.equal(plugin.transform(mapbox, require.resolve('mapbox-gl')), undefined); + assert.equal(plugin.transform('const x = 1;', '/unrelated/library.js'), undefined); +}); + +test('Mapbox coexists with unmodified external MapLibre as loaded by Longdo', () => { + const transformed = plugin.transform(mapbox, require.resolve('mapbox-gl')).code; + for (const order of ['maplibre-first', 'mapbox-first']) { + assert.deepEqual(JSON.parse(serializeWithBothRenderers(maplibre, order, transformed)), { + tiles: {$name: 'Set', 1: 1, 2: 2}, + }); + } }); test('Vite dependency optimization applies the same isolation to MapLibre', async () => {