mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
[mcp] Refactor (#33085)
Just some cleanup. Mainly, we now take the number of iterations as an argument. Everything else is just code movement and small tweaks.
This commit is contained in:
parent
b5450b0738
commit
dc2b11817b
|
|
@ -275,6 +275,83 @@ server.tool(
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
server.tool(
|
||||||
|
'review-react-runtime',
|
||||||
|
`Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.
|
||||||
|
<requirements>
|
||||||
|
This tool has some requirements on the code input:
|
||||||
|
- The react code that is passed into this tool MUST contain an App functional component without arrow function.
|
||||||
|
- DO NOT export anything since we can't parse export syntax with this tool.
|
||||||
|
- Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect
|
||||||
|
</requirements>
|
||||||
|
|
||||||
|
<goals>
|
||||||
|
- LCP - loading speed: good ≤ 2.5 s, needs-improvement 2.5-4 s, poor > 4 s
|
||||||
|
- INP - input responsiveness: good ≤ 200 ms, needs-improvement 200-500 ms, poor > 500 ms
|
||||||
|
- CLS - visual stability: good ≤ 0.10, needs-improvement 0.10-0.25, poor > 0.25
|
||||||
|
- (Optional: FCP ≤ 1.8 s, TTFB ≤ 0.8 s)
|
||||||
|
</goals>
|
||||||
|
|
||||||
|
<evaluation>
|
||||||
|
Classify each metric with the thresholds above. Identify the worst category in the order poor > needs-improvement > good.
|
||||||
|
</evaluation>
|
||||||
|
|
||||||
|
<iterate>
|
||||||
|
(repeat until every metric is good or two consecutive cycles show no gain)
|
||||||
|
- Apply one focused change based on the failing metric plus React-specific guidance:
|
||||||
|
- LCP: lazy-load off-screen images, inline critical CSS, preconnect, use React.lazy + Suspense for below-the-fold modules. if the user requests for it, use React Server Components for static content (Server Components).
|
||||||
|
- INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect.
|
||||||
|
- CLS: reserve space via explicit width/height or aspect-ratio, keep stable list keys, use fixed-size skeleton loaders, animate only transform/opacity, avoid inserting ads or banners without placeholders.
|
||||||
|
|
||||||
|
Stop when every metric is classified as good. Return the final metric table and the list of applied changes.
|
||||||
|
</iterate>
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
text: z.string(),
|
||||||
|
iterations: z.number().optional().default(2),
|
||||||
|
},
|
||||||
|
async ({text, iterations}) => {
|
||||||
|
try {
|
||||||
|
const results = await measurePerformance(text, iterations);
|
||||||
|
const formattedResults = `
|
||||||
|
# React Component Performance Results
|
||||||
|
|
||||||
|
## Mean Render Time
|
||||||
|
${results.renderTime / iterations}ms
|
||||||
|
|
||||||
|
## Mean Web Vitals
|
||||||
|
- Cumulative Layout Shift (CLS): ${results.webVitals.cls / iterations}ms
|
||||||
|
- Largest Contentful Paint (LCP): ${results.webVitals.lcp / iterations}ms
|
||||||
|
- Interaction to Next Paint (INP): ${results.webVitals.inp / iterations}ms
|
||||||
|
- First Input Delay (FID): ${results.webVitals.fid / iterations}ms
|
||||||
|
|
||||||
|
## Mean React Profiler
|
||||||
|
- Actual Duration: ${results.reactProfiler.actualDuration / iterations}ms
|
||||||
|
- Base Duration: ${results.reactProfiler.baseDuration / iterations}ms
|
||||||
|
`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text' as const,
|
||||||
|
text: formattedResults,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
isError: true,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: 'text' as const,
|
||||||
|
text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
server.prompt('review-react-code', () => ({
|
server.prompt('review-react-code', () => ({
|
||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
|
|
@ -354,129 +431,6 @@ Server Components - Shift data-heavy logic to the server whenever possible. Brea
|
||||||
],
|
],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
server.tool(
|
|
||||||
'review-react-runtime',
|
|
||||||
`Run this tool every time you propose a performance related change to verify if your suggestion actually improves performance.
|
|
||||||
<requirements>
|
|
||||||
This tool has some requirements on the code input:
|
|
||||||
- The react code that is passed into this tool MUST contain an App functional component without arrow function.
|
|
||||||
- DO NOT export anything since we can't parse export syntax with this tool.
|
|
||||||
- Only import React from 'react' and use all hooks and imports using the React. prefix like React.useState and React.useEffect
|
|
||||||
</requirements>
|
|
||||||
|
|
||||||
<goals>
|
|
||||||
- LCP - loading speed: good ≤ 2.5 s, needs-improvement 2.5-4 s, poor > 4 s
|
|
||||||
- INP - input responsiveness: good ≤ 200 ms, needs-improvement 200-500 ms, poor > 500 ms
|
|
||||||
- CLS - visual stability: good ≤ 0.10, needs-improvement 0.10-0.25, poor > 0.25
|
|
||||||
- (Optional: FCP ≤ 1.8 s, TTFB ≤ 0.8 s)
|
|
||||||
</goals>
|
|
||||||
|
|
||||||
<evaluation>
|
|
||||||
Classify each metric with the thresholds above. Identify the worst category in the order poor > needs-improvement > good.
|
|
||||||
</evaluation>
|
|
||||||
|
|
||||||
<iterate>
|
|
||||||
(repeat until every metric is good or two consecutive cycles show no gain)
|
|
||||||
- Apply one focused change based on the failing metric plus React-specific guidance:
|
|
||||||
- LCP: lazy-load off-screen images, inline critical CSS, preconnect, use React.lazy + Suspense for below-the-fold modules. if the user requests for it, use React Server Components for static content (Server Components).
|
|
||||||
- INP: wrap non-critical updates in useTransition, avoid calling setState inside useEffect.
|
|
||||||
- CLS: reserve space via explicit width/height or aspect-ratio, keep stable list keys, use fixed-size skeleton loaders, animate only transform/opacity, avoid inserting ads or banners without placeholders.
|
|
||||||
|
|
||||||
Stop when every metric is classified as good. Return the final metric table and the list of applied changes.
|
|
||||||
</iterate>
|
|
||||||
`,
|
|
||||||
{
|
|
||||||
text: z.string(),
|
|
||||||
},
|
|
||||||
async ({text}) => {
|
|
||||||
try {
|
|
||||||
const iterations = 20;
|
|
||||||
|
|
||||||
let perfData = {
|
|
||||||
renderTime: 0,
|
|
||||||
webVitals: {
|
|
||||||
cls: 0,
|
|
||||||
lcp: 0,
|
|
||||||
inp: 0,
|
|
||||||
fid: 0,
|
|
||||||
ttfb: 0,
|
|
||||||
},
|
|
||||||
reactProfilerMetrics: {
|
|
||||||
id: 0,
|
|
||||||
phase: 0,
|
|
||||||
actualDuration: 0,
|
|
||||||
baseDuration: 0,
|
|
||||||
startTime: 0,
|
|
||||||
commitTime: 0,
|
|
||||||
},
|
|
||||||
error: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < iterations; i++) {
|
|
||||||
const performanceResults = await measurePerformance(text);
|
|
||||||
perfData.renderTime += performanceResults.renderTime;
|
|
||||||
perfData.webVitals.cls += performanceResults.webVitals.cls || 0;
|
|
||||||
perfData.webVitals.lcp += performanceResults.webVitals.lcp || 0;
|
|
||||||
perfData.webVitals.inp += performanceResults.webVitals.inp || 0;
|
|
||||||
perfData.webVitals.fid += performanceResults.webVitals.fid || 0;
|
|
||||||
perfData.webVitals.ttfb += performanceResults.webVitals.ttfb || 0;
|
|
||||||
|
|
||||||
perfData.reactProfilerMetrics.id +=
|
|
||||||
performanceResults.reactProfilerMetrics.actualDuration || 0;
|
|
||||||
perfData.reactProfilerMetrics.phase +=
|
|
||||||
performanceResults.reactProfilerMetrics.phase || 0;
|
|
||||||
perfData.reactProfilerMetrics.actualDuration +=
|
|
||||||
performanceResults.reactProfilerMetrics.actualDuration || 0;
|
|
||||||
perfData.reactProfilerMetrics.baseDuration +=
|
|
||||||
performanceResults.reactProfilerMetrics.baseDuration || 0;
|
|
||||||
perfData.reactProfilerMetrics.startTime +=
|
|
||||||
performanceResults.reactProfilerMetrics.startTime || 0;
|
|
||||||
perfData.reactProfilerMetrics.commitTime +=
|
|
||||||
performanceResults.reactProfilerMetrics.commitTime || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formattedResults = `
|
|
||||||
# React Component Performance Results
|
|
||||||
|
|
||||||
## Mean Render Time
|
|
||||||
${perfData.renderTime / iterations}ms
|
|
||||||
|
|
||||||
## Mean Web Vitals
|
|
||||||
- Cumulative Layout Shift (CLS): ${perfData.webVitals.cls / iterations}
|
|
||||||
- Largest Contentful Paint (LCP): ${perfData.webVitals.lcp / iterations}ms
|
|
||||||
- Interaction to Next Paint (INP): ${perfData.webVitals.inp / iterations}ms
|
|
||||||
- First Input Delay (FID): ${perfData.webVitals.fid / iterations}ms
|
|
||||||
- Time to First Byte (TTFB): ${perfData.webVitals.ttfb / iterations}ms
|
|
||||||
|
|
||||||
## Mean React Profiler
|
|
||||||
- Actual Duration: ${perfData.reactProfilerMetrics.actualDuration / iterations}ms
|
|
||||||
- Base Duration: ${perfData.reactProfilerMetrics.baseDuration / iterations}ms
|
|
||||||
- Start Time: ${perfData.reactProfilerMetrics.startTime / iterations}ms
|
|
||||||
- Commit Time: ${perfData.reactProfilerMetrics.commitTime / iterations}ms
|
|
||||||
`;
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text' as const,
|
|
||||||
text: formattedResults,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
return {
|
|
||||||
isError: true,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: 'text' as const,
|
|
||||||
text: `Error measuring performance: ${error.message}\n\n${error.stack}`,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
await server.connect(transport);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,32 @@
|
||||||
import * as babel from '@babel/core';
|
import * as babel from '@babel/core';
|
||||||
import puppeteer from 'puppeteer';
|
import puppeteer from 'puppeteer';
|
||||||
|
|
||||||
export async function measurePerformance(code: string) {
|
type PerformanceResults = {
|
||||||
|
renderTime: number;
|
||||||
|
webVitals: {
|
||||||
|
cls: number;
|
||||||
|
lcp: number;
|
||||||
|
inp: number;
|
||||||
|
fid: number;
|
||||||
|
ttfb: number;
|
||||||
|
};
|
||||||
|
reactProfiler: {
|
||||||
|
id: number;
|
||||||
|
phase: number;
|
||||||
|
actualDuration: number;
|
||||||
|
baseDuration: number;
|
||||||
|
startTime: number;
|
||||||
|
commitTime: number;
|
||||||
|
};
|
||||||
|
error: Error | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function measurePerformance(
|
||||||
|
code: string,
|
||||||
|
iterations: number,
|
||||||
|
): Promise<PerformanceResults> {
|
||||||
const babelOptions = {
|
const babelOptions = {
|
||||||
|
filename: 'anonymous.tsx',
|
||||||
configFile: false,
|
configFile: false,
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
presets: [
|
presets: [
|
||||||
|
|
@ -12,16 +36,13 @@ export async function measurePerformance(code: string) {
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse the code to AST
|
|
||||||
const parsed = await babel.parseAsync(code, babelOptions);
|
const parsed = await babel.parseAsync(code, babelOptions);
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
throw new Error('Failed to parse code');
|
throw new Error('Failed to parse code');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform AST to browser-compatible JavaScript
|
|
||||||
const transformResult = await babel.transformFromAstAsync(parsed, undefined, {
|
const transformResult = await babel.transformFromAstAsync(parsed, undefined, {
|
||||||
...babelOptions,
|
...babelOptions,
|
||||||
filename: 'file.jsx',
|
|
||||||
plugins: [
|
plugins: [
|
||||||
() => ({
|
() => ({
|
||||||
visitor: {
|
visitor: {
|
||||||
|
|
@ -44,104 +65,158 @@ export async function measurePerformance(code: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const browser = await puppeteer.launch();
|
const browser = await puppeteer.launch();
|
||||||
|
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
await page.setViewport({width: 1280, height: 720});
|
await page.setViewport({width: 1280, height: 720});
|
||||||
const html = buildHtml(transpiled);
|
const html = buildHtml(transpiled);
|
||||||
await page.setContent(html, {waitUntil: 'networkidle0'});
|
|
||||||
|
|
||||||
await page.waitForFunction(
|
let performanceResults: PerformanceResults = {
|
||||||
'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
|
renderTime: 0,
|
||||||
);
|
webVitals: {
|
||||||
|
cls: 0,
|
||||||
|
lcp: 0,
|
||||||
|
inp: 0,
|
||||||
|
fid: 0,
|
||||||
|
ttfb: 0,
|
||||||
|
},
|
||||||
|
reactProfiler: {
|
||||||
|
id: 0,
|
||||||
|
phase: 0,
|
||||||
|
actualDuration: 0,
|
||||||
|
baseDuration: 0,
|
||||||
|
startTime: 0,
|
||||||
|
commitTime: 0,
|
||||||
|
},
|
||||||
|
error: null,
|
||||||
|
};
|
||||||
|
|
||||||
const result = await page.evaluate(() => {
|
for (let ii = 0; ii < iterations; ii++) {
|
||||||
return (window as any).__RESULT__;
|
await page.setContent(html, {waitUntil: 'networkidle0'});
|
||||||
});
|
await page.waitForFunction(
|
||||||
|
'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)',
|
||||||
|
);
|
||||||
|
// ui chaos monkey
|
||||||
|
await page.waitForFunction(`window.__RESULT__ !== undefined && (function() {
|
||||||
|
for (const el of [...document.querySelectorAll('a'), ...document.querySelectorAll('button')]) {
|
||||||
|
console.log(el);
|
||||||
|
el.click();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})() `);
|
||||||
|
const evaluationResult: PerformanceResults = await page.evaluate(() => {
|
||||||
|
return (window as any).__RESULT__;
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: investigate why webvital metrics are not populating correctly
|
||||||
|
performanceResults.renderTime += evaluationResult.renderTime;
|
||||||
|
performanceResults.webVitals.cls += evaluationResult.webVitals.cls || 0;
|
||||||
|
performanceResults.webVitals.lcp += evaluationResult.webVitals.lcp || 0;
|
||||||
|
performanceResults.webVitals.inp += evaluationResult.webVitals.inp || 0;
|
||||||
|
performanceResults.webVitals.fid += evaluationResult.webVitals.fid || 0;
|
||||||
|
performanceResults.webVitals.ttfb += evaluationResult.webVitals.ttfb || 0;
|
||||||
|
|
||||||
|
performanceResults.reactProfiler.id +=
|
||||||
|
evaluationResult.reactProfiler.actualDuration || 0;
|
||||||
|
performanceResults.reactProfiler.phase +=
|
||||||
|
evaluationResult.reactProfiler.phase || 0;
|
||||||
|
performanceResults.reactProfiler.actualDuration +=
|
||||||
|
evaluationResult.reactProfiler.actualDuration || 0;
|
||||||
|
performanceResults.reactProfiler.baseDuration +=
|
||||||
|
evaluationResult.reactProfiler.baseDuration || 0;
|
||||||
|
performanceResults.reactProfiler.startTime +=
|
||||||
|
evaluationResult.reactProfiler.startTime || 0;
|
||||||
|
performanceResults.reactProfiler.commitTime +=
|
||||||
|
evaluationResult.reactProfiler.commitTime || 0;
|
||||||
|
|
||||||
|
performanceResults.error = evaluationResult.error;
|
||||||
|
}
|
||||||
|
|
||||||
await browser.close();
|
await browser.close();
|
||||||
return result;
|
|
||||||
|
return performanceResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildHtml(transpiled: string) {
|
function buildHtml(transpiled: string) {
|
||||||
const html = `
|
const html = `
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<title>React Performance Test</title>
|
<title>React Performance Test</title>
|
||||||
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
|
||||||
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
|
||||||
<script src="https://unpkg.com/web-vitals@3.0.0/dist/web-vitals.iife.js"></script>
|
<script src="https://unpkg.com/web-vitals@3.0.0/dist/web-vitals.iife.js"></script>
|
||||||
<style>
|
<style>
|
||||||
body { margin: 0; }
|
body { margin: 0; }
|
||||||
#root { padding: 20px; }
|
#root { padding: 20px; }
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<script>
|
<script>
|
||||||
window.__RESULT__ = {
|
window.__RESULT__ = {
|
||||||
renderTime: null,
|
renderTime: null,
|
||||||
webVitals: {},
|
webVitals: {},
|
||||||
reactProfilerMetrics: {},
|
reactProfiler: {},
|
||||||
error: null
|
error: null
|
||||||
};
|
};
|
||||||
|
|
||||||
webVitals.onCLS((metric) => { window.__RESULT__.webVitals.cls = metric; });
|
webVitals.onCLS((metric) => { window.__RESULT__.webVitals.cls = metric; });
|
||||||
webVitals.onLCP((metric) => { window.__RESULT__.webVitals.lcp = metric; });
|
webVitals.onLCP((metric) => { window.__RESULT__.webVitals.lcp = metric; });
|
||||||
webVitals.onINP((metric) => { window.__RESULT__.webVitals.inp = metric; });
|
webVitals.onINP((metric) => { window.__RESULT__.webVitals.inp = metric; });
|
||||||
webVitals.onFID((metric) => { window.__RESULT__.webVitals.fid = metric; });
|
webVitals.onFID((metric) => { window.__RESULT__.webVitals.fid = metric; });
|
||||||
webVitals.onTTFB((metric) => { window.__RESULT__.webVitals.ttfb = metric; });
|
webVitals.onTTFB((metric) => { window.__RESULT__.webVitals.ttfb = metric; });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
${transpiled}
|
${transpiled}
|
||||||
|
|
||||||
window.App = App;
|
window.App = App;
|
||||||
|
|
||||||
// Render the component to the DOM with profiling
|
// Render the component to the DOM with profiling
|
||||||
const AppComponent = window.App || (() => React.createElement('div', null, 'No App component exported'));
|
const AppComponent = window.App || (() => React.createElement('div', null, 'No App component exported'));
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root'), {
|
const root = ReactDOM.createRoot(document.getElementById('root'), {
|
||||||
onUncaughtError: (error, errorInfo) => {
|
onUncaughtError: (error, errorInfo) => {
|
||||||
window.__RESULT__.error = error;
|
window.__RESULT__.error = error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderStart = performance.now()
|
const renderStart = performance.now()
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
React.createElement(React.Profiler, {
|
React.createElement(React.Profiler, {
|
||||||
id: 'App',
|
id: 'App',
|
||||||
onRender: (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
|
onRender: (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
|
||||||
window.__RESULT__.reactProfilerMetrics.id = id;
|
window.__RESULT__.reactProfiler.id = id;
|
||||||
window.__RESULT__.reactProfilerMetrics.phase = phase;
|
window.__RESULT__.reactProfiler.phase = phase;
|
||||||
window.__RESULT__.reactProfilerMetrics.actualDuration = actualDuration;
|
window.__RESULT__.reactProfiler.actualDuration = actualDuration;
|
||||||
window.__RESULT__.reactProfilerMetrics.baseDuration = baseDuration;
|
window.__RESULT__.reactProfiler.baseDuration = baseDuration;
|
||||||
window.__RESULT__.reactProfilerMetrics.startTime = startTime;
|
window.__RESULT__.reactProfiler.startTime = startTime;
|
||||||
window.__RESULT__.reactProfilerMetrics.commitTime = commitTime;
|
window.__RESULT__.reactProfiler.commitTime = commitTime;
|
||||||
}
|
}
|
||||||
}, React.createElement(AppComponent))
|
}, React.createElement(AppComponent))
|
||||||
);
|
);
|
||||||
|
|
||||||
const renderEnd = performance.now();
|
const renderEnd = performance.now();
|
||||||
|
|
||||||
window.__RESULT__.renderTime = renderEnd - renderStart;
|
window.__RESULT__.renderTime = renderEnd - renderStart;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error rendering component:', error);
|
console.error('Error rendering component:', error);
|
||||||
window.__RESULT__.error = {
|
window.__RESULT__.error = error;
|
||||||
message: error.message,
|
}
|
||||||
stack: error.stack
|
</script>
|
||||||
};
|
<script>
|
||||||
}
|
window.onerror = function(message, url, lineNumber) {
|
||||||
</script>
|
const formattedMessage = message + '@' + lineNumber;
|
||||||
<script>
|
if (window.__RESULT__.error && window.__RESULT__.error.message != null) {
|
||||||
window.onerror = function(message, url, lineNumber) {
|
window.__RESULT__.error = window.__RESULT__.error + '\n\n' + formattedMessage;
|
||||||
window.__RESULT__.error = message;
|
} else {
|
||||||
};
|
window.__RESULT__.error = message + formattedMessage;
|
||||||
</script>
|
}
|
||||||
</body>
|
};
|
||||||
</html>
|
</script>
|
||||||
`;
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user