module.exports = (options = {}) => { webpackLog({ name: 'config', id: 'webpack-config-loader' }); const name = 'config-loader'; const raw = load(options); return resolve(raw).then((result) => { const schema = merge({}, options.schema, webpackSchema); for (const target of [].concat(result.config)) { validate({ name, schema, target }); } return result; }); };
function createLogger(options) { let level = options.logLevel || 'info'; if (options.noInfo === true) { level = 'warn'; } if (options.quiet === true) { level = 'silent'; } return log({ name: 'wds', level, timestamp: options.logTime, }); }
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; };
// open: { app: <String>, path: <String> } port: 8080, protocol: 'http', }; module.exports = { apply(argv, configs, opts) { const flags = applyFlags(argv); const [first] = configs; const options = merge({}, defaults, opts, flags, first.serve); const { https } = options; const { hotClient } = options; weblog({ id: 'webpack-serve', level: options.logLevel, name: 'serve', timestamp: options.logTime, }); 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 = [first.context]; } else { options.content = [process.cwd()];
module.exports = (options = {}) => { const log = webpackLog({ name: 'config', id: 'webpack-config-loader' }); const requires = [].concat(options.require).filter((r) => !!r); // eslint-disable-next-line no-param-reassign options = Object.assign({ cwd: process.cwd() }, options); for (const module of requires) { try { const modulePath = resolvePath(module, { basedir: cwd }); log.info(chalk`Requiring the {cyan ${module}} module`); if (options.requireOptions) { const { requireOptions } = options; // eslint-disable-next-line import/no-dynamic-require, global-require require(modulePath)(requireOptions); } else { // eslint-disable-next-line import/no-dynamic-require, global-require require(modulePath); } } catch (e) { log.error(chalk`An error occurred while requiring: {grey ${module}}`); throw new RequireModuleError(e, module); } } let config = {}; let { configPath } = options; const explorer = cosmiconfig(prefix, cosmicOptions); try { let result; if (configPath) { result = explorer.loadSync(configPath) || {}; } else { result = explorer.searchSync(options.cwd) || {}; } ({ config, filepath: configPath } = result); log.debug(chalk`Found config at {grey ${configPath}}`); } catch (e) { /* istanbul ignore else */ if (configPath) { log.error(chalk`An error occurred while trying to load {grey ${configPath}} Did you forget to specify a --require?`); } else { log.error(chalk`An error occurred while trying to find a config file Did you forget to specify a --require?`); } throw new LoadConfigError(e, configPath); } if (!configPath && !options.allowMissing) { // prettier-ignore log.error(chalk`Unable to load a config from: {grey ${options.cwd}}`); const e = new Error(`Unable to load a config from: ${options.cwd}`); throw new LoadConfigError(e, configPath); } else { config = config || {}; configPath = configPath || ''; } return { config, configPath }; };
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; }); }, }; };
.then((results) => { const { options, configs } = results; const log = weblog({ name: 'serve', id: 'webpack-serve' }); options.bus = eventbus(options); const { bus } = options; if (!options.compiler) { try { options.compiler = webpack(configs.length > 1 ? configs : configs[0]); } catch (e) { throw new WebpackServeError( `An error was thrown while initializing Webpack\n ${e.toString()}` ); } } const compilers = options.compiler.compilers || [options.compiler]; for (const comp of compilers) { comp.hooks.compile.tap('WebpackServe', () => { bus.emit('build-started', { compiler: comp }); }); comp.hooks.done.tap('WebpackServe', (stats) => { const json = stats.toJson(); if (stats.hasErrors()) { bus.emit('compiler-error', { json, compiler: comp }); } if (stats.hasWarnings()) { bus.emit('compiler-warning', { json, compiler: comp }); } bus.emit('build-finished', { stats, compiler: comp }); }); } const { close, server, start } = getServer(options); start(options); let closing = false; const exit = (signal) => { if (!closing) { closing = true; close(() => { log.info(`Process Ended via ${signal}`); server.kill(); process.exit(0); }); } }; for (const signal of ['SIGINT', 'SIGTERM']) { process.on(signal, () => exit(signal)); } process.on('exit', exit); return Object.freeze({ close, compiler: options.compiler, on(...args) { options.bus.on(...args); }, options, }); })
'use strict'; const isPlainObject = require('lodash/isPlainObject'); const TimeFixPlugin = require('time-fix-plugin'); const weblog = require('webpack-log'); module.exports = { fromFunction(config, argv) { if (typeof config === 'function') { const log = weblog({ name: 'serve', id: 'webpack-serve' }); log.warn('It is not recommended to use configs which export a function. Please see the README for more information.'); return config('development', argv); } return config; }, // adds https://github.com/egoist/time-fix-plugin if not already added // to the config. timeFix(config) { if (config.plugins) { let timeFixFound = false; for (const plugin of config.plugins) { if (!timeFixFound && plugin instanceof TimeFixPlugin) { timeFixFound = true; } } if (!timeFixFound) { config.plugins.unshift(new TimeFixPlugin());
function run() { /* eslint-disable global-require */ const { existsSync: exists, statSync: stat } = require('fs'); const { sep } = require('path'); const { inspect } = require('util'); const chalk = require('chalk'); const meow = require('meow'); const woof = require('./'); const { help: commandHelp, load: getCommands } = require('./commands'); const { help: flagHelp, opts } = require('./flags'); /* eslint-enable global-require */ const flagOpts = { flags: opts() }; const log = weblog({ name: 'command', id: 'webpack-command-forced' }); const cli = meow( chalk` {underline Usage} $ webpack [<config>, ...options] $ webpack <entry-file> [...<entry-file>] <output-file> {underline Options} ${flagHelp()} For further documentation, visit {blue https://webpack.js.org/api/cli} {underline Commands} ${commandHelp()} Type \`webpack help <command>\` for more information {underline Examples} $ webpack $ webpack --help $ webpack entry.js $ webpack --config ../webpack.config.js `, flagOpts ); const commands = getCommands(); const [command] = cli.input; cli.argv = cli.flags; cli.commands = commands; cli.entries = []; const cmd = cli.commands[command]; if (cmd) { try { cmd.run(cli); } catch (e) { log.error(chalk`The {bold \`${command}\`} command threw an error:`); throw e; } } else { if (cli.input.length) { const problems = []; const isDir = (path) => stat(path).isDirectory(); const entries = []; for (let file of cli.input) { if (!exists(file)) { problems.push(file); } else { if (isDir(file)) { file += sep; } entries.push(file); } } if (problems.length) { const prefix = problems.length === cli.input.length ? 'The' : 'Some of the'; const message = `${prefix} input provided did not match any known commands or existing files: ${problems.join(' ')}`; log.error(message); /* istanbul ignore else */ if (process.env.CLI_TEST === 'true') { throw new Error(message); } else { // citing: http://www.tldp.org/LDP/abs/html/exitcodes.html process.exit(127); } } cli.entries = entries; } woof(cli).catch((e) => { let error = e; const errorType = error.constructor.name; const epilogue = chalk`: {dim use --log-level=debug for more error detail}`; let preamble = 'A webpack error occured while building'; let logStack = true; // NOTE: none of these conditionals need coverage. the catch has coverage // and that's what is important /* istanbul ignore if */ if (!(error instanceof Error)) { error = new Error( `webpack-command failed with a value of: ${inspect(error)}` ); } // Assume that errors are generated by webpack first: e.g. ModuleNotFoundError // If it's a WebpackCommandError then we goofed somehow. // If it's a generic Error it's likely that webpack is throwing a poorly // managed error, or a plugin has done something naughty. /* istanbul ignore if|else */ if (errorType === 'WebpackCommandError') { preamble = 'An error occured while running webpack-command'; } else if (errorType === 'Error') { /* istanbul ignore next */ preamble = 'An error occured while running webpack'; } else { logStack = false; } log.error(preamble + epilogue); /* istanbul ignore if */ if (logStack) { log.error(error.stack); } else { log.error(error.message); } /* istanbul ignore if */ if (cli.flags.logLevel === 'debug') { log.level = cli.flags.logLevel; } log.debug(inspect(error)); process.exitCode = 1; }); } }
return resolve(opts).then((configs) => { const [first] = configs; const options = merge({}, defaults, opts, flags, configs[0].serve); const https = pull(flags, 'https'); const open = pull(flags, 'open'); if (https) { options.https = https; } // separate logical block so that protocol will be set correctly whether // https is set via options in config or cli flag if (options.https) { options.protocol = 'https'; } /* istanbul ignore if */ if (open) { if (!open.path) { open.path = '/'; } options.open = open; } if (typeof options.content === 'string') { options.content = [options.content]; } if (!options.content || !options.content.length) { 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; return { configs, options }; });
} else { // eslint-disable-next-line valid-typeof result = typeof value === type; } } } return result; }, validate(opts) { const defaults = { throw: true }; const options = Object.assign({}, defaults, opts); const errors = []; const { argv, flags, prefix = 'webpack' } = options; const log = weblog({ name: prefix, id: `${prefix}-cli-util` }); const names = Object.keys(flags); const tableOptions = { align: ['', 'l', 'l'], stringLength(str) { return strip(str).length; }, }; const aliases = names .map((name) => flags[name].alias) .filter((alias) => !!alias); const uniqueArgs = new Set( Object.keys(argv) .map((arg) => decamel(arg, '-')) .filter((arg) => !aliases.includes(arg)) );