From e1bbd8c573c61055a563f6533e4f7d120b148485 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ilyas=20T=C3=BCrkben?= Date: Tue, 19 Nov 2024 22:07:14 +0100 Subject: [PATCH] fix in observer disposer logic --- app/App.js | 4 ++-- dist/picosm.js | 23 +++++++++++---------- dist/picosm.js.map | 4 ++-- package.json | 2 +- src/LitObserver.js | 21 ++++++++++---------- test/LitObserver.test.js | 43 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 71 insertions(+), 26 deletions(-) create mode 100644 test/LitObserver.test.js diff --git a/app/App.js b/app/App.js index 9e6a1dd..ff635a4 100644 --- a/app/App.js +++ b/app/App.js @@ -1,6 +1,6 @@ import { LitElement, html, css } from 'https://jspm.dev/npm:lit@2'; import { StoreObservable } from './model.js'; -import { makeObserver, reaction } from '../dist/picosm.js'; +import { litObserver, reaction } from '../dist/picosm.js'; /** * also see ../index.html @@ -178,4 +178,4 @@ export class App extends LitElement { } } -customElements.define('pico-demo', makeObserver(App, ['store'])); +customElements.define('pico-demo', litObserver(App, ['store'])); diff --git a/dist/picosm.js b/dist/picosm.js index 375f038..71f05ae 100644 --- a/dist/picosm.js +++ b/dist/picosm.js @@ -119,23 +119,23 @@ function untrack(target, source) { } // src/LitObserver.js -function makeObserver(constructor, properties) { +function litObserver(constructor, properties) { return class LitObserver extends constructor { + #observables = /* @__PURE__ */ new Set(); + #disposers = /* @__PURE__ */ new Set(); constructor(...args) { super(...args); - this.observables = /* @__PURE__ */ new Set(); - this.disposers = /* @__PURE__ */ new Set(); } trackProperties() { properties.forEach((property) => { const observable = this[property]; if (!observable?.__observers) return; - if (this.observables.has(observable)) { + if (this.#observables.has(observable)) { return; } - this.observables.add(observable); - observe(observable, this.requestUpdate.bind(this)); + this.#observables.add(observable); + this.#disposers.add(observe(observable, this.requestUpdate.bind(this))); }); } update(changedProperties) { @@ -144,22 +144,23 @@ function makeObserver(constructor, properties) { } connectedCallback() { super.connectedCallback(); - this.observables.forEach((o) => { - observe(o, this.requestUpdate.bind(this)); + this.#observables.forEach((o) => { + this.#disposers.add(observe(o, this.requestUpdate.bind(this))); }); } disconnectedCallback() { super.disconnectedCallback(); - this.disposers.forEach((disposer) => { + this.#disposers.forEach((disposer) => { disposer(); }); - this.disposers.clear(); + this.#disposers.clear(); + console.log(this.#disposers.size); } }; } export { + litObserver, makeObservable, - makeObserver, observe, reaction, track, diff --git a/dist/picosm.js.map b/dist/picosm.js.map index e4802f9..d521389 100644 --- a/dist/picosm.js.map +++ b/dist/picosm.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../src/makeObservable.js", "../src/reaction.js", "../src/track.js", "../src/LitObserver.js"], - "sourcesContent": ["// The Adobe confidential header is retained as it was provided.\n/*******************************************************************************\n *\n * ADOBE CONFIDENTIAL\n * __________________\n *\n * Copyright 2023 Adobe Systems Incorporated\n * All Rights Reserved.\n *\n * NOTICE: All information contained herein is, and remains\n * the property of Adobe Systems Incorporated and its suppliers,\n * if any. The intellectual and technical concepts contained\n * herein are proprietary to Adobe Systems Incorporated and its\n * suppliers and are protected by trade secret or copyright law.\n * Dissemination of this information or reproduction of this material\n * is strictly forbidden unless prior written permission is obtained\n * from Adobe Systems Incorporated.\n ******************************************************************************/\n\nfunction instrumentAction(target, methodName) {\n const prototype = Object.getPrototypeOf(target).prototype;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);\n\n if (descriptor && typeof descriptor.value === \"function\") {\n const originalMethod = descriptor.value;\n if (originalMethod.constructor.name === \"AsyncFunction\") {\n descriptor.value = async function(...args) {\n const response = await originalMethod.call(this, ...args);\n this.__resetComputedProperties();\n this.__notifyListeners();\n return response;\n };\n } else {\n descriptor.value = function(...args) {\n const response = originalMethod.call(this, ...args);\n this.__resetComputedProperties();\n this.__notifyListeners();\n return response;\n };\n }\n\n Object.defineProperty(prototype, methodName, descriptor);\n }\n}\n\nfunction instrumentComputed(target, getterName) {\n const prototype = Object.getPrototypeOf(target).prototype;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, getterName);\n\n if (descriptor && typeof descriptor.get === \"function\") {\n const originalGetter = descriptor.get;\n\n descriptor.get = function() {\n if (this.__computedValues.has(getterName)) {\n console.log(\"returning cached value\", getterName);\n return this.__computedValues.get(getterName);\n }\n const cachedValue = originalGetter.call(this);\n this.__computedValues.set(getterName, cachedValue);\n console.log(\"returning computed value\", getterName);\n return cachedValue;\n };\n\n Object.defineProperty(prototype, getterName, descriptor);\n }\n}\n\nexport function makeObservable(constructor, actions = [], computeds = []) {\n class SuperClass extends constructor {\n constructor(...args) {\n super(...args);\n this.__observers = new Set();\n this.__computedValues = new Map();\n this.__dependencies = new WeakMap();\n }\n\n __notifyListeners() {\n this.__observers.forEach(listener => {\n listener();\n });\n }\n\n __resetComputedProperties() {\n this.__computedValues.clear();\n }\n\n __observe(callback) {\n this.__observers.add(callback);\n return () => {\n this.__observers.delete(callback);\n };\n }\n }\n\n actions.forEach(methodName => {\n instrumentAction(SuperClass, methodName);\n });\n computeds.forEach(propertyName => {\n instrumentComputed(SuperClass, propertyName);\n });\n return SuperClass;\n}\n\nexport function observe(target, callback) {\n return target.__observe(callback);\n}\n", "/*******************************************************************************\n *\n * ADOBE CONFIDENTIAL\n * __________________\n *\n * Copyright 2023 Adobe Systems Incorporated\n * All Rights Reserved.\n *\n * NOTICE: All information contained herein is, and remains\n * the property of Adobe Systems Incorporated and its suppliers,\n * if any. The intellectual and technical concepts contained\n * herein are proprietary to Adobe Systems Incorporated and its\n * suppliers and are protected by trade secret or copyright law.\n * Dissemination of this information or reproduction of this material\n * is strictly forbidden unless prior written permission is obtained\n * from Adobe Systems Incorporated.\n ******************************************************************************/\n\nexport function reaction(target, callback, execute) {\n let lastProps = []\n return target.__observe(async () => {\n const props = callback(target)\n if (lastProps === props) return\n let shouldExecute = false\n for (let i = 0; i < props.length; i++) {\n if (lastProps[i] !== props[i]) {\n shouldExecute = true\n break\n }\n }\n if (shouldExecute) {\n lastProps = props;\n execute(...props)\n }\n })\n}\n\n", "export function track(target, source) {\n if (!target.__observers || !source?.__observers) return;\n const disposer = source.__observe(() => {\n target.__resetComputedProperties();\n target.__notifyListeners();\n });\n target.__dependencies.set(source, disposer);\n target.__resetComputedProperties();\n target.__notifyListeners();\n}\n\nexport function untrack(target, source) {\n if (!target.__observers || !source?.__observers) return;\n const disposer = target.__dependencies.get(source);\n if (disposer) {\n target.__dependencies.delete(source);\n disposer();\n target.__resetComputedProperties();\n target.__notifyListeners();\n }\n}\n", "/*******************************************************************************\n *\n * ADOBE CONFIDENTIAL\n * __________________\n *\n * Copyright 2023 Adobe Systems Incorporated\n * All Rights Reserved.\n *\n * NOTICE: All information contained herein is, and remains\n * the property of Adobe Systems Incorporated and its suppliers,\n * if any. The intellectual and technical concepts contained\n * herein are proprietary to Adobe Systems Incorporated and its\n * suppliers and are protected by trade secret or copyright law.\n * Dissemination of this information or reproduction of this material\n * is strictly forbidden unless prior written permission is obtained\n * from Adobe Systems Incorporated.\n ******************************************************************************/\n\nimport { observe } from './makeObservable';\n\nexport function makeObserver(constructor, properties) {\n return class LitObserver extends constructor {\n constructor(...args) {\n super(...args);\n this.observables = new Set();\n this.disposers = new Set();\n }\n\n trackProperties() {\n properties.forEach(property => {\n const observable = this[property];\n if (!observable?.__observers) return;\n if (this.observables.has(observable)) {\n return;\n }\n this.observables.add(observable);\n observe(observable, this.requestUpdate.bind(this));\n });\n }\n\n update(changedProperties) {\n super.update(changedProperties);\n this.trackProperties();\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.observables.forEach(o => {\n observe(o, this.requestUpdate.bind(this));\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.disposers.forEach(disposer => {\n disposer();\n });\n this.disposers.clear();\n }\n };\n}\n"], + "sourcesContent": ["// The Adobe confidential header is retained as it was provided.\n/*******************************************************************************\n *\n * ADOBE CONFIDENTIAL\n * __________________\n *\n * Copyright 2023 Adobe Systems Incorporated\n * All Rights Reserved.\n *\n * NOTICE: All information contained herein is, and remains\n * the property of Adobe Systems Incorporated and its suppliers,\n * if any. The intellectual and technical concepts contained\n * herein are proprietary to Adobe Systems Incorporated and its\n * suppliers and are protected by trade secret or copyright law.\n * Dissemination of this information or reproduction of this material\n * is strictly forbidden unless prior written permission is obtained\n * from Adobe Systems Incorporated.\n ******************************************************************************/\n\nfunction instrumentAction(target, methodName) {\n const prototype = Object.getPrototypeOf(target).prototype;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, methodName);\n\n if (descriptor && typeof descriptor.value === \"function\") {\n const originalMethod = descriptor.value;\n if (originalMethod.constructor.name === \"AsyncFunction\") {\n descriptor.value = async function(...args) {\n const response = await originalMethod.call(this, ...args);\n this.__resetComputedProperties();\n this.__notifyListeners();\n return response;\n };\n } else {\n descriptor.value = function(...args) {\n const response = originalMethod.call(this, ...args);\n this.__resetComputedProperties();\n this.__notifyListeners();\n return response;\n };\n }\n\n Object.defineProperty(prototype, methodName, descriptor);\n }\n}\n\nfunction instrumentComputed(target, getterName) {\n const prototype = Object.getPrototypeOf(target).prototype;\n const descriptor = Object.getOwnPropertyDescriptor(prototype, getterName);\n\n if (descriptor && typeof descriptor.get === \"function\") {\n const originalGetter = descriptor.get;\n\n descriptor.get = function() {\n if (this.__computedValues.has(getterName)) {\n console.log(\"returning cached value\", getterName);\n return this.__computedValues.get(getterName);\n }\n const cachedValue = originalGetter.call(this);\n this.__computedValues.set(getterName, cachedValue);\n console.log(\"returning computed value\", getterName);\n return cachedValue;\n };\n\n Object.defineProperty(prototype, getterName, descriptor);\n }\n}\n\nexport function makeObservable(constructor, actions = [], computeds = []) {\n class SuperClass extends constructor {\n constructor(...args) {\n super(...args);\n this.__observers = new Set();\n this.__computedValues = new Map();\n this.__dependencies = new WeakMap();\n }\n\n __notifyListeners() {\n this.__observers.forEach(listener => {\n listener();\n });\n }\n\n __resetComputedProperties() {\n this.__computedValues.clear();\n }\n\n __observe(callback) {\n this.__observers.add(callback);\n return () => {\n this.__observers.delete(callback);\n };\n }\n }\n\n actions.forEach(methodName => {\n instrumentAction(SuperClass, methodName);\n });\n computeds.forEach(propertyName => {\n instrumentComputed(SuperClass, propertyName);\n });\n return SuperClass;\n}\n\nexport function observe(target, callback) {\n return target.__observe(callback);\n}\n", "/*******************************************************************************\n *\n * ADOBE CONFIDENTIAL\n * __________________\n *\n * Copyright 2023 Adobe Systems Incorporated\n * All Rights Reserved.\n *\n * NOTICE: All information contained herein is, and remains\n * the property of Adobe Systems Incorporated and its suppliers,\n * if any. The intellectual and technical concepts contained\n * herein are proprietary to Adobe Systems Incorporated and its\n * suppliers and are protected by trade secret or copyright law.\n * Dissemination of this information or reproduction of this material\n * is strictly forbidden unless prior written permission is obtained\n * from Adobe Systems Incorporated.\n ******************************************************************************/\n\nexport function reaction(target, callback, execute) {\n let lastProps = []\n return target.__observe(async () => {\n const props = callback(target)\n if (lastProps === props) return\n let shouldExecute = false\n for (let i = 0; i < props.length; i++) {\n if (lastProps[i] !== props[i]) {\n shouldExecute = true\n break\n }\n }\n if (shouldExecute) {\n lastProps = props;\n execute(...props)\n }\n })\n}\n\n", "export function track(target, source) {\n if (!target.__observers || !source?.__observers) return;\n const disposer = source.__observe(() => {\n target.__resetComputedProperties();\n target.__notifyListeners();\n });\n target.__dependencies.set(source, disposer);\n target.__resetComputedProperties();\n target.__notifyListeners();\n}\n\nexport function untrack(target, source) {\n if (!target.__observers || !source?.__observers) return;\n const disposer = target.__dependencies.get(source);\n if (disposer) {\n target.__dependencies.delete(source);\n disposer();\n target.__resetComputedProperties();\n target.__notifyListeners();\n }\n}\n", "/*******************************************************************************\n *\n * ADOBE CONFIDENTIAL\n * __________________\n *\n * Copyright 2023 Adobe Systems Incorporated\n * All Rights Reserved.\n *\n * NOTICE: All information contained herein is, and remains\n * the property of Adobe Systems Incorporated and its suppliers,\n * if any. The intellectual and technical concepts contained\n * herein are proprietary to Adobe Systems Incorporated and its\n * suppliers and are protected by trade secret or copyright law.\n * Dissemination of this information or reproduction of this material\n * is strictly forbidden unless prior written permission is obtained\n * from Adobe Systems Incorporated.\n ******************************************************************************/\n\nimport { observe } from './makeObservable';\n\nexport function litObserver(constructor, properties) {\n return class LitObserver extends constructor {\n constructor(...args) {\n super(...args);\n this.observables = new Set();\n this.disposers = new Set();\n }\n\n trackProperties() {\n properties.forEach(property => {\n const observable = this[property];\n if (!observable?.__observers) return;\n if (this.observables.has(observable)) {\n return;\n }\n this.observables.add(observable);\n observe(observable, this.requestUpdate.bind(this));\n });\n }\n\n update(changedProperties) {\n super.update(changedProperties);\n this.trackProperties();\n }\n\n connectedCallback() {\n super.connectedCallback();\n this.observables.forEach(o => {\n observe(o, this.requestUpdate.bind(this));\n });\n }\n\n disconnectedCallback() {\n super.disconnectedCallback();\n this.disposers.forEach(disposer => {\n disposer();\n });\n this.disposers.clear();\n }\n };\n}\n"], "mappings": "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", - "names": ["instrumentAction", "target", "methodName", "prototype", "descriptor", "originalMethod", "args", "response", "instrumentComputed", "getterName", "originalGetter", "cachedValue", "makeObservable", "constructor", "actions", "computeds", "SuperClass", "listener", "callback", "propertyName", "observe", "reaction", "target", "callback", "execute", "lastProps", "props", "shouldExecute", "track", "target", "source", "disposer", "untrack", "makeObserver", "constructor", "properties", "args", "property", "observable", "observe", "changedProperties", "o", "disposer"] + "names": ["instrumentAction", "target", "methodName", "prototype", "descriptor", "originalMethod", "args", "response", "instrumentComputed", "getterName", "originalGetter", "cachedValue", "makeObservable", "constructor", "actions", "computeds", "SuperClass", "listener", "callback", "propertyName", "observe", "reaction", "target", "callback", "execute", "lastProps", "props", "shouldExecute", "track", "target", "source", "disposer", "untrack", "litObserver", "constructor", "properties", "args", "property", "observable", "observe", "changedProperties", "o", "disposer"] } diff --git a/package.json b/package.json index c7ff5a0..e2c9b66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "picosm", - "version": "1.0.1", + "version": "1.0.2", "main": "dist/picosm.js", "scripts": { "test": "web-test-runner test/**/*.test.js --node-resolve", diff --git a/src/LitObserver.js b/src/LitObserver.js index 0dae556..e552040 100644 --- a/src/LitObserver.js +++ b/src/LitObserver.js @@ -1,22 +1,22 @@ import { observe } from './makeObservable.js'; -export function makeObserver(constructor, properties) { +export function litObserver(constructor, properties) { return class LitObserver extends constructor { + #observables = new Set(); + #disposers = new Set(); constructor(...args) { super(...args); - this.observables = new Set(); - this.disposers = new Set(); } trackProperties() { properties.forEach((property) => { const observable = this[property]; if (!observable?.__observers) return; - if (this.observables.has(observable)) { + if (this.#observables.has(observable)) { return; } - this.observables.add(observable); - observe(observable, this.requestUpdate.bind(this)); + this.#observables.add(observable); + this.#disposers.add(observe(observable, this.requestUpdate.bind(this))); }); } @@ -27,17 +27,18 @@ export function makeObserver(constructor, properties) { connectedCallback() { super.connectedCallback(); - this.observables.forEach((o) => { - observe(o, this.requestUpdate.bind(this)); + this.#observables.forEach((o) => { + this.#disposers.add(observe(o, this.requestUpdate.bind(this))); }); } disconnectedCallback() { super.disconnectedCallback(); - this.disposers.forEach((disposer) => { + this.#disposers.forEach((disposer) => { disposer(); }); - this.disposers.clear(); + this.#disposers.clear(); + console.log(this.#disposers.size); } }; } diff --git a/test/LitObserver.test.js b/test/LitObserver.test.js new file mode 100644 index 0000000..8ee9abb --- /dev/null +++ b/test/LitObserver.test.js @@ -0,0 +1,43 @@ +import { fake } from 'sinon'; +import { expect } from '@esm-bundle/chai'; +import { makeObservable, observe } from '../src/makeObservable.js'; +import TestStore from './TestStore.js'; +import { html, LitElement } from 'lit'; +import { litObserver } from '../src/LitObserver.js'; + +class User { + firstName = 'John'; + lastName = ''; + setLastName(name) { + this.lastName = name; + } + + get name() { + return `${this.firstName} ${this.lastName}`; + } +} + +const UserObservable = makeObservable(User, ['setLastName', ['name']]); + +class HelloWorld extends LitElement { + static properties = { + user: { type: Object }, + }; + render() { + return html`

Hello, ${this.user?.name ?? 'World'}!

`; + } +} +customElements.define('hello-world', litObserver(HelloWorld, ['user'])); + +describe('LitOserver', () => { + it('should dispose observer', async () => { + const helloWorld = document.createElement('hello-world'); + document.body.appendChild(helloWorld); + await helloWorld.updateComplete; + expect(helloWorld.shadowRoot.textContent).to.equal('Hello, World!'); + helloWorld.user = new UserObservable(); + helloWorld.user.setLastName('Doe'); + await helloWorld.updateComplete; + expect(helloWorld.shadowRoot.textContent).to.equal('Hello, John Doe!'); + }); +});