Pass benchmark code through Babel before serving it to the browser

This will allow to use ES2015 constructs in benchmark code.

The change required introducing a small server, which serves both PEG.js
and benchmark code passed through Babel and bundled together. This
allowed to convert the benchmark to regular modules and to get rid of
the hackery that was previously needed to make it run both in Node.js
and in the browser.

Note the benchmark no longer exercises the browser version.

See #442.
redux
David Majda 8 years ago
parent 5c40fff136
commit 959f20f6e2

@ -24,7 +24,8 @@ PARSER_OUT_FILE_NEW = $(LIB_DIR)/parser.js.new
BROWSER_FILE_DEV = $(BROWSER_DIR)/peg-$(PEGJS_VERSION).js BROWSER_FILE_DEV = $(BROWSER_DIR)/peg-$(PEGJS_VERSION).js
BROWSER_FILE_MIN = $(BROWSER_DIR)/peg-$(PEGJS_VERSION).min.js BROWSER_FILE_MIN = $(BROWSER_DIR)/peg-$(PEGJS_VERSION).min.js
SPEC_SERVER_FILE = $(SPEC_DIR)/server SPEC_SERVER_FILE = $(SPEC_DIR)/server
BENCHMARK_SERVER_FILE = $(BENCHMARK_DIR)/server
VERSION_FILE = VERSION VERSION_FILE = VERSION
@ -108,6 +109,7 @@ lint:
$(SPEC_SERVER_FILE) \ $(SPEC_SERVER_FILE) \
$(BENCHMARK_DIR)/*.js \ $(BENCHMARK_DIR)/*.js \
$(BENCHMARK_RUN) \ $(BENCHMARK_RUN) \
$(BENCHMARK_SERVER_FILE) \
$(PEGJS) $(PEGJS)
.PHONY: all parser browser browserclean spec benchmark lint .PHONY: all parser browser browserclean spec benchmark lint

@ -0,0 +1,5 @@
{
"env": {
"commonjs": true
}
}

@ -39,18 +39,12 @@ All commands in the following steps need to be executed in PEG.js root directory
$ npm install $ npm install
``` ```
3. Build browser version of PEG.js: 3. Serve the benchmark suite using a web server:
```console ```console
$ make browser $ benchmark/server
``` ```
4. Serve PEG.js root directory using a web server: 4. Point your browser to the [benchmark suite](http://localhost:8000/).
```console 5. Click the **Run** button and wait for results.
$ node_modules/.bin/http-server
```
5. Point your browser to the [benchmark suite](http://localhost:8080/benchmark/index.html).
6. Click the **Run** button and wait for results.

@ -1,52 +1,42 @@
/* global module */
"use strict"; "use strict";
(function(root, factory) { var benchmarks = [
if (typeof module !== 'undefined' && module.exports) { {
module.exports = factory(); id: "json",
} else { title: "JSON",
root.benchmarks = factory(); tests: [
{ file: "example1.json", title: "Example 1" },
{ file: "example2.json", title: "Example 2" },
{ file: "example3.json", title: "Example 3" },
{ file: "example4.json", title: "Example 4" },
{ file: "example5.json", title: "Example 5" }
]
},
{
id: "css",
title: "CSS",
tests: [
{ file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
{ file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
{ file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
{ file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
{ file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
// Contains syntax errors.
// { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
{ file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
{ file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
// Contains syntax errors.
// { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
{ file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
{ file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
{ file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
{ file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
{ file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
{ file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
{ file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
{ file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
]
} }
}(this, function() { ];
return [
{
id: "json",
title: "JSON",
tests: [
{ file: "example1.json", title: "Example 1" },
{ file: "example2.json", title: "Example 2" },
{ file: "example3.json", title: "Example 3" },
{ file: "example4.json", title: "Example 4" },
{ file: "example5.json", title: "Example 5" }
]
},
{
id: "css",
title: "CSS",
tests: [
{ file: "blueprint/src/reset.css", title: "Blueprint - reset.css (source)" },
{ file: "blueprint/src/typography.css", title: "Blueprint - typography.css (source)" },
{ file: "blueprint/src/forms.css", title: "Blueprint - forms.css (source)" },
{ file: "blueprint/src/grid.css", title: "Blueprint - grid.css (source)" },
{ file: "blueprint/src/print.css", title: "Blueprint - print.css (source)" },
// Contains syntax errors.
// { file: "blueprint/src/ie.css", title: "Blueprint - ie.css (source)" },
{ file: "blueprint/min/screen.css", title: "Blueprint - screen.css (minified)" },
{ file: "blueprint/min/print.css", title: "Blueprint - print.css (minified)" },
// Contains syntax errors.
// { file: "blueprint/min/ie.css", title: "Blueprint - ie.css (minified)" },
{ file: "960.gs/src/reset.css", title: "960.gs - reset.css (source)" },
{ file: "960.gs/src/text.css", title: "960.gs - text.css (source)" },
{ file: "960.gs/src/960.css", title: "960.gs - 960.css (source)" },
{ file: "960.gs/src/960_24_col.css", title: "960.gs - 960_24_col.css (source)" },
{ file: "960.gs/min/reset.css", title: "960.gs - reset.css (minified)" },
{ file: "960.gs/min/text.css", title: "960.gs - text.css (minified)" },
{ file: "960.gs/min/960.css", title: "960.gs - 960.css (minified)" },
{ file: "960.gs/min/960_24_col.css", title: "960.gs - 960_24_col.css (minified)" }
]
}
];
})); module.exports = benchmarks;

@ -33,11 +33,8 @@
</tr> </tr>
</table> </table>
<script src="../browser/peg-0.10.0.js"></script>
<script src="vendor/jquery/jquery.js"></script> <script src="vendor/jquery/jquery.js"></script>
<script src="vendor/jquery.scrollto/jquery.scrollTo.js"></script> <script src="vendor/jquery.scrollto/jquery.scrollTo.js"></script>
<script src="benchmarks.js"></script> <script src="bundle.js"></script>
<script src="runner.js"></script>
<script src="index.js"></script>
</body> </body>
</html> </html>

@ -1,5 +1,7 @@
/* eslint-env browser, jquery */ /* eslint-env browser, jquery */
/* global benchmarks, Runner */
var benchmarks = require("./benchmarks.js"),
Runner = require("./runner.js");
$("#run").click(function() { $("#run").click(function() {
"use strict"; "use strict";

@ -6,10 +6,9 @@
"use strict"; "use strict";
var fs = require("fs"); var fs = require("fs");
var peg = require("../lib/peg");
var benchmarks = require("./benchmarks.js"); var benchmarks = require("./benchmarks.js");
var Runner = require("./runner.js")(peg); var Runner = require("./runner.js");
/* Results Table Manipulation */ /* Results Table Manipulation */

@ -1,131 +1,125 @@
/* global module, setTimeout */ /* global setTimeout */
"use strict"; "use strict";
(function(root, factory) { var peg = require("../lib/peg");
if (typeof module !== 'undefined' && module.exports) {
module.exports = factory;
} else {
root.Runner = factory(root.peg);
}
}(this, function(peg) {
return { var Runner = {
run: function(benchmarks, runCount, options, callbacks) { run: function(benchmarks, runCount, options, callbacks) {
/* Queue */ /* Queue */
var Q = { var Q = {
functions: [], functions: [],
add: function(f) { add: function(f) {
this.functions.push(f); this.functions.push(f);
}, },
run: function() { run: function() {
if (this.functions.length > 0) { if (this.functions.length > 0) {
this.functions.shift()(); this.functions.shift()();
/* /*
* We can't use |arguments.callee| here because |this| would get * We can't use |arguments.callee| here because |this| would get
* messed-up in that case. * messed-up in that case.
*/ */
setTimeout(function() { Q.run(); }, 0); setTimeout(function() { Q.run(); }, 0);
}
} }
}; }
/*
* The benchmark itself is factored out into several functions (some of them
* generated), which are enqueued and run one by one using |setTimeout|. We
* do this for two reasons:
*
* 1. To avoid bowser mechanism for interrupting long-running scripts to
* kick-in (or at least to not kick-in that often).
*
* 2. To ensure progressive rendering of results in the browser (some
* browsers do not render at all when running JavaScript code).
*
* The enqueued functions share state, which is all stored in the properties
* of the |state| object.
*/
var state = {};
function initialize() {
callbacks.start();
state.totalInputSize = 0;
state.totalParseTime = 0;
}
function benchmarkInitializer(benchmark) {
return function() {
callbacks.benchmarkStart(benchmark);
state.parser = peg.generate(
callbacks.readFile("../examples/" + benchmark.id + ".pegjs"),
options
);
state.benchmarkInputSize = 0;
state.benchmarkParseTime = 0;
}; };
}
function testRunner(benchmark, test) { /*
return function() { * The benchmark itself is factored out into several functions (some of them
var input, parseTime, averageParseTime, i, t; * generated), which are enqueued and run one by one using |setTimeout|. We
* do this for two reasons:
*
* 1. To avoid bowser mechanism for interrupting long-running scripts to
* kick-in (or at least to not kick-in that often).
*
* 2. To ensure progressive rendering of results in the browser (some
* browsers do not render at all when running JavaScript code).
*
* The enqueued functions share state, which is all stored in the properties
* of the |state| object.
*/
var state = {};
function initialize() {
callbacks.start();
state.totalInputSize = 0;
state.totalParseTime = 0;
}
callbacks.testStart(benchmark, test); function benchmarkInitializer(benchmark) {
return function() {
callbacks.benchmarkStart(benchmark);
state.parser = peg.generate(
callbacks.readFile("../examples/" + benchmark.id + ".pegjs"),
options
);
state.benchmarkInputSize = 0;
state.benchmarkParseTime = 0;
};
}
input = callbacks.readFile(benchmark.id + "/" + test.file); function testRunner(benchmark, test) {
return function() {
var input, parseTime, averageParseTime, i, t;
parseTime = 0; callbacks.testStart(benchmark, test);
for (i = 0; i < runCount; i++) {
t = (new Date()).getTime();
state.parser.parse(input);
parseTime += (new Date()).getTime() - t;
}
averageParseTime = parseTime / runCount;
callbacks.testFinish(benchmark, test, input.length, averageParseTime); input = callbacks.readFile(benchmark.id + "/" + test.file);
state.benchmarkInputSize += input.length; parseTime = 0;
state.benchmarkParseTime += averageParseTime; for (i = 0; i < runCount; i++) {
}; t = (new Date()).getTime();
} state.parser.parse(input);
parseTime += (new Date()).getTime() - t;
function benchmarkFinalizer(benchmark) { }
return function() { averageParseTime = parseTime / runCount;
callbacks.benchmarkFinish(
benchmark,
state.benchmarkInputSize,
state.benchmarkParseTime
);
state.totalInputSize += state.benchmarkInputSize;
state.totalParseTime += state.benchmarkParseTime;
};
}
function finalize() { callbacks.testFinish(benchmark, test, input.length, averageParseTime);
callbacks.finish(state.totalInputSize, state.totalParseTime);
}
/* Main */ state.benchmarkInputSize += input.length;
state.benchmarkParseTime += averageParseTime;
};
}
Q.add(initialize); function benchmarkFinalizer(benchmark) {
benchmarks.forEach(function(benchmark) { return function() {
Q.add(benchmarkInitializer(benchmark)); callbacks.benchmarkFinish(
benchmark.tests.forEach(function(test) { benchmark,
Q.add(testRunner(benchmark, test)); state.benchmarkInputSize,
}); state.benchmarkParseTime
Q.add(benchmarkFinalizer(benchmark)); );
});
Q.add(finalize); state.totalInputSize += state.benchmarkInputSize;
state.totalParseTime += state.benchmarkParseTime;
};
}
Q.run(); function finalize() {
callbacks.finish(state.totalInputSize, state.totalParseTime);
} }
};
})); /* Main */
Q.add(initialize);
benchmarks.forEach(function(benchmark) {
Q.add(benchmarkInitializer(benchmark));
benchmark.tests.forEach(function(test) {
Q.add(testRunner(benchmark, test));
});
Q.add(benchmarkFinalizer(benchmark));
});
Q.add(finalize);
Q.run();
}
};
module.exports = Runner;

@ -0,0 +1,38 @@
#!/usr/bin/env node
/* eslint-env node */
/* eslint no-console: 0 */
/*
* Small server whose main purpose is to ensure that both the benchmarked code
* and the benchmark get passed through Babel & Browserify before they are
* served to the browser.
*/
var express = require("express"),
logger = require("morgan"),
glob = require("glob"),
browserify = require("browserify"),
babelify = require("babelify");
var app = express();
app.use(logger("dev"));
app.use(express.static(__dirname));
app.use("/examples", express.static(__dirname + "/../examples"));
app.get("/bundle.js", function(req, res) {
var files = glob.sync(__dirname + "/**/*.js", {
ignore: __dirname + "/vendor/**/*"
});
browserify(files)
.transform(babelify, { presets: "es2015", compact: false })
.bundle()
.pipe(res);
});
app.listen(8000, function() {
console.log("Benchmark server running at http://localhost:8000...");
});
Loading…
Cancel
Save