Skip to content
Closed
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
37 changes: 36 additions & 1 deletion tools/webpack-plugin/src/BacktracePluginV4.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
import { ContentAppender, DebugIdGenerator } from '@backtrace/sourcemap-tools';
import { ContentAppender, DebugIdGenerator, SourceMapUploader } from '@backtrace/sourcemap-tools';
import crypto from 'crypto';
import path from 'path';
import { Compiler, WebpackPluginInstance } from 'webpack';
import { BacktraceWebpackSourceGenerator } from './BacktraceWebpackSourceGenerator';
import { BacktracePluginOptions } from './models/BacktracePluginOptions';

export class BacktracePluginV4 implements WebpackPluginInstance {
private readonly _sourceGenerator: BacktraceWebpackSourceGenerator;
private readonly _sourceMapUploader?: SourceMapUploader;

constructor(public readonly options?: BacktracePluginOptions) {
this._sourceGenerator = new BacktraceWebpackSourceGenerator(
options?.debugIdGenerator ?? new DebugIdGenerator(),
new ContentAppender(),
);

this._sourceMapUploader =
Copy link
Collaborator

@konraddysput konraddysput Jun 23, 2023

Choose a reason for hiding this comment

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

should we do anything if source map uploader is undefined? Is there any point of doing anything without it? Based on the pull request description the upload url should always be defined so the uploader should also be here

options?.sourceMapUploader ?? (options?.uploadUrl ? new SourceMapUploader(options.uploadUrl) : undefined);
}

public apply(compiler: Compiler) {
Expand Down Expand Up @@ -42,5 +47,35 @@ export class BacktracePluginV4 implements WebpackPluginInstance {
compilation.assets[key] = source;
}
});

const uploader = this._sourceMapUploader;
if (uploader) {
compiler.hooks.afterEmit.tapPromise(BacktracePluginV4.name, async (compilation) => {
const outputPath = compilation.outputOptions.path;
if (!outputPath) {
throw new Error('Output path is required to upload sourcemaps.');
}

for (const key in compilation.assets) {
if (!key.match(/\.(c|m)?jsx?\.map$/)) {
continue;
}

const sourceKey = key.replace(/.map$/, '');
const debugId = assetDebugIds.get(sourceKey);
if (!debugId) {
continue;
}

const sourceMapAsset = compilation.getAsset(key);
if (!sourceMapAsset) {
continue;
}

const sourceMapPath = path.join(outputPath, sourceMapAsset.name);
await uploader.upload(sourceMapPath, debugId);
}
});
}
}
}
65 changes: 58 additions & 7 deletions tools/webpack-plugin/src/BacktracePluginV5.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { ContentAppender, DebugIdGenerator } from '@backtrace/sourcemap-tools';
import { ContentAppender, DebugIdGenerator, SourceMapUploader } from '@backtrace/sourcemap-tools';
import crypto from 'crypto';
import { Compilation, Compiler, WebpackPluginInstance } from 'webpack';
import path from 'path';
import { AssetInfo, Compilation, Compiler, WebpackPluginInstance } from 'webpack';
import { SourceMapSource } from 'webpack-sources';
import { BacktraceWebpackSourceGenerator } from './BacktraceWebpackSourceGenerator';
import { BacktracePluginOptions } from './models/BacktracePluginOptions';

export class BacktracePluginV5 implements WebpackPluginInstance {
private readonly _sourceGenerator: BacktraceWebpackSourceGenerator;
private readonly _sourceMapUploader?: SourceMapUploader;

constructor(public readonly options?: BacktracePluginOptions) {
this._sourceGenerator = new BacktraceWebpackSourceGenerator(
options?.debugIdGenerator ?? new DebugIdGenerator(),
new ContentAppender(),
);

this._sourceMapUploader =
options?.sourceMapUploader ?? (options?.uploadUrl ? new SourceMapUploader(options.uploadUrl) : undefined);
}

public apply(compiler: Compiler) {
Expand Down Expand Up @@ -87,6 +92,43 @@ export class BacktracePluginV5 implements WebpackPluginInstance {
},
);
});

const uploader = this._sourceMapUploader;
if (uploader) {
compiler.hooks.afterEmit.tapPromise(BacktracePluginV5.name, async (compilation) => {
const outputPath = compilation.outputOptions.path;
if (!outputPath) {
throw new Error('Output path is required to upload sourcemaps.');
}

for (const key in compilation.assets) {
const asset = compilation.getAsset(key);
if (!asset) {
continue;
}

const debugId = assetDebugIds.get(key);
if (!debugId) {
continue;
}

const sourceMapKeys = this.getSourceMapKeys(asset.info);
if (!sourceMapKeys) {
continue;
}

for (const key of sourceMapKeys) {
const sourceMapAsset = compilation.getAsset(key);
if (!sourceMapAsset) {
continue;
}

const sourceMapPath = path.join(outputPath, sourceMapAsset.name);
await uploader.upload(sourceMapPath, debugId);
}
}
});
}
}

private injectSourceSnippet(compilation: Compilation, key: string, debugId: string): boolean {
Expand Down Expand Up @@ -143,15 +185,11 @@ export class BacktracePluginV5 implements WebpackPluginInstance {
return false;
}

let sourceMapKeys = assetInfo.related?.sourceMap;
const sourceMapKeys = this.getSourceMapKeys(assetInfo);
if (!sourceMapKeys) {
return false;
}

if (!Array.isArray(sourceMapKeys)) {
sourceMapKeys = [sourceMapKeys];
}

for (const sourceMapKey of sourceMapKeys) {
if (processedSourceMaps.has(sourceMapKey)) {
continue;
Expand All @@ -170,4 +208,17 @@ export class BacktracePluginV5 implements WebpackPluginInstance {

return true;
}

private getSourceMapKeys(assetInfo: AssetInfo) {
const sourceMapKeys = assetInfo.related?.sourceMap;
if (!sourceMapKeys) {
return undefined;
}

if (!Array.isArray(sourceMapKeys)) {
return [sourceMapKeys];
}

return sourceMapKeys;
}
}
4 changes: 3 additions & 1 deletion tools/webpack-plugin/src/models/BacktracePluginOptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { DebugIdGenerator } from '@backtrace/sourcemap-tools';
import { DebugIdGenerator, SourceMapUploader } from '@backtrace/sourcemap-tools';

export interface BacktracePluginOptions {
debugIdGenerator?: DebugIdGenerator;
sourceMapUploader?: SourceMapUploader;
uploadUrl?: string | URL;
}
31 changes: 31 additions & 0 deletions tools/webpack-plugin/tests/e2e/createE2ETest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { SourceMapUploader } from '@backtrace/sourcemap-tools';
import assert from 'assert';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
import webpack from 'webpack';
import {
asyncWebpack,
Expand All @@ -17,16 +20,29 @@ interface E2ETestOptions {
testSourceComment?: boolean;
testSourceMap?: boolean;
testSourceEval?: boolean;
testSourceMapUpload?: boolean;
}

export function createE2ETest(
configBuilder: (mode: webpack.Configuration['mode']) => webpack.Configuration,
opts?: E2ETestOptions,
) {
webpackModeTest((mode) => {
function mockUploader() {
return jest.spyOn(SourceMapUploader.prototype, 'upload').mockImplementation((_, debugId) =>
Promise.resolve({
debugId: debugId ?? crypto.randomUUID(),
rxid: crypto.randomUUID(),
}),
);
}

let result: webpack.Stats;
let uploadSpy: ReturnType<typeof mockUploader>;

beforeAll(async () => {
uploadSpy = mockUploader();

const config = configBuilder(mode);
if (config.output?.path) {
await removeDir(config.output.path);
Expand Down Expand Up @@ -95,6 +111,21 @@ export function createE2ETest(
await expectSourceMapSnippet(content);
}
});

if (opts?.testSourceMapUpload ?? true) {
it('should upload sourcemaps using SourceMapUploader', async () => {
const outputDir = result.compilation.outputOptions.path;
assert(outputDir);

const mapFiles = await getFiles(outputDir, /.js.map$/);
expect(mapFiles.length).toBeGreaterThan(0);

const uploadedFiles = uploadSpy.mock.calls.map((c) => path.resolve(c[0]));
for (const file of mapFiles) {
expect(uploadedFiles).toContain(path.resolve(file));
}
});
}
}
});
}
10 changes: 9 additions & 1 deletion tools/webpack-plugin/tests/e2e/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { SourceMapUploader } from '@backtrace/sourcemap-tools';
import fs from 'fs';
import path from 'path';
import webpack from 'webpack';
Expand Down Expand Up @@ -30,7 +31,14 @@ export function getBaseConfig(config: webpack.Configuration, options?: BaseConfi
},
],
},
plugins: [new BacktracePlugin(options?.pluginOptions ?? { debugIdGenerator: new TestDebugIdGenerator() })],
plugins: [
new BacktracePlugin(
options?.pluginOptions ?? {
debugIdGenerator: new TestDebugIdGenerator(),
sourceMapUploader: new SourceMapUploader('http://localhost'),
},
),
],
...config,
};
}
Expand Down