Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | 3x 3x 3x 3x 7x 7x 3x 3x 3x 3x 3x 3x 3x | type Mergeable = Record<string, unknown>;
/**
* Build an object that contains `value` at `path`, where `path` is a
* period-separated string.
*/
function buildObjectFromPath(path: string, value: unknown): Mergeable {
const segments = path.split(".");
const obj = {} as Mergeable;
let curr = obj;
for (const [i, s] of segments.entries()) {
curr[s] = i < segments.length - 1 ? {} : value;
curr = curr[s] as Mergeable;
}
return obj;
}
function deepMerge(target: Mergeable, source: Mergeable, override: boolean) {
for (const key of Object.keys(source)) {
Iif (typeof target[key] === "object" && typeof source[key] === "object") {
target[key] = deepMerge(
target[key] as Mergeable,
source[key] as Mergeable,
override
);
} else if (typeof target[key] === "undefined") {
target[key] = source[key];
E} else if (override) {
console.warn(
`Overwriting "${key}" of type "${typeof target[key]} with value of type ${typeof source[key]}"`
);
target[key] = source[key];
} else {
throw new Error(`"${key}" is already defined, but "override" is false`);
}
}
return target;
}
/**
* @param {string} path A period-separated string representing the path for the
* global variable.
* @param {*} value The value to expose globally.
* @param {boolean} [override=false] If true, forces the value to be assigned,
* even if it conflicts with an existing non-object variable at the specified
* path. Defaults to false.
* @returns {void}
* @throws {Error} Throws an error if `override` is false and a non-object
* value already exists at a nested path.
* @description
* This function exposes `value` into the global scope under the name given by
* `path`.
* The `path` parameter can be a period-separated string (e.g., 'myApp.data'),
* which will nest the `value` within a series of objects.
*
* If you call this function multiple times with overlapping paths, it will
* merge the values if both the new `value` and the existing variable at that
* path are objects.
* For example, if 'myApp.data' already exists as an object, calling
* `declareGlobal('myApp.config', { theme: 'dark' })` will add a `config`
* property to the `globalThis.myApp` object.
*
* An error will be thrown if a conflict occurs, such as trying to merge an
* object with an existing non-object value at the same path, unless the
* `override` parameter is set to true.
*
* @example
* // Declaring a simple global variable
* declareGlobal('myGlobalVar', 123);
* console.log(globalThis.myGlobalVar); // 123
*
* @example
* // Declaring a nested global object
* declareGlobal('myApp.user', { name: 'John Doe' });
* console.log(globalThis.myApp.user.name); // John Doe
*
* @example
* // Merging with an existing object
* declareGlobal('myApp-settings', { theme: 'light' });
* declareGlobal('myApp.settings', { language: 'en' });
* console.log(globalThis.myApp.settings); // { theme: 'light', language: 'en' }
*
* @example
* // Using override to force assignment and prevent an error
* declareGlobal('config', { api: 'v1' });
* declareGlobal('config', 'api/v2', true); // Overwrites the 'config' object
* console.log(globalThis.config); // 'api/v2'
*/
export default function declareGlobal(
path: string,
value: unknown,
override: boolean = false
): void {
deepMerge(globalThis, buildObjectFromPath(path, value), override);
}
// TODO: this approach is due to how we handled bundles in Webpack, using
// expose-loader to declare exports as an object in the global scope. A better
// approach would be to explicitly import the modules that use the function
// above inside the <script> tags that make use their exports (making sure to
// use modulepreload hints to not delay loading)
|