diff --git a/.travis.yml b/.travis.yml index f82abdee..2933a989 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,9 @@ node_js: - "7" - "8" script: - - npm test + # Fixes slow test execution on travis + # http://facebook.github.io/jest/docs/en/troubleshooting.html#tests-are-extremely-slow-on-docker-and-or-continuous-integration-ci-server + - "npm test -- --runInBand" - node dist/run.js sudo: false branches: diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..a520ccec --- /dev/null +++ b/jest.config.js @@ -0,0 +1,13 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const path = require("path"); + +module.exports = { + rootDir: path.resolve(__dirname, "src"), + transformIgnorePatterns: [ + "/node_modules/", + path.resolve(__dirname, "build") + ] +}; diff --git a/src/fixtures/webpack/a.js b/src/fixtures/webpack/a.js new file mode 100644 index 00000000..948e9f24 --- /dev/null +++ b/src/fixtures/webpack/a.js @@ -0,0 +1,5 @@ +import b from "./b.js"; + +const message = import("./c.js").then(c => c.default + b); + +export default message; diff --git a/src/fixtures/webpack/b.js b/src/fixtures/webpack/b.js new file mode 100644 index 00000000..888cae37 --- /dev/null +++ b/src/fixtures/webpack/b.js @@ -0,0 +1 @@ +module.exports = 42; diff --git a/src/fixtures/webpack/c.js b/src/fixtures/webpack/c.js new file mode 100644 index 00000000..64d245e2 --- /dev/null +++ b/src/fixtures/webpack/c.js @@ -0,0 +1 @@ +export default "Hello world"; diff --git a/src/mocks/chokidar/index.js b/src/mocks/chokidar/index.js new file mode 100644 index 00000000..39cdfdb8 --- /dev/null +++ b/src/mocks/chokidar/index.js @@ -0,0 +1,3 @@ +module.exports = { + watch: () => {} +}; diff --git a/src/mocks/clear-immediate.js b/src/mocks/clear-immediate.js new file mode 100644 index 00000000..2884160a --- /dev/null +++ b/src/mocks/clear-immediate.js @@ -0,0 +1,9 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const eventLoop = require("./event-loop"); + +module.exports = task => { + eventLoop.cancel(task); +}; diff --git a/src/mocks/event-loop.js b/src/mocks/event-loop.js new file mode 100644 index 00000000..fdfc7fa0 --- /dev/null +++ b/src/mocks/event-loop.js @@ -0,0 +1,27 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This is a very simple event loop implementation. It does not cover the exact behavior +// in node, especially not the differences between process.nextTick and setImmediate. +// It is, however, sufficient for the benchmark. +const tasks = []; + +module.exports = { + schedule(task) { + tasks.push(task); + }, + cancel(task) { + const i = tasks.indexOf(task); + + if (i > -1) { + tasks.splice(i, 1); + } + }, + run() { + let task; + while ((task = tasks.shift())) { + task(); + } + } +}; diff --git a/src/mocks/process.js b/src/mocks/process.js new file mode 100644 index 00000000..482b37a3 --- /dev/null +++ b/src/mocks/process.js @@ -0,0 +1,24 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const eventLoop = require("./event-loop"); + +exports.nextTick = function(fn) { + const args = Array.prototype.slice.call(arguments, 1); + eventLoop.schedule(() => { + fn.apply(null, args); + }); +}; + +exports.platform = exports.arch = exports.execPath = exports.title = "browser"; +exports.pid = 1; +exports.browser = true; +exports.env = {}; +exports.argv = []; +exports.cwd = () => "/"; +exports.binding = name => { + throw new Error("No such module. (Possibly not yet loaded)"); +}; +exports.exit = exports.kill = exports.umask = exports.dlopen = exports.uptime = exports.memoryUsage = exports.uvCounters = () => {}; +exports.features = {}; diff --git a/src/mocks/set-immediate.js b/src/mocks/set-immediate.js new file mode 100644 index 00000000..c05ff775 --- /dev/null +++ b/src/mocks/set-immediate.js @@ -0,0 +1,14 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const eventLoop = require("./event-loop"); + +module.exports = function(fn) { + const args = Array.prototype.slice.call(arguments, 1); + const task = () => { + fn.apply(null, args); + }; + eventLoop.schedule(task); + return task; +}; diff --git a/src/suite.js b/src/suite.js index ab00cf28..d9c1a838 100644 --- a/src/suite.js +++ b/src/suite.js @@ -22,5 +22,6 @@ suite.add(require("./source-map-benchmark")); suite.add(require("./typescript-benchmark")); suite.add(require("./uglify-js-benchmark")); suite.add(require("./uglify-es-benchmark")); +suite.add(require("./webpack-benchmark")); module.exports = suite; diff --git a/src/vfs.js b/src/vfs.js index 14e26070..e5c3aec4 100644 --- a/src/vfs.js +++ b/src/vfs.js @@ -79,5 +79,18 @@ fs.writeFileSync( "third_party/vue.runtime.esm-nobuble-2.4.4.js", require("raw-loader!../third_party/vue.runtime.esm-nobuble-2.4.4.js") ); +fs.mkdirpSync("/src/fixtures/webpack"); +fs.writeFileSync( + "/src/fixtures/webpack/a.js", + require("raw-loader!./fixtures/webpack/a.js") +); +fs.writeFileSync( + "/src/fixtures/webpack/b.js", + require("raw-loader!./fixtures/webpack/b.js") +); +fs.writeFileSync( + "/src/fixtures/webpack/c.js", + require("raw-loader!./fixtures/webpack/c.js") +); module.exports = fs; diff --git a/src/webpack-benchmark.js b/src/webpack-benchmark.js new file mode 100644 index 00000000..0cb8b9f1 --- /dev/null +++ b/src/webpack-benchmark.js @@ -0,0 +1,49 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const webpack = require("webpack"); +const eventLoop = require("./mocks/event-loop"); + +const payloads = [ + { + entry: "/src/fixtures/webpack/a.js" + } +].map(({ entry }, i) => ({ + entry, + output: { + path: "/dist/webpack/", + filename: `bundle.${i}.js` + }, + // Using bail option in order to receive noisy errors in the benchmark if something went wrong + bail: true, + // We need to define that because Firefox has a Object.prototype.watch function + watch: false +})); + +module.exports = { + name: "webpack", + fn() { + payloads.forEach(config => { + let finished = false; + + eventLoop.schedule(() => { + const compiler = webpack(config); + compiler.run((err, stats) => { + if (err) { + throw err; + } + if (stats.hasErrors()) { + throw stats.compilation.errors[0]; + } + finished = true; + }); + }); + eventLoop.run(); + + if (finished !== true) { + throw new Error("Webpack did not finish synchronously"); + } + }); + } +}; diff --git a/src/webpack-benchmark.test.js b/src/webpack-benchmark.test.js new file mode 100644 index 00000000..653edeaf --- /dev/null +++ b/src/webpack-benchmark.test.js @@ -0,0 +1,41 @@ +// Copyright 2017 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +const path = require("path"); +const webpack = require("webpack"); +const webpackConfig = require("../webpack.config.js"); + +const outputFile = path.resolve( + __dirname, + "..", + "build", + "webpack-benchmark.test.js" +); +let webpackBenchmark; + +beforeAll( + () => + new Promise((resolve, reject) => { + const baseConfig = webpackConfig[0]; + const config = Object.assign({}, baseConfig, { + entry: require.resolve("./webpack-benchmark.js") + }); + config.output = Object.assign({}, baseConfig.output, { + libraryTarget: "commonjs2", + path: path.dirname(outputFile), + filename: path.basename(outputFile) + }); + const compiler = webpack(config); + compiler.run(err => { + if (err) { + reject(err); + return; + } + webpackBenchmark = require(outputFile); + resolve(); + }); + }) +); + +it("webpack runs to completion", () => void webpackBenchmark.fn()); diff --git a/webpack.config.js b/webpack.config.js index 34996be1..808d51bb 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,10 +19,29 @@ module.exports = [ resolve: { alias: { fs: require.resolve("./src/vfs"), - module: require.resolve("./src/mocks/dummy") + "graceful-fs": require.resolve("./src/vfs"), + module: require.resolve("./src/mocks/dummy"), + chokidar: require.resolve("./src/mocks/chokidar"), + "uglify-js": require.resolve("./src/mocks/dummy"), + // These modules are used by virtualfs to fake async fs calls + "core-js/library/fn/set-immediate": require.resolve( + "./src/mocks/set-immediate" + ), + "core-js/library/fn/clear-immediate": require.resolve( + "./src/mocks/clear-immediate" + ) } }, + node: { + setImmediate: false, // this disables also clearImmediate + process: false + }, plugins: [ + new webpack.ProvidePlugin({ + setImmediate: require.resolve("./src/mocks/set-immediate"), + clearImmediate: require.resolve("./src/mocks/clear-immediate"), + process: require.resolve("./src/mocks/process") + }), new webpack.BannerPlugin({ banner: "// Required for JavaScript engine shells.\n" + @@ -46,10 +65,28 @@ module.exports = [ alias: { define: require.resolve("./src/mocks/dummy"), fs: require.resolve("./src/vfs"), - module: require.resolve("./src/mocks/dummy") + "graceful-fs": require.resolve("./src/vfs"), + module: require.resolve("./src/mocks/dummy"), + chokidar: require.resolve("./src/mocks/chokidar"), + "uglify-js": require.resolve("./src/mocks/dummy"), + "core-js/library/fn/set-immediate": require.resolve( + "./src/mocks/set-immediate" + ), + "core-js/library/fn/clear-immediate": require.resolve( + "./src/mocks/clear-immediate" + ) } }, + node: { + setImmediate: false, + process: false + }, plugins: [ + new webpack.ProvidePlugin({ + setImmediate: require.resolve("./src/mocks/set-immediate"), + clearImmediate: require.resolve("./src/mocks/clear-immediate"), + process: require.resolve("./src/mocks/process") + }), new CopyWebpackPlugin([{ from: "style.css" }, { from: "Logo.png" }]), new webpack.BannerPlugin({ banner: