diff --git a/benches/src/util.js b/benches/src/util.js index 9711bcd157..5eda4f5027 100644 --- a/benches/src/util.js +++ b/benches/src/util.js @@ -5,6 +5,12 @@ export { afterFrame }; export const measureName = 'duration'; +const majorTask = () => + new Promise(resolve => { + window.addEventListener('message', resolve, { once: true }); + window.postMessage('major task delay', '*'); + }); + let promise = null; export function afterFrameAsync() { if (promise === null) { @@ -19,10 +25,20 @@ export function afterFrameAsync() { return promise; } -export function measureMemory() { +export async function measureMemory() { if ('gc' in window && 'memory' in performance) { // Report results in MBs + performance.mark('gc-start'); window.gc(); + performance.measure('gc', 'gc-start'); + + // window.gc synchronously triggers one Major GC. However that MajorGC + // asynchronously triggers additional MajorGCs until the + // usedJSHeapSizeBefore and usedJSHeapSizeAfter are the same. Here, we'll + // wait a moment for some (hopefully all) additional GCs to finish before + // measuring the memory. + await majorTask(); + performance.mark('measure-memory'); window.usedJSHeapSize = performance.memory.usedJSHeapSize / 1e6; } else { window.usedJSHeapSize = 0; diff --git a/devtools/src/devtools.js b/devtools/src/devtools.js index 433bc7699f..b7efe8666f 100644 --- a/devtools/src/devtools.js +++ b/devtools/src/devtools.js @@ -2,7 +2,7 @@ import { options, Fragment, Component } from 'preact'; export function initDevTools() { if (typeof window != 'undefined' && window.__PREACT_DEVTOOLS__) { - window.__PREACT_DEVTOOLS__.attachPreact('10.13.1', options, { + window.__PREACT_DEVTOOLS__.attachPreact('10.13.2', options, { Fragment, Component }); diff --git a/hooks/test/browser/useImperativeHandle.test.js b/hooks/test/browser/useImperativeHandle.test.js index 24b9e13775..ac71065696 100644 --- a/hooks/test/browser/useImperativeHandle.test.js +++ b/hooks/test/browser/useImperativeHandle.test.js @@ -180,7 +180,7 @@ describe('useImperativeHandle', () => { expect(() => render(, scratch)).to.not.throw(); }); - it('should reset ref to null when the component get unmounted', () => { + it('should reset ref object to null when the component get unmounted', () => { let ref, createHandleSpy = sinon.spy(() => ({ test: () => 'test' })); @@ -198,4 +198,25 @@ describe('useImperativeHandle', () => { expect(createHandleSpy).to.have.been.calledOnce; expect(ref.current).to.equal(null); }); + + it('should reset ref callback to null when the component get unmounted', () => { + const ref = sinon.spy(); + const handle = { test: () => 'test' }; + const createHandleSpy = sinon.spy(() => handle); + + function Comp() { + useImperativeHandle(ref, createHandleSpy, [1]); + return

Test

; + } + + render(, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref).to.have.been.calledWith(handle); + + ref.resetHistory(); + + render(
, scratch); + expect(createHandleSpy).to.have.been.calledOnce; + expect(ref).to.have.been.calledWith(null); + }); }); diff --git a/package-lock.json b/package-lock.json index cea3238fd5..3ed2995121 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "preact", - "version": "10.13.1", + "version": "10.13.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "preact", - "version": "10.13.1", + "version": "10.13.2", "license": "MIT", "devDependencies": { "@actions/github": "^5.0.0", diff --git a/package.json b/package.json index 881a5b4998..24096672cc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "preact", "amdName": "preact", - "version": "10.13.1", + "version": "10.13.2", "private": false, "description": "Fast 3kb React-compatible Virtual DOM library.", "main": "dist/preact.js", diff --git a/src/diff/children.js b/src/diff/children.js index 6487b55b4c..4c388ed6f3 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -2,6 +2,7 @@ import { diff, unmount, applyRef } from './index'; import { createVNode, Fragment } from '../create-element'; import { EMPTY_OBJ, EMPTY_ARR } from '../constants'; import { getDomSibling } from '../component'; +import { isArray } from '../util'; /** * Diff the children of a virtual node @@ -70,7 +71,7 @@ export function diffChildren( null, childVNode ); - } else if (Array.isArray(childVNode)) { + } else if (isArray(childVNode)) { childVNode = newParentVNode._children[i] = createVNode( Fragment, { children: childVNode }, @@ -265,7 +266,7 @@ function reorderChildren(childVNode, oldDom, parentDom) { export function toChildArray(children, out) { out = out || []; if (children == null || typeof children == 'boolean') { - } else if (Array.isArray(children)) { + } else if (isArray(children)) { children.some(child => { toChildArray(child, out); }); diff --git a/src/diff/index.js b/src/diff/index.js index b6243a8bef..477663cd9e 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -3,7 +3,7 @@ import { Component, getDomSibling } from '../component'; import { Fragment } from '../create-element'; import { diffChildren } from './children'; import { diffProps, setProperty } from './props'; -import { assign, removeNode, slice } from '../util'; +import { assign, isArray, removeNode, slice } from '../util'; import options from '../options'; /** @@ -232,7 +232,7 @@ export function diff( diffChildren( parentDom, - Array.isArray(renderResult) ? renderResult : [renderResult], + isArray(renderResult) ? renderResult : [renderResult], newVNode, oldVNode, globalContext, @@ -438,7 +438,7 @@ function diffElementNodes( i = newVNode.props.children; diffChildren( dom, - Array.isArray(i) ? i : [i], + isArray(i) ? i : [i], newVNode, oldVNode, globalContext, diff --git a/src/diff/props.js b/src/diff/props.js index 3a589564ea..b0f2f9ee39 100644 --- a/src/diff/props.js +++ b/src/diff/props.js @@ -126,16 +126,16 @@ export function setProperty(dom, name, value, oldValue, isSvg) { } catch (e) {} } - // ARIA-attributes have a different notion of boolean values. - // The value `false` is different from the attribute not - // existing on the DOM, so we can't remove it. For non-boolean - // ARIA-attributes we could treat false as a removal, but the - // amount of exceptions would cost us too many bytes. On top of - // that other VDOM frameworks also always stringify `false`. + // aria- and data- attributes have no boolean representation. + // A `false` value is different from the attribute not being + // present, so we can't remove it. For non-boolean aria + // attributes we could treat false as a removal, but the + // amount of exceptions would cost too many bytes. On top of + // that other frameworks generally stringify `false`. if (typeof value === 'function') { // never serialize functions as attribute values - } else if (value != null && (value !== false || name.indexOf('-') != -1)) { + } else if (value != null && (value !== false || name[4] === '-')) { dom.setAttribute(name, value); } else { dom.removeAttribute(name); diff --git a/src/util.js b/src/util.js index 0e9b3b1e56..1c4b499bff 100644 --- a/src/util.js +++ b/src/util.js @@ -1,5 +1,7 @@ import { EMPTY_ARR } from './constants'; +export const isArray = Array.isArray; + /** * Assign properties from `props` to `obj` * @template O, P The obj and props types