varan
Version:
A webpack starter kit for offline-first bring-your-own-code apps with server side rendering
376 lines • 20.3 kB
JavaScript
;
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