Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/use-webpack-infrastructure-logger.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"webpack-bundle-analyzer": minor
---

Use Webpack's infrastructure logger when available (`compiler.getInfrastructureLogger('webpack-bundle-analyzer')`) and deprecate the plugin's `logLevel` option in favor of Webpack's native `infrastructureLogging` configuration.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ new BundleAnalyzerPlugin(options?: object)
| **`statsFilename`** | `{String}` | Default: `stats.json`. Name of webpack stats JSON file that will be generated if `generateStatsFile` is `true`. It can be either an absolute path or a path relative to a bundle output directory (which is output.path in webpack config). |
| **`statsOptions`** | `null` or `{Object}` | Default: `null`. Options for `stats.toJson()` method. For example you can exclude sources of your modules from stats file with `source: false` option. [See more options here](https://webpack.js.org/configuration/stats/). |
| **`excludeAssets`** | `{null\|pattern\|pattern[]}` where `pattern` equals to `{String\|RegExp\|function}` | Default: `null`. Patterns that will be used to match against asset names to exclude them from the report. If pattern is a string it will be converted to RegExp via `new RegExp(str)`. If pattern is a function it should have the following signature `(assetName: string) => boolean` and should return `true` to _exclude_ matching asset. If multiple patterns are provided asset should match at least one of them to be excluded. |
| **`logLevel`** | One of: `info`, `warn`, `error`, `silent` | Default: `info`. Used to control how much details the plugin outputs. |
| **`logLevel`** | One of: `info`, `warn`, `error`, `silent` | **Deprecated**. Default: `info`. Used to control how much details the plugin outputs. Please use webpack's [`infrastructureLogging`](https://webpack.js.org/configuration/infrastructureLogging/) configuration instead. |

### Absolute output paths

Expand Down
20 changes: 19 additions & 1 deletion src/BundleAnalyzerPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const viewer = require("./viewer");
/** @typedef {import("webpack").StatsCompilation} StatsCompilation */
/** @typedef {import("./sizeUtils").Algorithm} CompressionAlgorithm */
/** @typedef {import("./Logger").Level} LogLever */
/** @typedef {ReturnType<import("webpack").Compiler["getInfrastructureLogger"]>} WebpackLogger */
/** @typedef {import("./viewer").ViewerServerObj} ViewerServerObj */

/** @typedef {string | boolean | StatsOptions} PluginStatsOptions */
Expand Down Expand Up @@ -70,7 +71,7 @@ const analyzerStatsOptions = {
* @property {string=} statsFilename stats filename
* @property {PluginStatsOptions=} statsOptions stats options
* @property {ExcludeAssets=} excludeAssets exclude assets
* @property {LogLever=} logLevel exclude assets
* @property {LogLever=} logLevel (deprecated) log level
* @property {boolean=} startAnalyzer start analyzer
* @property {AnalyzerUrl=} analyzerUrl start analyzer
*/
Expand All @@ -80,6 +81,8 @@ class BundleAnalyzerPlugin {
* @param {Options=} opts options
*/
constructor(opts = {}) {
const hasCustomLogLevel = typeof opts.logLevel !== "undefined";

/** @type {Required<Omit<Options, "analyzerPort" | "statsOptions">> & { analyzerPort: number, statsOptions: undefined | PluginStatsOptions }} */
this.opts = {
analyzerMode: "server",
Expand All @@ -102,10 +105,13 @@ class BundleAnalyzerPlugin {
opts.analyzerPort === "auto" ? 0 : (opts.analyzerPort ?? 8888),
};

/** @type {boolean} */
this.hasCustomLogLevel = hasCustomLogLevel;
/** @type {Compiler | null} */
this.compiler = null;
/** @type {Promise<ViewerServerObj> | null} */
this.server = null;
/** @type {Logger | WebpackLogger} */
this.logger = new Logger(this.opts.logLevel);
}

Expand All @@ -115,6 +121,18 @@ class BundleAnalyzerPlugin {
apply(compiler) {
this.compiler = compiler;

if (compiler.getInfrastructureLogger) {
const infraLogger = compiler.getInfrastructureLogger(
"webpack-bundle-analyzer",
);
this.logger = Logger.createInfrastructureLoggerAdapter(
infraLogger,
this.hasCustomLogLevel ? this.opts.logLevel : undefined,
);
} else {
this.logger = new Logger(this.opts.logLevel);
}

/**
* @param {Stats} stats stats
* @param {(err?: Error) => void} callback callback
Expand Down
93 changes: 93 additions & 0 deletions src/Logger.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/** @typedef {import("./BundleAnalyzerPlugin").EXPECTED_ANY} EXPECTED_ANY */
/** @typedef {ReturnType<import("webpack").Compiler["getInfrastructureLogger"]>} WebpackLogger */

/** @typedef {"debug" | "info" | "warn" | "error" | "silent"} Level */

Expand Down Expand Up @@ -95,6 +96,98 @@ class Logger {
(LEVEL_TO_CONSOLE_METHOD.get(level) || level)
](...args);
}

/**
* @param {WebpackLogger} infrastructureLogger infrastructure logger
* @param {Level=} userLogLevel user log level
* @param {boolean=} warned whether deprecation warning has been logged
* @returns {WebpackLogger} logger adapter
*/
static createInfrastructureLoggerAdapter(
infrastructureLogger,
userLogLevel,
warned = false,
) {
if (typeof userLogLevel === "undefined") {
return infrastructureLogger;
}

const levelIndex = LEVELS.indexOf(userLogLevel);

if (levelIndex === -1) {
throw new Error(
`Invalid log level "${userLogLevel}". Use one of these: ${LEVELS.join(", ")}`,
);
}

/** @type {Set<Level>} */
const activeLevels = new Set();

for (const [i, level] of LEVELS.entries()) {
if (i >= levelIndex) activeLevels.add(level);
}

if (!warned && activeLevels.has("warn")) {
infrastructureLogger.warn(
"The 'logLevel' option is deprecated and will be removed in a future release. " +
"Please use webpack's 'infrastructureLogging.level' option instead.",
);
}

return new Proxy(infrastructureLogger, {
get(target, prop, receiver) {
if (prop === "activeLevels") {
return activeLevels;
}

if (prop === "setLogLevel") {
return (/** @type {Level} */ level) => {
const idx = LEVELS.indexOf(level);

if (idx === -1) {
throw new Error(
`Invalid log level "${level}". Use one of these: ${LEVELS.join(", ")}`,
);
}

activeLevels.clear();

for (const [i, l] of LEVELS.entries()) {
if (i >= idx) activeLevels.add(l);
}
};
}

if (prop === "getChildLogger") {
return (/** @type {string | (() => string)} */ name) =>
Logger.createInfrastructureLoggerAdapter(
target.getChildLogger(name),
userLogLevel,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the current log level for new child loggers.

setLogLevel updates activeLevels, but getChildLogger passes the original userLogLevel. For example, after changing "error" to "warn", a new child logger still suppresses warnings.

Track the current level and pass it to createInfrastructureLoggerAdapter. Add a test that creates a child after setLogLevel.

Proposed fix
-    const levelIndex = LEVELS.indexOf(userLogLevel);
+    let currentLevel = userLogLevel;
+    const levelIndex = LEVELS.indexOf(currentLevel);

         if (prop === "setLogLevel") {
           return (/** `@type` {Level} */ level) => {
             const idx = LEVELS.indexOf(level);

             if (idx === -1) {
               throw new Error(
                 `Invalid log level "${level}". Use one of these: ${LEVELS.join(", ")}`,
               );
             }

+            currentLevel = level;
             activeLevels.clear();

             for (const [i, l] of LEVELS.entries()) {
               if (i >= idx) activeLevels.add(l);
             }
           };
         }

             Logger.createInfrastructureLoggerAdapter(
               target.getChildLogger(name),
-              userLogLevel,
+              currentLevel,
               true,
             );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like a code flow which users should be using. So this is a false positive.

true,
);
}

const value = Reflect.get(target, prop, receiver);

if (typeof value === "function") {
const isManagedLevel =
LEVELS.includes(/** @type {Level} */ (prop)) || prop === "log";
const levelToCheck = prop === "log" ? "info" : prop;

if (
isManagedLevel &&
!activeLevels.has(/** @type {Level} */ (levelToCheck))
) {
return () => {};
}

return value.bind(target);
}

return value;
},
});
}
}

module.exports = Logger;
3 changes: 2 additions & 1 deletion src/analyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ const FILENAME_EXTENSIONS = /\.(js|mjs|cjs|bundle)$/iu;
/** @typedef {import("webpack").StatsAsset} StatsAsset */
/** @typedef {import("./BundleAnalyzerPlugin").CompressionAlgorithm} CompressionAlgorithm */
/** @typedef {import("./BundleAnalyzerPlugin").ExcludeAssets} ExcludeAssets */
/** @typedef {ReturnType<import("webpack").Compiler["getInfrastructureLogger"]>} WebpackLogger */

/**
* @typedef {object} AnalyzerOptions
Expand Down Expand Up @@ -225,7 +226,7 @@ function isEntryModule(statsModule) {

/**
* @typedef {object} ViewerDataOptions
* @property {Logger} logger logger
* @property {Logger | WebpackLogger} logger logger
* @property {CompressionAlgorithm} compressionAlgorithm compression algorithm
* @property {ExcludeAssets} excludeAssets exclude assets
*/
Expand Down
2 changes: 1 addition & 1 deletion src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const opener = require("opener");

/** @typedef {import("./BundleAnalyzerPlugin").ExcludeAssets} ExcludeAssets */
/** @typedef {import("./BundleAnalyzerPlugin").AnalyzerUrl} AnalyzerUrl */
/** @typedef {import("./Logger")} Logger */
/** @typedef {import("./Logger") | ReturnType<import("webpack").Compiler["getInfrastructureLogger"]>} Logger */

const MONTHS = [
"Jan",
Expand Down
7 changes: 4 additions & 3 deletions src/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const { open } = require("./utils");
/** @typedef {import("./BundleAnalyzerPlugin").ExcludeAssets} ExcludeAssets */
/** @typedef {import("./analyzer").ViewerDataOptions} ViewerDataOptions */
/** @typedef {import("./analyzer").ChartData} ChartData */
/** @typedef {ReturnType<import("webpack").Compiler["getInfrastructureLogger"]>} WebpackLogger */

const projectRoot = path.resolve(__dirname, "..");

Expand Down Expand Up @@ -107,7 +108,7 @@ function getChartData(analyzerOpts, bundleStats, bundleDir) {
* @property {string} host host
* @property {boolean} openBrowser true when need to open browser, otherwise false
* @property {string | null} bundleDir bundle dir
* @property {Logger} logger logger
* @property {Logger | WebpackLogger} logger logger
* @property {Sizes} defaultSizes default sizes
* @property {CompressionAlgorithm} compressionAlgorithm compression algorithm
* @property {ExcludeAssets | null} excludeAssets exclude assets
Expand Down Expand Up @@ -245,7 +246,7 @@ async function startServer(bundleStats, opts) {
* @property {string} reportFilename report filename
* @property {ReportTitle} reportTitle report title
* @property {string | null} bundleDir bundle dir
* @property {Logger} logger logger
* @property {Logger | WebpackLogger} logger logger
* @property {Sizes} defaultSizes default sizes
* @property {CompressionAlgorithm} compressionAlgorithm compression algorithm
* @property {ExcludeAssets} excludeAssets exclude assets
Expand Down Expand Up @@ -307,7 +308,7 @@ async function generateReport(bundleStats, opts) {
* @typedef {object} GenerateJSONReportOptions
* @property {string} reportFilename report filename
* @property {string | null} bundleDir bundle dir
* @property {Logger} logger logger
* @property {Logger | WebpackLogger} logger logger
* @property {ExcludeAssets} excludeAssets exclude assets
* @property {CompressionAlgorithm} compressionAlgorithm compression algorithm
*/
Expand Down
Loading
Loading