diff --git a/changelog.md b/changelog.md index a8d0b56..199d5d5 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - update default retroarch-emscripten-build version to v1.17.0 - disable some default inputs in RetroArch +- when some of the passed config items are objects, like `retroarchConfig`, they will be merged with the default items instead of overwriting the default items entirely ### Fixed - fix export name when using ESM build of retroarch diff --git a/src/nostalgist.ts b/src/nostalgist.ts index dd12379..60116f9 100644 --- a/src/nostalgist.ts +++ b/src/nostalgist.ts @@ -10,6 +10,7 @@ import type { NostalgistOptionsPartial, NostalgistResolveFileFunction, } from './types/nostalgist-options' +import { merge } from './utils' import { vendors } from './vendors' function baseName(url: string) { @@ -29,10 +30,9 @@ export class Nostalgist { private emulator: Emulator | undefined private constructor(options: NostalgistLaunchOptions) { - const mergedOptions = { - ...Nostalgist.globalOptions, - ...options, - } + const mergedOptions = {} + merge(mergedOptions, Nostalgist.globalOptions, options) + // @ts-expect-error we cannot infer the final type here this.options = mergedOptions } @@ -59,10 +59,7 @@ export class Nostalgist { * ``` */ static configure(options: NostalgistOptionsPartial) { - Nostalgist.globalOptions = { - ...Nostalgist.globalOptions, - ...options, - } + merge(Nostalgist.globalOptions, options) } /** @@ -477,10 +474,8 @@ export class Nostalgist { } if (element) { - return { - ...defaultAppearanceStyle, - ...style, - } + merge(defaultAppearanceStyle, style) + return defaultAppearanceStyle } const defaultLayoutStyle: Partial = { @@ -491,11 +486,8 @@ export class Nostalgist { height: '100%', zIndex: '1', } - return { - ...defaultLayoutStyle, - ...defaultAppearanceStyle, - ...style, - } + merge(defaultLayoutStyle, defaultAppearanceStyle, style) + return defaultLayoutStyle } private async getCoreOption() { @@ -610,17 +602,15 @@ export class Nostalgist { } private getRetroarchOption() { - return { - ...Nostalgist.globalOptions.retroarchConfig, - ...this.options.retroarchConfig, - } + const options = {} + merge(options, Nostalgist.globalOptions.retroarchConfig, this.options.retroarchConfig) + return options as typeof this.options.retroarchConfig } private getRetroarchCoreOption() { - return { - ...Nostalgist.globalOptions.retroarchCoreConfig, - ...this.options.retroarchCoreConfig, - } + const options = {} + merge(options, Nostalgist.globalOptions.retroarchCoreConfig, this.options.retroarchCoreConfig) + return options } private loadEmulator() { diff --git a/src/utils.ts b/src/utils.ts index ebe4a5e..a5c520e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -98,3 +98,39 @@ export async function importCoreJsAsESM({ name, js }: { name: string; js: string URL.revokeObjectURL(jsBlobUrl) } } + +function isNil(obj: unknown) { + return obj === undefined || obj === null +} + +function isPrimitive(obj: unknown) { + if (isNil(obj)) { + return true + } + const type = typeof obj + const primitevTypes = ['string', 'number', 'boolean', 'bigint', 'symbol', 'function'] + return primitevTypes.includes(type) +} + +function mergeSourceToTarget(target: any, source: any) { + for (const key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + const targetValue = target[key] + const sourceValue = source[key] + if (isPrimitive(sourceValue)) { + target[key] = sourceValue + } else if (Array.isArray(targetValue) && Array.isArray(sourceValue)) { + target[key] = [...targetValue, ...sourceValue] + } else { + target[key] = isPrimitive(targetValue) ? {} : target[key] + merge(target[key], sourceValue) + } + } + } +} + +export function merge(target: any, ...sources: any[]) { + for (const source of sources) { + mergeSourceToTarget(target, source) + } +} diff --git a/tests/integration/nostalgist.spec.ts b/tests/integration/nostalgist.spec.ts index 100878d..4ac5f9f 100644 --- a/tests/integration/nostalgist.spec.ts +++ b/tests/integration/nostalgist.spec.ts @@ -203,4 +203,29 @@ describe('nostalgist', () => { expect(file.fileContent.size).toBeGreaterThan(0) } }) + + test.only('Nostalgist.launch with nested options', async () => { + Nostalgist.configure({ + retroarchConfig: { + input_audio_mute: 'a', + input_menu_toggle: 'nul', + }, + }) + + const nostalgist = await Nostalgist.launch({ + core: 'fceumm', + rom: 'flappybird.nes', + retroarchConfig: { + input_max_users: 4, + input_audio_mute: 'b', + }, + }) + + const options = nostalgist.getOptions() + expect(options.retroarchConfig).toMatchObject({ + menu_driver: 'rgui', + input_menu_toggle: 'nul', + input_audio_mute: 'b', + }) + }) })