From 959f20f6e25edb7d4e7e25317e1118aca67794fd Mon Sep 17 00:00:00 2001 From: David Majda Date: Thu, 8 Sep 2016 14:31:32 +0200 Subject: [PATCH] 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. --- Makefile | 4 +- benchmark/.eslintrc.json | 5 + benchmark/README.md | 14 +-- benchmark/benchmarks.js | 86 +++++++--------- benchmark/index.html | 5 +- benchmark/index.js | 4 +- benchmark/run | 3 +- benchmark/runner.js | 210 +++++++++++++++++++-------------------- benchmark/server | 38 +++++++ 9 files changed, 195 insertions(+), 174 deletions(-) create mode 100644 benchmark/.eslintrc.json create mode 100755 benchmark/server diff --git a/Makefile b/Makefile index 6f9840a..f03570b 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,8 @@ PARSER_OUT_FILE_NEW = $(LIB_DIR)/parser.js.new BROWSER_FILE_DEV = $(BROWSER_DIR)/peg-$(PEGJS_VERSION).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 @@ -108,6 +109,7 @@ lint: $(SPEC_SERVER_FILE) \ $(BENCHMARK_DIR)/*.js \ $(BENCHMARK_RUN) \ + $(BENCHMARK_SERVER_FILE) \ $(PEGJS) .PHONY: all parser browser browserclean spec benchmark lint diff --git a/benchmark/.eslintrc.json b/benchmark/.eslintrc.json new file mode 100644 index 0000000..e84df8c --- /dev/null +++ b/benchmark/.eslintrc.json @@ -0,0 +1,5 @@ +{ + "env": { + "commonjs": true + } +} diff --git a/benchmark/README.md b/benchmark/README.md index 9b1bc1b..37bfded 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -39,18 +39,12 @@ All commands in the following steps need to be executed in PEG.js root directory $ npm install ``` - 3. Build browser version of PEG.js: + 3. Serve the benchmark suite using a web server: ```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 - $ 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. + 5. Click the **Run** button and wait for results. diff --git a/benchmark/benchmarks.js b/benchmark/benchmarks.js index 2eaff43..6987675 100644 --- a/benchmark/benchmarks.js +++ b/benchmark/benchmarks.js @@ -1,52 +1,42 @@ -/* global module */ - "use strict"; -(function(root, factory) { - if (typeof module !== 'undefined' && module.exports) { - module.exports = factory(); - } else { - root.benchmarks = factory(); +var benchmarks = [ + { + 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)" } + ] } -}(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; diff --git a/benchmark/index.html b/benchmark/index.html index 60c40cb..ec6a390 100644 --- a/benchmark/index.html +++ b/benchmark/index.html @@ -33,11 +33,8 @@ - - - - + diff --git a/benchmark/index.js b/benchmark/index.js index 9d77d95..97e54e5 100644 --- a/benchmark/index.js +++ b/benchmark/index.js @@ -1,5 +1,7 @@ /* eslint-env browser, jquery */ -/* global benchmarks, Runner */ + +var benchmarks = require("./benchmarks.js"), + Runner = require("./runner.js"); $("#run").click(function() { "use strict"; diff --git a/benchmark/run b/benchmark/run index 38581d1..7896f74 100755 --- a/benchmark/run +++ b/benchmark/run @@ -6,10 +6,9 @@ "use strict"; var fs = require("fs"); -var peg = require("../lib/peg"); var benchmarks = require("./benchmarks.js"); -var Runner = require("./runner.js")(peg); +var Runner = require("./runner.js"); /* Results Table Manipulation */ diff --git a/benchmark/runner.js b/benchmark/runner.js index 6787997..5090619 100644 --- a/benchmark/runner.js +++ b/benchmark/runner.js @@ -1,131 +1,125 @@ -/* global module, setTimeout */ +/* global setTimeout */ "use strict"; -(function(root, factory) { - if (typeof module !== 'undefined' && module.exports) { - module.exports = factory; - } else { - root.Runner = factory(root.peg); - } -}(this, function(peg) { +var peg = require("../lib/peg"); - return { - run: function(benchmarks, runCount, options, callbacks) { +var Runner = { + run: function(benchmarks, runCount, options, callbacks) { - /* Queue */ + /* Queue */ - var Q = { - functions: [], + var Q = { + functions: [], - add: function(f) { - this.functions.push(f); - }, + add: function(f) { + this.functions.push(f); + }, - run: function() { - if (this.functions.length > 0) { - this.functions.shift()(); + run: function() { + if (this.functions.length > 0) { + this.functions.shift()(); - /* - * We can't use |arguments.callee| here because |this| would get - * messed-up in that case. - */ - setTimeout(function() { Q.run(); }, 0); - } + /* + * We can't use |arguments.callee| here because |this| would get + * messed-up in that case. + */ + 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() { - var input, parseTime, averageParseTime, i, t; + /* + * 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; + } - 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; - for (i = 0; i < runCount; i++) { - t = (new Date()).getTime(); - state.parser.parse(input); - parseTime += (new Date()).getTime() - t; - } - averageParseTime = parseTime / runCount; + callbacks.testStart(benchmark, test); - callbacks.testFinish(benchmark, test, input.length, averageParseTime); + input = callbacks.readFile(benchmark.id + "/" + test.file); - state.benchmarkInputSize += input.length; - state.benchmarkParseTime += averageParseTime; - }; - } - - function benchmarkFinalizer(benchmark) { - return function() { - callbacks.benchmarkFinish( - benchmark, - state.benchmarkInputSize, - state.benchmarkParseTime - ); - - state.totalInputSize += state.benchmarkInputSize; - state.totalParseTime += state.benchmarkParseTime; - }; - } + parseTime = 0; + for (i = 0; i < runCount; i++) { + t = (new Date()).getTime(); + state.parser.parse(input); + parseTime += (new Date()).getTime() - t; + } + averageParseTime = parseTime / runCount; - function finalize() { - callbacks.finish(state.totalInputSize, state.totalParseTime); - } + callbacks.testFinish(benchmark, test, input.length, averageParseTime); - /* Main */ + state.benchmarkInputSize += input.length; + state.benchmarkParseTime += averageParseTime; + }; + } - 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); + function benchmarkFinalizer(benchmark) { + return function() { + callbacks.benchmarkFinish( + benchmark, + state.benchmarkInputSize, + state.benchmarkParseTime + ); + + 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; diff --git a/benchmark/server b/benchmark/server new file mode 100755 index 0000000..865a2fe --- /dev/null +++ b/benchmark/server @@ -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..."); +}); +