Skip to content

Commit

Permalink
refactor (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
privatenumber authored Dec 11, 2020
1 parent 8086228 commit d0af75e
Show file tree
Hide file tree
Showing 24 changed files with 6,016 additions and 6,919 deletions.
8 changes: 8 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
root = true

[*]
indent_style = tab
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v14.15.1
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 hiroki
Copyright (c) Hiroki Osame <hiroki[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
106 changes: 106 additions & 0 deletions lib/benchmark/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import assert from 'assert';
import execa from 'execa';
import Listr from 'listr';
import path from 'path';
import fs from 'fs';
import byteSize from 'byte-size';
import { getSize, getGzipSize } from '../utils.js';

// const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

async function benchmark({
minifier,
filePath,
sampleSize,
onIterationStart,
}) {
const results = [];

for (let i = 0; i < sampleSize; i += 1) {
onIterationStart(i + 1, results[i - 1]);
const subprocess = execa(process.execPath, [path.join('./lib/benchmark/minifiers/' + minifier), filePath], {
timeout: 1000 * 60,
});

subprocess.stdout.pipe(fs.createWriteStream(`logs/${Date.now()}-${minifier}-${path.basename(filePath, '.js')}.log`));

let jsonData;
try {
const result = await subprocess;
jsonData = JSON.parse(result.stdout);
} catch (err) {
console.log(err);
if (err.message.match('Command timed out')) {
console.log('Retrying...');
i -= 1; // Retry
continue;
}
throw err;
}
if (results.length > 0) {
assert(jsonData.size === results[0].size, 'Mismatching size');
}
results.push(jsonData);
}

const avgSpeed = results.reduce((current, next) => current + next.ms, 0) / results.length;

return {
ms: avgSpeed,
size: results[0].size,
gzipSize: results[0].gzipSize,
};
}

const task = {
title: 'Benchmarking',
task(ctx) {
ctx.results = {};
return new Listr(
ctx.artifacts.map(({ moduleName, version, path, code }) => {
return {
title: `Minifying: ${moduleName} (v${version})`,
task(ctx, task) {
const size = getSize(code);
const gzipSize = getGzipSize(code);
ctx.results[moduleName] = {
size,
gzipSize,
benchmarks: {},
};
task.output = `${byteSize(size)} (${byteSize(gzipSize)} gzipped)`;
return new Listr(
[
'babel-minify',
'esbuild',
'terser',
'terser.no-compress',
'uglify-js',
'uglify-js.no-compress',
].map((minifier) => ({
title: minifier,
async task(ctx, task) {
const sampleSize = 10;
const benchmarked = await benchmark({
minifier,
filePath: path,
sampleSize,
onIterationStart(iteration, lastBenchmark) {
task.title = `${minifier} [${iteration}/${sampleSize}]`;
if (lastBenchmark) {
task.output = 'Last benchmark: ' + lastBenchmark.ms.toLocaleString() + 'ms';
}
},
}).catch(err => ({}));
ctx.results[moduleName].benchmarks[minifier] = benchmarked;
},
}))
);
},
};
})
);
}
};

export default task;
9 changes: 9 additions & 0 deletions lib/benchmark/minifiers/babel-minify.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import babelMinify from 'babel-minify';
import measure from './measure.js';

measure(async (code) => {
const minified = await babelMinify(code, undefined, {
sourceMaps: false,
});
return minified.code;
});
14 changes: 14 additions & 0 deletions lib/benchmark/minifiers/esbuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import esbuild from 'esbuild';
import measure from './measure.js';

const service = await esbuild.startService();

await measure(async (code) => {
const minified = (await service.transform(code, {
minify: true,
sourcemap: false,
}));
return minified.code;
});

service.stop();
30 changes: 30 additions & 0 deletions lib/benchmark/minifiers/measure.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { promises as fs } from 'fs';
import { getSize, getGzipSize } from '../../utils.js';
import stripComments from 'strip-comments';

const srcCode = (await fs.readFile(process.argv[2])).toString();

export default async function (fn) {
let hrtime;
const start = process.hrtime();
let minifiedCode = await fn(srcCode);
hrtime = process.hrtime(start);

// Remove comments from all measurements
// console.time('stripComments');
// minifiedCode = stripComments(minifiedCode.trim(), {
// keepProtected: false,
// });
// console.timeEnd('stripComments'); Commenting it out becaues this takes 50s on three.js

const success = Boolean(minifiedCode);
const size = success && getSize(minifiedCode);
const gzipSize = success && getGzipSize(minifiedCode);

console.log(JSON.stringify({
ms: success ? (hrtime[0] * 1000) + (hrtime[1] / 1e6) : undefined,
success,
size,
gzipSize,
}));
};
9 changes: 9 additions & 0 deletions lib/benchmark/minifiers/terser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { minify } from 'terser';
import measure from './measure.js';

measure(async (code) => {
const minified = await minify(code, {
sourceMap: false,
});
return minified.code;
});
10 changes: 10 additions & 0 deletions lib/benchmark/minifiers/terser.no-compress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { minify } from 'terser';
import measure from './measure.js';

measure(async (code) => {
const minified = await minify(code, {
sourceMap: false,
compress: false,
});
return minified.code;
});
11 changes: 11 additions & 0 deletions lib/benchmark/minifiers/uglify-js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import UglifyJs from 'uglify-js';
import assert from 'assert';
import measure from './measure.js';

measure(async (code) => {
const minified = await UglifyJs.minify(code, {
sourceMap: false,
});
assert(!minified.error, minified.error);
return minified.code;
});
12 changes: 12 additions & 0 deletions lib/benchmark/minifiers/uglify-js.no-compress.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import UglifyJs from 'uglify-js';
import assert from 'assert';
import measure from './measure.js';

measure(async (code) => {
const minified = await UglifyJs.minify(code, {
sourceMap: false,
compress: false,
});
assert(!minified.error, minified.error);
return minified.code;
});
107 changes: 57 additions & 50 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,57 @@
const {promises: fs} = require('fs');
const _ = require('lodash');
const minifiers = require('./minifiers');
const md = require('./md');
const {getStrGzipSize} = require('./utils');

(async packages => {
let results = '';

for (const pkg of packages) {
const pkgPath = require.resolve(pkg);
const content = await fs.readFile(pkgPath); // eslint-disable-line no-await-in-loop
const benchmarks = await minifiers.runMinifiers(content.toString()); // eslint-disable-line no-await-in-loop

const ms = benchmarks.filter(b => b.ms);
_.minBy(ms, 'ms').annotation.time = '🐇';
_.maxBy(ms, 'ms').annotation.time = '🐢';

const size = benchmarks.filter(b => b.size);
_.minBy(size, 'size').annotation.size = '🐥';
_.maxBy(size, 'size').annotation.size = '🐷';

const gzip = benchmarks.filter(b => b.gzipSize);
_.minBy(gzip, 'gzipSize').annotation.gzip = '🐥';
_.maxBy(gzip, 'gzipSize').annotation.gzip = '🐷';

results += md.moduleSection({
pkg,
pkgPath,
size: content.length,
gzipSize: getStrGzipSize(content),
benchmarks
});
}

const mdString = md.readme({
minifiersList: minifiers.minifiers.map(m => `- [${m.name}](${m.repo})`).join('\n'),
results
});

await fs.writeFile('./readme.md', mdString);
})([
'lodash',
'vue/dist/vue.runtime.common.dev',
'react/cjs/react.development.js',
'moment',
'terser',
'd3/dist/d3',
'jquery'
].sort());
import { promises as fs } from 'fs';
import Listr from 'listr';
import { resolveModule, getVersion } from './utils.js';
import benchmark from './benchmark/index.js';
import updateReadme from './update-readme.js';
import path from 'path';

const { results } = await (new Listr([
benchmark,
updateReadme,
])).run({
artifacts: await Promise.all([
{
moduleName: 'lodash',
path: 'lodash',
},
{
moduleName: 'three',
path: 'three',
},
{
moduleName: 'vue',
path: 'vue/dist/vue.runtime.common.dev.js',
},
{
moduleName: 'react',
path: 'react/cjs/react.development.js',
},
{
moduleName: 'moment',
path: 'moment',
},
{
moduleName: 'd3',
path: 'd3/dist/d3.js',
},
{
moduleName: 'jquery',
path: 'jquery',
},
{
moduleName: 'terser',
path: path.resolve('node_modules/terser/dist/bundle.min.js'),
},
].sort((a, b) => a.moduleName.localeCompare(b.moduleName)).map(async (m) => {
const path = m.path.startsWith('/') ? m.path : (await resolveModule(m.path));
return {
moduleName: m.moduleName,
version: await getVersion(m.moduleName),
path,
code: await fs.readFile(path),
};
})),
}).catch(err => {
console.log(err);
return err.context;
});
60 changes: 0 additions & 60 deletions lib/md.js

This file was deleted.

Loading

0 comments on commit d0af75e

Please sign in to comment.