first commit
This commit is contained in:
Родитель
bb6a0cbd57
Коммит
57324467f4
|
@ -1,61 +1,2 @@
|
||||||
# Logs
|
|
||||||
logs
|
|
||||||
*.log
|
|
||||||
npm-debug.log*
|
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
node_modules/
|
||||||
jspm_packages/
|
lib/
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
|
|
||||||
# next.js build output
|
|
||||||
.next
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "wastedrendersdetector",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Tool to debug wasted renders",
|
||||||
|
"main": "lib/index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc",
|
||||||
|
"prepare": "yarn build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/Microsoft/WastedRendersDetector.git"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/Microsoft/WastedRendersDetector/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/Microsoft/WastedRendersDetector#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"react": ">=16.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": ">=16.0.0",
|
||||||
|
"typescript": "^3.3.1"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,117 @@
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
class WarningComponent extends React.Component<any> {
|
||||||
|
public render(): null {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public componentDidUpdate(prevProps: any): void {
|
||||||
|
if (!prevProps || !this.props) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If some props where added or removed then the update is legit.
|
||||||
|
if (propsAddedOrRemoved(prevProps, this.props)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const differences = deepSubstract(prevProps, this.props);
|
||||||
|
|
||||||
|
if (differences === null) {
|
||||||
|
console.error("🚨 WASTED RENDER 🚨, props changing reference for nothing: ", shallowSubstract(prevProps, this.props));
|
||||||
|
} else if (isOnlyFunctions(differences)) {
|
||||||
|
console.error("🚨 WASTED RENDER 🚨, props changed because of function reference(s) changing: ", differences);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function propsAddedOrRemoved(props1: any, props2: any): boolean {
|
||||||
|
const keysProps1 = Object.keys(props1);
|
||||||
|
const keysProps2 = Object.keys(props2);
|
||||||
|
return keysProps1.length !== keysProps2.length || keysProps1.some(k => keysProps2.indexOf(k) === -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Takes a difference object and check if it contains data or only functions.
|
||||||
|
*/
|
||||||
|
function isOnlyFunctions(a: any): boolean {
|
||||||
|
if (typeof a === "function") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof a !== "object") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(a).every(k => isOnlyFunctions(a[k]));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns "shallow" A - B
|
||||||
|
* Supported types: object
|
||||||
|
*/
|
||||||
|
function shallowSubstract(a: any, b: any): object | null {
|
||||||
|
const n: any = {};
|
||||||
|
Object.keys(a).forEach(key => {
|
||||||
|
if (a[key] !== b[key]) {
|
||||||
|
n[key] = a[key];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(n).length) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Returns "deep" A - B
|
||||||
|
* Supported types: any
|
||||||
|
* Not supported: circular references
|
||||||
|
*/
|
||||||
|
function deepSubstract(a: any, b: any): object | null {
|
||||||
|
if (a === b) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof a !== "object" || typeof b !== "object") {
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
const n: any = {};
|
||||||
|
|
||||||
|
Object.keys(a).forEach(key => {
|
||||||
|
if (Object.keys(b).indexOf(key) === -1) {
|
||||||
|
n[key] = a[key];
|
||||||
|
} else {
|
||||||
|
const diff = deepSubstract(a[key], b[key]);
|
||||||
|
if (diff !== null) {
|
||||||
|
n[key] = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Object.keys(n).length) {
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* HOC that checks that on every update whether the props update actually represents a real update.
|
||||||
|
* We do this by checking that props update represent a real update of data.
|
||||||
|
* We also check that the update is not solely due to a function changing reference.
|
||||||
|
* As a result, errors will be logged in the console in case of wasted renders.
|
||||||
|
* This component should only be used locally for troublshooting,
|
||||||
|
* no usage of it should be checked-in because using it is expensive.
|
||||||
|
*/
|
||||||
|
export function debugOnlyWastedRenderDetector<T>(Component: React.ComponentType<T>): React.ComponentType<T> {
|
||||||
|
return (props: T) => (
|
||||||
|
<React.Fragment>
|
||||||
|
<Component {...props} />
|
||||||
|
<WarningComponent {...props} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2015",
|
||||||
|
"lib": ["es2015", "dom"],
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "lib",
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"inlineSourceMap": true,
|
||||||
|
"inlineSources": false,
|
||||||
|
"jsx": "react",
|
||||||
|
"declaration": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"preserveWatchOutput": true
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||||
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@types/prop-types@*":
|
||||||
|
version "15.5.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.5.8.tgz#8ae4e0ea205fe95c3901a5a1df7f66495e3a56ce"
|
||||||
|
integrity sha512-3AQoUxQcQtLHsK25wtTWIoIpgYjH3vSDroZOUr7PpCHw/jLY1RB9z9E8dBT/OSmwStVgkRNvdh+ZHNiomRieaw==
|
||||||
|
|
||||||
|
"@types/react@>=16.0.0":
|
||||||
|
version "16.7.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.7.22.tgz#5bc6d166d5ac34b835756f0b736c7b1af0043e81"
|
||||||
|
integrity sha512-j/3tVoY09kHcTfbia4l67ofQn9xvktUvlC/4QN0KuBHAXlbU/wuGKMb8WfEb/vIcWxsOxHv559uYprkFDFfP8Q==
|
||||||
|
dependencies:
|
||||||
|
"@types/prop-types" "*"
|
||||||
|
csstype "^2.2.0"
|
||||||
|
|
||||||
|
csstype@^2.2.0:
|
||||||
|
version "2.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.2.tgz#3043d5e065454579afc7478a18de41909c8a2f01"
|
||||||
|
integrity sha512-Rl7PvTae0pflc1YtxtKbiSqq20Ts6vpIYOD5WBafl4y123DyHUeLrRdQP66sQW8/6gmX8jrYJLXwNeMqYVJcow==
|
||||||
|
|
||||||
|
"js-tokens@^3.0.0 || ^4.0.0":
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
|
||||||
|
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
|
||||||
|
|
||||||
|
loose-envify@^1.1.0, loose-envify@^1.3.1:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||||
|
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||||
|
dependencies:
|
||||||
|
js-tokens "^3.0.0 || ^4.0.0"
|
||||||
|
|
||||||
|
object-assign@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
|
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
|
||||||
|
|
||||||
|
prop-types@^15.6.2:
|
||||||
|
version "15.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102"
|
||||||
|
integrity sha512-3pboPvLiWD7dkI3qf3KbUe6hKFKa52w+AE0VCqECtf+QHAKgOL37tTaNCnuX1nAAQ4ZhyP+kYVKf8rLmJ/feDQ==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.3.1"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
|
react@>=16.0.0:
|
||||||
|
version "16.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react/-/react-16.7.0.tgz#b674ec396b0a5715873b350446f7ea0802ab6381"
|
||||||
|
integrity sha512-StCz3QY8lxTb5cl2HJxjwLFOXPIFQp+p+hxQfc8WE0QiLfCtIlKj8/+5tjjKm8uSTlAW+fCPaavGFS06V9Ar3A==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.1.0"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
scheduler "^0.12.0"
|
||||||
|
|
||||||
|
scheduler@^0.12.0:
|
||||||
|
version "0.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.12.0.tgz#8ab17699939c0aedc5a196a657743c496538647b"
|
||||||
|
integrity sha512-t7MBR28Akcp4Jm+QoR63XgAi9YgCUmgvDHqf5otgAj4QvdoBE4ImCX0ffehefePPG+aitiYHp0g/mW6s4Tp+dw==
|
||||||
|
dependencies:
|
||||||
|
loose-envify "^1.1.0"
|
||||||
|
object-assign "^4.1.1"
|
||||||
|
|
||||||
|
typescript@^3.3.1:
|
||||||
|
version "3.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b"
|
||||||
|
integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA==
|
Загрузка…
Ссылка в новой задаче