├── .gitignore ├── README.md ├── bin ├── build.sh ├── print-sizes.sh └── publish-site.sh ├── index.html ├── package.json └── script.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | lib 4 | dist 5 | script.es5.js 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cost-of-small-modules 2 | ========= 3 | 4 | A benchmark demonstrating the performance hit of different module bundlers, given different numbers of modules. 5 | 6 | **🔒 Update:** This benchmark is locked because I believe it has served its purpose in my 2016 blog post. I don't intend to maintain it or update it any further. 7 | 8 | [Blog post](https://nolanlawson.com/2016/08/15/the-cost-of-small-modules/) 9 | 10 | Changes since the blog post was published 11 | ---- 12 | 13 | - use `webpack -p` ([#6](https://github.com/nolanlawson/cost-of-small-modules/pull/6)) 14 | - use `gzip -9` ([#7](https://github.com/nolanlawson/cost-of-small-modules/pull/7)) 15 | - add RequireJS and RequireJS+Almond ([#5](https://github.com/nolanlawson/cost-of-small-modules/pull/5)) 16 | 17 | Build 18 | --- 19 | 20 | Check out the code, then: 21 | 22 | npm install 23 | 24 | To rebuild: 25 | 26 | npm run build 27 | 28 | To serve locally: 29 | 30 | npm run serve 31 | 32 | To publish to `gh-pages`: 33 | 34 | npm run publish-site 35 | -------------------------------------------------------------------------------- /bin/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PATH=$PATH:$(pwd)/node_modules/.bin 4 | 5 | rimraf lib dist 6 | mkdirp lib dist 7 | 8 | for num in 100 1000 5000; do 9 | 10 | mkdirp lib/cjs-${num} lib/es6-${num} 11 | 12 | echo "var total = 0" > lib/cjs-${num}/index.js 13 | echo "var total = 0" > lib/es6-${num}/index.js 14 | 15 | for ((i=0;i<${num};i++)); do 16 | echo "module.exports = ${i}" > lib/cjs-${num}/module_${i}.js 17 | echo "export default ${i}" > lib/es6-${num}/module_${i}.js 18 | echo "total += require('./module_${i}')" >> lib/cjs-${num}/index.js 19 | echo -e "import module_${i} from './module_${i}'\ntotal += module_${i}" >> lib/es6-${num}/index.js 20 | done 21 | 22 | echo "console.log(total)" >> lib/cjs-${num}/index.js 23 | echo "console.log(total)" >> lib/es6-${num}/index.js 24 | 25 | browserify ./lib/cjs-${num} > dist/browserify-${num}.js 26 | browserify -p bundle-collapser/plugin ./lib/cjs-${num} > dist/browserify-collapsed-${num}.js 27 | webpack -p --entry ./lib/cjs-${num} --output-filename dist/webpack-${num}.js >/dev/null 28 | rollup --format iife ./lib/es6-${num}/index.js > dist/rollup-${num}.js 29 | ccjs lib/es6-${num}/* --compilation_level=ADVANCED_OPTIMIZATIONS \ 30 | --language_in=ECMASCRIPT6_STRICT --output_wrapper="(function() {%output%})()" > dist/closure-${num}.js 31 | r.js -convert lib/cjs-${num} lib/amd-${num} 32 | r.js -o baseUrl=lib/amd-${num} paths.requireLib=../../node_modules/requirejs/require name=index \ 33 | include=requireLib out=dist/rjs-${num}.js optimize=none logLevel=4 34 | r.js -o baseUrl=lib/amd-${num} paths.almond=../../node_modules/almond/almond name=index \ 35 | include=almond out=dist/rjs-almond-${num}.js optimize=none logLevel=4 36 | 37 | done 38 | 39 | for file in dist/*; do 40 | echo $'_markLoaded();\n'"$(cat $file)" > $file 41 | echo $';\n_markFinished()' >> $file 42 | done 43 | 44 | for file in dist/*; do 45 | uglifyjs -mc < $file > "$(echo $file | sed 's/\.js/\.min.js/')" 46 | done 47 | 48 | buble script.js > script.es5.js 49 | -------------------------------------------------------------------------------- /bin/print-sizes.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo "ungzipped:" 4 | 5 | printf '||' 6 | for i in 100 1000 5000; do 7 | printf "$i modules" 8 | printf '|' 9 | done 10 | echo 11 | 12 | echo '| ---- | ---- | ---- | ---- |' 13 | 14 | for bundler in browserify browserify-collapsed webpack rollup closure rjs rjs-almond; do 15 | printf '|' 16 | printf $bundler 17 | printf '|' 18 | for i in 100 1000 5000; do 19 | printf "$(cat dist/$bundler-${i}.min.js | wc -c)" 20 | printf '|' 21 | done 22 | echo 23 | done 24 | 25 | echo "gzipped:" 26 | 27 | printf '||' 28 | for i in 100 1000 5000; do 29 | printf "$i modules" 30 | printf '|' 31 | done 32 | echo 33 | 34 | echo '| ---- | ---- | ---- | ---- |' 35 | 36 | for bundler in browserify browserify-collapsed webpack rollup closure rjs rjs-almond; do 37 | printf '|' 38 | printf $bundler 39 | printf '|' 40 | for i in 100 1000 5000; do 41 | printf "$(gzip -9 -c dist/$bundler-${i}.min.js | wc -c)" 42 | printf '|' 43 | done 44 | echo 45 | done 46 | -------------------------------------------------------------------------------- /bin/publish-site.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | npm run build 4 | BRANCH_NAME=build_"$RANDOM" 5 | git checkout -b $BRANCH_NAME 6 | git add -f dist/*.min.js script.es5.js 7 | git commit -m 'build' 8 | git push --force origin $BRANCH_NAME:gh-pages 9 | git checkout master 10 | git branch -D $BRANCH_NAME -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Cost of small modules: perf benchmark 5 | 6 | 7 | 8 | Fork me on GitHub 9 |

Cost of small modules: perf benchmark

10 |

11 | This benchmark loads JavaScript bundle files 12 | created by various bundlers, with various numbers of modules, and reports 13 | the median load time and run time as CSV. 14 |

15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cost-of-small-modules", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "Explore the cost of small modules for bundle size and execution", 6 | "scripts": { 7 | "publish-site": "bash bin/publish-site.sh", 8 | "build": "bash bin/build.sh", 9 | "prepublish": "npm run build", 10 | "print-sizes": "bash bin/print-sizes.sh", 11 | "serve": "serve -p 9000 --compress" 12 | }, 13 | "dependencies": { 14 | "almond": "^0.3.2", 15 | "browserify": "^13.1.0", 16 | "buble": "^0.13.1", 17 | "bundle-collapser": "^1.2.1", 18 | "closurecompiler": "^1.6.0", 19 | "lodash.times": "^4.3.2", 20 | "mkdirp": "^0.5.1", 21 | "requirejs": "^2.2.0", 22 | "rimraf": "^2.5.4", 23 | "rollup": "^0.34.7", 24 | "serve": "^1.4.0", 25 | "uglify-js": "^2.7.0", 26 | "webpack": "^1.13.1" 27 | }, 28 | "devDependencies": {}, 29 | "keywords": [], 30 | "author": "Nolan Lawson ", 31 | "license": "Apache-2.0" 32 | } 33 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict' 3 | 4 | /* global performance btoa */ 5 | 6 | // do this many iterations and then take the median 7 | var ITERATIONS = 15 8 | var NUM_MODULES = [ '100', '1000', '5000' ] 9 | var BUNDLERS = [ 'browserify', 'browserify-collapsed', 'webpack', 'rollup', 'closure', 'rjs', 'rjs-almond' ] 10 | 11 | var results = document.querySelector('#results') 12 | var button = document.querySelector('#run-test') 13 | 14 | window._markLoaded = function () { 15 | window._loadedTS = performance.now() 16 | } 17 | 18 | function round (num) { 19 | return Math.round(num * 100) / 100 20 | } 21 | 22 | function getMedianTime (bundler, numModules) { 23 | var promise = Promise.resolve() 24 | 25 | var runs = [] 26 | 27 | function iter () { 28 | promise = promise.then(() => { 29 | return new Promise(resolve => { 30 | window._markFinished = function () { 31 | var finish = performance.now() 32 | resolve({ 33 | loadTime: window._loadedTS - window._startTS, 34 | runTime: finish - window._loadedTS, 35 | totalTime: finish - window._startTS 36 | }) 37 | } 38 | var src = 'dist/' + bundler + '-' + numModules + '.min.js?nonce=' + btoa(Math.random()) 39 | var script = document.createElement('script') 40 | script.src = src 41 | window._startTS = performance.now() 42 | document.body.appendChild(script) 43 | }).then(res => runs.push(res)) 44 | }) 45 | } 46 | 47 | for (var i = 0; i < ITERATIONS; i++) { 48 | iter() 49 | } 50 | return promise.then(() => { 51 | var median = runs.sort((a, b) => a.totalTime - b.totalTime)[Math.floor(runs.length / 2)] 52 | results.innerHTML += [ 53 | bundler, 54 | round(median.loadTime), 55 | round(median.runTime), 56 | round(median.totalTime) 57 | ].join(',') + '\n' 58 | }) 59 | } 60 | 61 | function runTest () { 62 | results.innerHTML = '' 63 | var promise = Promise.resolve() 64 | NUM_MODULES.forEach(numModules => { 65 | BUNDLERS.forEach((bundler, i) => { 66 | promise = promise.then(() => { 67 | if (i === 0) { 68 | results.innerHTML += numModules + ' modules,,,\n' 69 | results.innerHTML += 'Bundler,Load time (ms),Run time (ms),Total time (ms)\n' 70 | } 71 | return getMedianTime(bundler, numModules) 72 | }) 73 | }) 74 | }) 75 | return promise 76 | } 77 | 78 | button.addEventListener('click', () => { 79 | button.disabled = true 80 | runTest().then(() => { 81 | button.disabled = false 82 | results.innerHTML += 'Done!\n' 83 | }).catch(console.log.bind(console)) 84 | }) 85 | 86 | document.body.addEventListener('click', e => { 87 | if (e.target.tagName === 'BUTTON') { 88 | 89 | } 90 | }) 91 | })() 92 | --------------------------------------------------------------------------------