diff --git a/bench/bench.js b/bench/bench.js index 36c2f30f9..74c79526d 100644 --- a/bench/bench.js +++ b/bench/bench.js @@ -1,4 +1,4 @@ -const { createRoot, createSignal, createEffect, createMemo } = require('../lib/solid'); +const { createRoot, createSignal, createEffect } = require('../lib/solid'); var now = typeof process === 'undefined' ? browserNow : nodeNow; @@ -226,8 +226,8 @@ function createComputation1000(ss, offset) { } function updateComputations1to1(n, sources) { - var s1 = sources[0], - c = createMemo(function () { return s1[0](); }); + var s1 = sources[0]; + createEffect(function () { return s1[0](); }); for (var i = 0; i < n; i++) { s1[1](i); } @@ -235,8 +235,8 @@ function updateComputations1to1(n, sources) { function updateComputations2to1(n, sources) { var s1 = sources[0], - s2 = sources[1], - c = createMemo(function () { return s1[0]() + s2[0](); }); + s2 = sources[1]; + createEffect(function () { return s1[0]() + s2[0](); }); for (var i = 0; i < n; i++) { s1[1](i); } @@ -246,42 +246,42 @@ function updateComputations4to1(n, sources) { var s1 = sources[0], s2 = sources[1], s3 = sources[2], - s4 = sources[3], - c = createMemo(function () { return s1[0]() + s2[0]() + s3[0]() + s4[0](); }); + s4 = sources[3]; + createEffect(function () { return s1[0]() + s2[0]() + s3[0]() + s4[0](); }); for (var i = 0; i < n; i++) { s1[1](i); } } function updateComputations1000to1(n, sources) { - var s1 = sources[0], - c = createMemo(function () { - var sum = 0; - for (var i = 0; i < 1000; i++) { - sum += sources[i][0](); - } - return sum; - }); + var s1 = sources[0]; + createEffect(function () { + var sum = 0; + for (var i = 0; i < 1000; i++) { + sum += sources[i][0](); + } + return sum; + }); for (var i = 0; i < n; i++) { s1[1](i); } } function updateComputations1to2(n, sources) { - var s1 = sources[0], - c1 = createMemo(function () { return s1[0](); }), - c2 = createMemo(function () { return s1[0](); }); + var s1 = sources[0]; + createEffect(function () { return s1[0](); }); + createEffect(function () { return s1[0](); }); for (var i = 0; i < n / 2; i++) { s1[1](i); } } function updateComputations1to4(n, sources) { - var s1 = sources[0], - c1 = createMemo(function () { return s1[0](); }), - c2 = createMemo(function () { return s1[0](); }), - c3 = createMemo(function () { return s1[0](); }), - c4 = createMemo(function () { return s1[0](); }); + var s1 = sources[0]; + createEffect(function () { return s1[0](); }); + createEffect(function () { return s1[0](); }); + createEffect(function () { return s1[0](); }); + createEffect(function () { return s1[0](); }); for (var i = 0; i < n / 4; i++) { s1[1](i); } diff --git a/documentation/api.md b/documentation/api.md index b3ff20b9d..bda2e5ab7 100644 --- a/documentation/api.md +++ b/documentation/api.md @@ -16,9 +16,9 @@ Creates a new effect that automatically tracks dependencies. 2nd argument is the Creates a new signal that can be used for reactive tracking. By default signals always notify on setting a value. However a comparator can be passed in to indicate whether the values should be considered equal and listeners not notified. -### `createMemo(prev => , initialValue): getValueFn` +### `createMemo(prev => , initialValue, comparatorFn): getValueFn` -Creates a readonly signal that recalculates it's value whenever the executed codes dependencies update. +Creates a readonly signal that recalculates it's value whenever the executed codes dependencies update. Memos only notify dependents when returned value changes. You can also set a custom comparator. ### `createDependentEffect(() => , dependencies, defer): void` diff --git a/dom-expressions.config.js b/dom-expressions.config.js index cf4ce67c9..bfc64504e 100644 --- a/dom-expressions.config.js +++ b/dom-expressions.config.js @@ -3,7 +3,7 @@ module.exports = { includeTypes: true, variables: { imports: [ `import { - createEffect as wrap, sample, createRoot as root, + createEffect as wrap, sample, createRoot as root, createMemo as memo, onCleanup as cleanup, setContext, registerSuspense, getContextOwner as currentContext } from '../solid.js'` ], diff --git a/package-lock.json b/package-lock.json index 820c5af5a..0a93a8249 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "solid-js", - "version": "0.7.13", + "version": "0.8.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1923,9 +1923,9 @@ "dev": true }, "dom-expressions": { - "version": "0.9.10", - "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.9.10.tgz", - "integrity": "sha512-XbTszuqiaYwQgomliuvMLTebaEcTcxrUfjcUPOgBZ6sT36OEtBBKWET3+GRdY2SovIKcTtnlYv5fqzx3r+Y/2Q==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/dom-expressions/-/dom-expressions-0.10.0.tgz", + "integrity": "sha512-0Nhy7ZQuwtOUn4Vf9DTYRjph1ttIvOWt6hcEonOOpwHVxMCZ4OYVa+3pzaEnt0I+UDnarq4K9jDDB3n4NgnlyA==", "dev": true, "requires": { "ejs": "^2.6.1" @@ -3047,9 +3047,9 @@ } }, "hyper-dom-expressions": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.9.2.tgz", - "integrity": "sha512-JHmhuKyTRTnnH2KVrMFcuc54XRRQdNH27GIKHIe+NvZ0x6EsjpGlq4LBaeT6vg+/776Q9J002rifqBmJ37STIg==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/hyper-dom-expressions/-/hyper-dom-expressions-0.10.0.tgz", + "integrity": "sha512-j4mu/CDAK9Oy2UirM7am7X2FHwgf2FojlVsDPVKg9iQ3Ad9shU/VfSZgm2q3CriNW9FS6Ni0/uWV5aFmKNXOFA==", "dev": true }, "iconv-lite": { @@ -4902,9 +4902,9 @@ } }, "lit-dom-expressions": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.9.2.tgz", - "integrity": "sha512-VKu64GnFCrWTfUjYkDGQ1thejt2o7B8eN7iXf/lltSsbHoW0rMzDZCFOvsp5j5F7Gduwn74AMhUn0VdqQ1ph4g==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/lit-dom-expressions/-/lit-dom-expressions-0.10.0.tgz", + "integrity": "sha512-W4nNCmsPvx3BhomPKofH9cvRI3seAgwohyIZXYm1bfcJnpMBXw2orRmRmN1zftFY2Z7r9Hm16i6zohT/eo319A==", "dev": true }, "load-json-file": { @@ -6203,16 +6203,22 @@ } }, "rollup": { - "version": "1.14.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.14.4.tgz", - "integrity": "sha512-sR5/cqbQUg72Lm2TZgjzI3/Q1V7osP/PXlqNpSLCMSTnSg8xQ9ECFQSNEG1OOjKzPMqboEqeayRyYzi+IfkDgQ==", + "version": "1.15.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.15.5.tgz", + "integrity": "sha512-Dix1YCY6BlsVK20SjQHBjKqxW2K+lqNr6BlCKxtdZuYqmUWLm8NzKHdrJyiFFjUO2hSI4wiC7apE+jAkDA3fEQ==", "dev": true, "requires": { "@types/estree": "0.0.39", - "@types/node": "^12.0.3", + "@types/node": "^12.0.8", "acorn": "^6.1.1" }, "dependencies": { + "@types/node": { + "version": "12.0.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.8.tgz", + "integrity": "sha512-b8bbUOTwzIY3V5vDTY1fIJ+ePKDUBqt2hC2woVGotdQQhG/2Sh62HOKHrT7ab+VerXAcPyAiTEipPu/FsreUtg==", + "dev": true + }, "acorn": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", @@ -7511,9 +7517,9 @@ } }, "typescript": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.1.tgz", - "integrity": "sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw==", + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.5.2.tgz", + "integrity": "sha512-7KxJovlYhTX5RaRbUdkAXN1KUZ8PwWlTzQdHV6xNqvuFOs7+WBo10TQUqT19Q/Jz2hk5v9TQDIhyLhhJY4p5AA==", "dev": true }, "uglify-js": { diff --git a/package.json b/package.json index cc8cd79e0..78534e077 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "solid-js", "description": "A declarative JavaScript library for building user interfaces.", - "version": "0.7.13", + "version": "0.8.0", "author": "Ryan Carniato", "license": "MIT", "repository": { @@ -26,15 +26,15 @@ "@babel/preset-typescript": "^7.3.3", "coveralls": "^3.0.4", "cpx": "^1.5.0", - "dom-expressions": "0.9.10", - "hyper-dom-expressions": "0.9.2", + "dom-expressions": "0.10.0", + "hyper-dom-expressions": "0.10.0", "jest": "~24.8.0", - "lit-dom-expressions": "0.9.2", + "lit-dom-expressions": "0.10.0", "npm-run-all": "^4.1.5", "rimraf": "^2.6.3", - "rollup": "^1.14.4", + "rollup": "^1.15.5", "rollup-plugin-babel": "^4.3.2", "rollup-plugin-node-resolve": "^5.0.1", - "typescript": "^3.5.1" + "typescript": "^3.5.2" } } diff --git a/src/dom/index.js b/src/dom/index.js index ba8775279..1fb375b3e 100644 --- a/src/dom/index.js +++ b/src/dom/index.js @@ -1,6 +1,6 @@ import { Attributes } from 'dom-expressions'; import { - createEffect as wrap, sample, createRoot as root, + createEffect as wrap, sample, createRoot as root, createMemo as memo, onCleanup as cleanup, setContext, registerSuspense, getContextOwner as currentContext } from '../solid.js'; @@ -248,74 +248,51 @@ export function spread(node, accessor) { export function when(parent, accessor, expr, options, marker) { let beforeNode, current, disposable; - const { afterRender, fallback } = options; + const { afterRender, fallback } = options, + condition = memo(accessor); if (marker !== undefined) beforeNode = marker ? marker.previousSibling : parent.lastChild; - cleanup(function dispose() { disposable && disposable(); }); - - wrap(cached => { - const value = accessor(); - if (value === cached) return cached; - return sample(() => { + wrap(() => { + const value = condition(); + sample(() => { parent = (marker && marker.parentNode) || (beforeNode && beforeNode.parentNode) || parent; - disposable && disposable(); clearAll(parent, current, marker, beforeNode && beforeNode.nextSibling); current = null; if (value == null || value === false) { afterRender && afterRender(current, marker); if (fallback) { - root(disposer => { - disposable = disposer; - addNode(parent, fallback(), marker, ++groupCounter, node => current = node); - }); + addNode(parent, fallback(), marker, ++groupCounter, node => current = node); } - } else { - root(disposer => { - disposable = disposer; - addNode(parent, expr(value), marker, ++groupCounter, node => current = node, afterRender && afterRender(node, marker)); - }); - } - return value; + } else addNode(parent, expr(value), marker, ++groupCounter, node => (current = node, afterRender && afterRender(node, marker))); }); }); } export function switchWhen(parent, conditions, _, options, marker) { let beforeNode, current, disposable; - const { fallback } = options; + const { fallback } = options, + evalConditions = memo(() => { + for (let i = 0; i < conditions.length; i++) { + if (conditions[i].condition()) return i; + } + return -1; + }); if (marker !== undefined) beforeNode = marker ? marker.previousSibling : parent.lastChild; - function evalConditions() { - for (let i = 0; i < conditions.length; i++) { - if (conditions[i].condition()) return {index: i, render: conditions[i].render, afterRender: conditions[i].options && conditions[i].options.afterRender}; - } - return {index: -1}; - } - cleanup(function dispose() { disposable && disposable(); }); - - wrap(cached => { - const {index, render, afterRender} = evalConditions(); - if (index === cached) return cached; - return sample(() => { + wrap(() => { + const index = evalConditions(); + sample(() => { parent = (marker && marker.parentNode) || (beforeNode && beforeNode.parentNode) || parent; - disposable && disposable(); clearAll(parent, current, marker, beforeNode && beforeNode.nextSibling); current = null; if (index < 0) { - afterRender && afterRender(null, marker); if (fallback) { - root(disposer => { - disposable = disposer; - addNode(parent, fallback(), marker, ++groupCounter, node => current = node); - }); + addNode(parent, fallback(), marker, ++groupCounter, node => current = node); } } else { - root(disposer => { - disposable = disposer; - addNode(parent, render(), marker, ++groupCounter, node => current = node, afterRender && afterRender(node, marker)); - }); + const afterRender = conditions[index].options && conditions[index].options.afterRender; + addNode(parent, conditions[index].render(), marker, ++groupCounter, node => current = node, afterRender && afterRender(node, marker)); } - return index; }); }); } diff --git a/src/signals.ts b/src/signals.ts index 7ef33b886..2c8de8ae9 100644 --- a/src/signals.ts +++ b/src/signals.ts @@ -43,19 +43,15 @@ export function createDependentEffect(fn: (v?: T) => T, deps: () => any | (() } } -export function createMemo(fn: (v: T | undefined) => T, value?: T): () => T { - +const EQUAL = (a: any, b: any) => a === b +export function createMemo(fn: (v: T | undefined) => T, value?: T, comparator?: (v?: T, p?: T) => boolean): () => T { if (Owner === null) console.warn("computations created without a root or parent will never be disposed"); - var { node, value: _value } = makeComputationNode(fn, value, false, false); - - if (node === null) { - return function computation() { return _value; } - } else { - return function computation() { - return node!.current(); - } - } + // TODO: Restore synchronicity + comparator || (comparator = EQUAL); + const [s, set] = createSignal(fn(undefined), comparator); + makeComputationNode(v => (set(v = fn(v)), v), value, false, false) + return s; }; export function createRoot(fn: (dispose: () => void) => T, detachedOwner?: ComputationNode): T { @@ -206,7 +202,6 @@ class ComputationNode { source1slot: number; sources: null | Log[]; sourceslots: null | number[]; - log: Log | null; owner: any; context: any; noRecycle?: boolean; @@ -221,23 +216,10 @@ class ComputationNode { this.source1slot = 0; this.sources = null; this.sourceslots = null; - this.log = null; this.owner = Owner; this.owned = null; this.cleanups = null; } - - current() { - if (Listener !== null) { - if (this.age === RootClock.time) { - if (this.state === RUNNING) throw new Error("circular dependency"); - else updateNode(this); // checks for state === STALE internally, so don't need to check here - } - logComputationRead(this); - } - - return this.value; - } } type Clock = { @@ -322,8 +304,7 @@ function lookup(owner: ComputationNode, key: symbol | string): any { return (owner && owner.context && owner.context[key]) || (owner.owner && lookup(owner.owner, key)); } -var makeComputationNodeResult = { node: null as null | ComputationNode, value: undefined as any }; -function makeComputationNode(fn: (v: T | undefined) => T, value: T | undefined, orphan: boolean, sample: boolean): { node: ComputationNode | null, value: T } { +function makeComputationNode(fn: (v: T | undefined) => T, value: T | undefined, orphan: boolean, sample: boolean): void { var node = getCandidateNode(), owner = Owner, listener = Listener, @@ -341,14 +322,8 @@ function makeComputationNode(fn: (v: T | undefined) => T, value: T | undefine Owner = owner; Listener = listener; - var recycled = recycleOrClaimNode(node, fn, value, orphan); - + recycleOrClaimNode(node, fn, value, orphan); if (toplevel) finishToplevelComputation(owner, listener); - - makeComputationNodeResult.node = recycled ? null : node; - makeComputationNodeResult.value = value!; - - return makeComputationNodeResult; } function execToplevelComputation(fn: (v: T | undefined) => T, value: T) { @@ -459,11 +434,6 @@ function logDataRead(data: DataNode) { logRead(data.log); } -function logComputationRead(node: ComputationNode) { - if (node.log === null) node.log = createLog(); - logRead(node.log); -} - function event() { // b/c we might be under a top level S.root(), have to preserve current root var owner = Owner; @@ -529,7 +499,6 @@ function markNodeStale(node: ComputationNode) { node.state = STALE; RootClock.updates.add(node); if (node.owned !== null) markOwnedNodesForDisposal(node.owned); - if (node.log !== null) markComputationsStale(node.log); } } @@ -617,7 +586,6 @@ function cleanupSource(source: Log, slot: number) { function dispose(node: ComputationNode) { node.fn = null; - node.log = null; node.owner = null; cleanupNode(node, true); } \ No newline at end of file