UNPKG

4.55 kBJavaScriptView Raw
1const fs = require('fs');
2const path = require('path');
3const {bold} = require('chalk');
4
5const Logger = require('./Logger');
6const viewer = require('./viewer');
7const utils = require('./utils');
8const {writeStats} = require('./statsUtils');
9
10class BundleAnalyzerPlugin {
11 constructor(opts = {}) {
12 this.opts = {
13 analyzerMode: 'server',
14 analyzerHost: '127.0.0.1',
15 reportFilename: null,
16 reportTitle: utils.defaultTitle,
17 defaultSizes: 'parsed',
18 openAnalyzer: true,
19 generateStatsFile: false,
20 statsFilename: 'stats.json',
21 statsOptions: null,
22 excludeAssets: null,
23 logLevel: 'info',
24 // deprecated
25 startAnalyzer: true,
26 ...opts,
27 analyzerPort: 'analyzerPort' in opts ? (opts.analyzerPort === 'auto' ? 0 : opts.analyzerPort) : 8888
28 };
29
30 this.server = null;
31 this.logger = new Logger(this.opts.logLevel);
32 }
33
34 apply(compiler) {
35 this.compiler = compiler;
36
37 const done = (stats, callback) => {
38 callback = callback || (() => {});
39
40 const actions = [];
41
42 if (this.opts.generateStatsFile) {
43 actions.push(() => this.generateStatsFile(stats.toJson(this.opts.statsOptions)));
44 }
45
46 // Handling deprecated `startAnalyzer` flag
47 if (this.opts.analyzerMode === 'server' && !this.opts.startAnalyzer) {
48 this.opts.analyzerMode = 'disabled';
49 }
50
51 if (this.opts.analyzerMode === 'server') {
52 actions.push(() => this.startAnalyzerServer(stats.toJson()));
53 } else if (this.opts.analyzerMode === 'static') {
54 actions.push(() => this.generateStaticReport(stats.toJson()));
55 } else if (this.opts.analyzerMode === 'json') {
56 actions.push(() => this.generateJSONReport(stats.toJson()));
57 }
58
59 if (actions.length) {
60 // Making analyzer logs to be after all webpack logs in the console
61 setImmediate(async () => {
62 try {
63 await Promise.all(actions.map(action => action()));
64 callback();
65 } catch (e) {
66 callback(e);
67 }
68 });
69 } else {
70 callback();
71 }
72 };
73
74 if (compiler.hooks) {
75 compiler.hooks.done.tapAsync('webpack-bundle-analyzer', done);
76 } else {
77 compiler.plugin('done', done);
78 }
79 }
80
81 async generateStatsFile(stats) {
82 const statsFilepath = path.resolve(this.compiler.outputPath, this.opts.statsFilename);
83 await fs.promises.mkdir(path.dirname(statsFilepath), {recursive: true});
84
85 try {
86 await writeStats(stats, statsFilepath);
87
88 this.logger.info(
89 `${bold('Webpack Bundle Analyzer')} saved stats file to ${bold(statsFilepath)}`
90 );
91 } catch (error) {
92 this.logger.error(
93 `${bold('Webpack Bundle Analyzer')} error saving stats file to ${bold(statsFilepath)}: ${error}`
94 );
95 }
96 }
97
98 async startAnalyzerServer(stats) {
99 if (this.server) {
100 (await this.server).updateChartData(stats);
101 } else {
102 this.server = viewer.startServer(stats, {
103 openBrowser: this.opts.openAnalyzer,
104 host: this.opts.analyzerHost,
105 port: this.opts.analyzerPort,
106 reportTitle: this.opts.reportTitle,
107 bundleDir: this.getBundleDirFromCompiler(),
108 logger: this.logger,
109 defaultSizes: this.opts.defaultSizes,
110 excludeAssets: this.opts.excludeAssets
111 });
112 }
113 }
114
115 async generateJSONReport(stats) {
116 await viewer.generateJSONReport(stats, {
117 reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.json'),
118 bundleDir: this.getBundleDirFromCompiler(),
119 logger: this.logger,
120 excludeAssets: this.opts.excludeAssets
121 });
122 }
123
124 async generateStaticReport(stats) {
125 await viewer.generateReport(stats, {
126 openBrowser: this.opts.openAnalyzer,
127 reportFilename: path.resolve(this.compiler.outputPath, this.opts.reportFilename || 'report.html'),
128 reportTitle: this.opts.reportTitle,
129 bundleDir: this.getBundleDirFromCompiler(),
130 logger: this.logger,
131 defaultSizes: this.opts.defaultSizes,
132 excludeAssets: this.opts.excludeAssets
133 });
134 }
135
136 getBundleDirFromCompiler() {
137 switch (this.compiler.outputFileSystem.constructor.name) {
138 case 'MemoryFileSystem':
139 return null;
140 // Detect AsyncMFS used by Nuxt 2.5 that replaces webpack's MFS during development
141 // Related: #274
142 case 'AsyncMFS':
143 return null;
144 default:
145 return this.compiler.outputPath;
146 }
147 }
148
149}
150
151module.exports = BundleAnalyzerPlugin;