From f9557bf11b81e42aafe84566295e788d1d38b93c Mon Sep 17 00:00:00 2001 From: Matan Borenkraout Date: Sat, 4 Jul 2020 14:34:18 +0300 Subject: [PATCH 1/5] fix: Fix build on react@next --- package.json | 4 +++- src/flush-microtasks.js | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 802a2e34..81e17a3a 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@reach/router": "^1.3.3", "@testing-library/jest-dom": "^5.10.1", "@types/react-dom": "^16.9.8", + "@types/scheduler": "^0.16.1", "dotenv-cli": "^3.1.0", "dtslint": "3.6.12", "kcd-scripts": "^6.2.3", @@ -61,7 +62,8 @@ }, "peerDependencies": { "react": "*", - "react-dom": "*" + "react-dom": "*", + "scheduler": "*" }, "eslintConfig": { "extends": "./node_modules/kcd-scripts/eslint.js", diff --git a/src/flush-microtasks.js b/src/flush-microtasks.js index 2638da04..e1746f3f 100644 --- a/src/flush-microtasks.js +++ b/src/flush-microtasks.js @@ -1,3 +1,7 @@ +import { + unstable_scheduleCallback as scheduleCallback, + unstable_NormalPriority as normalPriority, +} from 'scheduler' /* istanbul ignore file */ // the part of this file that we need tested is definitely being run // and the part that is not cannot easily have useful tests written @@ -59,7 +63,11 @@ export default function flushMicroTasks() { jest.advanceTimersByTime(0) resolve() } else { - enqueueTask(resolve) + scheduleCallback(normalPriority, () => { + enqueueTask(() => { + resolve() + }) + }) } }, } From 5ef6cb8a813b505b0cd9c8018cfabe8b57b1ccfb Mon Sep 17 00:00:00 2001 From: Matan Borenkraout Date: Sat, 4 Jul 2020 23:43:26 +0300 Subject: [PATCH 2/5] Working on ^16.9 and lower than 16.8 --- package.json | 4 +--- src/flush-microtasks.js | 21 ++++++++++++++++----- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 81e17a3a..802a2e34 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,6 @@ "@reach/router": "^1.3.3", "@testing-library/jest-dom": "^5.10.1", "@types/react-dom": "^16.9.8", - "@types/scheduler": "^0.16.1", "dotenv-cli": "^3.1.0", "dtslint": "3.6.12", "kcd-scripts": "^6.2.3", @@ -62,8 +61,7 @@ }, "peerDependencies": { "react": "*", - "react-dom": "*", - "scheduler": "*" + "react-dom": "*" }, "eslintConfig": { "extends": "./node_modules/kcd-scripts/eslint.js", diff --git a/src/flush-microtasks.js b/src/flush-microtasks.js index e1746f3f..c00702e3 100644 --- a/src/flush-microtasks.js +++ b/src/flush-microtasks.js @@ -1,7 +1,3 @@ -import { - unstable_scheduleCallback as scheduleCallback, - unstable_NormalPriority as normalPriority, -} from 'scheduler' /* istanbul ignore file */ // the part of this file that we need tested is definitely being run // and the part that is not cannot easily have useful tests written @@ -21,6 +17,10 @@ function getIsUsingFakeTimers() { let didWarnAboutMessageChannel = false let enqueueTask +const globalObj = typeof window === 'undefined' ? global : window + +let Scheduler = globalObj.Scheduler + try { // read require off the module object to get around the bundlers. // we don't want them to detect a require and bundle a Node polyfill. @@ -29,6 +29,8 @@ try { // assuming we're in node, let's try to get node's // version of setImmediate, bypassing fake timers if any. enqueueTask = nodeRequire.call(module, 'timers').setImmediate + // import React's scheduler so we'll be able to schedule our tasks later on. + Scheduler = nodeRequire.call(module, 'scheduler') } catch (_err) { // we're in a browser // we can't use regular timers because they may still be faked @@ -53,6 +55,15 @@ try { } } +// in case this react version has a Scheduler implementation, we use it, +// if not, we just create a function calling our callback +const scheduleCallback = Scheduler + ? Scheduler.scheduleCallback || Scheduler.unstable_scheduleCallback + : (_, cb) => cb() +const NormalPriority = Scheduler + ? Scheduler.NormalPriority || Scheduler.unstable_NormalPriority + : null + export default function flushMicroTasks() { return { then(resolve) { @@ -63,7 +74,7 @@ export default function flushMicroTasks() { jest.advanceTimersByTime(0) resolve() } else { - scheduleCallback(normalPriority, () => { + scheduleCallback(NormalPriority, () => { enqueueTask(() => { resolve() }) From 5d9f64db4dc0cfb02d08e4eaaa706b433e995b13 Mon Sep 17 00:00:00 2001 From: Matan Borenkraout Date: Sun, 5 Jul 2020 08:54:04 +0300 Subject: [PATCH 3/5] Solution works in 16.8 also --- src/flush-microtasks.js | 18 ++-------- src/scheduler-compat.js | 76 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 src/scheduler-compat.js diff --git a/src/flush-microtasks.js b/src/flush-microtasks.js index c00702e3..45a58aff 100644 --- a/src/flush-microtasks.js +++ b/src/flush-microtasks.js @@ -1,3 +1,5 @@ +import scheduleCallback from './scheduler-compat' + /* istanbul ignore file */ // the part of this file that we need tested is definitely being run // and the part that is not cannot easily have useful tests written @@ -17,9 +19,6 @@ function getIsUsingFakeTimers() { let didWarnAboutMessageChannel = false let enqueueTask -const globalObj = typeof window === 'undefined' ? global : window - -let Scheduler = globalObj.Scheduler try { // read require off the module object to get around the bundlers. @@ -29,8 +28,6 @@ try { // assuming we're in node, let's try to get node's // version of setImmediate, bypassing fake timers if any. enqueueTask = nodeRequire.call(module, 'timers').setImmediate - // import React's scheduler so we'll be able to schedule our tasks later on. - Scheduler = nodeRequire.call(module, 'scheduler') } catch (_err) { // we're in a browser // we can't use regular timers because they may still be faked @@ -55,15 +52,6 @@ try { } } -// in case this react version has a Scheduler implementation, we use it, -// if not, we just create a function calling our callback -const scheduleCallback = Scheduler - ? Scheduler.scheduleCallback || Scheduler.unstable_scheduleCallback - : (_, cb) => cb() -const NormalPriority = Scheduler - ? Scheduler.NormalPriority || Scheduler.unstable_NormalPriority - : null - export default function flushMicroTasks() { return { then(resolve) { @@ -74,7 +62,7 @@ export default function flushMicroTasks() { jest.advanceTimersByTime(0) resolve() } else { - scheduleCallback(NormalPriority, () => { + scheduleCallback(null, () => { enqueueTask(() => { resolve() }) diff --git a/src/scheduler-compat.js b/src/scheduler-compat.js new file mode 100644 index 00000000..2b03309b --- /dev/null +++ b/src/scheduler-compat.js @@ -0,0 +1,76 @@ +const globalObj = typeof window === 'undefined' ? global : window + +let Scheduler = globalObj.Scheduler +try { + const requireString = `require${Math.random()}`.slice(0, 7) + const nodeRequire = module && module[requireString] + // import React's scheduler so we'll be able to schedule our tasks later on. + Scheduler = nodeRequire.call(module, 'scheduler') +} catch (_err) { + console.error("The react version you're using doesn't support Scheduling") +} +// in case this react version has a Scheduler implementation, we use it, +// if not, we just create a function calling our callback +const NormalPriority = Scheduler + ? Scheduler.NormalPriority || Scheduler.unstable_NormalPriority + : null + +const isScheduleCallbackSupported = Scheduler !== undefined +let isModernScheduleCallbackSupported = null + +const errorHandler = e => { + // If the error originated from Scheduler, it means we're in v16.8.6 + if ( + e.message === 'callback is not a function' && + e.filename.includes('scheduler') + ) { + console.error(e.error.stack, e.error.detail) + e.preventDefault() + } else { + console.error(e.error) + } +} + +export default function scheduleCallback(_, cb) { + if (!isScheduleCallbackSupported) { + return cb() + } + + if (isModernScheduleCallbackSupported === null) { + // patch console.error here + const originalConsoleError = console.error + console.error = function error(...args) { + /* if console.error fired *with that specific message* */ + /* istanbul ignore next */ + const firstArgIsString = typeof args[0] === 'string' + if ( + firstArgIsString && + args[0].indexOf('TypeError: callback is not a function') === 0 + ) { + // v16.8.6 + isModernScheduleCallbackSupported = false + globalObj.removeEventListener('error', errorHandler) + console.error = originalConsoleError + return cb() + } else { + originalConsoleError.apply(console, args) + console.error = originalConsoleError + return cb() + } + } + + globalObj.addEventListener('error', errorHandler) + return Scheduler.unstable_scheduleCallback(NormalPriority, () => { + console.error = originalConsoleError + isModernScheduleCallbackSupported = true + globalObj.removeEventListener('error', errorHandler) + return cb() + }) + } else if (isModernScheduleCallbackSupported === false) { + return cb() + } + + return Scheduler.unstable_scheduleCallback(NormalPriority, cb) +} + +/* eslint no-console:0 */ From ee484feeb7a6124ef53011ccd8f439431d0b1fcc Mon Sep 17 00:00:00 2001 From: Matan Borenkraout Date: Sun, 5 Jul 2020 17:53:56 +0300 Subject: [PATCH 4/5] Fixes from code review --- package.json | 3 +- src/flush-microtasks.js | 29 ++++++++++++++-- src/scheduler-compat.js | 76 ----------------------------------------- 3 files changed, 29 insertions(+), 79 deletions(-) delete mode 100644 src/scheduler-compat.js diff --git a/package.json b/package.json index 802a2e34..ea03d463 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,8 @@ "license": "MIT", "dependencies": { "@babel/runtime": "^7.10.3", - "@testing-library/dom": "^7.17.1" + "@testing-library/dom": "^7.17.1", + "semver": "^7.3.2" }, "devDependencies": { "@reach/router": "^1.3.3", diff --git a/src/flush-microtasks.js b/src/flush-microtasks.js index 45a58aff..3a5d1902 100644 --- a/src/flush-microtasks.js +++ b/src/flush-microtasks.js @@ -1,4 +1,5 @@ -import scheduleCallback from './scheduler-compat' +import React from 'react' +import semver from 'semver' /* istanbul ignore file */ // the part of this file that we need tested is definitely being run @@ -17,6 +18,14 @@ function getIsUsingFakeTimers() { ) } +const globalObj = typeof window === 'undefined' ? global : window +let Scheduler = globalObj.Scheduler +const isModernScheduleCallbackSupported = semver.satisfies( + React.version, + '>16.8.6', + {includePrerelease: true}, +) + let didWarnAboutMessageChannel = false let enqueueTask @@ -28,6 +37,8 @@ try { // assuming we're in node, let's try to get node's // version of setImmediate, bypassing fake timers if any. enqueueTask = nodeRequire.call(module, 'timers').setImmediate + // import React's scheduler so we'll be able to schedule our tasks later on. + Scheduler = nodeRequire.call(module, 'scheduler') } catch (_err) { // we're in a browser // we can't use regular timers because they may still be faked @@ -52,6 +63,20 @@ try { } } +function scheduleCallback(cb) { + const NormalPriority = Scheduler + ? Scheduler.NormalPriority || Scheduler.unstable_NormalPriority + : null + + const scheduleFn = Scheduler + ? Scheduler.scheduleCallback || Scheduler.unstable_scheduleCallback + : callback => callback() + + return isModernScheduleCallbackSupported + ? scheduleFn(NormalPriority, cb) + : scheduleFn(cb) +} + export default function flushMicroTasks() { return { then(resolve) { @@ -62,7 +87,7 @@ export default function flushMicroTasks() { jest.advanceTimersByTime(0) resolve() } else { - scheduleCallback(null, () => { + scheduleCallback(() => { enqueueTask(() => { resolve() }) diff --git a/src/scheduler-compat.js b/src/scheduler-compat.js deleted file mode 100644 index 2b03309b..00000000 --- a/src/scheduler-compat.js +++ /dev/null @@ -1,76 +0,0 @@ -const globalObj = typeof window === 'undefined' ? global : window - -let Scheduler = globalObj.Scheduler -try { - const requireString = `require${Math.random()}`.slice(0, 7) - const nodeRequire = module && module[requireString] - // import React's scheduler so we'll be able to schedule our tasks later on. - Scheduler = nodeRequire.call(module, 'scheduler') -} catch (_err) { - console.error("The react version you're using doesn't support Scheduling") -} -// in case this react version has a Scheduler implementation, we use it, -// if not, we just create a function calling our callback -const NormalPriority = Scheduler - ? Scheduler.NormalPriority || Scheduler.unstable_NormalPriority - : null - -const isScheduleCallbackSupported = Scheduler !== undefined -let isModernScheduleCallbackSupported = null - -const errorHandler = e => { - // If the error originated from Scheduler, it means we're in v16.8.6 - if ( - e.message === 'callback is not a function' && - e.filename.includes('scheduler') - ) { - console.error(e.error.stack, e.error.detail) - e.preventDefault() - } else { - console.error(e.error) - } -} - -export default function scheduleCallback(_, cb) { - if (!isScheduleCallbackSupported) { - return cb() - } - - if (isModernScheduleCallbackSupported === null) { - // patch console.error here - const originalConsoleError = console.error - console.error = function error(...args) { - /* if console.error fired *with that specific message* */ - /* istanbul ignore next */ - const firstArgIsString = typeof args[0] === 'string' - if ( - firstArgIsString && - args[0].indexOf('TypeError: callback is not a function') === 0 - ) { - // v16.8.6 - isModernScheduleCallbackSupported = false - globalObj.removeEventListener('error', errorHandler) - console.error = originalConsoleError - return cb() - } else { - originalConsoleError.apply(console, args) - console.error = originalConsoleError - return cb() - } - } - - globalObj.addEventListener('error', errorHandler) - return Scheduler.unstable_scheduleCallback(NormalPriority, () => { - console.error = originalConsoleError - isModernScheduleCallbackSupported = true - globalObj.removeEventListener('error', errorHandler) - return cb() - }) - } else if (isModernScheduleCallbackSupported === false) { - return cb() - } - - return Scheduler.unstable_scheduleCallback(NormalPriority, cb) -} - -/* eslint no-console:0 */ From 081cccb59109a50c2e4777cd586d76ec1e46881e Mon Sep 17 00:00:00 2001 From: Matan Borenkraout Date: Sun, 5 Jul 2020 17:55:59 +0300 Subject: [PATCH 5/5] Import only the function we need --- src/flush-microtasks.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/flush-microtasks.js b/src/flush-microtasks.js index 3a5d1902..99b56ea5 100644 --- a/src/flush-microtasks.js +++ b/src/flush-microtasks.js @@ -1,5 +1,5 @@ import React from 'react' -import semver from 'semver' +import satisfies from 'semver/functions/satisfies' /* istanbul ignore file */ // the part of this file that we need tested is definitely being run @@ -20,11 +20,9 @@ function getIsUsingFakeTimers() { const globalObj = typeof window === 'undefined' ? global : window let Scheduler = globalObj.Scheduler -const isModernScheduleCallbackSupported = semver.satisfies( - React.version, - '>16.8.6', - {includePrerelease: true}, -) +const isModernScheduleCallbackSupported = satisfies(React.version, '>16.8.6', { + includePrerelease: true, +}) let didWarnAboutMessageChannel = false let enqueueTask