You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@superset.apache.org by ru...@apache.org on 2026/09/10 02:51:19 UTC
(superset) branch master updated: fix(maplibre): wire up maplibre-gl 6's worker for plugin-chart-point-cluster-map (#44042)
This is an automated email from the ASF dual-hosted git repository.
rusackas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 1820a162dbe fix(maplibre): wire up maplibre-gl 6's worker for plugin-chart-point-cluster-map (#44042)
1820a162dbe is described below
commit 1820a162dbe3964d3ad11cf0ff6cabe4aa8df6d9
Author: Evan Rusackas <ev...@preset.io>
AuthorDate: Wed Sep 9 19:51:11 2026 -0700
fix(maplibre): wire up maplibre-gl 6's worker for plugin-chart-point-cluster-map (#44042)
Co-authored-by: Claude Code <no...@anthropic.com>
---
.../src/MapLibre.tsx | 15 ++++++++--
.../src/mapLibreDefaults.ts | 24 ++++++++++++++++
.../src/transformProps.ts | 2 +-
.../test/MapLibre.test.tsx | 33 ++++++++++++++++++++++
.../types/global.d.ts | 23 +++++++++++++++
superset-frontend/spec/helpers/setup.ts | 7 +++++
superset-frontend/webpack.config.js | 18 +++++++++++-
7 files changed, 117 insertions(+), 5 deletions(-)
diff --git a/superset-frontend/plugins/plugin-chart-point-cluster-map/src/MapLibre.tsx b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/MapLibre.tsx
index d1f875804a3..a9b4603a754 100644
--- a/superset-frontend/plugins/plugin-chart-point-cluster-map/src/MapLibre.tsx
+++ b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/MapLibre.tsx
@@ -19,6 +19,7 @@
import { memo, useCallback, useEffect, useState } from 'react';
import { Map as MapLibreMap } from 'react-map-gl/maplibre';
import { Map as MapboxMap } from 'react-map-gl/mapbox';
+import * as maplibregl from 'maplibre-gl';
import { WebMercatorViewport } from '@math.gl/web-mercator';
import {
resolveMapStyle,
@@ -28,13 +29,21 @@ import { useTheme } from '@apache-superset/core/theme';
import { t } from '@apache-superset/core/translation';
import ScatterPlotOverlay from './components/ScatterPlotOverlay';
import { getMapboxApiKey } from './utils/mapbox';
+import { DEFAULT_POINT_RADIUS } from './mapLibreDefaults';
import 'maplibre-gl/dist/maplibre-gl.css';
import './MapLibre.css';
-const DEFAULT_MAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty';
+// maplibre-gl 6's ESM-only build derives its worker URL from
+// `import.meta.url`, which our (non-ESM-output) webpack bundle rewrites to
+// a build-time path that doesn't match maplibre's `^https?:` check, so
+// `WORKER_URL` silently resolves to `''` and no worker gets created. Point
+// it at the copy CopyPlugin emits alongside the rest of our static assets
+// (see webpack.config.js) instead, using the runtime-configurable public
+// path so this also works behind a reverse-proxy / APPLICATION_ROOT
+// prefix. This must run before the first MapLibre map mounts.
+maplibregl.setWorkerUrl(`${__webpack_public_path__}maplibre-gl-worker.mjs`);
-export const DEFAULT_MAX_ZOOM = 16;
-export const DEFAULT_POINT_RADIUS = 60;
+const DEFAULT_MAP_STYLE = 'https://tiles.openfreemap.org/styles/liberty';
interface Viewport {
longitude: number;
diff --git a/superset-frontend/plugins/plugin-chart-point-cluster-map/src/mapLibreDefaults.ts b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/mapLibreDefaults.ts
new file mode 100644
index 00000000000..70adceb1225
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/mapLibreDefaults.ts
@@ -0,0 +1,24 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Kept in their own module (rather than exported from MapLibre.tsx) so that
+// transformProps.ts can use them without pulling in MapLibre.tsx's
+// `maplibre-gl` import and its module-level setWorkerUrl() side effect.
+export const DEFAULT_MAX_ZOOM = 16;
+export const DEFAULT_POINT_RADIUS = 60;
diff --git a/superset-frontend/plugins/plugin-chart-point-cluster-map/src/transformProps.ts b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/transformProps.ts
index b70495ec7e3..04f67341b13 100644
--- a/superset-frontend/plugins/plugin-chart-point-cluster-map/src/transformProps.ts
+++ b/superset-frontend/plugins/plugin-chart-point-cluster-map/src/transformProps.ts
@@ -21,7 +21,7 @@ import Supercluster, {
} from 'supercluster';
import { ChartProps, getMapProviderMapStyle } from '@superset-ui/core';
import { t } from '@apache-superset/core/translation';
-import { DEFAULT_POINT_RADIUS, DEFAULT_MAX_ZOOM } from './MapLibre';
+import { DEFAULT_POINT_RADIUS, DEFAULT_MAX_ZOOM } from './mapLibreDefaults';
import roundDecimal from './utils/roundDecimal';
const NOOP = () => {};
diff --git a/superset-frontend/plugins/plugin-chart-point-cluster-map/test/MapLibre.test.tsx b/superset-frontend/plugins/plugin-chart-point-cluster-map/test/MapLibre.test.tsx
index 1db0cd24bdc..7bf5a353d2c 100644
--- a/superset-frontend/plugins/plugin-chart-point-cluster-map/test/MapLibre.test.tsx
+++ b/superset-frontend/plugins/plugin-chart-point-cluster-map/test/MapLibre.test.tsx
@@ -70,6 +70,23 @@ jest.mock('@apache-superset/core/theme', () => ({
jest.mock('maplibre-gl/dist/maplibre-gl.css', () => ({}));
jest.mock('../src/MapLibre.css', () => ({}));
+// maplibre-gl 6 is an ESM-only package with no "require"/"default" export
+// condition, so jest's (CJS-based) resolver can't locate the real module to
+// mock over it by name; { virtual: true } skips that resolution step.
+//
+// The mock jest.fn() is created inline (not hoisted out to a `const`)
+// because MapLibre.tsx calls maplibregl.setWorkerUrl() synchronously at
+// import time, and `import`/jest.mock() calls are themselves hoisted above
+// this file's plain `const` declarations — a `const` referenced here would
+// still be in its temporal dead zone when that import-time call fires.
+jest.mock(
+ 'maplibre-gl',
+ () => ({ __esModule: true, setWorkerUrl: jest.fn() }),
+ { virtual: true },
+);
+
+// eslint-disable-next-line import/first
+import * as maplibregl from 'maplibre-gl';
// eslint-disable-next-line import/first
import MapLibre from '../src/MapLibre';
@@ -94,6 +111,22 @@ const defaultProps = {
onViewportChange: jest.fn(),
};
+// Captured before the first jest.clearAllMocks() below wipes the call the
+// module made at import time.
+const setWorkerUrlCallAtImport = [
+ ...jest.mocked(maplibregl.setWorkerUrl).mock.calls,
+];
+
+test('points maplibre-gl at the CopyPlugin-emitted worker asset before any map can mount', () => {
+ // maplibre-gl 6's ESM-only build derives its worker URL from
+ // `import.meta.url`, which webpack rewrites to a build-time path that
+ // doesn't match maplibre's `^https?:` check, so the worker silently
+ // fails to start unless setWorkerUrl() is called first (see MapLibre.tsx).
+ expect(setWorkerUrlCallAtImport).toEqual([
+ ['/static/assets/maplibre-gl-worker.mjs'],
+ ]);
+});
+
beforeEach(() => {
lastMapProps = {};
document.body.innerHTML = '';
diff --git a/superset-frontend/plugins/plugin-chart-point-cluster-map/types/global.d.ts b/superset-frontend/plugins/plugin-chart-point-cluster-map/types/global.d.ts
new file mode 100644
index 00000000000..bffb6dd14a2
--- /dev/null
+++ b/superset-frontend/plugins/plugin-chart-point-cluster-map/types/global.d.ts
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// webpack's runtime-configurable public path, mirroring src/globals.d.ts.
+// Used to point maplibre-gl at its CopyPlugin-emitted worker asset (see
+// MapLibre.tsx) with the correct app-root / reverse-proxy prefix applied.
+declare var __webpack_public_path__: string;
diff --git a/superset-frontend/spec/helpers/setup.ts b/superset-frontend/spec/helpers/setup.ts
index 3d216e00af1..9888cad0f94 100644
--- a/superset-frontend/spec/helpers/setup.ts
+++ b/superset-frontend/spec/helpers/setup.ts
@@ -37,6 +37,13 @@ expect.extend(matchers);
// Allow JSX tests to have React import readily available
global.React = React;
+// webpack's runtime-configurable public path (see src/public-path.ts and
+// src/globals.d.ts). Modules that reference it at import time (e.g. to
+// build a static-asset URL, such as plugin-chart-point-cluster-map's
+// MapLibre.tsx) would otherwise throw ReferenceError under jest, since
+// nothing else in the test environment ever sets this webpack-only global.
+global.__webpack_public_path__ = '/static/assets/';
+
// Mock ace-builds globally for tests
jest.mock('ace-builds/src-min-noconflict/mode-handlebars', () => ({}));
jest.mock('ace-builds/src-min-noconflict/mode-css', () => ({}));
diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js
index 84bb13d7b5d..3ad02476f3a 100644
--- a/superset-frontend/webpack.config.js
+++ b/superset-frontend/webpack.config.js
@@ -151,7 +151,23 @@ const plugins = [
}),
new CopyPlugin({
- patterns: ['package.json', { from: 'src/assets/images', to: 'images' }],
+ patterns: [
+ 'package.json',
+ { from: 'src/assets/images', to: 'images' },
+ // maplibre-gl 6's ESM-only build loads its worker from a real URL
+ // instead of an inlined blob (see MapLibre.tsx for the matching
+ // maplibregl.setWorkerUrl() call). The worker's own bundle does a
+ // relative ESM import of its "shared" chunk, so both files must be
+ // copied verbatim, unhashed, into the same output directory.
+ {
+ from: 'node_modules/maplibre-gl/dist/maplibre-gl-worker.mjs',
+ to: 'maplibre-gl-worker.mjs',
+ },
+ {
+ from: 'node_modules/maplibre-gl/dist/maplibre-gl-shared.mjs',
+ to: 'maplibre-gl-shared.mjs',
+ },
+ ],
}),
// static pages