aboutsummaryrefslogtreecommitdiff
path: root/node_modules/webpack-serve/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/webpack-serve/lib')
-rw-r--r--node_modules/webpack-serve/lib/WebpackServeError.js1
-rw-r--r--node_modules/webpack-serve/lib/bus.js31
-rw-r--r--node_modules/webpack-serve/lib/config.js121
-rw-r--r--node_modules/webpack-serve/lib/global.js9
-rw-r--r--node_modules/webpack-serve/lib/options.js225
-rw-r--r--node_modules/webpack-serve/lib/server.js145
6 files changed, 532 insertions, 0 deletions
diff --git a/node_modules/webpack-serve/lib/WebpackServeError.js b/node_modules/webpack-serve/lib/WebpackServeError.js
new file mode 100644
index 00000000..92149305
--- /dev/null
+++ b/node_modules/webpack-serve/lib/WebpackServeError.js
@@ -0,0 +1 @@
+module.exports = class WebpackServeError extends Error {};
diff --git a/node_modules/webpack-serve/lib/bus.js b/node_modules/webpack-serve/lib/bus.js
new file mode 100644
index 00000000..57c834bc
--- /dev/null
+++ b/node_modules/webpack-serve/lib/bus.js
@@ -0,0 +1,31 @@
+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 (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/node_modules/webpack-serve/lib/config.js b/node_modules/webpack-serve/lib/config.js
new file mode 100644
index 00000000..490ce4b4
--- /dev/null
+++ b/node_modules/webpack-serve/lib/config.js
@@ -0,0 +1,121 @@
+const { isAbsolute, resolve } = require('path');
+
+const loader = require('@webpack-contrib/config-loader');
+const isPlainObject = require('lodash/isPlainObject');
+const TimeFixPlugin = require('time-fix-plugin');
+const { MultiCompiler } = require('webpack');
+
+// automagically wrap hot-client-invalid entry values in an array
+function toArray(config) {
+ const result = config;
+
+ if (typeof result.entry === 'string') {
+ result.entry = [result.entry];
+ } else if (typeof result.entry === 'undefined') {
+ // webpack v4 defaults an empty config to { entry: './src' }. but since we
+ // need an array, we'll mimic that default config.
+ result.entry = ['./src'];
+ } else if (isPlainObject(result.entry)) {
+ for (const key of Object.keys(result.entry)) {
+ const entry = result.entry[key];
+
+ if (!Array.isArray(entry)) {
+ result.entry[key] = [entry];
+ }
+ }
+ }
+
+ return result;
+}
+
+// adds https://github.com/egoist/time-fix-plugin if not already added
+// to the config.
+function timeFix(config) {
+ const result = config;
+
+ if (result.plugins) {
+ let timeFixFound = false;
+ for (const plugin of result.plugins) {
+ if (!timeFixFound && plugin instanceof TimeFixPlugin) {
+ timeFixFound = true;
+ }
+ }
+
+ if (!timeFixFound) {
+ result.plugins.unshift(new TimeFixPlugin());
+ }
+ } else {
+ result.plugins = [new TimeFixPlugin()];
+ }
+
+ return result;
+}
+
+function prepare(config) {
+ return timeFix(toArray(config));
+}
+
+module.exports = {
+ load(options) {
+ const { compiler, config } = options;
+ const flags = options.flags || {};
+ const require = flags.require || options.require;
+
+ // if someone passed us a config Object, then just resolve that object
+ // we're only going to load the config from file when we're given a file
+ // to load
+ if (isPlainObject(config) || Array.isArray(config)) {
+ const result = [].concat(config).map((conf) => prepare(conf));
+ return Promise.resolve(result);
+ }
+
+ // if a user has passed us a compiler directly, then pull the config from
+ // the compiler and use that
+ if (compiler) {
+ let configs = [];
+ if (compiler instanceof MultiCompiler) {
+ const { compilers } = compiler;
+ configs = compilers.map((c) => c.options);
+ } else {
+ configs = [compiler.options];
+ }
+
+ return Promise.resolve(configs);
+ }
+
+ const loaderOptions = {
+ allowMissing: true,
+ require,
+ schema: {
+ properties: {
+ serve: {
+ additionalProperties: true,
+ type: 'object',
+ },
+ },
+ },
+ };
+
+ if (typeof config === 'string') {
+ if (isAbsolute(config)) {
+ loaderOptions.configPath = config;
+ } else {
+ loaderOptions.configPath = resolve(process.cwd(), config);
+ }
+ }
+
+ return loader(loaderOptions).then((result) => {
+ let found = result.config;
+
+ if (!found) {
+ // webpack v4+ defaults an empty config to { entry: './src' }. but since we
+ // need an array, we'll mimic that default config.
+ found = { entry: ['./src'] };
+ }
+
+ found = [].concat(found).map((conf) => prepare(conf));
+
+ return found;
+ });
+ },
+};
diff --git a/node_modules/webpack-serve/lib/global.js b/node_modules/webpack-serve/lib/global.js
new file mode 100644
index 00000000..ca98e0ec
--- /dev/null
+++ b/node_modules/webpack-serve/lib/global.js
@@ -0,0 +1,9 @@
+/* istanbul ignore next */
+/* eslint-disable global-require, no-console, import/no-extraneous-dependencies */
+
+module.exports = {
+ register() {
+ require('v8-compile-cache');
+ require('loud-rejection/register');
+ },
+};
diff --git a/node_modules/webpack-serve/lib/options.js b/node_modules/webpack-serve/lib/options.js
new file mode 100644
index 00000000..867ce255
--- /dev/null
+++ b/node_modules/webpack-serve/lib/options.js
@@ -0,0 +1,225 @@
+const fs = require('fs');
+const path = require('path');
+
+const merge = require('lodash/merge');
+const weblog = require('webpack-log');
+
+const { load } = require('./config');
+
+const defaults = {
+ clipboard: true,
+ compiler: null,
+ config: {},
+ content: [],
+ dev: { publicPath: '/' },
+ flags: {},
+ host: 'localhost',
+ hot: {},
+ http2: false,
+ https: null,
+ // https: {
+ // key: fs.readFileSync('...key'),
+ // cert: fs.readFileSync('...cert'),
+ // pfx: ...,
+ // passphrase: ...
+ // },
+ logLevel: 'info',
+ logTime: false,
+ open: false,
+ // open: { app: <String>, path: <String> }
+ port: 8080,
+ protocol: 'http',
+};
+
+function parse(value) {
+ try {
+ return JSON.parse(value);
+ } catch (e) {
+ if (typeof value === 'string') {
+ return value;
+ }
+
+ process.emitWarning(e);
+ return null;
+ }
+}
+
+function pull(obj, prefix) {
+ let result;
+ for (const key of Object.keys(obj)) {
+ if (key.indexOf(prefix) === 0) {
+ if (!result) {
+ result = {};
+ }
+ const name = key.replace(prefix, '').toLowerCase();
+ result[name] = obj[key];
+ }
+ }
+ return result;
+}
+
+module.exports = (opts) => {
+ const flags = opts.flags || {};
+ const nodeVersion = parseInt(process.version.substring(1), 10);
+
+ return load(opts).then((configs) => {
+ const [first] = configs;
+ const options = merge({}, defaults, opts, flags, first.serve);
+ const httpsFlags = pull(flags, 'https');
+ const openFlags = pull(flags, 'open');
+
+ if (httpsFlags) {
+ options.https = httpsFlags;
+ }
+
+ if (options.https) {
+ const { https } = options;
+ for (const property of ['cert', 'key', 'pfx']) {
+ const value = https[property];
+ const isBuffer = value instanceof Buffer;
+
+ if (value && !isBuffer) {
+ // lstat seems to have a hard limit at 260
+ if (value.length < 260 && fs.lstatSync(value).isFile()) {
+ https[property] = fs.readFileSync(path.resolve(value));
+ }
+ }
+ }
+
+ if (https.pass) {
+ https.passphrase = https.pass;
+ delete https.pass;
+ }
+ options.protocol = 'https';
+ options.https = https;
+ }
+
+ /* istanbul ignore if */
+ if (openFlags) {
+ if (!openFlags.path) {
+ openFlags.path = '/';
+ }
+ options.open = openFlags;
+ }
+
+ if (typeof options.content === 'string') {
+ options.content = [options.content];
+ }
+
+ if (!options.content || !options.content.length) {
+ // if no context was specified in a config, and no --content options was
+ // used, then we need to derive the context, and content location, from
+ // the compiler.
+ if (first.context) {
+ options.content = [].concat(first.context);
+ }
+
+ options.content.push(process.cwd());
+ }
+
+ /* istanbul ignore if */
+ if (options.http2 && nodeVersion < 9) {
+ throw new TypeError(
+ 'webpack-serve: The `http2` option can only be used with Node v9 and higher.'
+ );
+ }
+
+ if (flags.dev && typeof flags.dev === 'string') {
+ options.dev = parse(flags.dev);
+
+ if (typeof options.dev !== 'object') {
+ throw new TypeError(
+ 'webpack-serve: The --dev flag must be a string contianing a valid JSON object.'
+ );
+ }
+ }
+
+ if (flags.hot && typeof flags.hot === 'string') {
+ options.hot = parse(flags.hot);
+
+ if (typeof options.hot !== 'object') {
+ throw new TypeError(
+ 'webpack-serve: The --hot flag must be a string contianing a valid JSON object.'
+ );
+ }
+ }
+
+ if (flags.hotClient === false) {
+ options.hot = false;
+ } else {
+ /* istanbul ignore if */
+ if (flags.hot === false) {
+ options.hot = Object.assign({}, options.hot, { hot: false });
+ }
+
+ /* istanbul ignore if */
+ if (flags.reload === false) {
+ options.hot = Object.assign({}, options.hot, { reload: false });
+ }
+ }
+
+ // because you just know someone is going to do this
+ if (options.hot === true) {
+ options.hot = defaults.hot;
+ }
+
+ if (options.hot) {
+ if (options.hot.host) {
+ const hotHost = options.hot.host.server || options.hot.host;
+ if (hotHost !== options.host) {
+ throw new TypeError(
+ 'webpack-serve: The `hot.host` (or `hot.host.server`) property must match the `host` option.'
+ );
+ }
+ } else {
+ options.hot.host = options.host;
+ }
+ }
+
+ /* istanbul ignore if */
+ if (flags.openApp) {
+ options.open = Object.assign({}, options.open, {
+ app: parse(flags.openApp),
+ });
+ }
+
+ /* istanbul ignore if */
+ if (flags.openPath) {
+ options.open = Object.assign({}, options.open, {
+ path: flags.openPath,
+ });
+ }
+
+ if (flags.logLevel) {
+ options.logLevel = flags.logLevel;
+ options.dev = Object.assign(options.dev, { logLevel: options.logLevel });
+ options.hot = Object.assign(options.hot, { logLevel: options.logLevel });
+ }
+
+ if (flags.logTime) {
+ options.logTime = true;
+ options.dev = Object.assign(options.dev, { logTime: true });
+ options.hot = Object.assign(options.hot, { logTime: true });
+ }
+
+ // initialize the shared log
+ weblog({
+ name: 'serve',
+ id: 'webpack-serve',
+ level: options.logLevel,
+ timestamp: options.logTime,
+ });
+
+ // cleanup - doing this here so as not to mutate the options passed in.
+ delete options.config;
+
+ // this isn't part of the webpack options schema, and doesn't need to be
+ delete configs[0].serve; // eslint-disable-line no-param-reassign
+
+ return { configs, options };
+ });
+};
+
+// this is exported so we can get coverage on the function
+// not intended to be used externally
+module.exports.parse = parse;
diff --git a/node_modules/webpack-serve/lib/server.js b/node_modules/webpack-serve/lib/server.js
new file mode 100644
index 00000000..071c4a12
--- /dev/null
+++ b/node_modules/webpack-serve/lib/server.js
@@ -0,0 +1,145 @@
+const http = require('http');
+const https = require('https');
+
+const chalk = require('chalk');
+const clip = require('clipboardy');
+const getPort = require('get-port');
+const killable = require('killable');
+const Koa = require('koa');
+const koaWebpack = require('koa-webpack');
+const serveStatic = require('@shellscape/koa-static/legacy');
+const urljoin = require('url-join');
+const weblog = require('webpack-log');
+
+const WebpackServeError = require('./WebpackServeError');
+
+module.exports = (options) => {
+ const app = new Koa();
+ const { bus } = options;
+ const log = weblog({ name: 'serve', id: 'webpack-serve' });
+ let http2;
+ let server;
+ let koaMiddleware;
+
+ const middleware = {
+ webpack: () => {
+ middleware.webpack.called = true;
+
+ // track the promise state in the event that someone calls server.close()
+ // quickly, before this promise resolves.
+
+ try {
+ koaMiddleware = koaWebpack({
+ // be explicit about what we want to pass, rather than passing the entire
+ // options object
+ compiler: options.compiler,
+ dev: options.dev,
+ hot: options.hot,
+ });
+ app.use(koaMiddleware);
+ middleware.webpack.state = Promise.resolve();
+ } catch (e) {
+ /* istanbul ignore next*/
+ middleware.webpack.state = Promise.reject(e);
+ /* istanbul ignore next*/
+ throw new WebpackServeError(
+ `An error was thrown while initializing koa-webpack\n ${e.toString()}`
+ );
+ }
+ },
+ content: (staticOptions) => {
+ middleware.content.called = true;
+ for (const dir of options.content) {
+ app.use(serveStatic(dir, staticOptions || {}));
+ }
+ },
+ };
+
+ if (options.http2) {
+ // the check for if we can do this is in options.js
+ http2 = require('http2'); // eslint-disable-line
+ }
+
+ if (options.https) {
+ if (http2) {
+ server = http2.createSecureServer(options.https, app.callback());
+ } else {
+ server = https.createServer(options.https, app.callback());
+ }
+ } else {
+ server = (http2 || http).createServer(app.callback());
+ }
+
+ killable(server);
+
+ server.on('error', (err) => log.error(err));
+
+ return {
+ server,
+ close(callback) {
+ server.kill(() => {
+ if (middleware.webpack.called) {
+ middleware.webpack.state.then(() => koaMiddleware.close(callback));
+ }
+ });
+ },
+ start() {
+ server.once('listening', () => {
+ const uri = `${options.protocol}://${options.host}:${options.port}`;
+
+ log.info(chalk`Project is running at {blue ${uri}}`);
+
+ if (options.clipboard && !options.open) {
+ /* istanbul ignore next*/
+ try {
+ clip.writeSync(uri);
+ log.info(chalk.grey('Server URI copied to clipboard'));
+ } catch (error) {
+ log.warn(
+ chalk.grey(
+ 'Failed to copy server URI to clipboard. ' +
+ "Use logLevel: 'debug' for more information."
+ )
+ );
+ log.debug(error);
+ }
+ }
+
+ bus.emit('listening', { server, options });
+
+ if (options.open) {
+ const open = require('opn'); // eslint-disable-line global-require
+ open(urljoin(uri, options.open.path || ''), options.open.app || {});
+ }
+ });
+
+ return Promise.all([
+ getPort({ port: options.port, host: options.host }),
+ getPort({ port: options.hot.port || 8081, host: options.host }),
+ ]).then(([port, hotPort]) => {
+ /* eslint-disable no-param-reassign */
+ options.port = port;
+ if (options.hot) {
+ options.hot.port = hotPort;
+ }
+
+ if (typeof options.add === 'function') {
+ options.add(app, middleware, options);
+ }
+
+ if (!middleware.content.called) {
+ middleware.content();
+ }
+
+ // allow consumers to specifically order middleware
+ if (!middleware.webpack.called) {
+ middleware.webpack();
+ }
+
+ server.listen(options.port, options.host);
+
+ return server;
+ });
+ },
+ };
+};