Skip to content
Open
Show file tree
Hide file tree
Changes from 17 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Pass document's `lang` to Tiny-SDF to render Simplified and Traditional Chinese characters ([#6223](https://github.com/maplibre/maplibre-gl-js/issues/6223))
- Enable `global-state` expressions in layout properties([#6209](https://github.com/maplibre/maplibre-gl-js/pull/6209))
- Align typescript types generation with docs generation and avoid exporting non-exported types [#6217](https://github.com/maplibre/maplibre-gl-js/pull/6217)
- ESM bundle ([#6254](https://github.com/maplibre/maplibre-gl-js/pull/6254))

- _...Add new stuff here..._

Expand Down
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,21 @@
"description": "BSD licensed community fork of mapbox-gl, a WebGL interactive maps library",
"version": "5.6.2",
"main": "dist/maplibre-gl.js",
"module": "dist/maplibre-gl.mjs",
"style": "dist/maplibre-gl.css",
"exports": {
".": {
"types": "./dist/maplibre-gl.d.ts",
"import": "./dist/maplibre-gl.mjs",
"require": "./dist/maplibre-gl.js"
},
"./dist/maplibre-gl": {
"types": "./dist/maplibre-gl.d.ts",
"import": "./dist/maplibre-gl.mjs",
"require": "./dist/maplibre-gl.js"
},
"./dist/maplibre-gl.css": "./dist/maplibre-gl.css"
},
"license": "BSD-3-Clause",
"homepage": "https://maplibre.org/",
"funding": "https://github.com/maplibre/maplibre-gl-js?sponsor=1",
Expand Down
7 changes: 6 additions & 1 deletion rollup.config.csp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,13 @@ const config = (input: InputOption, file: string, format: ModuleFormat): RollupO
});

const configs = [
// UMD/IIFE builds for CSP
config('src/index.ts', `dist/maplibre-gl-csp${outputPostfix}.js`, 'umd'),
config('src/source/worker.ts', `dist/maplibre-gl-csp-worker${outputPostfix}.js`, 'iife')
config('src/source/worker.ts', `dist/maplibre-gl-csp-worker${outputPostfix}.js`, 'iife'),

// ESM builds for CSP
config('src/index.ts', `dist/maplibre-gl-csp${outputPostfix}.mjs`, 'es'),
config('src/source/worker.ts', `dist/maplibre-gl-csp-worker${outputPostfix}.mjs`, 'es')
];

export default configs;
34 changes: 34 additions & 0 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

const production = BUILD === 'production';
const outputFile = production ? 'dist/maplibre-gl.js' : 'dist/maplibre-gl-dev.js';
const esmOutputFile = production ? 'dist/maplibre-gl.mjs' : 'dist/maplibre-gl-dev.mjs';

Check warning on line 11 in rollup.config.ts

View workflow job for this annotation

GitHub Actions / Code Hygiene

'esmOutputFile' is assigned a value but never used

const config: RollupOptions[] = [{
// Rollup will use code splitting to bundle GL JS into three "chunks":
Expand Down Expand Up @@ -64,4 +65,37 @@
],
}];

// ESM builds (following CSP pattern - separate main and worker files)
// Users must explicitly call setWorkerUrl() when using these builds
const esmConfig: RollupOptions[] = [
{
// ESM main bundle
input: 'src/index.ts',
output: {
file: production ? 'dist/maplibre-gl.mjs' : 'dist/maplibre-gl-dev.mjs',
format: 'es',
sourcemap: true,
indent: false,
banner
},
treeshake: production,
plugins: plugins(production)
},
{
// ESM worker bundle
input: 'src/source/worker.ts',
output: {
file: production ? 'dist/maplibre-gl-worker.mjs' : 'dist/maplibre-gl-worker-dev.mjs',
format: 'es',
sourcemap: true,
indent: false,
banner
},
treeshake: production,
plugins: plugins(production)
}
];

config.push(...esmConfig);

export default config;
12 changes: 12 additions & 0 deletions src/util/web_worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,17 @@ export interface WorkerGlobalScopeInterface {
}

export function workerFactory() {
// Check if we should use module workers (for ESM builds)
const useModuleWorker = config.WORKER_URL && config.WORKER_URL.endsWith('.mjs');

if (useModuleWorker) {
try {
return new Worker(config.WORKER_URL, {type: 'module'});
} catch (e) {
// Fallback to regular worker if module workers not supported
console.warn('Module worker not supported, falling back to classic worker', e);
}
}

return new Worker(config.WORKER_URL);
}
74 changes: 74 additions & 0 deletions test/build/esm.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {describe, test, expect} from 'vitest';
import fs from 'fs';
import path from 'path';

describe('ESM build', () => {
test('ESM main bundle exists and exports expected API', () => {
const esmPath = path.join(process.cwd(), 'dist/maplibre-gl-dev.mjs');
expect(fs.existsSync(esmPath)).toBe(true);

const content = fs.readFileSync(esmPath, 'utf8');

// Check for ES module exports at the end of the file
expect(content).toMatch(/export\s+\{[^}]+\};\s*$/m);
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not super thrilled with checking the string content of the file, is there a better way to check this?

expect(content).toContain('Map');
expect(content).toContain('Marker');
expect(content).toContain('Popup');
expect(content).toContain('setWorkerUrl');
expect(content).toContain('getWorkerUrl');

// The bundle should use ES module format (export statements)
// Note: Some dependencies may contain module.exports internally,
// but the bundle itself should export using ES module syntax
expect(content).toMatch(/^export\s+\{/m);
});

test('ESM worker bundle exists', () => {
const workerPath = path.join(process.cwd(), 'dist/maplibre-gl-worker-dev.mjs');
expect(fs.existsSync(workerPath)).toBe(true);

const content = fs.readFileSync(workerPath, 'utf8');

// Worker should be an ES module
// The worker doesn't export anything but should not use AMD
expect(content).not.toContain('define.amd');

// Should contain worker-specific code
expect(content).toContain('Actor');
});

test('Production ESM builds exist', () => {
// These might not exist if only dev build was run
const mainProd = path.join(process.cwd(), 'dist/maplibre-gl.mjs');
const workerProd = path.join(process.cwd(), 'dist/maplibre-gl-worker.mjs');

if (fs.existsSync(mainProd)) {
const content = fs.readFileSync(mainProd, 'utf8');
expect(content).toContain('export {');

Check failure on line 47 in test/build/esm.test.ts

View workflow job for this annotation

GitHub Actions / Build tests (ubuntu-latest)

test/build/esm.test.ts > ESM build > Production ESM builds exist

AssertionError: expected '/**\n * MapLibre GL JS\n * @license 3…' to contain 'export {' - Expected + Received - export { + /** + * MapLibre GL JS + * @license 3-Clause BSD. Full text of license: https://github.com/maplibre/maplibre-gl-js/blob/v5.6.2/LICENSE.txt + */ + var t,e,i="5.6.2";function r(t,e,i,r){return new(i||(i=Promise))((function(n,s){function o(t){try{l(r.next(t))}catch(t){s(t)}}function a(t){try{l(r.throw(t))}catch(t){s(t)}}function l(t){var e;t.done?n(t.value):(e=t.value,e instanceof i?e:new i((function(t){t(e)}))).then(o,a)}l((r=r.apply(t,e||[])).next())}))}function n(t,e){this.x=t,this.y=e}function s(t){return t&&t.__esModule&&Object.prototype.hasOwnProperty.call(t,"default")?t.default:t}"function"==typeof SuppressedError&&SuppressedError,n.prototype={clone(){return new n(this.x,this.y)},add(t){return this.clone()._add(t)},sub(t){return this.clone()._sub(t)},multByPoint(t){return this.clone()._multByPoint(t)},divByPoint(t){return this.clone()._divByPoint(t)},mult(t){return this.clone()._mult(t)},div(t){return this.clone()._div(t)},rotate(t){return this.clone()._rotate(t)},rotateAround(t,e){return this.clone()._rotateAround(t,e)},matMult(t){return this.clone()._matMult(t)},unit(){return this.clone()._unit()},perp(){return this.clone()._perp()},round(){return this.clone()._round()},mag(){return Math.sqrt(this.x*this.x+this.y*this.y)},equals(t){return this.x===t.x&&this.y===t.y},dist(t){return Math.sqrt(this.distSqr(t))},distSqr(t){const e=t.x-this.x,i=t.y-this.y;return e*e+i*i},angle(){return Math.atan2(this.y,this.x)},angleTo(t){return Math.atan2(this.y-t.y,this.x-t.x)},angleWith(t){return this.angleWithSep(t.x,t.y)},angleWithSep(t,e){return Math.atan2(this.x*e-this.y*t,this.x*t+this.y*e)},_matMult(t){const e=t[2]*this.x+t[3]*this.y;return this.x=t[0]*this.x+t[1]*this.y,this.y=e,this},_add(t){return this.x+=t.x,this.y+=t.y,this},_sub(t){return this.x-=t.x,this.y-=t.y,this},_mult(t){return this.x*=t,this.y*=t,this},_div(t){return this.x/=t,this.y/=t,this},_multByPoint(t){return this.x*=t.x,this.y*=t.y,this},_divByPoint(t){return this.x/=t.x,this.y/=t.y,this},_unit(){return this._div(this.mag()),this},_perp(){const t=this.y;return this.y=this.x,this.x=-t,this},_rotate(t){const e=Math.cos(t),i=Math.sin(t),r=i*this.x+e*this.y;return this.x=e*this.x-i*this.y,this.y=r,this},_rotateAround(t,e){const i=Math.cos(t),r=Math.sin(t),n=e.y+r*(this.x-e.x)+i*(this.y-e.y);return this.x=e.x+i*(this.x-e.x)-r*(this.y-e.y),this.y=n,this},_round(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},constructor:n},n.convert=function(t){if(t instanceof n)return t;if(Array.isArray(t))return new n(+t[0],+t[1]);if(void 0!==t.x&&void 0!==t.y)return new n(+t.x,+t.y);throw new Error("Expected [x, y] or {x, y} point format")};var o=function(){if(e)return t;function i(t,e,i,r){this.cx=3*t,this.bx=3*(i-t)-this.cx,this.ax=1-this.cx-this.bx,this.cy=3*e,this.by=3*(r-e)-this.cy,this.ay=1-this.cy-this.by,this.p1x=t,this.p1y=e,this.p2x=i,this.p2y=r}return e=1,t=i,i.prototype={sampleCurveX:function(t){return((this.ax*t+this.bx)*t+this.cx)*t},sampleCurveY:function(t){return((this.ay*t+this.by)*t+this.cy)*t},sampleCurveDerivativeX:function(t){return(3*this.ax*t+2*this.bx)*t+this.cx},solveCurveX:function(t,e){if(void 0===e&&(e=1e-6),t<0)return 0;if(t>1)return 1;for(var i=t,r=0;r<8;r++){var n=this.sampleCurveX(i)-t;if(Math.abs(n)<e)return i;var s=this.sampleCurveDerivativeX(i);if(Math.abs(s)<1e-6)break;i-=n/s}var o=0,a=1;for(i=t,r=0;r<20&&(n=this.sampleCurveX(i),!(Math.abs(n-t)<e));r++)t>n?o=i:a=i,i=.5*(a-o)+o;return i},solve:function(t,e){return this.sampleCurveY(this.solveCurveX(t,e))}},t}(),a=s(o);let l,c;function h(){return null==l&&(l="undefined"!=typeof OffscreenCanvas&&new OffscreenCanvas(1,1).getContext("2d")&&"function"==typeof createImageBitmap),l}var u=1e-6,p="undefined"!=typeof Float32Array?Float32Array:Array;function d(){var t=new p(4);return p!=Float32Array&&(t[1]=0,t[2]=0),t[0]=1,t[3]=1,t}function f(){var t=new p(9);return p!=Float32Array&&(t[1]=0,t[2]=0,t[3]=0,t[5]=0,t[6]=0,t[7]=0),t[0]=1,t[4]=1,t[8]=1,t}function m
}

if (fs.existsSync(workerProd)) {
const content = fs.readFileSync(workerProd, 'utf8');
// Should be ES module format with export statement
expect(content).toContain('export');
}
});

test('CSP ESM builds follow same pattern', () => {
const cspMain = path.join(process.cwd(), 'dist/maplibre-gl-csp-dev.mjs');
const cspWorker = path.join(process.cwd(), 'dist/maplibre-gl-csp-worker-dev.mjs');

if (fs.existsSync(cspMain)) {
const content = fs.readFileSync(cspMain, 'utf8');
expect(content).toContain('export {');
// Should be ES module format with export statement
expect(content).toContain('export');
}

if (fs.existsSync(cspWorker)) {
const content = fs.readFileSync(cspWorker, 'utf8');
// Should be ES module format with export statement
expect(content).toContain('export');
}
});
});
2 changes: 1 addition & 1 deletion test/build/sourcemaps.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,6 @@ describe('main sourcemap', () => {
const s1 = setMinus(actualEntriesInSourcemapJSON, expectedEntriesInSourcemapJSON);
expect(s1.length).toBeLessThan(5);
const s2 = setMinus(expectedEntriesInSourcemapJSON, actualEntriesInSourcemapJSON);
expect(s2.length).toBeLessThan(16);
expect(s2.length).toBeLessThan(28);
});
});
10 changes: 7 additions & 3 deletions test/examples/filter-layer-symbols-using-global-state.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link rel='stylesheet' href='../../dist/maplibre-gl.css' />
<script src='../../dist/maplibre-gl-dev.js'></script>
<style>
body {
margin: 0;
Expand Down Expand Up @@ -41,7 +40,12 @@
</fieldset>
</body>

<script>
<script type="module">
import * as maplibregl from '../../dist/maplibre-gl-dev.mjs';

// Set the worker URL for ESM build (CSP-style pattern)
maplibregl.setWorkerUrl('../../dist/maplibre-gl-worker-dev.mjs');

const map = new maplibregl.Map({
container: 'map',
style: 'https://demotiles.maplibre.org/style.json',
Expand Down Expand Up @@ -103,4 +107,4 @@
});
</script>

</html>
</html>
Loading