@@ -12,7 +12,7 @@ import Terrain, {DrapeRenderMode} from './terrain';
1212import Fog from './fog' ;
1313import Snow from './snow' ;
1414import Rain from './rain' ;
15- import { pick , clone , deepEqual , filterObject , cartesianPositionToSpherical , warnOnce } from '../util/util' ;
15+ import { clone , deepEqual , filterObject , cartesianPositionToSpherical , warnOnce } from '../util/util' ;
1616import { getJSON , getReferrer , ResourceType } from '../util/ajax' ;
1717import { isMapboxURL } from '../util/mapbox_url' ;
1818import { stripQueryParameters } from '../util/url' ;
@@ -26,6 +26,7 @@ import {createExpression} from '../style-spec/expression/index';
2626import { HD , prepareHD as prepareHDMain } from '../../modules/hd_main' ;
2727import { prepareStandard as prepareStandardMain } from '../../modules/standard_main' ;
2828import { HD_ROAD_COVERAGE_SOURCE_LAYER } from '../source/frc_coverage_snapshot' ;
29+ import { DebugModule , prepareDebug } from '../../modules/debug' ;
2930import {
3031 validateStyle ,
3132 validateLayoutProperty ,
@@ -52,7 +53,6 @@ import styleSpec from '../style-spec/reference/latest';
5253import { getGlobalWorkerPool as getWorkerPool } from '../util/worker_pool_factory' ;
5354import deref from '../style-spec/deref' ;
5455import emptyStyle from '../style-spec/empty' ;
55- import diffStyles , { operations as diffOperations } from '../style-spec/diff' ;
5656import {
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