diff options
Diffstat (limited to 'node_modules/webpack-serve/lib')
| -rw-r--r-- | node_modules/webpack-serve/lib/WebpackServeError.js | 1 | ||||
| -rw-r--r-- | node_modules/webpack-serve/lib/bus.js | 31 | ||||
| -rw-r--r-- | node_modules/webpack-serve/lib/config.js | 121 | ||||
| -rw-r--r-- | node_modules/webpack-serve/lib/global.js | 9 | ||||
| -rw-r--r-- | node_modules/webpack-serve/lib/options.js | 225 | ||||
| -rw-r--r-- | node_modules/webpack-serve/lib/server.js | 145 |
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; + }); + }, + }; +}; |
