diff --git a/src/components/page-react-timings/App.tsx b/src/components/page-react-timings/App.tsx index ba5eaaa..42c1e8a 100644 --- a/src/components/page-react-timings/App.tsx +++ b/src/components/page-react-timings/App.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { log } from "./tasks"; const getRandomColor = () => { return `lch(${Math.floor(Math.random() * 20) + 50}% ${ @@ -19,60 +20,10 @@ declare global { }; } -const TimingContext = React.createContext(false); - -function measureTiming(label: string, color: string, withTiming: boolean) { - if (withTiming) { - console.log(performance.now().toFixed(1) + "ms " + label, color); - queueMicrotask(function measureTiming_microTask() { - console.log( - performance.now().toFixed(1) + "ms microtask " + label, - color - ); - }); - requestAnimationFrame(function measureTiming_requestAnimationFrame() { - console.log( - performance.now().toFixed(1) + "ms requestAnimationFrame " + label, - color - ); - }); - scheduler.postTask( - function measureTiming_userBlockingTask() { - console.log( - performance.now().toFixed(1) + "ms user-blocking task " + label, - color - ); - }, - { - priority: "user-blocking", - } - ); - scheduler.postTask( - function measureTiming_userVisibleTask() { - console.log( - performance.now().toFixed(1) + "ms user-visible task " + label, - color - ); - }, - { - priority: "user-visible", - } - ); - scheduler.postTask( - function measureTiming_backgroundTask() { - console.log( - performance.now().toFixed(1) + "ms background task " + label, - color - ); - }, - { - priority: "background", - } - ); - } else { - console.log(label, color); - } -} +const TimingContext = React.createContext({ + tasks: false, + timings: false, +}); class ErrorBoundary extends React.Component< { children?: React.ReactNode }, @@ -141,8 +92,8 @@ class ClassWithMetrics extends React.Component<{ color = getRandomColor(); componentId = String.fromCharCode(i++); - log(message: string) { - measureTiming( + boundLog(message: string) { + log( `%cComponent ${this.componentId}: ${message}`, `color: ${this.color}`, this.context @@ -152,48 +103,48 @@ class ClassWithMetrics extends React.Component<{ constructor(props: { children?: React.ReactNode; dashed?: boolean }) { super(props); - this.log("constructor"); + this.boundLog("constructor"); } override componentWillMount() { - this.log("componentWillMount"); + this.boundLog("componentWillMount"); } override componentWillReceiveProps() { - this.log("componentWillReceiveProps"); + this.boundLog("componentWillReceiveProps"); } override componentWillUpdate() { - this.log("componentWillUpdate"); + this.boundLog("componentWillUpdate"); } override componentDidMount() { - this.log("componentDidMount"); + this.boundLog("componentDidMount"); } override componentDidUpdate() { - this.log("componentDidUpdate"); + this.boundLog("componentDidUpdate"); } override shouldComponentUpdate() { - this.log("shouldComponentUpdate"); + this.boundLog("shouldComponentUpdate"); return true; } override getSnapshotBeforeUpdate() { - this.log("getSnapshotBeforeUpdate"); + this.boundLog("getSnapshotBeforeUpdate"); } override componentWillUnmount() { - this.log("componentWillUnmount"); + this.boundLog("componentWillUnmount"); } override render() { - this.log("render"); + this.boundLog("render"); return (
@@ -263,9 +214,9 @@ const useMetrics = () => { const componentId = React.useMemo(() => String.fromCharCode(i++), []); const withTiming = React.useContext(TimingContext); - const log = React.useCallback( + const boundLog = React.useCallback( (message: string) => { - measureTiming( + log( `%cComponent ${componentId}: ${message}`, `color: ${color}`, withTiming @@ -274,18 +225,18 @@ const useMetrics = () => { [withTiming] ); - log("render"); + boundLog("render"); const [s, forceUpdate] = React.useState({}); React.useMemo(function useMemo() { - log("useMemo"); + boundLog("useMemo"); }, []); React.useInsertionEffect( function useInsertionEffect() { - log("useInsertionEffect"); + boundLog("useInsertionEffect"); return function useInsertionEffect_cleanup() { - log("useInsertionEffect cleanup"); + boundLog("useInsertionEffect cleanup"); }; }, [s] @@ -293,9 +244,9 @@ const useMetrics = () => { React.useLayoutEffect( function useLayoutEffect() { - log("useLayoutEffect"); + boundLog("useLayoutEffect"); return function useLayoutEffect_cleanup() { - log("useLayoutEffect cleanup"); + boundLog("useLayoutEffect cleanup"); }; }, [s] @@ -303,15 +254,15 @@ const useMetrics = () => { React.useEffect( function useEffect() { - log("useEffect"); + boundLog("useEffect"); return function useEffect_cleanup() { - log("useEffect cleanup"); + boundLog("useEffect cleanup"); }; }, [s] ); - return { log, color, componentId, forceUpdate }; + return { log: boundLog, color, componentId, forceUpdate }; }; function FunctionWithMetrics({ @@ -403,21 +354,39 @@ export default function App() { ); const Example: (() => JSX.Element) | undefined = examples[example!]; - const [withTiming, setTiming] = React.useState(false); + const [withTimings, setTiming] = React.useState(false); + const [withTasks, setTasks] = React.useState(false); + const contextValue = React.useMemo( + () => ({ timings: withTimings, tasks: withTasks }), + [withTimings, withTasks] + ); return (
- Timing {withTiming ? "enabled " : "disabled "} + Timing {withTimings ? "enabled " : "disabled "} + +
+
+ Tasks {withTasks ? "enabled " : "disabled "}
@@ -444,9 +413,11 @@ export default function App() { ))}
- + - {Example && } + {Example && ( + + )}
diff --git a/src/components/page-react-timings/tasks.ts b/src/components/page-react-timings/tasks.ts new file mode 100644 index 0000000..98a0e07 --- /dev/null +++ b/src/components/page-react-timings/tasks.ts @@ -0,0 +1,96 @@ +const div = document.createElement("div"); +document.body.appendChild(div); + +export function syncTask(cb: (type: string) => void) { + (function syncCallback() { + cb("sync"); + })(); +} +export function microTask(cb: (type: string) => void) { + queueMicrotask(function microTaskCallback() { + cb("micro task"); + }); +} +export function userBlockingTask(cb: (type: string) => void) { + scheduler.postTask( + function userBlockingTaskCallback() { + cb("task (user-blocking)"); + }, + { priority: "user-blocking" } + ); +} +export function userVisibleTask(cb: (type: string) => void) { + scheduler.postTask( + function userVisibleTaskCallback() { + cb("task (user-visible)"); + }, + { priority: "user-visible" } + ); +} +export function backgroundTask(cb: (type: string) => void) { + scheduler.postTask( + function backgroundTaskCallback() { + cb("task (background)"); + }, + { priority: "background" } + ); +} +export function animationTask(cb: (type: string) => void) { + requestAnimationFrame(function animationTaskCallback() { + cb("animation frame"); + }); +} + +export function startAllTasks(cb: (type: string) => void) { + syncTask(cb); + microTask(cb); + userBlockingTask(cb); + userVisibleTask(cb); + backgroundTask(cb); + animationTask(cb); +} + +function getNow(timings: boolean) { + if (!timings) { + return ""; + } + + return ( + "Now: " + + performance.now().toFixed(1) + + "ms | Timeline: " + + document.timeline.currentTime + + "ms | " + ); +} + +export function log( + label: string, + color: string, + { + tasks, + timings = false, + }: { + tasks?: boolean; + timings?: boolean; + } = {} +) { + if (!tasks) { + console.log(getNow(timings) + label, color); + return; + } + + const timingStyle = [ + "font-style: italic; text-decoration-line: underline", + "", + ]; + + const cb = (type: string) => + console.log( + getNow(timings) + "%c" + type + "%c " + label, + ...timingStyle, + color + ); + + startAllTasks(cb); +}