UNPKG

@loaders.gl/core

Version:

The core API for working with loaders.gl loaders and writers

163 lines (162 loc) 6.79 kB
// loaders.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import { registerJSModules } from '@loaders.gl/loader-utils'; import { isPureObject, isObject } from "../../javascript-utils/is-type.js"; import { probeLog, NullLog } from "./loggers.js"; import { DEFAULT_LOADER_OPTIONS, REMOVED_LOADER_OPTIONS } from "./option-defaults.js"; /** * Helper for safely accessing global loaders.gl variables * Wraps initialization of global variable in function to defeat overly aggressive tree-shakers */ export function getGlobalLoaderState() { // @ts-ignore globalThis.loaders = globalThis.loaders || {}; // @ts-ignore const { loaders } = globalThis; // Add _state object to keep separate from modules added to globalThis.loaders if (!loaders._state) { loaders._state = {}; } return loaders._state; } /** * Store global loader options on the global object to increase chances of cross loaders-version interoperability * NOTE: This use case is not reliable but can help when testing new versions of loaders.gl with existing frameworks * @returns global loader options merged with default loader options */ export function getGlobalLoaderOptions() { const state = getGlobalLoaderState(); // Ensure all default loader options from this library are mentioned state.globalOptions = state.globalOptions || { ...DEFAULT_LOADER_OPTIONS }; return state.globalOptions; } /** * Set global loader options * @param options */ export function setGlobalOptions(options) { const state = getGlobalLoaderState(); const globalOptions = getGlobalLoaderOptions(); // @ts-expect-error First param looks incorrect state.globalOptions = normalizeOptionsInternal(globalOptions, options); // Make sure any new modules are registered registerJSModules(options.modules); } /** * Merges options with global opts and loader defaults, also injects baseUri * @param options * @param loader * @param loaders * @param url */ export function normalizeOptions(options, loader, loaders, url) { loaders = loaders || []; loaders = Array.isArray(loaders) ? loaders : [loaders]; validateOptions(options, loaders); return normalizeOptionsInternal(loader, options, url); } // VALIDATE OPTIONS /** * Warn for unsupported options * @param options * @param loaders */ function validateOptions(options, loaders) { // Check top level options validateOptionsObject(options, null, DEFAULT_LOADER_OPTIONS, REMOVED_LOADER_OPTIONS, loaders); for (const loader of loaders) { // Get the scoped, loader specific options from the user supplied options const idOptions = ((options && options[loader.id]) || {}); // Get scoped, loader specific default and deprecated options from the selected loader const loaderOptions = (loader.options && loader.options[loader.id]) || {}; const deprecatedOptions = (loader.deprecatedOptions && loader.deprecatedOptions[loader.id]) || {}; // Validate loader specific options // @ts-ignore validateOptionsObject(idOptions, loader.id, loaderOptions, deprecatedOptions, loaders); } } // eslint-disable-next-line max-params, complexity function validateOptionsObject(options, id, defaultOptions, deprecatedOptions, loaders) { const loaderName = id || 'Top level'; const prefix = id ? `${id}.` : ''; for (const key in options) { // If top level option value is an object it could options for a loader, so ignore const isSubOptions = !id && isObject(options[key]); const isBaseUriOption = key === 'baseUri' && !id; const isWorkerUrlOption = key === 'workerUrl' && id; // <loader>.workerUrl requires special handling as it is now auto-generated and no longer specified as a default option. if (!(key in defaultOptions) && !isBaseUriOption && !isWorkerUrlOption) { // Issue deprecation warnings if (key in deprecatedOptions) { probeLog.warn(`${loaderName} loader option \'${prefix}${key}\' no longer supported, use \'${deprecatedOptions[key]}\'`)(); } else if (!isSubOptions) { const suggestion = findSimilarOption(key, loaders); probeLog.warn(`${loaderName} loader option \'${prefix}${key}\' not recognized. ${suggestion}`)(); } } } } function findSimilarOption(optionKey, loaders) { const lowerCaseOptionKey = optionKey.toLowerCase(); let bestSuggestion = ''; for (const loader of loaders) { for (const key in loader.options) { if (optionKey === key) { return `Did you mean \'${loader.id}.${key}\'?`; } const lowerCaseKey = key.toLowerCase(); const isPartialMatch = lowerCaseOptionKey.startsWith(lowerCaseKey) || lowerCaseKey.startsWith(lowerCaseOptionKey); if (isPartialMatch) { bestSuggestion = bestSuggestion || `Did you mean \'${loader.id}.${key}\'?`; } } } return bestSuggestion; } function normalizeOptionsInternal(loader, options, url) { const loaderDefaultOptions = loader.options || {}; const mergedOptions = { ...loaderDefaultOptions }; addUrlOptions(mergedOptions, url); // LOGGING: options.log can be set to `null` to defeat logging if (mergedOptions.log === null) { mergedOptions.log = new NullLog(); } mergeNestedFields(mergedOptions, getGlobalLoaderOptions()); mergeNestedFields(mergedOptions, options); return mergedOptions; } // Merge nested options objects function mergeNestedFields(mergedOptions, options) { for (const key in options) { // Check for nested options // object in options => either no key in defaultOptions or object in defaultOptions if (key in options) { const value = options[key]; if (isPureObject(value) && isPureObject(mergedOptions[key])) { mergedOptions[key] = { ...mergedOptions[key], ...options[key] }; } else { mergedOptions[key] = options[key]; } } // else: No need to merge nested opts, and the initial merge already copied over the nested options } } /** * Harvest information from the url * @deprecated This is mainly there to support a hack in the GLTFLoader * TODO - baseUri should be a directory, i.e. remove file component from baseUri * TODO - extract extension? * TODO - extract query parameters? * TODO - should these be injected on context instead of options? */ function addUrlOptions(options, url) { if (url && !('baseUri' in options)) { options.baseUri = url; } }