This repository has been archived by the owner on Sep 21, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 24
/
index.js
198 lines (176 loc) · 7.64 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
'use strict'
const { promisify } = require('util')
const writeFile = promisify(require('fs').writeFile)
const { join } = require('path')
const mkdirp = require('mkdirp')
const stripIndents = require('common-tags').stripIndents
const prettyMs = require('pretty-ms')
const cmdsMap = require('./lib/commandsMap')
const benchmark = require('./lib/recordBenchmark')
const generateSvg = require('./lib/generateSvg')
const LIMIT_RUNS = 3
const fixtures = [
/*{
name: 'react-app',
mdDesc: '## React App\n\nThe app\'s `package.json` [here](./fixtures/react-app/package.json)'
},
{
name: 'ember-quickstart',
mdDesc: '## Ember App\n\nThe app\'s `package.json` [here](./fixtures/ember-quickstart/package.json)'
},
{
name: 'angular-quickstart',
mdDesc: '## Angular App\n\nThe app\'s `package.json` [here](./fixtures/angular-quickstart/package.json)'
},
{
name: 'medium-size-app',
mdDesc: '## Medium Size App\n\nThe app\'s `package.json` [here](./fixtures/medium-size-app/package.json)'
},*/
{
name: 'alotta-files',
mdDesc: '## Lots of Files\n\nThe app\'s `package.json` [here](./fixtures/alotta-files/package.json)'
}
]
const tests = [
'firstInstall',
'repeatInstall',
'withWarmCacheAndLockfile',
'withWarmCache',
'withLockfile',
'withWarmCacheAndModules',
'withWarmModulesAndLockfile',
'withWarmModules',
'updatedDependencies'
]
const testDescriptions = [
[ // firstInstall
'clean install'
],
[ // repeatInstall
'with cache',
'with lockfile',
'with node_modules'
],
[ // withWarmCacheAndLockfile
'with cache',
'with lockfile'
],
[ // withWarmCache
'with cache'
],
[ // withLockfile
'with lockfile'
],
[ // withWarmCacheAndModules
'with cache',
'with node_modules'
],
[ // withWarmModulesAndLockfile
'with node_modules',
'with lockfile'
],
[ // withWarmModules
'with node_modules'
],
[ // updatedDependencies
'update'
]
]
const toArray = (pms, resultsObj) => {
/**
* Make array of all similar installs grouped together:
* [
* [ npm.firstInstall, yarn.firstInstall, pnpm.firstInstall ],
* [ npm.repeatInstall, yarn.repeatInstall, pnpm.repeatInstall ],
* ...
* ]
*/
return tests
.map((test) => pms
.map((pm) => resultsObj[pm][test])
.map((time) => Math.round(time / 100) / 10) // round to `x.x` seconds
)
}
run()
.then(() => console.log('done'))
.catch(err => console.error(err))
async function run () {
const pms = [ 'npm', 'pnpm', 'yarn', 'yarn_pnp' ]
const sections = []
const svgs = []
for (const fixture of fixtures) {
const npmRes = min(await benchmark(cmdsMap.npm, fixture.name, {limitRuns: LIMIT_RUNS, hasNodeModules: true}))
const yarnRes = min(await benchmark(cmdsMap.yarn, fixture.name, {limitRuns: LIMIT_RUNS, hasNodeModules: true}))
const yarnPnPRes = min(await benchmark(cmdsMap.yarn_pnp, fixture.name, {limitRuns: LIMIT_RUNS, hasNodeModules: false}))
const pnpmRes = min(await benchmark(cmdsMap.pnpm, fixture.name, {limitRuns: LIMIT_RUNS, hasNodeModules: true}))
const resArray = toArray(pms, {
'npm': npmRes,
'pnpm': pnpmRes,
'yarn': yarnRes,
'yarn_pnp': yarnPnPRes,
})
sections.push(stripIndents`
${fixture.mdDesc}
| action | cache | lockfile | node_modules| npm | pnpm | Yarn | Yarn PnP |
| --- | --- | --- | --- | --- | --- | --- | --- |
| install | | | | ${prettyMs(npmRes.firstInstall)} | ${prettyMs(pnpmRes.firstInstall)} | ${prettyMs(yarnRes.firstInstall)} | ${prettyMs(yarnPnPRes.firstInstall)} |
| install | ✔ | ✔ | ✔ | ${prettyMs(npmRes.repeatInstall)} | ${prettyMs(pnpmRes.repeatInstall)} | ${prettyMs(yarnRes.repeatInstall)} | n/a |
| install | ✔ | ✔ | | ${prettyMs(npmRes.withWarmCacheAndLockfile)} | ${prettyMs(pnpmRes.withWarmCacheAndLockfile)} | ${prettyMs(yarnRes.withWarmCacheAndLockfile)} | ${prettyMs(yarnPnPRes.withWarmCacheAndLockfile)} |
| install | ✔ | | | ${prettyMs(npmRes.withWarmCache)} | ${prettyMs(pnpmRes.withWarmCache)} | ${prettyMs(yarnRes.withWarmCache)} | ${prettyMs(yarnPnPRes.withWarmCache)} |
| install | | ✔ | | ${prettyMs(npmRes.withLockfile)} | ${prettyMs(pnpmRes.withLockfile)} | ${prettyMs(yarnRes.withLockfile)} | ${prettyMs(yarnPnPRes.withLockfile)} |
| install | ✔ | | ✔ | ${prettyMs(npmRes.withWarmCacheAndModules)} | ${prettyMs(pnpmRes.withWarmCacheAndModules)} | ${prettyMs(yarnRes.withWarmCacheAndModules)} | n/a |
| install | | ✔ | ✔ | ${prettyMs(npmRes.withWarmModulesAndLockfile)} | ${prettyMs(pnpmRes.withWarmModulesAndLockfile)} | ${prettyMs(yarnRes.withWarmModulesAndLockfile)} | n/a |
| install | | | ✔ | ${prettyMs(npmRes.withWarmModules)} | ${prettyMs(pnpmRes.withWarmModules)} | ${prettyMs(yarnRes.withWarmModules)} | n/a |
| update | n/a | n/a | n/a | ${prettyMs(npmRes.updatedDependencies)} | ${prettyMs(pnpmRes.updatedDependencies)} | ${prettyMs(yarnRes.updatedDependencies)} | ${prettyMs(yarnPnPRes.updatedDependencies)} |
![Graph of the ${fixture.name} results](./results/imgs/${fixture.name}.svg)
`)
svgs.push({
path: join(__dirname, 'results', 'imgs', `${fixture.name}.svg`),
file: generateSvg(resArray, [cmdsMap.npm, cmdsMap.pnpm, cmdsMap.yarn, cmdsMap.yarn_pnp], testDescriptions)
})
}
// make sure folder exists
mkdirp.sync(join(__dirname, 'results', 'imgs'))
const introduction = stripIndents`
# Benchmarks of JavaScript Package Managers
This benchmark compares the performance of [npm](https://github.com/npm/cli), [pnpm](https://github.com/pnpm/pnpm) and [Yarn](https://github.com/yarnpkg/yarn) (both regular and PnP variant).
`
const explanation = stripIndents`
Here's a quick explanation of how these tests could apply to the real world:
- \`clean install\`: How long it takes to run a totally fresh install: no lockfile present, no packages in the cache, no \`node_modules\` folder.
- \`with cache\`, \`with lockfile\`, \`with node_modules\`: After the first install is done, the install command is run again.
- \`with cache\`, \`with lockfile\`: When a repo is fetched by a developer and installation is first run.
- \`with cache\`: Same as the one above, but the package manager doesn't have a lockfile to work from.
- \`with lockfile\`: When an installation runs on a CI server.
- \`with cache\`, \`with node_modules\`: The lockfile is deleted and the install command is run again.
- \`with node_modules\`, \`with lockfile\`: The package cache is deleted and the install command is run again.
- \`with node_modules\`: The package cache and the lockfile is deleted and the install command is run again.
- \`update\`: Updating your dependencies by changing the version in the \`package.json\` and running the install command again.
`
await Promise.all(
[
Promise.all(svgs.map((file) => writeFile(file.path, file.file, 'utf-8'))),
writeFile('README.md', stripIndents`
${introduction}
${explanation}
${sections.join('\n\n')}`, 'utf8')
]
).catch((err) => { throw err })
}
function average (benchmarkResults) {
const results = {}
tests.forEach(test => {
results[test] = benchmarkResults.map(res => res[test]).reduce(sum, 0) / benchmarkResults.length
})
return results
}
function min (benchmarkResults) {
const results = {}
tests.forEach(test => {
results[test] = Math.min.apply(Math, benchmarkResults.map(res => res[test]))
})
return results
}
function sum (a, b) {
return a + b
}