diff --git a/.gitignore b/.gitignore index ca804356..f154dce5 100644 --- a/.gitignore +++ b/.gitignore @@ -58,7 +58,7 @@ typings/ .env .idea -/dist/ +/dist* website/build website/.docusaurus .rts2* diff --git a/perf-testing/.gitignore b/perf-testing/.gitignore new file mode 100644 index 00000000..8a07e4df --- /dev/null +++ b/perf-testing/.gitignore @@ -0,0 +1,35 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# CPU profiles +*.cpuprofile + +# Yarn +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions \ No newline at end of file diff --git a/perf-testing/README.md b/perf-testing/README.md new file mode 100644 index 00000000..e8d83dfe --- /dev/null +++ b/perf-testing/README.md @@ -0,0 +1,71 @@ +# Immer Performance Testing + +This directory contains performance testing tools for Immer, allowing you to benchmark different versions of Immer and analyze CPU profiles to identify performance bottlenecks. + +## Setup + +1. **Install dependencies** (in this directory): + + ```bash + yarn install + ``` + +2. **Build Immer first**: + + ```bash + yarn build-immer + ``` + +3. **Build the benchmark bundle**: + ```bash + yarn build + ``` + +Alternately, you can rebuild both Immer and the benchmarking script: + +```bash +yarn build-with-latest +``` + +## Usage + +### Running Benchmarks + +To run the performance benchmarks: + +```bash +# Run basic benchmarks, with relative version speed comparisons +yarn benchmark + +# Run the benchmarks, but also generate a CPU profile +yarn profile +``` + +### Analyzing CPU Profiles + +After running `yarn profile`, you'll get a `.cpuprofile` file. To analyze it: + +```bash +# Analyze the most recent profile +yarn analyze-profile your-profile.cpuprofile +``` + +## What's Included + +- **immutability-benchmarks.mjs**: Main benchmark script comparing different Immer versions +- **read-cpuprofile.js**: Advanced CPU profile analyzer with sourcemap support +- **rolldown.config.js**: Bundler configuration that eliminates `process.env` overhead + +## Benchmark Versions + +The benchmarks compare: + +- **immer7-10**: Historical Immer versions +- **immer10Perf**: Current development version (references `../dist`) +- **vanilla**: Pure JavaScript implementations for baseline comparison + +## Key Features + +- **Sourcemap support**: CPU profile analysis includes original function names +- **Version-aware analysis**: Breaks down performance by Immer version +- **Production bundling**: Uses Rolldown to eliminate development overhead diff --git a/perf-testing/immutability-benchmarks.mjs b/perf-testing/immutability-benchmarks.mjs new file mode 100644 index 00000000..0afeb0c3 --- /dev/null +++ b/perf-testing/immutability-benchmarks.mjs @@ -0,0 +1,245 @@ +/* eslint-disable no-inner-declarations */ +import "source-map-support/register" + +import {produce as produce5, setAutoFreeze as setAutoFreeze5} from "immer5" +import {produce as produce6, setAutoFreeze as setAutoFreeze6} from "immer6" +import {produce as produce7, setAutoFreeze as setAutoFreeze7} from "immer7" +import {produce as produce8, setAutoFreeze as setAutoFreeze8} from "immer8" +import {produce as produce9, setAutoFreeze as setAutoFreeze9} from "immer9" +import {produce as produce10, setAutoFreeze as setAutoFreeze10} from "immer10" +import { + produce as produce10Perf, + setAutoFreeze as setAutoFreeze10Perf + // Uncomment when using a build of Immer that exposes this function, + // and enable the corresponding line in the setStrictIteration object below. + // setUseStrictIteration as setUseStrictIteration10Perf +} from "immer10Perf" +import {create as produceMutative} from "mutative" +import { + produce as produceMutativeCompat, + setAutoFreeze as setAutoFreezeMutativeCompat +} from "mutative-compat" +import {bench, run, group, summary} from "mitata" + +function createInitialState() { + const initialState = { + largeArray: Array.from({length: 10000}, (_, i) => ({ + id: i, + value: Math.random(), + nested: {key: `key-${i}`, data: Math.random()}, + moreNested: { + items: Array.from({length: 100}, (_, i) => ({id: i, name: String(i)})) + } + })), + otherData: Array.from({length: 10000}, (_, i) => ({ + id: i, + name: `name-${i}`, + isActive: i % 2 === 0 + })) + } + return initialState +} + +const MAX = 1 + +const add = index => ({ + type: "test/addItem", + payload: {id: index, value: index, nested: {data: index}} +}) +const remove = index => ({type: "test/removeItem", payload: index}) +const filter = index => ({type: "test/filterItem", payload: index}) +const update = index => ({ + type: "test/updateItem", + payload: {id: index, value: index, nestedData: index} +}) +const concat = index => ({ + type: "test/concatArray", + payload: Array.from({length: 500}, (_, i) => ({id: i, value: index})) +}) + +const actions = { + add, + remove, + filter, + update, + concat +} + +const immerProducers = { + // immer5: produce5, + // immer6: produce6, + immer7: produce7, + immer8: produce8, + immer9: produce9, + immer10: produce10, + immer10Perf: produce10Perf + // mutative: produceMutative, + // mutativeCompat: produceMutativeCompat +} + +const noop = () => {} + +const setAutoFreezes = { + vanilla: noop, + immer5: setAutoFreeze5, + immer6: setAutoFreeze6, + immer7: setAutoFreeze7, + immer8: setAutoFreeze8, + immer9: setAutoFreeze9, + immer10: setAutoFreeze10, + immer10Perf: setAutoFreeze10Perf, + mutative: noop, + mutativeCompat: setAutoFreezeMutativeCompat +} + +const setStrictIteration = { + vanilla: noop, + immer5: noop, + immer6: noop, + immer7: noop, + immer8: noop, + immer9: noop, + immer10: noop, + immer10Perf: noop, // setUseStrictIteration10Perf, + mutative: noop, + mutativeCompat: noop +} + +const vanillaReducer = (state = createInitialState(), action) => { + switch (action.type) { + case "test/addItem": + return { + ...state, + largeArray: [...state.largeArray, action.payload] + } + case "test/removeItem": { + const newArray = state.largeArray.slice() + newArray.splice(action.payload, 1) + return { + ...state, + largeArray: newArray + } + } + case "test/filterItem": { + const newArray = state.largeArray.filter( + (item, i) => i !== action.payload + ) + return { + ...state, + largeArray: newArray + } + } + case "test/updateItem": { + return { + ...state, + largeArray: state.largeArray.map(item => + item.id === action.payload.id + ? { + ...item, + value: action.payload.value, + nested: {...item.nested, data: action.payload.nestedData} + } + : item + ) + } + } + case "test/concatArray": { + const length = state.largeArray.length + const newArray = action.payload.concat(state.largeArray) + newArray.length = length + return { + ...state, + largeArray: newArray + } + } + default: + return state + } +} + +const createImmerReducer = produce => { + const immerReducer = (state = createInitialState(), action) => + produce(state, draft => { + switch (action.type) { + case "test/addItem": + draft.largeArray.push(action.payload) + break + case "test/removeItem": + draft.largeArray.splice(action.payload, 1) + break + case "test/filterItem": { + draft.largeArray = draft.largeArray.filter( + (item, i) => i !== action.payload + ) + break + } + case "test/updateItem": { + const item = draft.largeArray.find( + item => item.id === action.payload.id + ) + item.value = action.payload.value + item.nested.data = action.payload.nestedData + break + } + case "test/concatArray": { + const length = state.largeArray.length + const newArray = action.payload.concat(state.largeArray) + newArray.length = length + draft.largeArray = newArray + break + } + } + }) + + return immerReducer +} + +function mapValues(obj, fn) { + const result = {} + for (const key in obj) { + result[key] = fn(obj[key]) + } + return result +} + +const reducers = { + vanilla: vanillaReducer, + ...mapValues(immerProducers, createImmerReducer) +} + +function createBenchmarks() { + for (const action in actions) { + summary(function() { + bench(`$action: $version (freeze: $freeze)`, function*(args) { + const version = args.get("version") + const freeze = args.get("freeze") + const action = args.get("action") + + const initialState = createInitialState() + + function benchMethod() { + setAutoFreezes[version](freeze) + setStrictIteration[version](false) + for (let i = 0; i < MAX; i++) { + reducers[version](initialState, actions[action](i)) + } + setAutoFreezes[version](false) + } + + yield benchMethod + }).args({ + version: Object.keys(reducers), + freeze: [false, true], + action: [action] + }) + }) + } +} + +async function main() { + createBenchmarks() + await run() + process.exit(0) +} + +main() diff --git a/perf-testing/package.json b/perf-testing/package.json new file mode 100644 index 00000000..81a43736 --- /dev/null +++ b/perf-testing/package.json @@ -0,0 +1,34 @@ +{ + "name": "immer-perf-testing", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "benchmark": "cross-env NO_COLOR=true node --expose-gc --enable-source-maps dist/immutability-benchmarks.js", + "build": "rolldown -c rolldown.config.js", + "profile": "node --cpu-prof --expose-gc dist/immutability-benchmarks.js", + "analyze-profile": "node read-cpuprofile.js", + "build-immer": "cd .. && yarn build", + "build-with-latest": "yarn build-immer && yarn build", + "build-and-benchmark": "yarn build-with-latest && yarn benchmark", + "build-and-profile": "yarn build-with-latest && yarn profile" + }, + "dependencies": { + "cross-env": "^7.0.3", + "immer5": "npm:immer@5", + "immer6": "npm:immer@6", + "immer7": "npm:immer@7", + "immer8": "npm:immer@8", + "immer9": "npm:immer@9", + "immer10": "npm:immer@10", + "mitata": "^1.0.34", + "mutative": "^1.1.0", + "mutative-compat": "^0.1.2", + "pprof-format": "^2.2.1", + "source-map": "^0.7.4", + "source-map-support": "^0.5.21" + }, + "devDependencies": { + "rolldown": "1.0.0-beta.23" + } +} diff --git a/perf-testing/read-cpuprofile.js b/perf-testing/read-cpuprofile.js new file mode 100644 index 00000000..b3c16af3 --- /dev/null +++ b/perf-testing/read-cpuprofile.js @@ -0,0 +1,563 @@ +import fs from "fs" +import {SourceMapConsumer} from "source-map" + +const profileName = process.argv[2] + +if (!profileName || !fs.existsSync(profileName)) { + console.error("Usage: node read-cpuprofile.js ") + process.exit(1) +} + +const profile = JSON.parse(fs.readFileSync(profileName, "utf8")) + +// Load multiple sourcemaps for better function name resolution +const sourceMapConsumers = new Map() + +// Load main bundled sourcemap +try { + const mainSourceMapPath = "dist/immutability-benchmarks.js.map" + if (fs.existsSync(mainSourceMapPath)) { + const sourceMapContent = fs.readFileSync(mainSourceMapPath, "utf8") + const consumer = await SourceMapConsumer.with( + sourceMapContent, + null, + consumer => consumer + ) + sourceMapConsumers.set("main", consumer) + } +} catch (error) { + console.warn("Could not load main sourcemap:", error.message) +} + +const cjsMinMap = "immer.cjs.production.min.js.map" +const cjsProdMap = "cjs/immer.cjs.production.js.map" + +const immerVersionMaps = { + 5: cjsMinMap, + 6: cjsMinMap, + 7: cjsMinMap, + 8: cjsMinMap, + 9: cjsMinMap, + 10: cjsProdMap, + "10Perf": cjsProdMap +} + +// Load individual Immer version sourcemaps +for (const [version, mapName] of Object.entries(immerVersionMaps)) { + try { + const immerParentPath = + version === "10Perf" ? ".." : `./node_modules/immer${version}` + const sourcemapPath = `${immerParentPath}/dist/${mapName}` + if (fs.existsSync(sourcemapPath)) { + const sourceMapContent = fs.readFileSync(sourcemapPath, "utf8") + const consumer = await SourceMapConsumer.with( + sourceMapContent, + null, + consumer => consumer + ) + sourceMapConsumers.set(`v${version}`, consumer) + console.log(`Loaded sourcemap for Immer v${version}`) + } + } catch (error) { + console.warn( + `Could not load sourcemap for Immer v${version}:`, + error.message + ) + } +} + +console.log(`Loaded ${sourceMapConsumers.size} sourcemaps total\n`) + +// Function to extract Immer version from source path +function extractImmerVersion(sourcePath) { + if (!sourcePath) return "unknown" + + // Match patterns like: immer@7.0.15, immer@8.0.1, etc. + const versionMatch = sourcePath.match(/immer@(\d+(?:\.\d+)*)/) + if (versionMatch) return `v${versionMatch[1]}` + + // Match patterns like: immer5, immer6, immer7, etc. + const simpleVersionMatch = sourcePath.match(/immer(\d+(?:Perf)?)/) + if (simpleVersionMatch) return `v${simpleVersionMatch[1]}` + + // Check for local builds + if (sourcePath.includes("../../dist")) return "v10Perf" + + return "unknown" +} + +// Function to categorize function types for better analysis +function categorizeFunctionType(functionName, sourcePath, location) { + const lowerName = functionName.toLowerCase() + const lowerSource = (sourcePath || "").toLowerCase() + + // Node.js internal functions + const nodeInternals = [ + "requirebuiltin", + "compileforiternalloader", + "writegeneric", + "writestream", + "getheapstatistics", + "open", + "read", + "write", + "stat", + "close", + "readdir", + "createreadstream", + "createwritestream", + "emitwarning", + "process", + "nextick", + "setimmediate", + "settimeout", + "clearimmediate", + "cleartimeout" + ] + + if (nodeInternals.some(internal => lowerName.includes(internal))) { + return "node-internal" + } + + // V8 engine functions + const v8Functions = [ + "get", + "set", + "value", + "call", + "apply", + "bind", + "construct", + "defineProperty", + "getownpropertydescriptor", + "hasownproperty" + ] + + if (v8Functions.some(v8fn => lowerName === v8fn) && location === "unknown") { + return "v8-internal" + } + + // Benchmark/test code + const benchmarkFunctions = [ + "benchmethod", + "main", + "immerreducer", + "vanillareducer", + "createimmerreducer", + "createbenchmarks", + "run", + "bench", + "group", + "summary" + ] + + if (benchmarkFunctions.some(bench => lowerName.includes(bench))) { + return "benchmark" + } + + // Third-party libraries (from node_modules) + if ( + lowerSource.includes("node_modules") || + sourcePath?.includes("node_modules") + ) { + return "third-party" + } + + // Immer functions + if ( + lowerName.includes("immer") || + lowerName.includes("produce") || + lowerName.includes("preparecopy") || + lowerName.includes("finalize") || + lowerName.includes("isdraft") || + lowerName.includes("current") || + lowerName.includes("proxy") + ) { + return "immer" + } + + // Anonymous functions in known files + if ( + functionName === "(anonymous)" && + (location.includes("main.mjs") || location.includes("lib.mjs")) + ) { + return "benchmark" + } + + return "application" +} + +// Enhanced minified function name mapping based on common patterns +const minifiedFunctionPatterns = { + // Common Immer function patterns across versions + n$1: "prepareCopy", + e$1: "finalize", + M$2: "finalizeProperty", + t$1: "isDraft", + r$1: "current", + o$1: "isPlainObject", + i$1: "shallowCopy", + a$1: "each", + u$1: "readPropFromProto", + s$1: "createProxy", + c$1: "createProxyProxy", + l$1: "markChanged", + f$1: "freeze", + d$1: "die" +} + +// TODO Not sure if this actually helps +function enhanceMinifiedName(functionName, version) { + // Try direct pattern matching first + if (minifiedFunctionPatterns[functionName]) { + return minifiedFunctionPatterns[functionName] + } + + // Try pattern matching with version-specific adjustments + const basePattern = functionName.replace(/\$\d+$/, "") + for (const [pattern, realName] of Object.entries(minifiedFunctionPatterns)) { + if (pattern.startsWith(basePattern)) { + return realName + } + } + + return functionName +} + +// Function to resolve minified function names using appropriate sourcemap +function resolveOriginalName(callFrame) { + if (!callFrame.url || !callFrame.url.includes("immutability-benchmarks.js")) { + return { + name: callFrame.functionName || "(anonymous)", + version: "unknown", + location: "unknown" + } + } + + // First try main sourcemap + const mainConsumer = sourceMapConsumers.get("main") + if (mainConsumer) { + try { + const originalPosition = mainConsumer.originalPositionFor({ + line: callFrame.lineNumber + 1, // V8 uses 0-based, sourcemap uses 1-based + column: callFrame.columnNumber + }) + + if (originalPosition.source) { + const sourceFile = originalPosition.source.split("/").pop() || "unknown" + let version = extractImmerVersion(originalPosition.source) + let functionName = + originalPosition.name || callFrame.functionName || "(anonymous)" + const location = `${sourceFile}:${originalPosition.line}` + + // Enhanced version detection for better accuracy + if (version === "unknown") { + // Check for node_modules path pattern like ../node_modules/mitata/src/lib.mjs + const nodeModulesMatch = originalPosition.source.match( + /node_modules\/([^\/]+)/ + ) + if (nodeModulesMatch) { + version = nodeModulesMatch[1] // Extract library name like "mitata" + } else { + version = categorizeFunctionType( + functionName, + originalPosition.source, + location + ) + } + } + + // If we detected a specific Immer version, try to get better resolution from that version's sourcemap + if (version !== "unknown") { + const versionConsumer = sourceMapConsumers.get(version) + if (versionConsumer) { + try { + // For minified functions, try to resolve using the version-specific sourcemap + if ( + functionName.length <= 3 || + /^[a-zA-Z]\$?\d*$/.test(functionName) + ) { + const versionPosition = versionConsumer.originalPositionFor({ + line: 1, // Most minified files are single line + column: callFrame.columnNumber + }) + + if ( + versionPosition.name && + versionPosition.name !== functionName + ) { + return { + name: versionPosition.name, + version: version, + location: `${versionPosition.source?.split("/").pop() || + sourceFile}:${versionPosition.line}` + } + } + } + } catch (error) { + // Continue with main sourcemap result + } + } + } + + return { + name: functionName, + version: version, + location: location + } + } + } catch (error) { + // Fallback to original name if sourcemap resolution fails + } + } + + return { + name: callFrame.functionName || "(anonymous)", + version: "unknown", + location: "unknown" + } +} + +// Extract function call statistics +const functionStats = new Map() +const versionStats = new Map() // Track stats by Immer version +const categoryStats = new Map() // Track stats by function category +const samples = profile.samples || [] +const timeDeltas = profile.timeDeltas || [] + +// Process samples to count function calls +samples.forEach((nodeId, index) => { + const node = profile.nodes[nodeId] + if (node && node.callFrame) { + const resolved = resolveOriginalName(node.callFrame) + const fileName = node.callFrame.url || "" + const sampleCount = timeDeltas[index] || 1 + + // Categorize the function + const category = categorizeFunctionType( + resolved.name, + resolved.source || fileName, + resolved.location + ) + + // Create key with version info + const key = `${resolved.name} [${resolved.version}] (${resolved.location})` + functionStats.set(key, (functionStats.get(key) || 0) + sampleCount) + + // Track by version + if (!versionStats.has(resolved.version)) { + versionStats.set(resolved.version, new Map()) + } + const versionMap = versionStats.get(resolved.version) + const versionKey = `${resolved.name} (${resolved.location})` + versionMap.set(versionKey, (versionMap.get(versionKey) || 0) + sampleCount) + + // Track by category + if (!categoryStats.has(category)) { + categoryStats.set(category, new Map()) + } + const categoryMap = categoryStats.get(category) + const categoryKey = `${resolved.name} [${resolved.version}] (${resolved.location})` + categoryMap.set( + categoryKey, + (categoryMap.get(categoryKey) || 0) + sampleCount + ) + } +}) + +// Show breakdown by Immer version +console.log("\n\nBreakdown by Immer Version:") +console.log("===========================") +const sortedVersions = Array.from(versionStats.entries()) + .map(([version, funcMap]) => { + const totalSamples = Array.from(funcMap.values()).reduce( + (sum, count) => sum + count, + 0 + ) + return {version, totalSamples, functions: funcMap} + }) + .sort((a, b) => b.totalSamples - a.totalSamples) + +// don't log "mitata" or "node" +sortedVersions + .filter(sv => sv.version.startsWith("v")) + .forEach(({version, totalSamples, functions}) => { + if (totalSamples < 25000) return // Skip low-impact versions + console.log(`\n${version}: ${totalSamples} total samples`) + const topFunctions = Array.from(functions.entries()) + .sort((a, b) => b[1] - a[1]) + .slice(0, 20) + + topFunctions.forEach(([func, samples], index) => { + console.log(` ${index + 1}. ${func}: ${samples} samples`) + }) + }) + +// Performance comparison between versions for key functions +console.log("\n\nPerformance Comparison - Key Functions by Version:") +console.log("==================================================") +const keyFunctions = [ + "prepareCopy", + "finalize", + "finalizeProperty", + "each", + "isPlainObject" +] + +keyFunctions.forEach(funcName => { + console.log(`\n${funcName}:`) + const versionComparison = [] + + versionStats.forEach((functions, version) => { + let totalSamples = 0 + functions.forEach((samples, func) => { + if (func.toLowerCase().includes(funcName.toLowerCase())) { + totalSamples += samples + } + }) + if (totalSamples > 0) { + versionComparison.push({version, samples: totalSamples}) + } + }) + + versionComparison + .sort((a, b) => b.samples - a.samples) + .forEach(({version, samples}) => { + console.log(` ${version}: ${samples} samples`) + }) +}) + +// // Analysis by function category +// console.log("\n\nBreakdown by Function Category:") +// console.log("===============================") +// const sortedCategories = Array.from(categoryStats.entries()) +// .map(([category, funcMap]) => { +// const totalSamples = Array.from(funcMap.values()).reduce( +// (sum, count) => sum + count, +// 0 +// ) +// return {category, totalSamples, functions: funcMap} +// }) +// .sort((a, b) => b.totalSamples - a.totalSamples) + +// const totalSamples = Array.from(functionStats.values()).reduce( +// (sum, samples) => sum + samples, +// 0 +// ) + +// sortedCategories.forEach(({category, totalSamples: catSamples, functions}) => { +// const percentage = ((catSamples / totalSamples) * 100).toFixed(1) +// console.log(`\n${category}: ${catSamples} samples (${percentage}%)`) + +// // Show top functions in this category +// const topFunctions = Array.from(functions.entries()) +// .sort((a, b) => b[1] - a[1]) +// .slice(0, 10) + +// topFunctions.forEach(([func, samples], index) => { +// const funcPercentage = ((samples / catSamples) * 100).toFixed(1) +// console.log( +// ` ${index + +// 1}. ${func}: ${samples} samples (${funcPercentage}% of category)` +// ) +// }) +// }) + +// // Analysis of truly uncategorized functions +// console.log("\n\nAnalysis of uncategorized functions:") +// console.log("====================================") + +// // Get functions that are still in the 'application' category with unknown version +// // These are the ones that need better categorization +// const uncategorizedStats = [] +// categoryStats.get("application")?.forEach((samples, funcKey) => { +// if (funcKey.includes("[unknown]")) { +// uncategorizedStats.push([funcKey, samples]) +// } +// }) + +// // Also check for any functions that might have been missed entirely +// const versionUnknownStats = Array.from(functionStats.entries()).filter( +// ([func]) => { +// // Only include functions that are both version unknown AND not properly categorized +// if (!func.includes("[unknown]")) return false + +// // Check if this function was categorized as something other than 'application' +// const funcName = func.split(" [")[0] +// const category = categorizeFunctionType(funcName, "", "unknown") +// return category === "application" +// } +// ) + +// // Combine and deduplicate +// const allUncategorized = new Map() +// uncategorizedStats.forEach(([func, samples]) => { +// allUncategorized.set(func, samples) +// }) +// versionUnknownStats.forEach(([func, samples]) => { +// if (!allUncategorized.has(func)) { +// allUncategorized.set(func, samples) +// } +// }) + +// const sortedUncategorized = Array.from(allUncategorized.entries()) +// .sort((a, b) => b[1] - a[1]) +// .slice(0, 15) + +// if (sortedUncategorized.length > 0) { +// console.log( +// "Top uncategorized functions (may need better categorization logic):" +// ) +// sortedUncategorized.forEach(([func, samples], index) => { +// console.log(` ${index + 1}. ${func}: ${samples} samples`) +// }) + +// const totalUncategorizedSamples = sortedUncategorized.reduce( +// (sum, [, samples]) => sum + samples, +// 0 +// ) +// const totalSamples = Array.from(functionStats.values()).reduce( +// (sum, samples) => sum + samples, +// 0 +// ) +// const uncategorizedPercentage = ( +// (totalUncategorizedSamples / totalSamples) * +// 100 +// ).toFixed(1) +// console.log( +// `\nTotal uncategorized samples: ${totalUncategorizedSamples} (${uncategorizedPercentage}% of total)` +// ) +// } else { +// console.log("All functions are properly categorized!") +// } + +// Summary statistics +console.log("\n\nSummary Statistics:") +console.log("===================") +const totalSamples = Array.from(functionStats.values()).reduce( + (sum, samples) => sum + samples, + 0 +) +console.log(`Total CPU samples analyzed: ${totalSamples}`) + +const versionBreakdown = Array.from(versionStats.entries()) + .map(([version, funcMap]) => { + const samples = Array.from(funcMap.values()).reduce( + (sum, count) => sum + count, + 0 + ) + const percentage = ((samples / totalSamples) * 100).toFixed(1) + return {version, samples, percentage} + }) + .sort((a, b) => b.samples - a.samples) + +console.log("\nVersion breakdown:") +versionBreakdown.forEach(({version, samples, percentage}) => { + if (samples < 25000) return // Skip low-impact versions + console.log(` ${version}: ${samples} samples (${percentage}%)`) +}) + +// Clean up sourcemap consumers +sourceMapConsumers.forEach(consumer => { + consumer.destroy() +}) diff --git a/perf-testing/rolldown.config.js b/perf-testing/rolldown.config.js new file mode 100644 index 00000000..73810728 --- /dev/null +++ b/perf-testing/rolldown.config.js @@ -0,0 +1,18 @@ +export default { + input: "immutability-benchmarks.mjs", + output: { + file: "dist/immutability-benchmarks.js", + sourcemap: true + }, + platform: "node", + define: { + "process.env.NODE_ENV": JSON.stringify("production") + }, + external: ["bun:jsc", "@mitata/counters"], + resolve: { + alias: { + immer10Perf: "../dist/immer.mjs", + immer: "../dist/immer.mjs" + } + } +} diff --git a/perf-testing/yarn.lock b/perf-testing/yarn.lock new file mode 100644 index 00000000..78454a3d --- /dev/null +++ b/perf-testing/yarn.lock @@ -0,0 +1,275 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@emnapi/core@^1.4.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@emnapi/core/-/core-1.5.0.tgz#85cd84537ec989cebb2343606a1ee663ce4edaf0" + integrity sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg== + dependencies: + "@emnapi/wasi-threads" "1.1.0" + tslib "^2.4.0" + +"@emnapi/runtime@^1.4.3": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.5.0.tgz#9aebfcb9b17195dce3ab53c86787a6b7d058db73" + integrity sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz#60b2102fddc9ccb78607e4a3cf8403ea69be41bf" + integrity sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ== + dependencies: + tslib "^2.4.0" + +"@napi-rs/wasm-runtime@^0.2.10": + version "0.2.12" + resolved "https://registry.yarnpkg.com/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz#3e78a8b96e6c33a6c517e1894efbd5385a7cb6f2" + integrity sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ== + dependencies: + "@emnapi/core" "^1.4.3" + "@emnapi/runtime" "^1.4.3" + "@tybys/wasm-util" "^0.10.0" + +"@oxc-project/runtime@=0.75.0": + version "0.75.0" + resolved "https://registry.yarnpkg.com/@oxc-project/runtime/-/runtime-0.75.0.tgz#701c39b220d1604fb6eefd91648063eaf9ce8210" + integrity sha512-gzRmVI/vorsPmbDXt7GD4Uh2lD3rCOku/1xWPB4Yx48k0EP4TZmzQudWapjN4+7Vv+rgXr0RqCHQadeaMvdBuw== + +"@oxc-project/types@=0.75.0": + version "0.75.0" + resolved "https://registry.yarnpkg.com/@oxc-project/types/-/types-0.75.0.tgz#bd453282ab21d2c29429299e44e15ed72999ec92" + integrity sha512-QMW+06WOXs7+F301Y3X0VpmWhwuQVc/X/RP2zF9OIwvSMmsif3xURS2wxbakFIABYsytgBcHpUcFepVS0Qnd3A== + +"@rolldown/binding-darwin-arm64@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-beta.23.tgz#a549fb1813cf5a0274e6dc9af4ead059bc2980b0" + integrity sha512-rppgXFU4+dNDPQvPsfovUuYfDgMoATDomKGjIRR5bIU98BYkQF1fm+87trApilfWSosLQP9JsXOoUJO/EMrspQ== + +"@rolldown/binding-darwin-x64@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-beta.23.tgz#df2c64187242fb1eddec3e248e711be90e0b900b" + integrity sha512-aFo1v7GKysuwSAfsyNcBb9mj3M+wxMCu3N+DcTD5eAaz3mFex6l+2b/vLGaTWNrCMoWhRxV8rTaI1eFoMVdSuQ== + +"@rolldown/binding-freebsd-x64@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-beta.23.tgz#9fa3232cc9499aaaf40d7e71fb8fc5a68cf8b444" + integrity sha512-/NzbXIFIR5KR+fZ351K1qONekakXpiPhUX55ydP6ok8iKdG7bTbgs6dlMg7Ow0E2DKlQoTbZbPTUY3kTzmNrsQ== + +"@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-beta.23.tgz#e592b649703e96dbfd34d8ae0479b3474c702fb6" + integrity sha512-vPnCHxjyR4ZVj9x6sLJMCAdBY99RPe6Mnwxb5BSaE6ccHzvy015xtsIEG7H9E9pVj3yfI/om77jrP+YA5IqL3w== + +"@rolldown/binding-linux-arm64-gnu@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-beta.23.tgz#a8ea0e24bb04ce530847f5c6bddcfd44b80e8a19" + integrity sha512-PFBBnj9JqLOL8gjZtoVGfOXe0PSpnPUXE+JuMcWz568K/p4Zzk7lDDHl7guD95wVtV89TmfaRwK2PWd9vKxHtg== + +"@rolldown/binding-linux-arm64-musl@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-beta.23.tgz#779ce3e3ac167315556613eeecd85951cd5c78de" + integrity sha512-KyQRLofVP78yUCXT90YmEzxK6I9VCBeOTSyOrs40Qx0Q0XwaGVwxo7sKj2SmnqxribdcouBA3CfNZC4ZNcyEnQ== + +"@rolldown/binding-linux-x64-gnu@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-beta.23.tgz#dee1986bb2701c1d2e72c6b3fab5555e0b140320" + integrity sha512-EubfEsJyjQbKK9j3Ez1hhbIOsttABb07Z7PhMRcVYW0wrVr8SfKLew9pULIMfcSNnoz8QqzoI4lOSmezJ9bYWw== + +"@rolldown/binding-linux-x64-musl@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-beta.23.tgz#fc1b8bbf8d89c30885a9ea075d43be30bd4ee033" + integrity sha512-MUAthvl3I/+hySltZuj5ClKiq8fAMqExeBnxadLFShwWCbdHKFd+aRjBxxzarPcnqbDlTaOCUaAaYmQTOTOHSg== + +"@rolldown/binding-wasm32-wasi@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-beta.23.tgz#3a1a9527877e062959c46137312642fa0a4d0458" + integrity sha512-YI7QMQU01QFVNTEaQt3ysrq+wGBwLdFVFEGO64CoZ3gTsr/HulU8gvgR+67coQOlQC9iO/Hm1bvkBtceLxKrnA== + dependencies: + "@napi-rs/wasm-runtime" "^0.2.10" + +"@rolldown/binding-win32-arm64-msvc@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-beta.23.tgz#178ddc36c7fa16ff84909f6cbd04721a323972e8" + integrity sha512-JdHx6Hli53etB/QsZL1tjpf4qa87kNcwPdx4iVicP/kL7po6k5bHoS5/l/nRRccwPh7BlPlB2uoEuTwJygJosQ== + +"@rolldown/binding-win32-ia32-msvc@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.0.0-beta.23.tgz#1013dc1ff79b48087da8e645ac22eac7739c82a5" + integrity sha512-rMZ0QBmcDND97+5unXxquKvSudV8tz6S7tBY3gOYlqMFEDIRX0BAgxaqQBQbq34ZxB9bXwGdjuau3LZHGreB6g== + +"@rolldown/binding-win32-x64-msvc@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-beta.23.tgz#64402bb345a99f675e633fe46617314e1733351f" + integrity sha512-0PqE7vGIpA+XT+qxAYJQKTrB5zz8vJiuCOInfY/ks/QOs6ZZ9Os8bdNkcpCy4rYo+GMZn0Q8CwyPu4uexWB1aA== + +"@rolldown/pluginutils@1.0.0-beta.23": + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.23.tgz#f896db6e79da076e1cb3b2eb13d0009824e3cb38" + integrity sha512-lLCP4LUecUGBLq8EfkbY2esGYyvZj5ee+WZG12+mVnQH48b46SVbwp+0vJkD+6Pnsc+u9SWarBV9sQ5mVwmb5g== + +"@tybys/wasm-util@^0.10.0": + version "0.10.0" + resolved "https://registry.yarnpkg.com/@tybys/wasm-util/-/wasm-util-0.10.0.tgz#2fd3cd754b94b378734ce17058d0507c45c88369" + integrity sha512-VyyPYFlOMNylG45GoAe0xDoLwWuowvf92F9kySqzYh8vmYm7D2u4iUJKa1tOUpS70Ku13ASrOkS4ScXFsTaCNQ== + dependencies: + tslib "^2.4.0" + +ansis@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/ansis/-/ansis-4.1.0.tgz#cd43ecd3f814f37223e518291c0e0b04f2915a0d" + integrity sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1: + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +"immer10@npm:immer@10": + version "10.1.3" + resolved "https://registry.yarnpkg.com/immer/-/immer-10.1.3.tgz#e38a0b97db59949d31d9b381b04c2e441b1c3747" + integrity sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw== + +"immer10Perf@file:..": + version "10.0.3-beta" + +"immer5@npm:immer@5": + version "5.3.6" + resolved "https://registry.yarnpkg.com/immer/-/immer-5.3.6.tgz#51eab8cbbeb13075fe2244250f221598818cac04" + integrity sha512-pqWQ6ozVfNOUDjrLfm4Pt7q4Q12cGw2HUZgry4Q5+Myxu9nmHRkWBpI0J4+MK0AxbdFtdMTwEGVl7Vd+vEiK+A== + +"immer6@npm:immer@6": + version "6.0.9" + resolved "https://registry.yarnpkg.com/immer/-/immer-6.0.9.tgz#b9dd69b8e69b3a12391e87db1e3ff535d1b26485" + integrity sha512-SyCYnAuiRf67Lvk0VkwFvwtDoEiCMjeamnHvRfnVDyc7re1/rQrNxuL+jJ7lA3WvdC4uznrvbmm+clJ9+XXatg== + +"immer7@npm:immer@7": + version "7.0.15" + resolved "https://registry.yarnpkg.com/immer/-/immer-7.0.15.tgz#dc3bc6db87401659d2e737c67a21b227c484a4ad" + integrity sha512-yM7jo9+hvYgvdCQdqvhCNRRio0SCXc8xDPzA25SvKWa7b1WVPjLwQs1VYU5JPXjcJPTqAa5NP5dqpORGYBQ2AA== + +"immer8@npm:immer@8": + version "8.0.4" + resolved "https://registry.yarnpkg.com/immer/-/immer-8.0.4.tgz#3a21605a4e2dded852fb2afd208ad50969737b7a" + integrity sha512-jMfL18P+/6P6epANRvRk6q8t+3gGhqsJ9EuJ25AXE+9bNTYtssvzeYbEd0mXRYWCmmXSIbnlpz6vd6iJlmGGGQ== + +"immer9@npm:immer@9": + version "9.0.21" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.21.tgz#1e025ea31a40f24fb064f1fef23e931496330176" + integrity sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA== + +"immer@file:..": + version "10.0.3-beta" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +mitata@^1.0.34: + version "1.0.34" + resolved "https://registry.yarnpkg.com/mitata/-/mitata-1.0.34.tgz#131f500d58c0bdc958095ab64f637d6fe1cf101a" + integrity sha512-Mc3zrtNBKIMeHSCQ0XqRLo1vbdIx1wvFV9c8NJAiyho6AjNfMY8bVhbS12bwciUdd1t4rj8099CH3N3NFahaUA== + +mutative-compat@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/mutative-compat/-/mutative-compat-0.1.2.tgz#d1926069e42de35e3754458fcd37754339bf3bf6" + integrity sha512-XF+l3jkY4XGZ2IZzdkS0tu0DtYsKJZcSQcoxHu+gvHkNWS1Xd+B4yOLqbhFAx46jzuB4BWIN/SoXB1E5yDAKjg== + +mutative@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mutative/-/mutative-1.2.0.tgz#db75b60b436ad0a940e2e47bffb6661dd77c2754" + integrity sha512-1muFw45Lwjso6TSBGiXfbjKS01fVSD/qaqBfTo/gXgp79e8KM4Sa1XP/S4iN2/DvSdIZgjFJI+JIhC7eKf3GTg== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +pprof-format@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.2.1.tgz#64d32207fb46990349eb52825defb449d6ccc9b4" + integrity sha512-p4tVN7iK19ccDqQv8heyobzUmbHyds4N2FI6aBMcXz6y99MglTWDxIyhFkNaLeEXs6IFUEzT0zya0icbSLLY0g== + +rolldown@1.0.0-beta.23: + version "1.0.0-beta.23" + resolved "https://registry.yarnpkg.com/rolldown/-/rolldown-1.0.0-beta.23.tgz#2da1867b1f1ffdbcf77d9489495bb92ba4a3b373" + integrity sha512-+/TR2YSZxLTtDAfG9LHlYqsHO6jtvr9qxaRD77E+PCAQi5X47bJkgiZsjDmE1jGR19NfYegWToOvSe6E+8NfwA== + dependencies: + "@oxc-project/runtime" "=0.75.0" + "@oxc-project/types" "=0.75.0" + "@rolldown/pluginutils" "1.0.0-beta.23" + ansis "^4.0.0" + optionalDependencies: + "@rolldown/binding-darwin-arm64" "1.0.0-beta.23" + "@rolldown/binding-darwin-x64" "1.0.0-beta.23" + "@rolldown/binding-freebsd-x64" "1.0.0-beta.23" + "@rolldown/binding-linux-arm-gnueabihf" "1.0.0-beta.23" + "@rolldown/binding-linux-arm64-gnu" "1.0.0-beta.23" + "@rolldown/binding-linux-arm64-musl" "1.0.0-beta.23" + "@rolldown/binding-linux-x64-gnu" "1.0.0-beta.23" + "@rolldown/binding-linux-x64-musl" "1.0.0-beta.23" + "@rolldown/binding-wasm32-wasi" "1.0.0-beta.23" + "@rolldown/binding-win32-arm64-msvc" "1.0.0-beta.23" + "@rolldown/binding-win32-ia32-msvc" "1.0.0-beta.23" + "@rolldown/binding-win32-x64-msvc" "1.0.0-beta.23" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +source-map-support@^0.5.21: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +source-map@^0.7.4: + version "0.7.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.6.tgz#a3658ab87e5b6429c8a1f3ba0083d4c61ca3ef02" + integrity sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ== + +tslib@^2.4.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0"