From 3bd62dc1bc4e13ca5351fac9202a305f8cf63096 Mon Sep 17 00:00:00 2001 From: Andrew Powell Date: Tue, 6 Mar 2018 13:03:53 -0500 Subject: [PATCH] Lots of misc (#47) * improving event bus tests, throwing on invalid options * fix: configs with partial defaults should be valid * add host option to getPort call to ensure correct results * remove webpack 3 test * update package lock * add docs for on and add options * improve object detection and bus test --- README.md | 57 ++++++++++++++++++- lib/bus.js | 8 ++- lib/config.js | 4 ++ lib/options.js | 5 +- lib/server.js | 7 ++- package-lock.json | 49 ++++++++-------- .../fixtures/basic/webpack.function.config.js | 1 + .../webpack.no-entry.config.js | 17 ++++++ test/tests/api.js | 9 +++ test/tests/bus.js | 18 ++++++ test/tests/cli.js | 13 ----- 11 files changed, 141 insertions(+), 47 deletions(-) create mode 100644 test/fixtures/webpack-4-defaults/webpack.no-entry.config.js diff --git a/README.md b/README.md index ed76251..b33eef9 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,10 @@ Type: `Object` Options for initializing and controlling the server provided. +##### add + +Please see [Add-On Features](#add-on-features). + ##### compiler Type: `webpack` @@ -269,6 +273,22 @@ Default: `false` Instruct `webpack-serve` to prepend each line of log output with a `[HH:mm:ss]` timestamp. +##### on + +Type: `Object` +Default: `null` + +While running `webpack-serve` from the command line, it can sometimes be useful +to subscribe to events from the module's event bus _within your config_. This +option can be used for that purpose. The option's value must be an `Object` +matching a `key:handler`, `String: Function` pattern. eg: + +```js +on: { + 'listening': () => { console.log('listening'); } +} +``` + ##### open Type: `Boolean|Object` @@ -318,7 +338,42 @@ features that those familiar with `webpack-dev-server` have come to rely on. Thi makes the module far easier to maintain, which ultimately benefits the user. Luckily, flexibility baked into `webpack-serve` makes it a snap to add-on features. -Listed below are some of the add-on patterns that can be found in +You can leverage this by using the `add` option. The value of the option should +be a `Function` matching the following signature: + +```js +add: (app, middleware, options) => { + // ... +} +``` + +### `add` Function Parameters + +- `app` The underlying Koa app +- `middleware` An object containing accessor functions to call both +`webpack-dev-middleware` and the `koa-static` middleware. +- `options` - The internal options object used by `webpack-serve` + +Some add-on patterns may require changing the order of middleware used in the +`app`. For instance, if adding routes or using a separate router with the `app` +where routes must be added last, you'll need to call the `middleware` functions +early on. `webpack-serve` recognizes these calls and will not execute them again. +If these calls were omitted, `webpack-serve` would execute both in the default, +last in line, order. + +```js +add: (app, middleware, options) => { + // since we're manipulating the order of middleware added, we need to handle + // adding these two internal middleware functions. + middleware.webpack(); + middleware.content(); + + // router *must* be the last middleware added + app.use(router.routes()); +} +``` + +Listed below are some of the add-on patterns and recipes that can be found in [docs/addons](docs/addons): - [bonjour](docs/addons/bonjour.config.js) diff --git a/lib/bus.js b/lib/bus.js index d799ec4..faf8645 100644 --- a/lib/bus.js +++ b/lib/bus.js @@ -1,21 +1,27 @@ 'use strict'; +const isPlainObject = require('lodash/isPlainObject'); const nanobus = require('nanobus'); const weblog = require('webpack-log'); +const WebpackServeError = require('./WebpackServeError'); module.exports = (options) => { const log = weblog({ name: 'serve', id: 'webpack-serve' }); const bus = nanobus(); - if (typeof options.on === 'object') { + if (isPlainObject(options.on)) { for (const event of Object.keys(options.on)) { const fn = options.on[event]; if (typeof fn === 'function') { log.info(`Subscribed to '${event}' event`); bus.on(event, fn); + } else { + throw new WebpackServeError(`The value for an \`on\` event handler must be a Function. event: ${event}`); } } + } else if (options.on) { + throw new WebpackServeError('The value for the `on` option must be an Object. Please see the README.'); } return bus; diff --git a/lib/config.js b/lib/config.js index 2c8ba44..b1ff75e 100644 --- a/lib/config.js +++ b/lib/config.js @@ -39,6 +39,10 @@ module.exports = { toArray(config) { if (typeof config.entry === 'string') { config.entry = [config.entry]; + } else if (typeof config.entry === 'undefined') { + // webpack v4 defaults an empty config to { entry: './src' }. but since we + // need an array, we'll mimic that default config. + config.entry = ['./src']; } else if (isPlainObject(config.entry)) { for (const key of Object.keys(config.entry)) { const entry = config.entry[key]; diff --git a/lib/options.js b/lib/options.js index ec11bf8..72faca7 100644 --- a/lib/options.js +++ b/lib/options.js @@ -4,11 +4,8 @@ const path = require('path'); const merge = require('lodash/merge'); const weblog = require('webpack-log'); const MultiCompiler = require('webpack/lib/MultiCompiler'); -const webpackPackage = require('webpack/package.json'); const { fromFunction } = require('./config'); -const webpackVersion = parseInt(webpackPackage.version, 10); - const defaults = { clipboard: true, compiler: null, @@ -51,7 +48,7 @@ function resolve(options) { // webpack v4 defaults an empty config to { entry: './src' }. but since we // need an array, we'll mimic that default config. - if (webpackVersion > 3 && !options.config && (!options.flags || !options.flags.config)) { + if (!options.config && (!options.flags || !options.flags.config)) { options.config = { entry: ['./src'] }; } diff --git a/lib/server.js b/lib/server.js index 2eb8995..dca49c7 100644 --- a/lib/server.js +++ b/lib/server.js @@ -17,7 +17,6 @@ module.exports = (options) => { const app = new Koa(); const { bus } = options; const log = weblog({ name: 'serve', id: 'webpack-serve' }); - const uri = `${options.protocol}://${options.host}:${options.port}`; let http2; let server; let koaMiddleware; @@ -90,6 +89,8 @@ module.exports = (options) => { } server.once('listening', () => { + const uri = `${options.protocol}://${options.host}:${options.port}`; + log.info(chalk`Project is running at {blue ${uri}}`); if (options.clipboard) { @@ -112,8 +113,8 @@ module.exports = (options) => { }); return Promise.all([ - getPort({ port: options.port }), - getPort({ port: options.hot.port || 8081 }) + getPort({ port: options.port, host: options.host }), + getPort({ port: options.hot.port || 8081, host: options.host }) ]) .then(([port, hotPort]) => { options.port = port; diff --git a/package-lock.json b/package-lock.json index 5ba36c3..4de288f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -517,7 +517,7 @@ "convert-source-map": "1.5.1", "debug": "2.6.9", "json5": "0.5.1", - "lodash": "4.17.4", + "lodash": "4.17.5", "minimatch": "3.0.4", "path-is-absolute": "1.0.1", "private": "0.1.8", @@ -547,7 +547,7 @@ "babel-types": "6.26.0", "detect-indent": "4.0.0", "jsesc": "1.3.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "source-map": "0.5.7", "trim-right": "1.0.1" }, @@ -603,7 +603,7 @@ "babel-helper-function-name": "6.24.1", "babel-runtime": "6.26.0", "babel-types": "6.26.0", - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "babel-helper-explode-assignable-expression": { @@ -680,7 +680,7 @@ "requires": { "babel-runtime": "6.26.0", "babel-types": "6.26.0", - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "babel-helper-remap-async-to-generator": { @@ -890,7 +890,7 @@ "babel-template": "6.26.0", "babel-traverse": "6.26.0", "babel-types": "6.26.0", - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "babel-plugin-transform-es2015-classes": { @@ -1291,7 +1291,7 @@ "babel-runtime": "6.26.0", "core-js": "2.5.3", "home-or-tmp": "2.0.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "mkdirp": "0.5.1", "source-map-support": "0.4.18" } @@ -1316,7 +1316,7 @@ "babel-traverse": "6.26.0", "babel-types": "6.26.0", "babylon": "6.18.0", - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "babel-traverse": { @@ -1333,7 +1333,7 @@ "debug": "2.6.9", "globals": "9.18.0", "invariant": "2.2.2", - "lodash": "4.17.4" + "lodash": "4.17.5" }, "dependencies": { "debug": { @@ -1355,7 +1355,7 @@ "requires": { "babel-runtime": "6.26.0", "esutils": "2.0.2", - "lodash": "4.17.4", + "lodash": "4.17.5", "to-fast-properties": "1.0.3" } }, @@ -3077,7 +3077,7 @@ "js-yaml": "3.10.0", "json-stable-stringify-without-jsonify": "1.0.1", "levn": "0.3.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "minimatch": "3.0.4", "mkdirp": "0.5.1", "natural-compare": "1.4.0", @@ -5192,7 +5192,7 @@ "integrity": "sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "growl": { @@ -5532,7 +5532,7 @@ "cli-width": "2.2.0", "external-editor": "2.1.0", "figures": "2.0.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "mute-stream": "0.0.7", "run-async": "2.3.0", "rx-lite": "4.0.8", @@ -6021,7 +6021,7 @@ "babylon": "6.18.0", "colors": "1.1.2", "flow-parser": "0.66.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "micromatch": "2.3.11", "node-dir": "0.1.8", "nomnom": "1.8.1", @@ -6657,10 +6657,9 @@ } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", - "dev": true + "version": "4.17.5", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", + "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, "lodash.cond": { "version": "4.5.2", @@ -11483,7 +11482,7 @@ "ajv": "5.5.2", "ajv-keywords": "2.1.1", "chalk": "2.3.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "slice-ansi": "1.0.0", "string-width": "2.1.1" }, @@ -12312,7 +12311,7 @@ "integrity": "sha1-YaKau2/MAm/qd+VtHG7FOnlZUfQ=", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "4.17.5" } } } @@ -12392,7 +12391,7 @@ "jscodeshift": "0.4.1", "listr": "0.12.0", "loader-utils": "1.1.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "log-symbols": "2.1.0", "mkdirp": "0.5.1", "p-each-series": "1.0.0", @@ -12499,7 +12498,7 @@ "find-up": "2.1.0", "github-username": "4.1.0", "istextorbinary": "2.2.1", - "lodash": "4.17.4", + "lodash": "4.17.5", "mem-fs-editor": "3.0.2", "minimist": "1.2.0", "mkdirp": "0.5.1", @@ -12538,7 +12537,7 @@ "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", "dev": true, "requires": { - "lodash": "4.17.4" + "lodash": "4.17.5" } }, "chalk": { @@ -12638,7 +12637,7 @@ "cli-width": "2.2.0", "external-editor": "1.1.1", "figures": "1.7.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "mute-stream": "0.0.6", "pinkie-promise": "2.0.1", "run-async": "2.3.0", @@ -12793,7 +12792,7 @@ "globby": "4.1.0", "grouped-queue": "0.3.3", "inquirer": "1.2.3", - "lodash": "4.17.4", + "lodash": "4.17.5", "log-symbols": "1.0.2", "mem-fs": "1.1.3", "text-table": "0.2.0", @@ -13100,7 +13099,7 @@ "grouped-queue": "0.3.3", "inquirer": "3.3.0", "is-scoped": "1.0.0", - "lodash": "4.17.4", + "lodash": "4.17.5", "log-symbols": "2.2.0", "mem-fs": "1.1.3", "text-table": "0.2.0", diff --git a/test/fixtures/basic/webpack.function.config.js b/test/fixtures/basic/webpack.function.config.js index f94adb5..94dfcae 100644 --- a/test/fixtures/basic/webpack.function.config.js +++ b/test/fixtures/basic/webpack.function.config.js @@ -6,6 +6,7 @@ const webpack = require('webpack'); // eslint-disable-next-line no-unused-vars module.exports = function config(env, argv) { return { + mode: 'development', context: __dirname, devtool: 'source-map', entry: ['./app.js'], diff --git a/test/fixtures/webpack-4-defaults/webpack.no-entry.config.js b/test/fixtures/webpack-4-defaults/webpack.no-entry.config.js new file mode 100644 index 0000000..e11462c --- /dev/null +++ b/test/fixtures/webpack-4-defaults/webpack.no-entry.config.js @@ -0,0 +1,17 @@ +'use strict'; + +const path = require('path'); +const webpack = require('webpack'); + +module.exports = { + mode: 'development', + context: __dirname, + output: { + filename: './output.js', + path: path.resolve(__dirname) + }, + plugins: [ + new webpack.NamedModulesPlugin() + ], + serve: {} +}; diff --git a/test/tests/api.js b/test/tests/api.js index bb8b432..00362fb 100644 --- a/test/tests/api.js +++ b/test/tests/api.js @@ -75,6 +75,15 @@ describe('webpack-serve API', () => { }); }); + t('should serve with partial webpack 4 defaults', (done) => { + const config = load('./fixtures/webpack-4-defaults/webpack.no-entry.config.js'); + serve({ config }).then((server) => { + assert(server); + + setTimeout(() => server.close(done), 1000); + }); + }); + t('should have copied the uri to the clipboard', () => { assert.equal(clip.readSync(), 'http://localhost:8080'); }); diff --git a/test/tests/bus.js b/test/tests/bus.js index c38cd14..0ae0a75 100644 --- a/test/tests/bus.js +++ b/test/tests/bus.js @@ -16,4 +16,22 @@ describe('webpack-serve Event Bus', () => { bus.emit('foo'); }); + + it('should not allow non-object options', () => { + const init = () => { + eventbus({ on: 'foo' }); + }; + assert.throws(init); + }); + + it('should not allow a non-function handler', () => { + const init = () => { + eventbus({ + on: { + foo: 'bar' + } + }); + }; + assert.throws(init); + }); }); diff --git a/test/tests/cli.js b/test/tests/cli.js index 14f8f45..6b4bc88 100644 --- a/test/tests/cli.js +++ b/test/tests/cli.js @@ -5,12 +5,10 @@ const assert = require('power-assert'); const execa = require('execa'); const fetch = require('node-fetch'); const strip = require('strip-ansi'); -const webpackPackage = require('webpack/package.json'); const { pause, t, timeout } = require('../util'); const cliPath = path.resolve(__dirname, '../../cli.js'); const configPath = path.resolve(__dirname, '../fixtures/basic/webpack.config.js'); -const webpackVersion = parseInt(webpackPackage.version, 10); function pipe(proc) { // eslint-disable-line no-unused-vars const stream = proc.stdout; @@ -35,17 +33,6 @@ describe('webpack-serve CLI', () => { before(pause); beforeEach(pause); - if (webpackVersion < 4) { - t('should show help', (done) => { - const proc = execa(cliPath); - - proc.then((result) => { - assert(strip(result.stdout).indexOf('Usage') > 0); - done(); - }); - }); - } - t('should show help with --help', (done) => { const proc = execa(cliPath, ['--help']);