|
26 | 26 | import { transform } from "@babel/core";
|
27 | 27 | import plugin from "../src/index";
|
28 | 28 |
|
| 29 | +const getTime = (): number => { |
| 30 | + if (typeof performance !== "undefined" && performance.now) { |
| 31 | + return performance.now(); |
| 32 | + } |
| 33 | + return Date.now(); |
| 34 | +}; |
| 35 | + |
29 | 36 | const BananasPizzaAppStandardInput = `import React, { Component } from 'react';
|
30 | 37 | import { StyleSheet, Text, TextInput, View, Image, UIManager } from 'react-native';
|
31 | 38 |
|
@@ -1741,3 +1748,140 @@ export default function TestComponent() {
|
1741 | 1748 | expect(result?.code).toMatchSnapshot();
|
1742 | 1749 | });
|
1743 | 1750 | });
|
| 1751 | + |
| 1752 | +describe("Fragment Performance Benchmarks", () => { |
| 1753 | + interface TestFile { |
| 1754 | + filename: string; |
| 1755 | + code: string; |
| 1756 | + } |
| 1757 | + |
| 1758 | + interface BenchmarkResult { |
| 1759 | + avg: number; |
| 1760 | + min: number; |
| 1761 | + max: number; |
| 1762 | + median: number; |
| 1763 | + } |
| 1764 | + |
| 1765 | + const createTestProject = (fileCount: number, fragmentRatio = 0.4): TestFile[] => { |
| 1766 | + const files: TestFile[] = []; |
| 1767 | + |
| 1768 | + for (let i = 0; i < fileCount; i++) { |
| 1769 | + const hasFragment = Math.random() < fragmentRatio; |
| 1770 | + const patterns = [ |
| 1771 | + `import React, { Fragment } from 'react';`, |
| 1772 | + `import { Fragment } from 'react';`, |
| 1773 | + `import * as React from 'react';`, |
| 1774 | + `import React from 'react';\nconst { Fragment } = React;`, |
| 1775 | + `import MyReact from 'react';\nconst { Fragment: F } = MyReact;`, |
| 1776 | + ]; |
| 1777 | + |
| 1778 | + const pattern = patterns[Math.floor(Math.random() * patterns.length)] as string; |
| 1779 | + const fragmentType = getFragmentType(pattern); |
| 1780 | + |
| 1781 | + // Add imports to other files for realistic import traversal |
| 1782 | + const imports: string[] = []; |
| 1783 | + const numImports = Math.min(3, i); |
| 1784 | + for (let j = 0; j < numImports; j++) { |
| 1785 | + imports.push(`import Component${j} from './component${j}';`); |
| 1786 | + } |
| 1787 | + |
| 1788 | + const code = `${pattern} |
| 1789 | +${imports.join("\n")} |
| 1790 | +
|
| 1791 | +export default function Component${i}() { |
| 1792 | + return ${ |
| 1793 | + hasFragment |
| 1794 | + ? `<${fragmentType}><div>Content ${i}</div></${fragmentType}>` |
| 1795 | + : `<div>Component ${i}</div>` |
| 1796 | + }; |
| 1797 | +}`; |
| 1798 | + |
| 1799 | + files.push({ filename: `component${i}.js`, code }); |
| 1800 | + } |
| 1801 | + |
| 1802 | + return files; |
| 1803 | + }; |
| 1804 | + |
| 1805 | + const getFragmentType = (pattern: string): string => { |
| 1806 | + if (pattern.includes("Fragment: F")) return "F"; |
| 1807 | + if (pattern.includes("Fragment }")) return "Fragment"; |
| 1808 | + if (pattern.includes("* as React")) return "React.Fragment"; |
| 1809 | + if (pattern.includes("const { Fragment }")) return "Fragment"; |
| 1810 | + return "Fragment"; |
| 1811 | + }; |
| 1812 | + |
| 1813 | + const runBenchmark = (files: TestFile[], iterations = 10): BenchmarkResult => { |
| 1814 | + const times: number[] = []; |
| 1815 | + |
| 1816 | + for (let i = 0; i < iterations; i++) { |
| 1817 | + const start = getTime(); |
| 1818 | + |
| 1819 | + // Transform all files (simulating your plugin processing) |
| 1820 | + files.forEach((file: TestFile) => { |
| 1821 | + const result = transform(file.code, { |
| 1822 | + filename: `/${file.filename}`, |
| 1823 | + configFile: false, |
| 1824 | + presets: ["@babel/preset-react"], |
| 1825 | + plugins: [plugin], |
| 1826 | + }); |
| 1827 | + if (!result?.code) throw new Error("Transform failed"); |
| 1828 | + }); |
| 1829 | + |
| 1830 | + const end = getTime(); |
| 1831 | + times.push(end - start); |
| 1832 | + } |
| 1833 | + |
| 1834 | + const sortedTimes = [...times].sort((a, b) => a - b); |
| 1835 | + return { |
| 1836 | + avg: times.reduce((a, b) => a + b) / times.length, |
| 1837 | + min: Math.min(...times), |
| 1838 | + max: Math.max(...times), |
| 1839 | + median: sortedTimes[Math.floor(sortedTimes.length / 2)], |
| 1840 | + }; |
| 1841 | + }; |
| 1842 | + |
| 1843 | + // Test different project sizes |
| 1844 | + const scenarios = [ |
| 1845 | + { name: "small" as const, files: 20, expectedMaxTime: 50 }, |
| 1846 | + { name: "medium" as const, files: 50, expectedMaxTime: 200 }, |
| 1847 | + { name: "large" as const, files: 200, expectedMaxTime: 500 }, |
| 1848 | + ]; |
| 1849 | + |
| 1850 | + scenarios.forEach((scenario) => { |
| 1851 | + it(`should perform well on ${scenario.name} projects (${scenario.files} files)`, () => { |
| 1852 | + const testFiles = createTestProject(scenario.files); |
| 1853 | + const results = runBenchmark(testFiles); |
| 1854 | + |
| 1855 | + expect(results.avg).toBeGreaterThan(0); |
| 1856 | + expect(results.avg).toBeLessThan(scenario.expectedMaxTime); |
| 1857 | + expect(results.median).toBeLessThan(scenario.expectedMaxTime * 0.8); |
| 1858 | + }); |
| 1859 | + }); |
| 1860 | + |
| 1861 | + it("should handle fragment-heavy projects efficiently", () => { |
| 1862 | + const fragmentHeavyFiles = createTestProject(25, 0.8); |
| 1863 | + const normalFiles = createTestProject(25, 0.2); |
| 1864 | + |
| 1865 | + const fragmentResults = runBenchmark(fragmentHeavyFiles, 5); |
| 1866 | + const normalResults = runBenchmark(normalFiles, 5); |
| 1867 | + |
| 1868 | + const overhead = (fragmentResults.avg - normalResults.avg) / normalResults.avg; |
| 1869 | + |
| 1870 | + expect(overhead).toBeLessThan(0.5); // Less than 50% overhead |
| 1871 | + expect(fragmentResults.avg).toBeLessThan(300); |
| 1872 | + }); |
| 1873 | + |
| 1874 | + it("should not consume excessive memory", () => { |
| 1875 | + const testFiles = createTestProject(50); |
| 1876 | + |
| 1877 | + if (global.gc) global.gc(); |
| 1878 | + |
| 1879 | + const memBefore = process.memoryUsage().heapUsed; |
| 1880 | + runBenchmark(testFiles, 1); |
| 1881 | + const memAfter = process.memoryUsage().heapUsed; |
| 1882 | + |
| 1883 | + const memoryDelta = (memAfter - memBefore) / 1024 / 1024; |
| 1884 | + |
| 1885 | + expect(memoryDelta).toBeLessThan(50); |
| 1886 | + }); |
| 1887 | +}); |
0 commit comments