UNPKG

varan

Version:

A webpack starter kit for offline-first bring-your-own-code apps with server side rendering

376 lines 20.3 kB
#!/usr/bin/env node "use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); require("source-map-support/register"); const dotenv_1 = __importDefault(require("dotenv")); dotenv_1.default.config(); /* eslint-disable import/first */ const commander_1 = __importDefault(require("commander")); const path_1 = __importDefault(require("path")); const update_notifier_1 = __importDefault(require("update-notifier")); const chalk_1 = __importDefault(require("chalk")); const ora_1 = __importDefault(require("ora")); const url_1 = require("url"); const lodash_1 = require("lodash"); const table_1 = require("table"); const filesize_1 = __importDefault(require("filesize")); const createLogger_1 = __importDefault(require("../lib/createLogger")); const emojis_1 = __importDefault(require("../lib/emojis")); const index_1 = require("../index"); const getCompilerStats_1 = __importDefault(require("../lib/getCompilerStats")); /* eslint-enable import/first */ // eslint-disable-next-line const pkg = require('../../package.json'); // Init process.on('unhandledRejection', (err) => { throw err; }); const resolve = (file) => { const varanLocalPath = `varan/`; return file.startsWith(varanLocalPath) ? path_1.default.resolve(__dirname, '..', '..') + path_1.default.sep + file.substr(varanLocalPath.length) : file && path_1.default.resolve(process.cwd(), file); }; // Check for updates update_notifier_1.default({ pkg }).notify(); // Setup program commander_1.default.usage('<command> [options]').version(pkg.version); /** * Build application */ commander_1.default .command('build [files...]') .option('--env <environment>', 'Environment to use.', 'production') .option('-a, --analyze', 'Analyze build') .option('-s, --silent', 'Disable output') .action(async (files, opts) => { const log = createLogger_1.default({ silent: opts.silent }); const env = (opts && opts.env) || 'production'; const configs = (files.length > 0 && files.map(resolve)) || [ path_1.default.resolve(__dirname, '..', '..', 'webpack', 'server'), path_1.default.resolve(__dirname, '..', '..', 'webpack', 'client'), ]; try { log.info(); log.info(` Building ${chalk_1.default.cyan(configs.length.toString())} configs for ${chalk_1.default.cyan(env)} using ${chalk_1.default.cyan(pkg.name)} ${chalk_1.default.cyan(`v${pkg.version}`)} ${chalk_1.default.green(emojis_1.default.robot)}`); log.info(); /** * Run build */ const { result, options } = await index_1.build(Object.assign(Object.assign({}, opts), { verbose: !opts.silent, configs, env })); const hasWarnings = result.stats.some((s) => s.warnings.length > 0); // Fetch statistics log.info(); log.info(` ${chalk_1.default.green(emojis_1.default.rocket)} Success! ${hasWarnings ? `${chalk_1.default.yellow('With warnings. See below for more information!')} ` : ''}${chalk_1.default.green(emojis_1.default.rocket)}`); log.info(` Compiled ${chalk_1.default.cyan(result.totals.numberOfConfigs.toString())} configs for ${chalk_1.default.cyan(opts.env)} in ${chalk_1.default.cyan(`${result.totals.timings.duration}ms`)}`); log.info(); // Create beautiful presentation of build statistics result.stats.forEach((stat, i) => { if (stat.build) { // Padding if (i > 0) { log.info(); log.info(); } const tableTotals = [ [ `Config file (${chalk_1.default.cyan((i + 1).toString())}/${chalk_1.default.cyan(result.totals.numberOfConfigs.toString())})`, chalk_1.default.cyan(stat.build.configFile), ], ['Target directory', chalk_1.default.cyan(stat.build.outputPath || '-')], ['Duration', chalk_1.default.cyan(`${result.totals.timings.perCompiler[i].duration.toString()}ms`)], ]; log.info(table_1.table(tableTotals, { border: table_1.getBorderCharacters(`void`), columns: { 0: { paddingLeft: 2, width: 25, }, }, drawHorizontalLine: () => false, })); // Log assets if (stat.currentBuild) { const isChunk = (assetOrChunk) => { return !!assetOrChunk.assets; }; const tableHeaders = ['Asset [chunk]', 'Size', 'Gzipped', 'Brotli']; const printRelativeSize = (key, current, comparisonObject, warnSize) => { if (!current[key]) return null; let out = filesize_1.default(current[key]); if (warnSize && current[key] > warnSize) out = chalk_1.default.yellow(out); if (comparisonObject && comparisonObject[current.name]) { const previous = comparisonObject[current.name]; if (previous[key]) { if (current[key] > previous[key]) out += ` + ${chalk_1.default.yellow(filesize_1.default(current[key] - previous[key]))}`; else if (current[key] === previous[key]) out += ` + ${chalk_1.default.yellow(filesize_1.default(current[key] - previous[key]))}`; else out += ` - ${chalk_1.default.green(filesize_1.default(previous[key] - current[key]))}`; } } return out; }; const printSize = (key, current, chunk = false) => printRelativeSize(key, current, (stat.previousBuild && stat.previousBuild[chunk ? 'chunks' : 'assets']) || undefined, chunk ? options.warnChunkSize : options.warnAssetSize); const ignoreExtensions = ['.br', '.gz']; const tableRows = [ ...Object.values(stat.currentBuild.chunks), ...Object.values(stat.currentBuild.assets) .filter((asset) => !ignoreExtensions.includes(path_1.default.extname(asset.name).toLocaleLowerCase())) .filter((asset) => Object.keys(asset.chunks).length === 0), ] .sort((a, b) => b.size - a.size) .reduce((acc, cur) => { if (isChunk(cur)) { const chunkTooBig = options.warnChunkSize && cur.size > options.warnChunkSize; const name = chunkTooBig ? chalk_1.default.yellow(`[${cur.name}]`) : chalk_1.default.gray(`[${cur.name}]`); acc.push([ name, chalk_1.default.gray(printSize('size', cur, true) || ''), chalk_1.default.gray(printSize('gzip', cur, true) || ''), chalk_1.default.gray(printSize('brotli', cur, true) || ''), ]); Object.values(cur.assets) .sort((a, b) => b.size - a.size) .forEach((asset) => { let assetName; if (options.warnAssetSize && asset.size > options.warnAssetSize) assetName = chalk_1.default.yellow(asset.name); else assetName = asset.name; acc.push([ ` ${assetName}${Object.keys(asset.chunks).length > 1 ? ' +' : ''}`, printSize('size', asset), printSize('gzip', asset), printSize('brotli', asset), ]); }); } else { const name = options.warnAssetSize && cur.size > options.warnAssetSize ? chalk_1.default.yellow(cur.name) : cur.name; acc.push([name, printSize('size', cur), printSize('gzip', cur), printSize('brotli', cur)]); } return acc; }, []); const previousBuildSum = stat.previousBuild && Object.values(stat.previousBuild.assets) .filter((asset) => !!((stat.currentBuild && stat.currentBuild.assets[asset.name]) || false)) .reduce((acc, cur) => { ['size', 'gzip', 'brotli'].forEach((k) => { if (cur[k]) acc.sum[k] = acc.sum[k] ? (acc.sum[k] += cur[k]) : cur[k]; }); return acc; }, { sum: { name: 'sum' } }); const currentBuildSum = Object.values(stat.currentBuild.assets).reduce((acc, cur) => { ['size', 'gzip', 'brotli'].forEach((k) => { if (cur[k]) acc[k] = acc[k] ? (acc[k] += cur[k]) : cur[k]; }); return acc; }, { name: 'sum' }); const tableSumRow = [ 'SUM', printRelativeSize('size', currentBuildSum, previousBuildSum || undefined), printRelativeSize('gzip', currentBuildSum, previousBuildSum || undefined), printRelativeSize('brotli', currentBuildSum, previousBuildSum || undefined), ].map((c) => chalk_1.default.bold(c || '')); const tableBuildStats = [tableHeaders, ...tableRows, tableSumRow]; // Show table log.info(table_1.table(tableBuildStats, { drawHorizontalLine: (index, size) => index === 0 || index === 1 || index === size - 1 || index === size, })); } // Log warnings if (stat.warnings.length > 0) { log.warn(` ${chalk_1.default.yellow(`${emojis_1.default.warning} Warnings`)}`); stat.warnings.forEach((warning) => log.warn(` ${chalk_1.default.yellow(emojis_1.default.smallSquare)} ${warning}`)); } // Log errors if (stat.errors.length > 0) { log.error(` ${chalk_1.default.red(`${emojis_1.default.failure} Errors`)}`); stat.errors.forEach((error) => log.error(` ${chalk_1.default.red(emojis_1.default.smallSquare)} ${error}`)); } } }); } catch (err) { log.error(); log.error(` ${chalk_1.default.red(emojis_1.default.failure)} Failure! Failed to build project ${chalk_1.default.red(emojis_1.default.failure)}`); if (err.details) log.error(` ${chalk_1.default.cyan('Details:')} ${err.details}`); if (err.errors) log.error(` ${err.errors}`); else if (err.stack) log.error(` ${err.stack}`); log.error(); } }); /** * Development watching mode */ commander_1.default .command('watch [files...]') .usage('[options] [files...] -- --inspect') .option('-s, --silent', 'Disable output') .option('--host <host>', 'Specify host for both client and server to bind on') .option('--client-port <port number>', 'Specify client dev server port to listen on', (port) => parseInt(port, 10)) .option('--server-port <port number>', 'Specify server port to listen on', (port) => parseInt(port, 10)) .option('--env <development|production>', 'Environment to use') .option('--open', 'Open app in browser automatically?') .action(async (rawFiles, opts) => { const log = createLogger_1.default({ silent: opts.silent }); // eslint-disable-next-line no-param-reassign opts.args = process.argv.includes('--') ? process.argv.slice(process.argv.indexOf('--') + 1) : []; const env = (opts && opts.env) || 'development'; const files = rawFiles.filter((f) => !opts.args.includes(f)); const configs = (files.length > 0 && files.map(resolve)) || [ path_1.default.resolve(__dirname, '..', '..', 'webpack', 'server'), path_1.default.resolve(__dirname, '..', '..', 'webpack', 'client'), ]; try { log.info(); log.info(` Watching project with ${chalk_1.default.cyan(configs.length.toString())} configs in ${chalk_1.default.cyan(env)} mode using ${chalk_1.default.cyan(pkg.name)} ${chalk_1.default.cyan(`v${pkg.version}`)} ${chalk_1.default.green(emojis_1.default.robot)}`); log.info(); /** * Run watcher */ const watcher = await index_1.watch({ configs, env, verbose: !opts.silent, devServerHost: opts && opts.host, devServerPort: opts && opts.clientPort, serverHost: opts && opts.host, serverPort: opts && opts.serverPort, args: opts && opts.args, openBrowser: opts && opts.open, silent: opts && opts.silent, }); const hasWarnings = (watcher.client && watcher.client.warnings.length > 0) || (watcher.server && watcher.server.warnings.length > 0); // Fetch statistics log.info(); log.info(); log.info(` ${chalk_1.default.green(emojis_1.default.rocket)} Success! ${hasWarnings ? `${chalk_1.default.yellow('With warnings. See below for more information!')} ` : ''}${chalk_1.default.green(emojis_1.default.rocket)}`); log.info(` Compiled ${chalk_1.default.cyan(watcher.totals.numberOfConfigs.toString())} configs in ${chalk_1.default.cyan(`${watcher.totals.timings.duration}ms`)}`); if (watcher.server) log.info(` Server compiled in ${chalk_1.default.cyan(`${watcher.totals.timings.perCompiler[0].duration}ms`)}`); if (watcher.client) log.info(` Client compiled in ${chalk_1.default.cyan(`${watcher.totals.timings.perCompiler[watcher.server ? 1 : 0].duration}ms`)}`); log.info(); log.info(); // Integrate with server if (watcher.server) { const serverCompileSpinner = ora_1.default({ spinner: 'circleHalves', text: chalk_1.default.bold('Server recompiling'), discardStdin: false, }); // Begin recompile watcher.server.compiler.hooks.invalid.tap(pkg.name, () => { serverCompileSpinner.start(); }); watcher.server.compiler.hooks.done.tap(pkg.name, (stats) => { const compileStats = getCompilerStats_1.default(stats); const info = stats.toJson(); if (stats.hasErrors()) { serverCompileSpinner.fail(chalk_1.default.bold(`Server failed to recompile in ${chalk_1.default.cyan(`${compileStats.timings.duration}ms`)} due to ${chalk_1.default.red('errors')}`)); // Log errors if (info.errors.length > 0) setImmediate(() => info.errors.forEach((error) => log.error(error))); } else { serverCompileSpinner.succeed(chalk_1.default.bold(`Server recompiled in ${chalk_1.default.cyan(`${compileStats.timings.duration}ms`)}`)); } }); // Pass logging through if (watcher.server.runner.stdout) { watcher.server.runner.stdout.on('data', (data) => !data.toString().startsWith('[HMR]') && log.info(`${chalk_1.default.cyan(`${emojis_1.default.speechBalloon} SERVER:`)} ${data.toString()}`)); } if (watcher.server.runner.stderr) { watcher.server.runner.stderr.on('data', (data) => log.error(`${chalk_1.default.cyan(`${emojis_1.default.speechBalloon} SERVER:`)} ${data.toString()}`)); } } // Integrate with client if (watcher.client) { const clientCompileSpinner = ora_1.default({ spinner: 'hearts', text: chalk_1.default.bold('Client recompiling'), discardStdin: false, }); // Begin recompile watcher.client.compiler.hooks.invalid.tap(pkg.name, () => { clientCompileSpinner.start(); }); watcher.client.compiler.hooks.done.tap(pkg.name, (stats) => { const compileStats = getCompilerStats_1.default(stats); if (stats.hasErrors()) { const stat = stats.toJson(); clientCompileSpinner.fail(chalk_1.default.bold(`Client failed to recompile in ${chalk_1.default.cyan(`${compileStats.timings.duration}ms`)} due to ${chalk_1.default.red('errors')}`)); stat.errors.forEach((error) => log.error(` ${chalk_1.default.red(emojis_1.default.smallSquare)} ${error}`)); } else { clientCompileSpinner.succeed(chalk_1.default.bold(`Client recompiled in ${chalk_1.default.cyan(`${compileStats.timings.duration}ms`)}`)); } }); const urlToClient = lodash_1.get(watcher, 'client.compiler.options.output.publicPath', url_1.format({ protocol: opts.devServerProtocol, hostname: ['0.0.0.0', '::'].includes(opts.devServerHost) ? 'localhost' : opts.devServerHost, port: opts.devServerPort, pathname: '/', })); log.info(` Development server is now ready and you can view your project in the browser`); log.info(); log.info(` ${chalk_1.default.cyan(emojis_1.default.pointRight)} ${chalk_1.default.bold(chalk_1.default.cyan(urlToClient))} ${chalk_1.default.cyan(emojis_1.default.pointLeft)}`); log.info(); log.info(); } // Register close listeners ['SIGTERM', 'SIGINT'].forEach((signal) => process.on(signal, async () => { try { log.info(`Received ${signal}. Shutting down gracefully.`); let isDone = false; if (watcher) { await Promise.race([ watcher.close().then(() => { isDone = true; }), new Promise((resolvePromise) => setTimeout(() => !isDone && resolvePromise(), 5000)), ]); } process.exit(0); } catch (err) { log.error(`Failed to handle ${signal} gracefully. Exiting with status code 1`); process.exit(1); } })); } catch (err) { log.error(); log.error(` ${chalk_1.default.red(emojis_1.default.failure)} Failure! Failed to watch project ${chalk_1.default.red(emojis_1.default.failure)}`); if (err.details) log.error(` ${chalk_1.default.cyan('Details:')} ${err.details}`); if (err.errors) log.error(` ${err.errors}`); else if (err.stack) log.error(` ${err.stack}`); log.error(); } }); // Run if (!process.argv.slice(2).length) commander_1.default.outputHelp(); else commander_1.default.parse(process.argv); //# sourceMappingURL=varan.js.map