Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions examples/basic/scripts/vite-maplibre-registry.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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} */
Expand All @@ -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',
}));
},
Expand All @@ -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 };
},
};
}
24 changes: 18 additions & 6 deletions examples/basic/scripts/vite-maplibre-registry.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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])}));
`);
}
Expand All @@ -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 () => {
Expand Down
Loading