From 21bdb2b23509675ac58da461d56aaf06b797705f Mon Sep 17 00:00:00 2001 From: Michael Cousins Date: Fri, 21 Jun 2024 10:25:25 -0400 Subject: [PATCH] Fix memory leak and potential props clash --- .changeset/pink-kiwis-flash.md | 5 +++++ package.json | 1 + src/lib/Container.svelte | 5 ++++- src/lib/pure.js | 29 +++++++++++++++++++---------- 4 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 .changeset/pink-kiwis-flash.md diff --git a/.changeset/pink-kiwis-flash.md b/.changeset/pink-kiwis-flash.md new file mode 100644 index 0000000..5b08dde --- /dev/null +++ b/.changeset/pink-kiwis-flash.md @@ -0,0 +1,5 @@ +--- +'@threlte/test': patch +--- + +Fix memory leak and potential props clash diff --git a/package.json b/package.json index 4896082..f387d4c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "license": "MIT", "version": "0.2.3", "scripts": { + "all": "npm run check && npm run lint && npm run test && npm run build", "dev": "vite dev", "build": "vite build && npm run package", "preview": "vite preview", diff --git a/src/lib/Container.svelte b/src/lib/Container.svelte index 2906161..a739602 100644 --- a/src/lib/Container.svelte +++ b/src/lib/Container.svelte @@ -14,6 +14,9 @@ /** @type {typeof import('svelte').SvelteComponent} */ export let component + /** @type {Record | undefined} */ + export let componentProps + /** @type {import('svelte').SvelteComponent | undefined} */ export let ref = undefined @@ -65,5 +68,5 @@ - + diff --git a/src/lib/pure.js b/src/lib/pure.js index e947063..cd0dae2 100644 --- a/src/lib/pure.js +++ b/src/lib/pure.js @@ -3,20 +3,18 @@ import Container from './Container.svelte' import { Core } from './core.svelte.js' /** + * Check if a value is a plain object. * * @param {unknown} maybeObj - * @returns {maybeObj is object} + * @returns {maybeObj is Record} */ const isObject = (maybeObj) => { - return typeof maybeObj === 'object' && maybeObj !== null + return typeof maybeObj === 'object' && maybeObj !== null && !Array.isArray(maybeObj) } /** @type {Set} */ const targetCache = new Set() -/** @type {Set} */ -const canvasCache = new Set() - /** @type {Set} */ const componentCache = new Set() @@ -28,9 +26,13 @@ const componentCache = new Set() /** * * @param {Record} options - * @returns {Record & { target?: HTMLElement }} + * @returns {Record & { target?: HTMLElement, props?: Record }} */ const checkProps = (options) => { + if (!isObject(options)) { + throw new TypeError('Render options must be a plain object') + } + const keys = Object.keys(options) const isProps = !keys.some((option) => { return Core.componentOptions.includes(option) @@ -43,7 +45,7 @@ const checkProps = (options) => { }) if (unrecognizedOptions.length > 0) { - throw Error(` + throw new TypeError(` Unknown options were found [${unrecognizedOptions}]. This might happen if you've mixed passing in props with Svelte options into the render function. Valid Svelte options are [${Core.componentOptions}]. You can either change the prop names, or pass in your @@ -52,6 +54,10 @@ const checkProps = (options) => { `) } + if (options.props && !isObject(options.props)) { + throw new TypeError('`props` option must be a plain object') + } + return options } @@ -95,7 +101,6 @@ export const render = (Component, componentOptions = {}, renderOptions = {}) => /** @type {HTMLCanvasElement} */ const canvas = renderOptions.canvas ?? document.createElement('canvas') - canvasCache.add(canvas) /** @type {any} */ const ComponentConstructor = 'default' in Component ? Component.default : Component @@ -103,14 +108,17 @@ export const render = (Component, componentOptions = {}, renderOptions = {}) => /** @type {any} */ const anyContainer = Container + /** @type {Record | undefined} */ + let componentProps = checkedOptions.props + const component = Core.renderComponent( anyContainer, { ...checkedOptions, props: { - ...(isObject(checkedOptions.props) ? checkedOptions.props : {}), canvas, component: ComponentConstructor, + componentProps, userSize: renderOptions.userSize, }, target, @@ -175,7 +183,8 @@ export const render = (Component, componentOptions = {}, renderOptions = {}) => * @param {Record} props */ rerender: async (props) => { - Core.updateProps(component, props) + componentProps = { ...componentProps, ...props } + Core.updateProps(component, { componentProps }) await Svelte.tick() },