Skip to content

Commit d3c7760

Browse files
dcojgithub-actions[bot]
authored andcommitted
Extracts optional production debug code to an asynchronously loaded debug module
GitOrigin-RevId: 0753cdaab1204ef1d564d16cf85e56ee9611b88f
1 parent 28f1d69 commit d3c7760

10 files changed

Lines changed: 188 additions & 42 deletions

File tree

modules/debug.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// UMD/CDN entry: the Debug namespace is available synchronously at import time,
2+
// and `prepareDebug()` is a no-op that resolves immediately. The Rollup
3+
// `esm-substitution-resolver` plugin swaps this file for `debug_esm.ts` in ESM
4+
// builds, where the debug chunk is dynamically imported instead.
5+
export {DebugModule} from './debug_imports';
6+
7+
export async function prepareDebug() { return Promise.resolve(); }

modules/debug_esm.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {warnOnce} from '../src/util/util';
2+
3+
import type {DebugModule as DebugModuleType} from './debug_imports';
4+
5+
// ESM entry: the DebugModule namespace starts out empty. `prepareDebug()` dynamically
6+
// imports the debug chunk and copies the validators onto `DebugModule` in place, so
7+
// callers that captured a reference to `DebugModule` before loading see the populated
8+
// surface afterwards.
9+
export const DebugModule: Partial<typeof DebugModuleType> = {loaded: false};
10+
11+
let pending: Promise<void> | null = null;
12+
13+
export function prepareDebug(): Promise<void> {
14+
if (pending !== null) return pending;
15+
pending = import('./debug_imports').then(({DebugModule: loaded}) => {
16+
Object.assign(DebugModule, loaded);
17+
}).catch(() => {
18+
warnOnce('Could not load Debug module; style validation is disabled.');
19+
});
20+
return pending;
21+
}

modules/debug_imports.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Non-production code paths: style-spec validators, the style-diff algorithm,
2+
// and the debug-overlay draw functions. Lifted out of `core.js` so consumers
3+
// that disable validation (`{validate: false}`), never call
4+
// `setStyle(..., {diff: true})` after initial load, and never toggle any
5+
// `map.show*` debug flag never download these ~25KB gz.
6+
//
7+
// Importing this file is what causes Rollup to emit the `debug.js` chunk
8+
// (see chunkFileNames in rollup.config.esm.ts).
9+
import {
10+
validateStyle,
11+
validateSource,
12+
validateLight,
13+
validateLights,
14+
validateTerrain,
15+
validateFog,
16+
validateSnow,
17+
validateRain,
18+
validateLayer,
19+
validateFilter,
20+
validatePaintProperty,
21+
validateLayoutProperty,
22+
validateModel,
23+
} from '../src/style-spec/validate_style.min';
24+
import diffStyles from '../src/style-spec/diff';
25+
import drawDebug, {drawDebugPadding, drawDebugQueryGeometry} from '../src/render/draw_debug';
26+
import drawCollisionDebug from '../src/render/draw_collision_debug';
27+
28+
// Named `DebugModule` (not `Debug`) so call sites aren't stripped out by the
29+
// Rollup config for prod builds.
30+
export const DebugModule = {
31+
loaded: true,
32+
validateStyle,
33+
validateSource,
34+
validateLight,
35+
validateLights,
36+
validateTerrain,
37+
validateFog,
38+
validateSnow,
39+
validateRain,
40+
validateLayer,
41+
validateFilter,
42+
validatePaintProperty,
43+
validateLayoutProperty,
44+
validateModel,
45+
diffStyles,
46+
drawDebug,
47+
drawDebugPadding,
48+
drawDebugQueryGeometry,
49+
drawCollisionDebug,
50+
};

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@
230230
"dist/esm/*.js"
231231
]
232232
},
233+
{
234+
"limit": "50 kb",
235+
"name": "Debug Module (brotli)",
236+
"path": "dist/esm/debug.js"
237+
},
233238
{
234239
"gzip": true,
235240
"limit": "600 kb",

rollup.config.esm.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ function esmConfig(dir: string, workerSuffix: string, emitVisualizer = false): R
2828
if (chunk.facadeModuleId.endsWith('hd_worker_imports.ts')) return 'hd.worker.js';
2929
if (chunk.facadeModuleId.endsWith('standard_main_imports.ts')) return 'standard.shared.js';
3030
if (chunk.facadeModuleId.endsWith('standard_worker_imports.ts')) return 'standard.worker.js';
31+
if (chunk.facadeModuleId.endsWith('debug_imports.ts')) return 'debug.js';
3132
}
3233
// Identify each code-split chunk by a foundational module/file rather than by
3334
// chunk.name, which is derived from rollup's representative-module selection
@@ -92,7 +93,7 @@ export default (): RollupOptions[] => {
9293
];
9394
};
9495

95-
const filesToSub = new Set(['hd_main', 'hd_worker', 'standard_main', 'standard_worker']);
96+
const filesToSub = new Set(['hd_main', 'hd_worker', 'standard_main', 'standard_worker', 'debug']);
9697

9798
function esmSubstitutions(workerSuffix: string): Plugin {
9899
return {

src/render/draw_symbol.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Point from '@mapbox/point-geometry';
2-
import drawCollisionDebug from './draw_collision_debug';
2+
import {DebugModule} from '../../modules/debug';
33
import SegmentVector from '../data/segment';
44
import * as symbolProjection from '../symbol/projection';
55
import {mat4, vec3, vec4} from 'gl-matrix';
@@ -111,12 +111,12 @@ function drawSymbols(painter: Painter, sourceCache: SourceCache, layer: SymbolSt
111111
}
112112
}
113113

114-
if (sourceCache.map.showCollisionBoxes) {
114+
if (sourceCache.map.showCollisionBoxes && DebugModule.drawCollisionDebug) {
115115

116-
drawCollisionDebug(painter, sourceCache, layer, coords, layer.paint.get('text-translate'),
116+
DebugModule.drawCollisionDebug(painter, sourceCache, layer, coords, layer.paint.get('text-translate'),
117117
layer.paint.get('text-translate-anchor'), true);
118118

119-
drawCollisionDebug(painter, sourceCache, layer, coords, layer.paint.get('icon-translate'),
119+
DebugModule.drawCollisionDebug(painter, sourceCache, layer, coords, layer.paint.get('icon-translate'),
120120
layer.paint.get('icon-translate-anchor'), false);
121121
}
122122
}

src/render/painter.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ import {HD, prepareHD} from '../../modules/hd_main';
3232
import hillshade from './draw_hillshade';
3333
import raster, {prepare as prepareRaster} from './draw_raster';
3434
import background from './draw_background';
35-
import {default as drawDebug, drawDebugPadding, drawDebugQueryGeometry} from './draw_debug';
35+
import {DebugModule} from '../../modules/debug';
3636
import custom from './draw_custom';
3737
import sky from './draw_sky';
3838
import Atmosphere from './draw_atmosphere';
@@ -1531,16 +1531,16 @@ class Painter {
15311531
}
15321532
});
15331533
if (selectedSource) {
1534-
if (this.options.showTileBoundaries) {
1534+
if (this.options.showTileBoundaries && DebugModule.drawDebug) {
15351535
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1536-
drawDebug(this, selectedSource, selectedSource.getVisibleCoordinates(), Color.red, false, this.options.showParseStatus);
1536+
DebugModule.drawDebug(this, selectedSource, selectedSource.getVisibleCoordinates(), Color.red, false, this.options.showParseStatus);
15371537
}
15381538

15391539
Debug.run(() => {
15401540
if (!selectedSource) return;
1541-
if (this.options.showQueryGeometry) {
1541+
if (this.options.showQueryGeometry && DebugModule.drawDebugQueryGeometry) {
15421542
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
1543-
drawDebugQueryGeometry(this, selectedSource, selectedSource.getVisibleCoordinates());
1543+
DebugModule.drawDebugQueryGeometry(this, selectedSource, selectedSource.getVisibleCoordinates());
15441544
}
15451545
if (this.options.showTileAABBs) {
15461546
// eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
@@ -1551,13 +1551,13 @@ class Painter {
15511551
}
15521552

15531553
Debug.run(() => {
1554-
if (this.terrain && this._debugParams.showTerrainProxyTiles) {
1555-
drawDebug(this, this.terrain.proxySourceCache, this.terrain.proxyCoords, new Color(1.0, 0.8, 0.1, 1.0), true, this.options.showParseStatus);
1554+
if (this.terrain && this._debugParams.showTerrainProxyTiles && DebugModule.drawDebug) {
1555+
DebugModule.drawDebug(this, this.terrain.proxySourceCache, this.terrain.proxyCoords, new Color(1.0, 0.8, 0.1, 1.0), true, this.options.showParseStatus);
15561556
}
15571557
});
15581558

1559-
if (this.options.showPadding) {
1560-
drawDebugPadding(this);
1559+
if (this.options.showPadding && DebugModule.drawDebugPadding) {
1560+
DebugModule.drawDebugPadding(this);
15611561
}
15621562

15631563
// Set defaults for most GL values so that anyone using the state after the render

src/style/style.ts

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Terrain, {DrapeRenderMode} from './terrain';
1212
import Fog from './fog';
1313
import Snow from './snow';
1414
import Rain from './rain';
15-
import {pick, clone, deepEqual, filterObject, cartesianPositionToSpherical, warnOnce} from '../util/util';
15+
import {clone, deepEqual, filterObject, cartesianPositionToSpherical, warnOnce} from '../util/util';
1616
import {getJSON, getReferrer, ResourceType} from '../util/ajax';
1717
import {isMapboxURL} from '../util/mapbox_url';
1818
import {stripQueryParameters} from '../util/url';
@@ -26,6 +26,7 @@ import {createExpression} from '../style-spec/expression/index';
2626
import {HD, prepareHD as prepareHDMain} from '../../modules/hd_main';
2727
import {prepareStandard as prepareStandardMain} from '../../modules/standard_main';
2828
import {HD_ROAD_COVERAGE_SOURCE_LAYER} from '../source/frc_coverage_snapshot';
29+
import {DebugModule, prepareDebug} from '../../modules/debug';
2930
import {
3031
validateStyle,
3132
validateLayoutProperty,
@@ -52,7 +53,6 @@ import styleSpec from '../style-spec/reference/latest';
5253
import {getGlobalWorkerPool as getWorkerPool} from '../util/worker_pool_factory';
5354
import deref from '../style-spec/deref';
5455
import emptyStyle from '../style-spec/empty';
55-
import diffStyles, {operations as diffOperations} from '../style-spec/diff';
5656
import {
5757
registerForPluginStateChange,
5858
evented as rtlTextPluginEvented,
@@ -177,7 +177,9 @@ const createConfigExpression = (option: OptionSpecification, value: unknown) =>
177177
return createExpression(value, propertySpec);
178178
};
179179

180-
const supportedDiffOperations = pick(diffOperations, [
180+
// Operations the diff algorithm may emit that we handle incrementally without a full restyle.
181+
// Maintained as a plain Set of string constants — see src/style-spec/diff.ts.
182+
const supportedDiffOperations: ReadonlySet<string> = new Set([
181183
'addLayer',
182184
'removeLayer',
183185
'setLights',
@@ -207,7 +209,7 @@ const supportedDiffOperations = pick(diffOperations, [
207209
// 'setSprite',
208210
]);
209211

210-
const ignoredDiffOperations = pick(diffOperations, [
212+
const ignoredDiffOperations: ReadonlySet<string> = new Set([
211213
'setCenter',
212214
'setZoom',
213215
'setBearing',
@@ -645,6 +647,11 @@ class Style extends Evented<MapEvents> {
645647
_diffStyle(style: StyleSpecification | string, onStarted: (err: Error | null, isUpdateNeeded: boolean) => void, onFinished?: () => void) {
646648
this.globalId = this._getGlobalId(style);
647649

650+
// Fetch dev chunk, for string/URL path the fetch parallelises with the JSON request;
651+
// for object path, validation will run synchronously and no-op for the first call
652+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
653+
prepareDebug();
654+
648655
const handleStyle = (json: StyleSpecification, callback: (err: Error | null, isUpdateNeeded: boolean) => void) => {
649656
try {
650657
callback(null, this.setState(json, onFinished));
@@ -684,12 +691,18 @@ class Style extends Evented<MapEvents> {
684691
const validate = typeof options.validate === 'boolean' ?
685692
options.validate : !isMapboxURL(url);
686693

694+
// Preload the dev-chunk fetch with the style JSON
695+
// correctness is enforced via await in `_load`
696+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
697+
if (validate) prepareDebug();
698+
687699
this.globalId = this._getGlobalId(url);
688700
url = this.map._requestManager.normalizeStyleURL(url, options.accessToken);
689701
this.resolvedImports.add(url);
690702

691703
const cachedImport = this.importsCache.get(url);
692-
if (cachedImport) return this._load(cachedImport, validate);
704+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
705+
if (cachedImport) { this._load(cachedImport, validate); return; }
693706

694707
const controller = new AbortController();
695708
this._request = {cancel: () => controller.abort()};
@@ -698,6 +711,7 @@ class Style extends Evented<MapEvents> {
698711
const {data: json} = await getJSON<StyleSpecification>(request, controller.signal);
699712
this._request = null;
700713
this.importsCache.set(url, json);
714+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
701715
this._load(json, validate);
702716
};
703717
load().catch((err: Error) => {
@@ -709,15 +723,23 @@ class Style extends Evented<MapEvents> {
709723
loadJSON(json: StyleSpecification, options: StyleSetterOptions = {}): void {
710724
this.fire(new Event('dataloading', {dataType: 'style'}));
711725

726+
const validate = options.validate !== false;
727+
// Preload the dev-chunk fetch with the browser.frame()
728+
// correctness is enforced via await in `_load`
729+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
730+
if (validate) prepareDebug();
731+
712732
this.globalId = this._getGlobalId(json);
713733
this._request = browser.frame(() => {
714734
this._request = null;
715-
this._load(json, options.validate !== false);
735+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
736+
this._load(json, validate);
716737
});
717738
}
718739

719740
loadEmpty() {
720741
this.fire(new Event('dataloading', {dataType: 'style'}));
742+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
721743
this._load(empty, false);
722744
}
723745

@@ -764,7 +786,12 @@ class Style extends Evented<MapEvents> {
764786
// unnecessary animation frame delay when the data is already available.
765787
style.fire(new Event('dataloading', {dataType: 'style'}));
766788
style.globalId = style._getGlobalId(json);
767-
queueMicrotask(() => style._load(json, validate));
789+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
790+
if (validate) prepareDebug();
791+
queueMicrotask(() => {
792+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
793+
style._load(json, validate);
794+
});
768795
} else {
769796
style.loadJSON(json, {validate});
770797
}
@@ -896,7 +923,7 @@ class Style extends Evented<MapEvents> {
896923
return this.isRootStyle() && (json.fragment || (!!json.schema && json.fragment !== false));
897924
}
898925

899-
_load(json: StyleSpecification, validate: boolean) {
926+
async _load(json: StyleSpecification, validate: boolean) {
900927
// This style was loaded as a root style, but it is marked as a fragment and/or has a schema. We instead load
901928
// it as an import with the well-known ID "basemap" to make sure that we don't expose the internals.
902929
if (this._isInternalStyle(json)) {
@@ -907,14 +934,23 @@ class Style extends Evented<MapEvents> {
907934
...(json.zoom ? {zoom: json.zoom} : {}),
908935
...(json.light ? {light: json.light} : {})}) as StyleSpecification;
909936
this._importedAsBasemap = true;
937+
// eslint-disable-next-line @typescript-eslint/no-floating-promises
910938
this._load(style, validate);
911939
return;
912940
}
913941

914942
this.updateConfig(this._config, json.schema);
915943

916-
if (validate && emitValidationErrors(this, validateStyle(json))) {
917-
return;
944+
// In ESM builds, the dev chunk (validators) is dynamically imported.
945+
// Await it before validating top-level style JSON so the call below
946+
// is meaningful — `validateStyle` no-ops when `Debug` isn't loaded yet.
947+
if (validate) {
948+
await prepareDebug();
949+
// Check the map wasn't removed while awaiting the dev chunk.
950+
if (!this.dispatcher.actors.length) return;
951+
if (emitValidationErrors(this, validateStyle(json))) {
952+
return;
953+
}
918954
}
919955

920956
this._loaded = true;
@@ -2233,14 +2269,19 @@ class Style extends Evented<MapEvents> {
22332269
nextState = clone(nextState);
22342270
nextState.layers = deref(nextState.layers);
22352271

2236-
const changes = diffStyles(this.serialize(), nextState)
2237-
.filter(op => !(op.command in ignoredDiffOperations));
2272+
// `DebugModule.diffStyles` should be preloaded by `_diffStyle` before this runs
2273+
// otherwise throw so `_diffStyle`'s try/catch falls back to full restyle
2274+
if (!DebugModule.diffStyles) {
2275+
throw new Error('Debug module not loaded; cannot diff style.');
2276+
}
2277+
const changes = DebugModule.diffStyles(this.serialize(), nextState)
2278+
.filter(op => !ignoredDiffOperations.has(op.command));
22382279

22392280
if (changes.length === 0) {
22402281
return false;
22412282
}
22422283

2243-
const unimplementedOps = changes.filter(op => !(op.command in supportedDiffOperations));
2284+
const unimplementedOps = changes.filter(op => !supportedDiffOperations.has(op.command));
22442285
if (unimplementedOps.length > 0) {
22452286
throw new Error(`Unimplemented: ${unimplementedOps.map(op => op.command).join(', ')}.`);
22462287
}

0 commit comments

Comments
 (0)