1 | const path = require('path');
|
2 | const fs = require('fs');
|
3 | const http = require('http');
|
4 |
|
5 | const WebSocket = require('ws');
|
6 | const sirv = require('sirv');
|
7 | const _ = require('lodash');
|
8 | const {bold} = require('chalk');
|
9 |
|
10 | const Logger = require('./Logger');
|
11 | const analyzer = require('./analyzer');
|
12 | const {open} = require('./utils');
|
13 | const {renderViewer} = require('./template');
|
14 |
|
15 | const projectRoot = path.resolve(__dirname, '..');
|
16 |
|
17 | function resolveTitle(reportTitle) {
|
18 | if (typeof reportTitle === 'function') {
|
19 | return reportTitle();
|
20 | } else {
|
21 | return reportTitle;
|
22 | }
|
23 | }
|
24 |
|
25 | module.exports = {
|
26 | startServer,
|
27 | generateReport,
|
28 | generateJSONReport,
|
29 |
|
30 | start: startServer
|
31 | };
|
32 |
|
33 | async function startServer(bundleStats, opts) {
|
34 | const {
|
35 | port = 8888,
|
36 | host = '127.0.0.1',
|
37 | openBrowser = true,
|
38 | bundleDir = null,
|
39 | logger = new Logger(),
|
40 | defaultSizes = 'parsed',
|
41 | excludeAssets = null,
|
42 | reportTitle
|
43 | } = opts || {};
|
44 |
|
45 | const analyzerOpts = {logger, excludeAssets};
|
46 |
|
47 | let chartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
48 |
|
49 | if (!chartData) return;
|
50 |
|
51 | const sirvMiddleware = sirv(`${projectRoot}/public`, {
|
52 |
|
53 | dev: true
|
54 | });
|
55 |
|
56 | const server = http.createServer((req, res) => {
|
57 | if (req.method === 'GET' && req.url === '/') {
|
58 | const html = renderViewer({
|
59 | mode: 'server',
|
60 | title: resolveTitle(reportTitle),
|
61 | chartData,
|
62 | defaultSizes,
|
63 | enableWebSocket: true
|
64 | });
|
65 | res.writeHead(200, {'Content-Type': 'text/html'});
|
66 | res.end(html);
|
67 | } else {
|
68 | sirvMiddleware(req, res);
|
69 | }
|
70 | });
|
71 |
|
72 | await new Promise(resolve => {
|
73 | server.listen(port, host, () => {
|
74 | resolve();
|
75 |
|
76 | const url = `http://${host}:${server.address().port}`;
|
77 |
|
78 | logger.info(
|
79 | `${bold('Webpack Bundle Analyzer')} is started at ${bold(url)}\n` +
|
80 | `Use ${bold('Ctrl+C')} to close it`
|
81 | );
|
82 |
|
83 | if (openBrowser) {
|
84 | open(url, logger);
|
85 | }
|
86 | });
|
87 | });
|
88 |
|
89 | const wss = new WebSocket.Server({server});
|
90 |
|
91 | wss.on('connection', ws => {
|
92 | ws.on('error', err => {
|
93 |
|
94 | if (err.errno) return;
|
95 |
|
96 | logger.info(err.message);
|
97 | });
|
98 | });
|
99 |
|
100 | return {
|
101 | ws: wss,
|
102 | http: server,
|
103 | updateChartData
|
104 | };
|
105 |
|
106 | function updateChartData(bundleStats) {
|
107 | const newChartData = getChartData(analyzerOpts, bundleStats, bundleDir);
|
108 |
|
109 | if (!newChartData) return;
|
110 |
|
111 | chartData = newChartData;
|
112 |
|
113 | wss.clients.forEach(client => {
|
114 | if (client.readyState === WebSocket.OPEN) {
|
115 | client.send(JSON.stringify({
|
116 | event: 'chartDataUpdated',
|
117 | data: newChartData
|
118 | }));
|
119 | }
|
120 | });
|
121 | }
|
122 | }
|
123 |
|
124 | async function generateReport(bundleStats, opts) {
|
125 | const {
|
126 | openBrowser = true,
|
127 | reportFilename,
|
128 | reportTitle,
|
129 | bundleDir = null,
|
130 | logger = new Logger(),
|
131 | defaultSizes = 'parsed',
|
132 | excludeAssets = null
|
133 | } = opts || {};
|
134 |
|
135 | const chartData = getChartData({logger, excludeAssets}, bundleStats, bundleDir);
|
136 |
|
137 | if (!chartData) return;
|
138 |
|
139 | const reportHtml = renderViewer({
|
140 | mode: 'static',
|
141 | title: resolveTitle(reportTitle),
|
142 | chartData,
|
143 | defaultSizes,
|
144 | enableWebSocket: false
|
145 | });
|
146 | const reportFilepath = path.resolve(bundleDir || process.cwd(), reportFilename);
|
147 |
|
148 | fs.mkdirSync(path.dirname(reportFilepath), {recursive: true});
|
149 | fs.writeFileSync(reportFilepath, reportHtml);
|
150 |
|
151 | logger.info(`${bold('Webpack Bundle Analyzer')} saved report to ${bold(reportFilepath)}`);
|
152 |
|
153 | if (openBrowser) {
|
154 | open(`file://${reportFilepath}`, logger);
|
155 | }
|
156 | }
|
157 |
|
158 | async function generateJSONReport(bundleStats, opts) {
|
159 | const {reportFilename, bundleDir = null, logger = new Logger(), excludeAssets = null} = opts || {};
|
160 |
|
161 | const chartData = getChartData({logger, excludeAssets}, bundleStats, bundleDir);
|
162 |
|
163 | if (!chartData) return;
|
164 |
|
165 | await fs.promises.mkdir(path.dirname(reportFilename), {recursive: true});
|
166 | await fs.promises.writeFile(reportFilename, JSON.stringify(chartData));
|
167 |
|
168 | logger.info(`${bold('Webpack Bundle Analyzer')} saved JSON report to ${bold(reportFilename)}`);
|
169 | }
|
170 |
|
171 | function getChartData(analyzerOpts, ...args) {
|
172 | let chartData;
|
173 | const {logger} = analyzerOpts;
|
174 |
|
175 | try {
|
176 | chartData = analyzer.getViewerData(...args, analyzerOpts);
|
177 | } catch (err) {
|
178 | logger.error(`Could't analyze webpack bundle:\n${err}`);
|
179 | logger.debug(err.stack);
|
180 | chartData = null;
|
181 | }
|
182 |
|
183 | if (_.isPlainObject(chartData) && _.isEmpty(chartData)) {
|
184 | logger.error("Could't find any javascript bundles in provided stats file");
|
185 | chartData = null;
|
186 | }
|
187 |
|
188 | return chartData;
|
189 | }
|