"use strict"; const path = require('path'); const fs = require('fs'); const http = require('http'); const WebSocket = require('ws'); const sirv = require('sirv'); const { bold } = require('picocolors'); const Logger = require('./Logger'); const analyzer = require('./analyzer'); const { open } = require('./utils'); const { renderViewer } = require('./template'); const projectRoot = path.resolve(__dirname, '..'); function resolveTitle(reportTitle) { if (typeof reportTitle === 'function') { return reportTitle(); } else { return reportTitle; } } module.exports = { startServer, generateReport, generateJSONReport, getEntrypoints, // deprecated start: startServer }; async function startServer(bundleStats, opts) { const { port = 8888, host = '127.0.0.1', openBrowser = true, bundleDir = null, logger = new Logger(), defaultSizes = 'parsed', excludeAssets = null, reportTitle, analyzerUrl } = opts || {}; const analyzerOpts = { logger, excludeAssets }; let chartData = getChartData(analyzerOpts, bundleStats, bundleDir); const entrypoints = getEntrypoints(bundleStats); if (!chartData) return; const sirvMiddleware = sirv(`${projectRoot}/public`, { // disables caching and traverse the file system on every request dev: true }); const server = http.createServer((req, res) => { if (req.method === 'GET' && req.url === '/') { const html = renderViewer({ mode: 'server', title: resolveTitle(reportTitle), chartData, entrypoints, defaultSizes, enableWebSocket: true }); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); } else { sirvMiddleware(req, res); } }); await new Promise(resolve => { server.listen(port, host, () => { resolve(); const url = analyzerUrl({ listenPort: port, listenHost: host, boundAddress: server.address() }); logger.info(`${bold('Webpack Bundle Analyzer')} is started at ${bold(url)}\n` + `Use ${bold('Ctrl+C')} to close it`); if (openBrowser) { open(url, logger); } }); }); const wss = new WebSocket.Server({ server }); wss.on('connection', ws => { ws.on('error', err => { // Ignore network errors like `ECONNRESET`, `EPIPE`, etc. if (err.errno) return; logger.info(err.message); }); }); return { ws: wss, http: server, updateChartData }; function updateChartData(bundleStats) { const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir); if (!newChartData) return; chartData = newChartData; wss.clients.forEach(client => { if (client.readyState === WebSocket.OPEN) { client.send(JSON.stringify({ event: 'chartDataUpdated', data: newChartData })); } }); } } async function generateReport(bundleStats, opts) { const { openBrowser = true, reportFilename, reportTitle, bundleDir = null, logger = new Logger(), defaultSizes = 'parsed', excludeAssets = null } = opts || {}; const chartData = getChartData({ logger, excludeAssets }, bundleStats, bundleDir); const entrypoints = getEntrypoints(bundleStats); if (!chartData) return; const reportHtml = renderViewer({ mode: 'static', title: resolveTitle(reportTitle), chartData, entrypoints, defaultSizes, enableWebSocket: false }); const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename); fs.mkdirSync(path.dirname(reportFilepath), { recursive: true }); fs.writeFileSync(reportFilepath, reportHtml); logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`); if (openBrowser) { open(`file://${reportFilepath}`, logger); } } async function generateJSONReport(bundleStats, opts) { const { reportFilename, bundleDir = null, logger = new Logger(), excludeAssets = null } = opts || {}; const chartData = getChartData({ logger, excludeAssets }, bundleStats, bundleDir); if (!chartData) return; await fs.promises.mkdir(path.dirname(reportFilename), { recursive: true }); await fs.promises.writeFile(reportFilename, JSON.stringify(chartData)); logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`); } function getChartData(analyzerOpts, ...args) { let chartData; const { logger } = analyzerOpts; try { chartData = analyzer.getViewerData(...args, analyzerOpts); } catch (err) { logger.error(`Could't analyze webpack bundle:\n${err}`); logger.debug(err.stack); chartData = null; } // chartData can either be an array (bundleInfo[]) or null. It can't be an plain object anyway if ( // analyzer.getViewerData() doesn't failed in the previous step chartData && !Array.isArray(chartData)) { logger.error("Could't find any javascript bundles in provided stats file"); chartData = null; } return chartData; } function getEntrypoints(bundleStats) { if (bundleStats === null || bundleStats === undefined || !bundleStats.entrypoints) { return []; } return Object.values(bundleStats.entrypoints).map(entrypoint => entrypoint.name); }