You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
296 lines
13 KiB
296 lines
13 KiB
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
|