var _a, _b; import { __addDisposableResource, __assign, __awaiter, __disposeResources, __extends, __generator, __rest } from "tslib"; import * as React from "react"; import { TextEncoder, TextDecoder } from "util"; (_a = global.TextEncoder) !== null && _a !== void 0 ? _a : (global.TextEncoder = TextEncoder); // @ts-ignore (_b = global.TextDecoder) !== null && _b !== void 0 ? _b : (global.TextDecoder = TextDecoder); import { RenderInstance } from "./Render.js"; import { applyStackTrace, captureStackTrace } from "./traces.js"; import { ProfilerContextProvider, useProfilerContext } from "./context.js"; import { disableActWarnings } from "../disposables/index.js"; /** only used for passing around data internally */ var _stackTrace = Symbol(); /** @internal */ export function profile(_a) { var Component = _a.Component, options = __rest(_a, ["Component"]); var Profiler = createProfiler(options); return Object.assign(function ProfiledComponent(props) { return (React.createElement(Profiler, null, React.createElement(Component, __assign({}, props)))); }, { mergeSnapshot: Profiler.mergeSnapshot, replaceSnapshot: Profiler.replaceSnapshot, getCurrentRender: Profiler.getCurrentRender, peekRender: Profiler.peekRender, takeRender: Profiler.takeRender, totalRenderCount: Profiler.totalRenderCount, waitForNextRender: Profiler.waitForNextRender, get renders() { return Profiler.renders; }, }); } /** @internal */ export function createProfiler(_a) { var _b = _a === void 0 ? {} : _a, onRender = _b.onRender, _c = _b.snapshotDOM, snapshotDOM = _c === void 0 ? false : _c, initialSnapshot = _b.initialSnapshot, skipNonTrackingRenders = _b.skipNonTrackingRenders; var nextRender; var resolveNextRender; var rejectNextRender; var snapshotRef = { current: initialSnapshot }; var replaceSnapshot = function (snap) { if (typeof snap === "function") { if (!initialSnapshot) { throw new Error("Cannot use a function to update the snapshot if no initial snapshot was provided."); } snapshotRef.current = snap(typeof snapshotRef.current === "object" ? __assign({}, snapshotRef.current) : snapshotRef.current); } else { snapshotRef.current = snap; } }; var mergeSnapshot = function (partialSnapshot) { replaceSnapshot(function (snapshot) { return (__assign(__assign({}, snapshot), (typeof partialSnapshot === "function" ? partialSnapshot(snapshot) : partialSnapshot))); }); }; var profilerContext = { renderedComponents: [], }; var profilerOnRender = function (id, phase, actualDuration, baseDuration, startTime, commitTime) { if (skipNonTrackingRenders && profilerContext.renderedComponents.length === 0) { return; } var baseRender = { id: id, phase: phase, actualDuration: actualDuration, baseDuration: baseDuration, startTime: startTime, commitTime: commitTime, count: Profiler.renders.length + 1, }; try { /* * The `onRender` function could contain `expect` calls that throw * `JestAssertionError`s - but we are still inside of React, where errors * might be swallowed. * So we record them and re-throw them in `takeRender` * Additionally, we reject the `waitForNextRender` promise. */ onRender === null || onRender === void 0 ? void 0 : onRender(__assign(__assign({}, baseRender), { replaceSnapshot: replaceSnapshot, mergeSnapshot: mergeSnapshot, snapshot: snapshotRef.current })); var snapshot = snapshotRef.current; var domSnapshot = snapshotDOM ? window.document.body.innerHTML : undefined; var render = new RenderInstance(baseRender, snapshot, domSnapshot, profilerContext.renderedComponents); profilerContext.renderedComponents = []; Profiler.renders.push(render); resolveNextRender === null || resolveNextRender === void 0 ? void 0 : resolveNextRender(render); } catch (error) { Profiler.renders.push({ phase: "snapshotError", count: Profiler.renders.length, error: error, }); rejectNextRender === null || rejectNextRender === void 0 ? void 0 : rejectNextRender(error); } finally { nextRender = resolveNextRender = rejectNextRender = undefined; } }; var iteratorPosition = 0; var Profiler = Object.assign(function (_a) { var children = _a.children; return (React.createElement(ProfilerContextProvider, { value: profilerContext }, React.createElement(React.Profiler, { id: "test", onRender: profilerOnRender }, children))); }, { replaceSnapshot: replaceSnapshot, mergeSnapshot: mergeSnapshot, }, { renders: new Array(), totalRenderCount: function () { return Profiler.renders.length; }, peekRender: function (options) { if (options === void 0) { options = {}; } return __awaiter(this, void 0, void 0, function () { var render; var _a; return __generator(this, function (_b) { if (iteratorPosition < Profiler.renders.length) { render = Profiler.renders[iteratorPosition]; if (render.phase === "snapshotError") { throw render.error; } return [2 /*return*/, render]; } return [2 /*return*/, Profiler.waitForNextRender(__assign((_a = {}, _a[_stackTrace] = captureStackTrace(Profiler.peekRender), _a), options))]; }); }); }, takeRender: function (options) { if (options === void 0) { options = {}; } return __awaiter(this, void 0, void 0, function () { var env_1, _disabledActWarnings, error, e_1, e_2; var _a; return __generator(this, function (_b) { switch (_b.label) { case 0: env_1 = { stack: [], error: void 0, hasError: false }; _b.label = 1; case 1: _b.trys.push([1, 7, 8, 9]); _disabledActWarnings = __addDisposableResource(env_1, disableActWarnings(), false); error = undefined; _b.label = 2; case 2: _b.trys.push([2, 4, 5, 6]); return [4 /*yield*/, Profiler.peekRender(__assign((_a = {}, _a[_stackTrace] = captureStackTrace(Profiler.takeRender), _a), options))]; case 3: return [2 /*return*/, _b.sent()]; case 4: e_1 = _b.sent(); error = e_1; throw e_1; case 5: if (!(error && error instanceof WaitForRenderTimeoutError)) { iteratorPosition++; } return [7 /*endfinally*/]; case 6: return [3 /*break*/, 9]; case 7: e_2 = _b.sent(); env_1.error = e_2; env_1.hasError = true; return [3 /*break*/, 9]; case 8: __disposeResources(env_1); return [7 /*endfinally*/]; case 9: return [2 /*return*/]; } }); }); }, getCurrentRender: function () { // The "current" render should point at the same render that the most // recent `takeRender` call returned, so we need to get the "previous" // iterator position, otherwise `takeRender` advances the iterator // to the next render. This means we need to call `takeRender` at least // once before we can get a current render. var currentPosition = iteratorPosition - 1; if (currentPosition < 0) { throw new Error("No current render available. You need to call `takeRender` before you can get the current render."); } var render = Profiler.renders[currentPosition]; if (render.phase === "snapshotError") { throw render.error; } return render; }, waitForNextRender: function (_a) { var _b = _a === void 0 ? {} : _a, _c = _b.timeout, timeout = _c === void 0 ? 1000 : _c, // capture the stack trace here so its stack trace is as close to the calling code as possible _d = _stackTrace, // capture the stack trace here so its stack trace is as close to the calling code as possible _e = _b[_d], // capture the stack trace here so its stack trace is as close to the calling code as possible stackTrace = _e === void 0 ? captureStackTrace(Profiler.waitForNextRender) : _e; if (!nextRender) { nextRender = Promise.race([ new Promise(function (resolve, reject) { resolveNextRender = resolve; rejectNextRender = reject; }), new Promise(function (_, reject) { return setTimeout(function () { return reject(applyStackTrace(new WaitForRenderTimeoutError(), stackTrace)); }, timeout); }), ]); } return nextRender; }, }); return Profiler; } /** @internal */ var WaitForRenderTimeoutError = /** @class */ (function (_super) { __extends(WaitForRenderTimeoutError, _super); function WaitForRenderTimeoutError() { var _newTarget = this.constructor; var _this = _super.call(this, "Exceeded timeout waiting for next render.") || this; Object.setPrototypeOf(_this, _newTarget.prototype); return _this; } return WaitForRenderTimeoutError; }(Error)); export { WaitForRenderTimeoutError }; /** @internal */ export function profileHook(renderCallback) { var Profiler = createProfiler(); var ProfiledHook = function (props) { Profiler.replaceSnapshot(renderCallback(props)); return null; }; return Object.assign(function App(props) { return (React.createElement(Profiler, null, React.createElement(ProfiledHook, __assign({}, props)))); }, { Profiler: Profiler, }, { renders: Profiler.renders, totalSnapshotCount: Profiler.totalRenderCount, peekSnapshot: function (options) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, Profiler.peekRender(options)]; case 1: return [2 /*return*/, (_a.sent()).snapshot]; } }); }); }, takeSnapshot: function (options) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, Profiler.takeRender(options)]; case 1: return [2 /*return*/, (_a.sent()).snapshot]; } }); }); }, getCurrentSnapshot: function () { return Profiler.getCurrentRender().snapshot; }, waitForNextSnapshot: function (options) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { switch (_a.label) { case 0: return [4 /*yield*/, Profiler.waitForNextRender(options)]; case 1: return [2 /*return*/, (_a.sent()).snapshot]; } }); }); }, }); } function resolveHookOwner() { var _a, _b, _c; return (_c = (_b = (_a = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) === null || _a === void 0 ? void 0 : _a.ReactCurrentOwner) === null || _b === void 0 ? void 0 : _b.current) === null || _c === void 0 ? void 0 : _c.elementType; } export function useTrackRenders(_a) { var _b = _a === void 0 ? {} : _a, name = _b.name; var component = name || resolveHookOwner(); if (!component) { throw new Error("useTrackRender: Unable to determine component. Please ensure the hook is called inside a rendered component or provide a `name` option."); } var ctx = useProfilerContext(); if (!ctx) { throw new Error("useTrackComponentRender: A Profiler must be created and rendered to track component renders"); } React.useLayoutEffect(function () { ctx.renderedComponents.unshift(component); }); } //# sourceMappingURL=profile.js.map