Skip to content

Commit

Permalink
Improve exports (#3601)
Browse files Browse the repository at this point in the history
* Initial commit - not working...

* Fixing initializing with esm exports (#3600)

* Fixed custom define AMD function in order work with esm exports

* Refactored custom define function to be a bit more stable for changes

It still fragile to changes of dependencies order tho

* Fix lint

* Fix lint

* Fix tests - missing return

* Update test/integration/render/run_render_tests.ts

Co-authored-by: Evgeniy Timokhov <timocov@gmail.com>

* Update test/integration/render/run_render_tests.ts

Co-authored-by: Evgeniy Timokhov <timocov@gmail.com>

* Fixes to the docs, change back to umd, add pmtiles image

* Fix lint and missing files

* A few small corrections

* Updated changelog, reduced complexity in prelude, update docs

* Revert control changes

* Revert changes to navigation control

* remove file that is fixed in another PR.

* Revert changes in docs images generator script

* Remove unused types

* Add expelicitly export reference types

* Rename control options to reduce name collision

* Improve docs generation, export everything that is needed for the docs

* Remove debug additions to API

---------

Co-authored-by: Evgeniy Timokhov <timocov@gmail.com>
  • Loading branch information
HarelM and timocov authored Jan 25, 2024
1 parent fa7372f commit 15afd54
Show file tree
Hide file tree
Showing 33 changed files with 429 additions and 456 deletions.
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
## main

### ✨ Features and improvements

- ⚠️ Remove all global getters and setters from `maplibregl`, this means the the following methods have changed:
`maplibregl.version` => `getVersion()`
`maplibregl.workerCount` => `getWorkerCount()`, `setWorkerCount(...)`
`maplibregl.maxParallelImageRequests` => `getMaxParallelImageRequests()`, `setMaxParallelImageRequests(...)`
`maplibregl.workerUrl` => `getWorkerUrl()`, `setWorkerUrl(...)`
This is to avoid the need to use a global object and allow named exports/imports ([#3601](https://github.com/maplibre/maplibre-gl-js/issues/3601))
- _...Add new stuff here..._

### 🐞 Bug fixes
Expand Down
97 changes: 54 additions & 43 deletions build/generate-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,51 +50,62 @@ ${indexArrayItem.description}
return indexMarkdown;
}

if (!fs.existsSync(typedocConfig.out)) {
throw new Error('Please run typedoc generation first!');
/**
* Builds the README.md file by parsing the modules.md file generated by typedoc.
*/
function generateReadme() {
const modulesFile = path.join(typedocConfig.out, 'modules.md');
const content = fs.readFileSync(modulesFile, 'utf-8');
let lines = content.split('\n');
const classesLineIndex = lines.indexOf(lines.find(l => l.endsWith('Classes')) as string);
lines = lines.splice(2, classesLineIndex - 2);
const contentString = generateAPIIntroMarkdown(lines);
fs.writeFileSync(path.join(typedocConfig.out, 'README.md'), contentString);
fs.rmSync(modulesFile);
}

fs.rmSync(path.join(typedocConfig.out, 'README.md'));
// Intro file for the API
const modulesFile = path.join(typedocConfig.out, 'modules.md');
const content = fs.readFileSync(modulesFile, 'utf-8');
let lines = content.split('\n');
const classesLineIndex = lines.indexOf(lines.find(l => l.endsWith('Classes')) as string);
lines = lines.splice(2, classesLineIndex - 2);
const contentString = generateAPIIntroMarkdown(lines);
fs.writeFileSync(path.join(typedocConfig.out, 'README.md'), contentString);
fs.rmSync(modulesFile);

// Examples manupilation
const examplesDocsFolder = path.join('docs', 'examples');
if (fs.existsSync(examplesDocsFolder)) {
fs.rmSync(examplesDocsFolder, {recursive: true, force: true});
}
fs.mkdirSync(examplesDocsFolder);
const examplesFolder = path.join('test', 'examples');
const files = fs.readdirSync(examplesFolder).filter(f => f.endsWith('html'));
const maplibreUnpgk = `https://unpkg.com/maplibre-gl@${packageJson.version}/`;
const indexArray = [] as HtmlDoc[];
for (const file of files) {
const htmlFile = path.join(examplesFolder, file);
let htmlContent = fs.readFileSync(htmlFile, 'utf-8');
htmlContent = htmlContent.replace(/\.\.\/\.\.\//g, maplibreUnpgk);
htmlContent = htmlContent.replace(/-dev.js/g, '.js');
const htmlContentLines = htmlContent.split('\n');
const title = htmlContentLines.find(l => l.includes('<title'))?.replace('<title>', '').replace('</title>', '').trim();
const description = htmlContentLines.find(l => l.includes('og:description'))?.replace(/.*content=\"(.*)\".*/, '$1');
fs.writeFileSync(path.join(examplesDocsFolder, file), htmlContent);
const mdFileName = file.replace('.html', '.md');
indexArray.push({
title: title!,
description: description!,
mdFileName
});
const exampleMarkdown = generateMarkdownForExample(title, description, file, htmlContent);
fs.writeFileSync(path.join(examplesDocsFolder, mdFileName), exampleMarkdown);
}
/**
* This takes the examples folder with all the html files and generates a markdown file for each of them.
* It also create an index file with all the examples and their images.
*/
function generateExamplesFolder() {
const examplesDocsFolder = path.join('docs', 'examples');
if (fs.existsSync(examplesDocsFolder)) {
fs.rmSync(examplesDocsFolder, {recursive: true, force: true});
}
fs.mkdirSync(examplesDocsFolder);
const examplesFolder = path.join('test', 'examples');
const files = fs.readdirSync(examplesFolder).filter(f => f.endsWith('html'));
const maplibreUnpgk = `https://unpkg.com/maplibre-gl@${packageJson.version}/`;
const indexArray = [] as HtmlDoc[];
for (const file of files) {
const htmlFile = path.join(examplesFolder, file);
let htmlContent = fs.readFileSync(htmlFile, 'utf-8');
htmlContent = htmlContent.replace(/\.\.\/\.\.\//g, maplibreUnpgk);
htmlContent = htmlContent.replace(/-dev.js/g, '.js');
const htmlContentLines = htmlContent.split('\n');
const title = htmlContentLines.find(l => l.includes('<title'))?.replace('<title>', '').replace('</title>', '').trim();
const description = htmlContentLines.find(l => l.includes('og:description'))?.replace(/.*content=\"(.*)\".*/, '$1');
fs.writeFileSync(path.join(examplesDocsFolder, file), htmlContent);
const mdFileName = file.replace('.html', '.md');
indexArray.push({
title: title!,
description: description!,
mdFileName
});
const exampleMarkdown = generateMarkdownForExample(title, description, file, htmlContent);
fs.writeFileSync(path.join(examplesDocsFolder, mdFileName), exampleMarkdown);
}

const indexMarkdown = generateMarkdownIndexFileOfAllExamples(indexArray);
fs.writeFileSync(path.join(examplesDocsFolder, 'index.md'), indexMarkdown);
const indexMarkdown = generateMarkdownIndexFileOfAllExamples(indexArray);
fs.writeFileSync(path.join(examplesDocsFolder, 'index.md'), indexMarkdown);
}

// !!Main flow start here!!
if (!fs.existsSync(typedocConfig.out)) {
throw new Error('Please run typedoc generation first!');
}
fs.rmSync(path.join(typedocConfig.out, 'README.md'));
generateReadme();
generateExamplesFolder();
console.log('Docs generation completed, to see it in action run\n docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material');
42 changes: 25 additions & 17 deletions build/rollup/bundle_prelude.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
/* eslint-disable */

var shared, worker, maplibregl;
// define gets called three times: one for each chunk. we rely on the order
// they're imported to know which is which
function define(_, chunk) {
if (!shared) {
shared = chunk;
} else if (!worker) {
worker = chunk;
} else {
var workerBundleString = 'var sharedChunk = {}; (' + shared + ')(sharedChunk); (' + worker + ')(sharedChunk);'
var maplibregl = {};
var modules = {};
function define(moduleName, _dependencies, moduleFactory) {
modules[moduleName] = moduleFactory;

var sharedChunk = {};
shared(sharedChunk);
maplibregl = chunk(sharedChunk);
if (typeof window !== 'undefined') {
maplibregl.workerUrl = window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' }));
}
// to get the list of modules see generated dist/maplibre-gl-dev.js file (look for `define(` calls)
if (moduleName !== 'index') {
return;
}
}

// we assume that when an index module is initializing then other modules are loaded already
var workerBundleString = 'var sharedModule = {}; (' + modules.shared + ')(sharedModule); (' + modules.worker + ')(sharedModule);'

var sharedModule = {};
// the order of arguments of a module factory depends on rollup (it decides who is whose dependency)
// to check the correct order, see dist/maplibre-gl-dev.js file (look for `define(` calls)
// we assume that for our 3 chunks it will generate 3 modules and their order is predefined like the following
modules.shared(sharedModule);
modules.index(maplibregl, sharedModule);

if (typeof window !== 'undefined') {
maplibregl.setWorkerUrl(window.URL.createObjectURL(new Blob([workerBundleString], { type: 'text/javascript' })));
}

return maplibregl;
};

33 changes: 4 additions & 29 deletions build/rollup/maplibregl.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,42 +6,17 @@
// The three "chunks" imported here are produced by a first Rollup pass,
// which outputs them as AMD modules.

// Shared dependencies, i.e.:
/*
define(['exports'], function (exports) {
// Code for all common dependencies
// Each module's exports are attached attached to 'exports' (with
// names rewritten to avoid collisions, etc.)
})
*/
// Shared dependencies
import '../../staging/maplibregl/shared';

// Worker and its unique dependencies, i.e.:
/*
define(['./shared.js'], function (__shared__js) {
// Code for worker script and its unique dependencies.
// Expects the output of 'shared' module to be passed in as an argument,
// since all references to common deps look like, e.g.,
// __shared__js.shapeText().
});
*/
// When this wrapper function is passed to our custom define() above,
// Worker and its unique dependencies
// When this wrapper function is passed to our custom define() in build/rollup/bundle_prelude.js,
// it gets stringified, together with the shared wrapper (using
// Function.toString()), and the resulting string of code is made into a
// Blob URL that gets used by the main module to create the web workers.
import '../../staging/maplibregl/worker';

// Main module and its unique dependencies
/*
define(['./shared.js'], function (__shared__js) {
// Code for main GL JS module and its unique dependencies.
// Expects the output of 'shared' module to be passed in as an argument,
// since all references to common deps look like, e.g.,
// __shared__js.shapeText().
//
// Returns the actual maplibregl (i.e. src/index.js)
});
*/
// Main module and its dependencies
import '../../staging/maplibregl/index';

export default maplibregl;
4 changes: 2 additions & 2 deletions build/rollup_plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const plugins = (production: boolean): Plugin[] => [
}),
production && strip({
sourceMap: true,
functions: ['PerformanceUtils.*', 'Debug.*']
functions: ['PerformanceUtils.*']
}),
production && terser({
compress: {
Expand All @@ -46,7 +46,7 @@ export const plugins = (production: boolean): Plugin[] => [
// https://github.com/mapbox/mapbox-gl-js/pull/6956
ignoreGlobal: true
})
].filter(Boolean);
].filter(Boolean) as Plugin[];

export const watchStagingPlugin: Plugin = {
name: 'watch-external',
Expand Down
6 changes: 3 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ MapLibre GL JS is a TypeScript library that uses WebGL to render interactive map
This documentation is divided into several sections:

* [**Main**](./API/README.md) - The Main section holds the following classes
* [`Map`](./API/classes/Map.md) object is the map on your page. It lets you access methods and properties for interacting with the map's style and layers, respond to events, and manipulate the user's perspective with the camera.
* [`MaplibreGL`](./API/classes/default.md) object is MapLibre GL JS's global properties and options that you might want to access while initializing your map or accessing information about its status.
* [`Map`](./API/classes/Map/) object is the map on your page. It lets you access methods and properties for interacting with the map's style and layers, respond to events, and manipulate the user's perspective with the camera.
* [`Global Functions`](./API/functions/addProtocol/) let you set global properties and options that you might want to access while initializing your map or accessing information about its status.
* [**Markers and Controls**](./API/README.md#markers-and-controls) - This section describes the user interface elements that you can add to your map. The items in this section exist outside of the map's `canvas` element. This consists of `Marker`, `Popup` and all the controls.
* [**Geography and geometry**](./API/README.md#geography-and-geometry) - This section includes general utilities and types that relate to working with and manipulating geographic information or geometries.
* [**User interaction handlers**](./API/README.md#handlers) - The items in this section relate to the ways in which the map responds to user input.
Expand All @@ -52,7 +52,7 @@ For strict CSP environments without `worker-src blob: ; child-src blob:` enabled

```html
<script>
maplibregl.workerUrl = "${urls.js().replace('.js', '-csp-worker.js')}";
maplibregl.setWorkerUrl("${urls.js().replace('.js', '-csp-worker.js')}");
...
</script>
```
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@
"generate-shaders": "node --no-warnings --loader ts-node/esm build/generate-shaders.ts",
"generate-struct-arrays": "node --no-warnings --loader ts-node/esm build/generate-struct-arrays.ts",
"generate-style-code": "node --no-warnings --loader ts-node/esm build/generate-style-code.ts",
"generate-typings": "dts-bundle-generator --umd-module-name=maplibregl -o ./dist/maplibre-gl.d.ts ./src/index.ts",
"generate-typings": "dts-bundle-generator --export-referenced-types --umd-module-name=maplibregl -o ./dist/maplibre-gl.d.ts ./src/index.ts",
"generate-docs": "typedoc && node --no-warnings --loader ts-node/esm build/generate-docs.ts",
"generate-images": "node --no-warnings --loader ts-node/esm build/generate-doc-images.ts",
"build-dist": "run-p --print-label build-css generate-typings && run-p --print-label build-dev build-csp-dev && run-p --print-label build-prod build-csp",
Expand Down
3 changes: 3 additions & 0 deletions rollup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ const config: RollupOptions[] = [{
sourcemap: 'inline',
indent: false,
chunkFileNames: 'shared.js',
amd: {
autoId: true,
},
minifyInternalExports: production
},
onwarn: (message) => {
Expand Down
26 changes: 13 additions & 13 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {config} from './util/config';
import maplibre from './index';
import {addProtocol, getWorkerCount, removeProtocol, getVersion} from './index';
import {getJSON, getArrayBuffer} from './util/ajax';
import {ImageRequest} from './util/image_request';
import {isAbortError} from './util/abort_error';
Expand All @@ -13,31 +13,31 @@ describe('maplibre', () => {
});

test('workerCount', () => {
expect(typeof maplibre.workerCount === 'number').toBeTruthy();
expect(typeof getWorkerCount() === 'number').toBeTruthy();
});

test('addProtocol', () => {
const protocolName = 'custom';
expect(Object.keys(config.REGISTERED_PROTOCOLS)).toHaveLength(0);

maplibre.addProtocol(protocolName, async () => Promise.resolve({} as any));
addProtocol(protocolName, async () => Promise.resolve({} as any));
expect(Object.keys(config.REGISTERED_PROTOCOLS)[0]).toBe(protocolName);
});

test('removeProtocol', () => {
const protocolName = 'custom';
expect(Object.keys(config.REGISTERED_PROTOCOLS)).toHaveLength(0);

maplibre.addProtocol(protocolName, () => Promise.resolve({} as any));
addProtocol(protocolName, () => Promise.resolve({} as any));
expect(Object.keys(config.REGISTERED_PROTOCOLS)[0]).toBe(protocolName);

maplibre.removeProtocol(protocolName);
removeProtocol(protocolName);
expect(Object.keys(config.REGISTERED_PROTOCOLS)).toHaveLength(0);
});

test('#addProtocol - getJSON', async () => {
let protocolCallbackCalled = false;
maplibre.addProtocol('custom', () => {
addProtocol('custom', () => {
protocolCallbackCalled = true;
return Promise.resolve({data: {'foo': 'bar'}});
});
Expand All @@ -48,7 +48,7 @@ describe('maplibre', () => {

test('#addProtocol - getArrayBuffer', async () => {
let protocolCallbackCalled = false;
maplibre.addProtocol('custom', () => {
addProtocol('custom', () => {
protocolCallbackCalled = true;
return Promise.resolve({data: new ArrayBuffer(1), cacheControl: 'cache-control', expires: 'expires'});
});
Expand All @@ -61,7 +61,7 @@ describe('maplibre', () => {

test('#addProtocol - returning ImageBitmap for getImage', async () => {
let protocolCallbackCalled = false;
maplibre.addProtocol('custom', () => {
addProtocol('custom', () => {
protocolCallbackCalled = true;
return Promise.resolve({data: new ImageBitmap()});
});
Expand All @@ -73,7 +73,7 @@ describe('maplibre', () => {

test('#addProtocol - returning HTMLImageElement for getImage', async () => {
let protocolCallbackCalled = false;
maplibre.addProtocol('custom', () => {
addProtocol('custom', () => {
protocolCallbackCalled = true;
return Promise.resolve({data: new Image()});
});
Expand All @@ -83,7 +83,7 @@ describe('maplibre', () => {
});

test('#addProtocol - error', () => {
maplibre.addProtocol('custom', () => Promise.reject(new Error('test error')));
addProtocol('custom', () => Promise.reject(new Error('test error')));

getJSON({url: 'custom://test/url/json'}, new AbortController()).catch((error) => {
expect(error).toBeTruthy();
Expand All @@ -92,7 +92,7 @@ describe('maplibre', () => {

test('#addProtocol - Cancel request', async () => {
let cancelCalled = false;
maplibre.addProtocol('custom', (_req, abortController) => {
addProtocol('custom', (_req, abortController) => {
abortController.signal.addEventListener('abort', () => {
cancelCalled = true;
});
Expand All @@ -111,11 +111,11 @@ describe('maplibre', () => {
});

test('version', () => {
expect(typeof maplibre.version === 'string').toBeTruthy();
expect(typeof getVersion() === 'string').toBeTruthy();

// Semver regex: https://gist.github.com/jhorsman/62eeea161a13b80e39f5249281e17c39
// Backslashes are doubled to escape them
const regexp = new RegExp('^([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?(?:\\+[0-9A-Za-z-]+)?$');
expect(regexp.test(maplibre.version)).toBeTruthy();
expect(regexp.test(getVersion())).toBeTruthy();
});
});
Loading

0 comments on commit 15afd54

Please sign in to comment.