maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
103 lines (90 loc) • 3.6 kB
text/typescript
import packageJSON from '../../package.json' with {type: 'json'};
import {type AddProtocolAction, config} from './config.ts';
import type {default as MaplibreWorker} from '../source/worker.ts';
import type {WorkerSourceConstructor} from '../source/worker_source.ts';
import type {GetResourceResponse, RequestParameters} from './ajax.ts';
import type {ProjectionHandler} from '../source/projection_handler_worker.ts';
export interface WorkerGlobalScopeInterface {
registerWorkerSource: (sourceName: string, sourceConstructor: WorkerSourceConstructor) => void;
registerRTLTextPlugin: (_: any) => void;
addProtocol: (customProtocol: string, loadFn: AddProtocolAction) => void;
removeProtocol: (customProtocol: string) => void;
makeRequest: (request: RequestParameters, abortController: AbortController) => Promise<GetResourceResponse<any>>;
/**
* @experimental
* Sets the workers' `ProjectionHandler`, for GeoJSON sources on a map in a registered projection; `null` removes it.
*/
setProjectionHandler: (handler: ProjectionHandler | null) => void;
worker: MaplibreWorker;
}
function isCrossOrigin(url: string): boolean {
if (!url) return false;
const loc = (globalThis as any).location;
if (!loc) return false;
try {
return new URL(url, loc.href).origin !== loc.origin;
} catch {
return false;
}
}
/**
* The URL of the worker bundle next to this module, carrying the package version as a `v` query
* parameter so that a browser does not serve a worker cached from an older release.
*/
function defaultWorkerUrl(): string {
const moduleUrl = import.meta.url;
if (!/^https?:/.test(moduleUrl)) return '';
const workerName = moduleUrl.endsWith('-dev.mjs')
? 'maplibre-gl-worker-dev.mjs'
: 'maplibre-gl-worker.mjs';
const url = new URL(`./${workerName}`, moduleUrl);
url.searchParams.set('v', packageJSON.version);
return url.href;
}
function isClassicWorkerUrl(url: string): boolean {
return url.endsWith('.cjs') || url.split(/[?#]/)[0].endsWith('.cjs');
}
function createWorker(url: string, asModule: boolean): Worker {
if (asModule) {
try {
return new Worker(url, {type: 'module'});
} catch (e) {
console.warn('Module worker not supported, falling back to classic worker', e);
}
}
return new Worker(url);
}
async function fetchAsBlobUrl(url: string): Promise<string> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to fetch worker script (${response.status}): ${url}`);
}
const code = await response.text();
const blob = new Blob([code], {type: 'text/javascript'});
return URL.createObjectURL(blob);
}
function importAsBlobUrl(url: string): string {
const blob = new Blob([`import ${JSON.stringify(new URL(url, import.meta.url).href)}`], {type: 'text/javascript'});
return URL.createObjectURL(blob);
}
export async function workerFactory(): Promise<Worker> {
const url = config.WORKER_URL || defaultWorkerUrl();
const asModule = !isClassicWorkerUrl(url);
if (!isCrossOrigin(url)) {
return createWorker(url, asModule);
}
if (asModule) {
const blobUrl = importAsBlobUrl(url);
try {
return createWorker(blobUrl, asModule);
} finally {
URL.revokeObjectURL(blobUrl);
}
}
const blobUrl = await fetchAsBlobUrl(url);
try {
return createWorker(blobUrl, asModule);
} finally {
URL.revokeObjectURL(blobUrl);
}
}