diff --git a/package-lock.json b/package-lock.json index fe8c30d4..8df8d2e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6301,9 +6301,8 @@ }, "node_modules/json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/json5": { "version": "1.0.2", @@ -6404,9 +6403,8 @@ }, "node_modules/lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/lodash.memoize": { "version": "4.1.2", @@ -6738,9 +6736,8 @@ }, "node_modules/nock": { "version": "13.3.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.1.tgz", - "integrity": "sha512-vHnopocZuI93p2ccivFyGuUfzjq2fxNyNurp7816mlT5V5HF4SzXu8lvLrVzBbNqzs+ODooZ6OksuSUNM7Njkw==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.0", "json-stringify-safe": "^5.0.1", @@ -7392,9 +7389,8 @@ }, "node_modules/propagate": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -10617,6 +10613,8 @@ "ts-loader-webpack-4": "npm:ts-loader@^8.4.0", "typescript": "^5.0.4", "webpack-4": "npm:webpack@^4.46.0", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0", "webpack-sources-webpack-4": "npm:webpack-sources@^1.4.1" }, "engines": { @@ -11077,6 +11075,8 @@ "ts-loader-webpack-4": "npm:ts-loader@^8.4.0", "typescript": "^5.0.4", "webpack-4": "npm:webpack@^4.46.0", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0", "webpack-sources-webpack-4": "npm:webpack-sources@^1.4.1" } }, @@ -14898,8 +14898,6 @@ }, "json-stringify-safe": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", "dev": true }, "json5": { @@ -14960,8 +14958,6 @@ }, "lodash": { "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash.memoize": { @@ -15197,8 +15193,6 @@ }, "nock": { "version": "13.3.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-13.3.1.tgz", - "integrity": "sha512-vHnopocZuI93p2ccivFyGuUfzjq2fxNyNurp7816mlT5V5HF4SzXu8lvLrVzBbNqzs+ODooZ6OksuSUNM7Njkw==", "dev": true, "requires": { "debug": "^4.1.0", @@ -15616,8 +15610,6 @@ }, "propagate": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", "dev": true }, "prr": { diff --git a/tools/webpack-plugin/.gitignore b/tools/webpack-plugin/.gitignore new file mode 100644 index 00000000..2f9f20c3 --- /dev/null +++ b/tools/webpack-plugin/.gitignore @@ -0,0 +1 @@ +webpackBuild/ diff --git a/tools/webpack-plugin/package.json b/tools/webpack-plugin/package.json index 97754e47..81585197 100644 --- a/tools/webpack-plugin/package.json +++ b/tools/webpack-plugin/package.json @@ -9,6 +9,7 @@ }, "scripts": { "build": "tsc -b ./tsconfig.build.json", + "build:webpack": "webpack", "clean": "tsc -b ./tsconfig.build.json --clean && rimraf \"lib\"", "format": "prettier --write '**/*.ts'", "lint": "eslint . --ext .ts", @@ -42,9 +43,11 @@ "jest": "^29.5.0", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3 || ^8.4.0", + "ts-loader-webpack-4": "npm:ts-loader@^8.4.0", "typescript": "^5.0.4", "webpack-4": "npm:webpack@^4.46.0", - "ts-loader-webpack-4": "npm:ts-loader@^8.4.0", + "webpack-cli": "^5.1.4", + "webpack-node-externals": "^3.0.0", "webpack-sources-webpack-4": "npm:webpack-sources@^1.4.1" }, "dependencies": { diff --git a/tools/webpack-plugin/src/BacktracePlugin.ts b/tools/webpack-plugin/src/BacktracePlugin.ts index 72952102..6cbbcd3d 100644 --- a/tools/webpack-plugin/src/BacktracePlugin.ts +++ b/tools/webpack-plugin/src/BacktracePlugin.ts @@ -1,6 +1,8 @@ import { DebugIdGenerator, SourceMapUploader, SourceProcessor } from '@backtrace/sourcemap-tools'; import path from 'path'; import webpack, { WebpackPluginInstance } from 'webpack'; +import { statsPrinter } from './helpers/statsPrinter'; +import { AssetStats } from './models/AssetStats'; import { BacktracePluginOptions } from './models/BacktracePluginOptions'; export class BacktracePlugin implements WebpackPluginInstance { @@ -15,6 +17,8 @@ export class BacktracePlugin implements WebpackPluginInstance { } public apply(compiler: webpack.Compiler) { + const assetStats = new Map(); + compiler.hooks.afterEmit.tapPromise(BacktracePlugin.name, async (compilation) => { const logger = compilation.getLogger(BacktracePlugin.name); if (!compilation.outputOptions.path) { @@ -48,33 +52,47 @@ export class BacktracePlugin implements WebpackPluginInstance { logger.log(`received ${entries.length} files for processing`); for (const [asset, sourcePath, sourceMapPath] of entries) { + const stats: AssetStats = {}; + assetStats.set(asset, stats); + let debugId: string; logger.time(`[${asset}] process source and sourcemap`); try { debugId = await this._sourceProcessor.processSourceAndSourceMapFiles(sourcePath, sourceMapPath); + stats.debugId = debugId; + stats.processSource = true; + logger.timeEnd(`[${asset}] process source and sourcemap`); } catch (err) { logger.error(`[${asset}] process source and sourcemap failed:`, err); + stats.processSource = err instanceof Error ? err : new Error('Unknown error.'); continue; } finally { logger.timeEnd(`[${asset}] process source and sourcemap`); } if (!this._sourceMapUploader) { - logger.info(`[${asset}] file processed`); + logger.log(`[${asset}] file processed`); continue; } logger.time(`[${asset}] upload sourcemap`); try { - await this._sourceMapUploader.upload(sourceMapPath, debugId); - logger.info(`[${asset}] file processed and sourcemap uploaded`); + const result = await this._sourceMapUploader.upload(sourceMapPath, debugId); + stats.sourceMapUpload = result; + logger.log(`[${asset}] file processed and sourcemap uploaded`); } catch (err) { logger.error(`[${asset}] upload sourcemap failed:`, err); + stats.sourceMapUpload = err instanceof Error ? err : new Error('Unknown error.'); } finally { logger.timeEnd(`[${asset}] upload sourcemap`); } } + + const printer = statsPrinter(compilation.getLogger(BacktracePlugin.name)); + for (const [key, stats] of assetStats) { + printer(key, stats); + } }); } } diff --git a/tools/webpack-plugin/src/helpers/statsPrinter.ts b/tools/webpack-plugin/src/helpers/statsPrinter.ts new file mode 100644 index 00000000..73f4a81c --- /dev/null +++ b/tools/webpack-plugin/src/helpers/statsPrinter.ts @@ -0,0 +1,52 @@ +import webpack from 'webpack'; +import { AssetStats } from '../models/AssetStats'; + +function statToString(stat: boolean | string | Error) { + if (typeof stat === 'string') { + return stat; + } + + if (typeof stat === 'boolean') { + return stat ? 'successful' : 'skipped'; + } + + return stat.message; +} + +export function statsPrinter(logger: webpack.Compilation['logger']) { + return function printStats(key: string, stats: AssetStats) { + const errors = [stats.sourceMapUpload, stats.processSource].some((v) => v instanceof Error); + + const infoLog = errors + ? (...args: unknown[]) => logger.error(...args) + : (...args: unknown[]) => logger.info(...args); + + const debugLog = (...args: unknown[]) => logger.log(...args); + + if (!errors) { + if (!!stats.sourceMapUpload && !(stats.sourceMapUpload instanceof Error)) { + infoLog(`[${key}] processed file and uploaded sourcemap successfully`); + } else { + infoLog(`[${key}] processed file successfully`); + } + } else { + infoLog(`[${key}] processed file with errors`); + } + + debugLog(`\tdebugId: ${stats.debugId ?? ''}`); + + if (stats.processSource != null) { + debugLog(`\tsource snippet append: ${statToString(stats.processSource) ?? ''}`); + } + + if (stats.sourceMapUpload != null) { + if (stats.sourceMapUpload === false || stats.sourceMapUpload instanceof Error) { + debugLog(`\tsourcemap upload: ${statToString(stats.sourceMapUpload)}`); + } else { + debugLog( + `\tsourcemap upload: yes, rxid: ${stats.sourceMapUpload.rxid}, debugId: ${stats.sourceMapUpload.debugId}`, + ); + } + } + }; +} diff --git a/tools/webpack-plugin/src/models/AssetStats.ts b/tools/webpack-plugin/src/models/AssetStats.ts new file mode 100644 index 00000000..1d1cb2a6 --- /dev/null +++ b/tools/webpack-plugin/src/models/AssetStats.ts @@ -0,0 +1,7 @@ +import { UploadResult } from '@backtrace/sourcemap-tools'; + +export interface AssetStats { + debugId?: string; + processSource?: boolean | string | Error; + sourceMapUpload?: false | UploadResult | Error; +} diff --git a/tools/webpack-plugin/webpack.config.js b/tools/webpack-plugin/webpack.config.js new file mode 100644 index 00000000..5a09c65a --- /dev/null +++ b/tools/webpack-plugin/webpack.config.js @@ -0,0 +1,39 @@ +const path = require('path'); +const { BacktracePlugin } = require('./lib'); +const nodeExternals = require('webpack-node-externals'); + +/** @type {import('webpack').Configuration} */ +module.exports = { + entry: './src/index.ts', + devtool: 'source-map', + mode: 'production', + target: 'node', + externalsPresets: { node: true }, + resolve: { + extensions: ['.ts', '.js'], + }, + stats: { + logging: 'verbose', + }, + module: { + rules: [ + { + test: /.ts$/, + loader: 'ts-loader', + options: { + configFile: 'tsconfig.build.json', + }, + }, + ], + }, + output: { + path: path.join(__dirname, './webpackBuild'), + filename: '[name].js', + }, + externals: [ + nodeExternals({ + additionalModuleDirs: ['../../node_modules'], + }), + ], + plugins: [new BacktracePlugin({})], +};