diff --git a/tools/webpack-plugin/jest.config.js b/tools/webpack-plugin/jest.config.js deleted file mode 100644 index c0919546..00000000 --- a/tools/webpack-plugin/jest.config.js +++ /dev/null @@ -1,6 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - testPathIgnorePatterns: ['e2e'], -}; diff --git a/tools/webpack-plugin/package.json b/tools/webpack-plugin/package.json index 039c73d4..97754e47 100644 --- a/tools/webpack-plugin/package.json +++ b/tools/webpack-plugin/package.json @@ -12,10 +12,7 @@ "clean": "tsc -b ./tsconfig.build.json --clean && rimraf \"lib\"", "format": "prettier --write '**/*.ts'", "lint": "eslint . --ext .ts", - "watch": "tsc -b ./tsconfig.packages.json -w", - "test": "npm run test:webpackv5 && npm run test:webpackv4", - "test:webpackv4": "NODE_ENV=test jest --config ./webpack4.jest.config.js", - "test:webpackv5": "NODE_ENV=test jest", + "watch": "tsc -b ./tsconfig.build.json -w", "test:e2e": "npm run test:e2e:webpackv5 && npm run test:e2e:webpackv4", "test:e2e:webpackv4": "NODE_ENV=test jest --config ./webpack4.e2e.jest.config.js", "test:e2e:webpackv5": "NODE_ENV=test jest --config ./e2e.jest.config.js" diff --git a/tools/webpack-plugin/src/BacktracePlugin.ts b/tools/webpack-plugin/src/BacktracePlugin.ts index 542a5326..72952102 100644 --- a/tools/webpack-plugin/src/BacktracePlugin.ts +++ b/tools/webpack-plugin/src/BacktracePlugin.ts @@ -1,19 +1,80 @@ -import webpack from 'webpack'; -import { BacktracePluginV4 } from './BacktracePluginV4'; -import { BacktracePluginV5 } from './BacktracePluginV5'; - -let BacktracePlugin: typeof BacktracePluginV4 | typeof BacktracePluginV5; - -const version = process.env.WEBPACK_VERSION ?? webpack.version[0]; -switch (version) { - case '4': - BacktracePlugin = BacktracePluginV4; - break; - case '5': - BacktracePlugin = BacktracePluginV5; - break; - default: - throw new Error(`Webpack version ${version} is not supported.`); -} +import { DebugIdGenerator, SourceMapUploader, SourceProcessor } from '@backtrace/sourcemap-tools'; +import path from 'path'; +import webpack, { WebpackPluginInstance } from 'webpack'; +import { BacktracePluginOptions } from './models/BacktracePluginOptions'; + +export class BacktracePlugin implements WebpackPluginInstance { + private readonly _sourceProcessor: SourceProcessor; + private readonly _sourceMapUploader?: SourceMapUploader; + + constructor(public readonly options?: BacktracePluginOptions) { + this._sourceProcessor = new SourceProcessor(new DebugIdGenerator()); + this._sourceMapUploader = options?.uploadUrl + ? new SourceMapUploader(options.uploadUrl, options.uploadOptions) + : undefined; + } + + public apply(compiler: webpack.Compiler) { + compiler.hooks.afterEmit.tapPromise(BacktracePlugin.name, async (compilation) => { + const logger = compilation.getLogger(BacktracePlugin.name); + if (!compilation.outputOptions.path) { + logger.error( + 'Skipping everything because outputOptions.path is not set. If you see this error, please report this to Backtrace.', + ); + return; + } + + const entries: [string, string, string][] = []; + + for (const asset in compilation.assets) { + if (!asset.match(/\.(c|m)?jsx?$/)) { + logger.debug(`[${asset}] skipping processing, extension does not match`); + continue; + } + + const map = asset + '.map'; + if (!compilation.assets[map]) { + logger.debug(`[${asset}] skipping processing, map file not found`); + continue; + } -export { BacktracePlugin }; + const assetPath = path.join(compilation.outputOptions.path, asset); + const sourceMapPath = path.join(compilation.outputOptions.path, map); + + logger.debug(`adding asset ${assetPath} with sourcemap ${sourceMapPath}`); + entries.push([asset, assetPath, sourceMapPath]); + } + + logger.log(`received ${entries.length} files for processing`); + + for (const [asset, sourcePath, sourceMapPath] of entries) { + let debugId: string; + + logger.time(`[${asset}] process source and sourcemap`); + try { + debugId = await this._sourceProcessor.processSourceAndSourceMapFiles(sourcePath, sourceMapPath); + } catch (err) { + logger.error(`[${asset}] process source and sourcemap failed:`, err); + continue; + } finally { + logger.timeEnd(`[${asset}] process source and sourcemap`); + } + + if (!this._sourceMapUploader) { + logger.info(`[${asset}] file processed`); + continue; + } + + logger.time(`[${asset}] upload sourcemap`); + try { + await this._sourceMapUploader.upload(sourceMapPath, debugId); + logger.info(`[${asset}] file processed and sourcemap uploaded`); + } catch (err) { + logger.error(`[${asset}] upload sourcemap failed:`, err); + } finally { + logger.timeEnd(`[${asset}] upload sourcemap`); + } + } + }); + } +} diff --git a/tools/webpack-plugin/src/BacktracePluginV4.ts b/tools/webpack-plugin/src/BacktracePluginV4.ts deleted file mode 100644 index 04ecf814..00000000 --- a/tools/webpack-plugin/src/BacktracePluginV4.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ContentAppender, DebugIdGenerator } from '@backtrace/sourcemap-tools'; -import crypto from 'crypto'; -import { Compiler, WebpackPluginInstance } from 'webpack'; -import { BacktraceWebpackSourceGenerator } from './BacktraceWebpackSourceGenerator'; -import { BacktracePluginOptions } from './models/BacktracePluginOptions'; - -export class BacktracePluginV4 implements WebpackPluginInstance { - private readonly _sourceGenerator: BacktraceWebpackSourceGenerator; - - constructor(public readonly options?: BacktracePluginOptions) { - this._sourceGenerator = new BacktraceWebpackSourceGenerator( - options?.debugIdGenerator ?? new DebugIdGenerator(), - new ContentAppender(), - ); - } - - public apply(compiler: Compiler) { - const assetDebugIds = new Map(); - - compiler.hooks.emit.tap(BacktracePluginV4.name, (compilation) => { - for (const key in compilation.assets) { - let source = compilation.assets[key]; - - let debugId; - if (key.match(/.(c|m)?jsx?$/)) { - debugId = crypto.randomUUID(); - assetDebugIds.set(key, debugId); - - source = this._sourceGenerator.addDebugIdToSource(source as never, debugId) as typeof source; - source = this._sourceGenerator.addDebugIdCommentToSource(source as never, debugId) as typeof source; - } else if (key.match(/\.(c|m)?jsx?\.map$/)) { - // The .map replacement should account for most of the use cases - const sourceKey = key.replace(/.map$/, ''); - debugId = assetDebugIds.get(sourceKey); - if (!debugId) { - continue; - } - - source = this._sourceGenerator.addDebugIdToRawSourceMap(source as never, debugId) as never; - } - - compilation.assets[key] = source; - } - }); - } -} diff --git a/tools/webpack-plugin/src/BacktracePluginV5.ts b/tools/webpack-plugin/src/BacktracePluginV5.ts deleted file mode 100644 index 5ae43047..00000000 --- a/tools/webpack-plugin/src/BacktracePluginV5.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { ContentAppender, DebugIdGenerator } from '@backtrace/sourcemap-tools'; -import crypto from 'crypto'; -import { 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; - - constructor(public readonly options?: BacktracePluginOptions) { - this._sourceGenerator = new BacktraceWebpackSourceGenerator( - options?.debugIdGenerator ?? new DebugIdGenerator(), - new ContentAppender(), - ); - } - - public apply(compiler: Compiler) { - const assetDebugIds = new Map(); - const processedSourceMapsForSources = new Set(); - - compiler.hooks.thisCompilation.tap(BacktracePluginV5.name, (compilation) => { - compilation.hooks.processAssets.tap( - { - name: BacktracePluginV5.name, - stage: Compilation.PROCESS_ASSETS_STAGE_ADDITIONS, - }, - (assets) => { - for (const key in assets) { - const debugId = crypto.randomUUID(); - assetDebugIds.set(key, debugId); - - this.injectSourceSnippet(compilation, key, debugId); - } - }, - ); - - compilation.hooks.processAssets.tap( - { - name: BacktracePluginV5.name, - stage: Compilation.PROCESS_ASSETS_STAGE_DEV_TOOLING, - additionalAssets: true, - }, - (assets) => { - for (const key in assets) { - const debugId = assetDebugIds.get(key); - if (!debugId) { - continue; - } - - if (this.injectSourceMapDebugId(compilation, key, debugId)) { - processedSourceMapsForSources.add(key); - } - } - return; - }, - ); - - const processedSourceMaps = new Set(); - compilation.hooks.processAssets.tap( - { - name: BacktracePluginV5.name, - stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE, - }, - (assets) => { - for (const key in assets) { - const asset = compilation.getAsset(key); - if (!asset) { - continue; - } - - const debugId = assetDebugIds.get(key); - if (!debugId) { - continue; - } - - this.injectSourceComment(compilation, key, debugId); - - // If the sourcemap has not been processed for some reason, - // attempt to manually append the information - if (!processedSourceMapsForSources.has(key)) { - if (this.appendSourceMapDebugId(compilation, key, debugId, processedSourceMaps)) { - processedSourceMapsForSources.add(key); - } - } - } - }, - ); - }); - } - - private injectSourceSnippet(compilation: Compilation, key: string, debugId: string): boolean { - const asset = compilation.getAsset(key); - if (!asset) { - return false; - } - - const newSource = this._sourceGenerator.addDebugIdToSource(asset.source as never, debugId); - - compilation.updateAsset(key, newSource as never); - return true; - } - - private injectSourceMapDebugId(compilation: Compilation, key: string, debugId: string): boolean { - const asset = compilation.getAsset(key); - if (!asset) { - return false; - } - - if (!(asset.source instanceof SourceMapSource)) { - return false; - } - - const newSource = this._sourceGenerator.addDebugIdToSourceMap(asset.source, debugId); - compilation.updateAsset(key, newSource as never); - - return true; - } - - private injectSourceComment(compilation: Compilation, key: string, debugId: string): boolean { - const asset = compilation.getAsset(key); - if (!asset) { - return false; - } - - const newSource = this._sourceGenerator.addDebugIdCommentToSource(asset.source as never, debugId); - compilation.updateAsset(key, newSource as never); - - return true; - } - - /** - * Manually appends debug ID keys to the sourcemap file. - */ - private appendSourceMapDebugId( - compilation: Compilation, - key: string, - debugId: string, - processedSourceMaps: Set, - ): boolean { - const assetInfo = compilation.assetsInfo.get(key); - if (!assetInfo) { - return false; - } - - let sourceMapKeys = assetInfo.related?.sourceMap; - if (!sourceMapKeys) { - return false; - } - - if (!Array.isArray(sourceMapKeys)) { - sourceMapKeys = [sourceMapKeys]; - } - - for (const sourceMapKey of sourceMapKeys) { - if (processedSourceMaps.has(sourceMapKey)) { - continue; - } - - const sourceMapAsset = compilation.getAsset(sourceMapKey); - if (!sourceMapAsset) { - continue; - } - - const newSource = this._sourceGenerator.addDebugIdToRawSourceMap(sourceMapAsset.source as never, debugId); - compilation.updateAsset(sourceMapKey, newSource as never); - - processedSourceMaps.add(sourceMapKey); - } - - return true; - } -} diff --git a/tools/webpack-plugin/src/BacktraceWebpackSourceGenerator.ts b/tools/webpack-plugin/src/BacktraceWebpackSourceGenerator.ts deleted file mode 100644 index 19b2bc74..00000000 --- a/tools/webpack-plugin/src/BacktraceWebpackSourceGenerator.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ContentAppender, DebugIdGenerator } from '@backtrace/sourcemap-tools'; -import type { Source } from 'webpack-sources'; -import { ConcatSource, RawSource, SourceMapSource } from 'webpack-sources'; - -export class BacktraceWebpackSourceGenerator { - constructor( - private readonly _debugIdGenerator: DebugIdGenerator, - private readonly _contentAppender: ContentAppender, - ) {} - - public addDebugIdToSource(source: Source, debugId: string): ConcatSource { - const sourceSnippet = this._debugIdGenerator.generateSourceSnippet(debugId); - return new ConcatSource(source, '\n' + sourceSnippet); - } - - public addDebugIdCommentToSource(source: Source, debugId: string): ConcatSource { - const comment = this._debugIdGenerator.generateSourceComment(debugId); - return new ConcatSource(source, '\n' + comment); - } - - public addDebugIdToSourceMap(sourceMapSource: SourceMapSource, debugId: string): SourceMapSource { - const { source, map } = sourceMapSource.sourceAndMap(); - if (!map) { - return sourceMapSource; - } - - const newMap = this._debugIdGenerator.addSourceMapKey(map, debugId); - - // The file name does not matter at this point, and it is set to 'x' in Webpack - return new SourceMapSource(source as string, 'x', newMap as never); - } - - public addDebugIdToRawSourceMap(source: Source, debugId: string): RawSource { - let sourceMapSource = (source.source() as Buffer).toString('utf8'); - const debugSourceMapObj = this._debugIdGenerator.addSourceMapKey({}, debugId); - sourceMapSource = this._contentAppender.appendToJSON(sourceMapSource, debugSourceMapObj); - return new RawSource(sourceMapSource); - } -} diff --git a/tools/webpack-plugin/src/index.ts b/tools/webpack-plugin/src/index.ts index 7b12f012..127704fa 100644 --- a/tools/webpack-plugin/src/index.ts +++ b/tools/webpack-plugin/src/index.ts @@ -1,7 +1,4 @@ import { BacktracePlugin } from './BacktracePlugin'; - -export { BacktracePluginV4 } from './BacktracePluginV4'; -export { BacktracePluginV5 } from './BacktracePluginV5'; export { BacktracePluginOptions } from './models/BacktracePluginOptions'; export { BacktracePlugin }; export default BacktracePlugin; diff --git a/tools/webpack-plugin/src/models/BacktracePluginOptions.ts b/tools/webpack-plugin/src/models/BacktracePluginOptions.ts index 862564fc..e402d1e4 100644 --- a/tools/webpack-plugin/src/models/BacktracePluginOptions.ts +++ b/tools/webpack-plugin/src/models/BacktracePluginOptions.ts @@ -1,5 +1,16 @@ -import { DebugIdGenerator } from '@backtrace/sourcemap-tools'; +import { SourceMapUploaderOptions } from '@backtrace/sourcemap-tools'; export interface BacktracePluginOptions { - debugIdGenerator?: DebugIdGenerator; + /** + * Upload URL for uploading sourcemap files. + * See Source Maps Integration Guide for your instance for more information. + * + * If not set, the sourcemaps will not be uploaded. The sources will be still processed and ready for manual upload. + */ + uploadUrl?: string | URL; + + /** + * Additional upload options. + */ + uploadOptions?: SourceMapUploaderOptions; } diff --git a/tools/webpack-plugin/tests/e2e/createE2ETest.ts b/tools/webpack-plugin/tests/e2e/createE2ETest.ts index 698ec401..65e03f46 100644 --- a/tools/webpack-plugin/tests/e2e/createE2ETest.ts +++ b/tools/webpack-plugin/tests/e2e/createE2ETest.ts @@ -1,32 +1,37 @@ +import { SourceMapUploader, SourceProcessor } from '@backtrace/sourcemap-tools'; import assert from 'assert'; import fs from 'fs'; +import path from 'path'; import webpack from 'webpack'; -import { - asyncWebpack, - expectSourceComment, - expectSourceMapSnippet, - expectSourceSnippet, - expectSuccess, - getFiles, - removeDir, - webpackModeTest, -} from './helpers'; +import { asyncWebpack, expectSuccess, getFiles, removeDir, webpackModeTest } from './helpers'; -interface E2ETestOptions { - testSourceFunction?: boolean; - testSourceComment?: boolean; - testSourceMap?: boolean; - testSourceEval?: boolean; -} - -export function createE2ETest( - configBuilder: (mode: webpack.Configuration['mode']) => webpack.Configuration, - opts?: E2ETestOptions, -) { +export function createE2ETest(configBuilder: (mode: webpack.Configuration['mode']) => webpack.Configuration) { webpackModeTest((mode) => { + function mockUploader() { + return jest.spyOn(SourceMapUploader.prototype, 'upload').mockImplementation((_, debugId) => + Promise.resolve({ + debugId: debugId ?? crypto.randomUUID(), + rxid: crypto.randomUUID(), + }), + ); + } + + function mockProcessor() { + return jest + .spyOn(SourceProcessor.prototype, 'processSourceAndSourceMapFiles') + .mockImplementation(async (_, __, debugId) => debugId ?? 'debugId'); + } + let result: webpack.Stats; + let uploadSpy: ReturnType; + let processSpy: ReturnType; beforeAll(async () => { + jest.resetAllMocks(); + + uploadSpy = mockUploader(); + processSpy = mockProcessor(); + const config = configBuilder(mode); if (config.output?.path) { await removeDir(config.output.path); @@ -37,64 +42,40 @@ export function createE2ETest( result = webpackResult; }, 120000); - if (opts?.testSourceFunction ?? true) { - it('should inject function into emitted source files', async () => { - const outputDir = result.compilation.outputOptions.path; - assert(outputDir); - - const jsFiles = await getFiles(outputDir, /.js$/); - expect(jsFiles.length).toBeGreaterThan(0); - - for (const file of jsFiles) { - const content = await fs.promises.readFile(file, 'utf8'); - await expectSourceSnippet(content); - } - }); - } - - if (opts?.testSourceComment ?? true) { - it('should inject debug ID comment into emitted source files', async () => { - const outputDir = result.compilation.outputOptions.path; - assert(outputDir); - - const jsFiles = await getFiles(outputDir, /.js$/); - expect(jsFiles.length).toBeGreaterThan(0); - - for (const file of jsFiles) { - const content = await fs.promises.readFile(file, 'utf8'); - await expectSourceComment(content); - } - }); - } - - if (opts?.testSourceEval ?? true) { - it('should eval emitted source without syntax errors', async () => { - const outputDir = result.compilation.outputOptions.path; - assert(outputDir); - - const jsFiles = await getFiles(outputDir, /.js$/); - expect(jsFiles.length).toBeGreaterThan(0); - - for (const file of jsFiles) { - const content = await fs.promises.readFile(file, 'utf8'); - expect(() => eval(content)).not.toThrowError(SyntaxError); - } - }); - } + it('should call SourceProcessor for every emitted source file and sourcemap pair', async () => { + const outputDir = result.compilation.outputOptions.path; + assert(outputDir); + + const jsFiles = await getFiles(outputDir, /.js$/); + expect(jsFiles.length).toBeGreaterThan(0); + + const processedPairs = processSpy.mock.calls.map( + ([p1, p2]) => [path.resolve(p1), path.resolve(p2)] as const, + ); + for (const file of jsFiles) { + const content = await fs.promises.readFile(file, 'utf8'); + const matches = [...content.matchAll(/^\/\/# sourceMappingURL=(.+)$/gm)]; + expect(matches.length).toEqual(1); + const [, sourceMapPath] = matches[0]; + + expect(processedPairs).toContainEqual([ + path.resolve(file), + path.resolve(path.dirname(file), sourceMapPath), + ]); + } + }); - if (opts?.testSourceMap ?? true) { - it('should inject debug ID into emitted sourcemap files', async () => { - const outputDir = result.compilation.outputOptions.path; - assert(outputDir); + it('should call SourceMapUploader for every emitted sourcemap', async () => { + const outputDir = result.compilation.outputOptions.path; + assert(outputDir); - const mapFiles = await getFiles(outputDir, /.js.map$/); - expect(mapFiles.length).toBeGreaterThan(0); + const mapFiles = await getFiles(outputDir, /.js.map$/); + expect(mapFiles.length).toBeGreaterThan(0); - for (const file of mapFiles) { - const content = await fs.promises.readFile(file, 'utf8'); - await expectSourceMapSnippet(content); - } - }); - } + const uploadedFiles = uploadSpy.mock.calls.map((c) => path.resolve(c[0])); + for (const file of mapFiles) { + expect(uploadedFiles).toContain(path.resolve(file)); + } + }); }); } diff --git a/tools/webpack-plugin/tests/e2e/helpers.ts b/tools/webpack-plugin/tests/e2e/helpers.ts index dc215b18..f835a954 100644 --- a/tools/webpack-plugin/tests/e2e/helpers.ts +++ b/tools/webpack-plugin/tests/e2e/helpers.ts @@ -30,7 +30,7 @@ export function getBaseConfig(config: webpack.Configuration, options?: BaseConfi }, ], }, - plugins: [new BacktracePlugin(options?.pluginOptions ?? { debugIdGenerator: new TestDebugIdGenerator() })], + plugins: [new BacktracePlugin({ uploadUrl: 'https://localhost', ...options?.pluginOptions })], ...config, }; } diff --git a/tools/webpack-plugin/tests/e2e/no-sourcemaps/no-sourcemaps.spec.ts b/tools/webpack-plugin/tests/e2e/no-sourcemaps/no-sourcemaps.spec.ts index 58bd4bc7..9ee2d8eb 100644 --- a/tools/webpack-plugin/tests/e2e/no-sourcemaps/no-sourcemaps.spec.ts +++ b/tools/webpack-plugin/tests/e2e/no-sourcemaps/no-sourcemaps.spec.ts @@ -1,13 +1,13 @@ +import { SourceProcessor } from '@backtrace/sourcemap-tools'; import path from 'path'; -import { createE2ETest } from '../createE2ETest'; -import { getBaseConfig } from '../helpers'; +import { asyncWebpack, expectSuccess, getBaseConfig, removeDir, webpackModeTest } from '../helpers'; describe('No sourcemaps', () => { const outputDir = path.join(__dirname, './output'); - createE2ETest( - (mode) => - getBaseConfig( + webpackModeTest((mode) => { + it('should not call SourceProcessor when devtool is false', async () => { + const config = getBaseConfig( { mode, devtool: false, @@ -18,9 +18,18 @@ describe('No sourcemaps', () => { }, }, { tsconfigPath: path.join(__dirname, './tsconfig.test.json') }, - ), - { - testSourceMap: false, - }, - ); + ); + + if (config.output?.path) { + await removeDir(config.output.path); + } + + const sourceProcessorSpy = jest.spyOn(SourceProcessor.prototype, 'processSourceAndSourceMap'); + + const webpackResult = await asyncWebpack(config); + expectSuccess(webpackResult); + + expect(sourceProcessorSpy).not.toBeCalled(); + }, 120000); + }); }); diff --git a/tools/webpack-plugin/tests/unit/BacktraceWebpackSourceGenerator.spec.ts b/tools/webpack-plugin/tests/unit/BacktraceWebpackSourceGenerator.spec.ts deleted file mode 100644 index 45109996..00000000 --- a/tools/webpack-plugin/tests/unit/BacktraceWebpackSourceGenerator.spec.ts +++ /dev/null @@ -1,300 +0,0 @@ -import { ContentAppender, DebugIdGenerator } from '@backtrace/sourcemap-tools'; -import webpack from 'webpack'; -import { ConcatSource, RawSource, SourceMapSource } from 'webpack-sources'; -import { BacktraceWebpackSourceGenerator } from '../../src/BacktraceWebpackSourceGenerator'; - -describe('BacktraceWebpackSourceGenerator', () => { - function createTestSourceMap() { - const sourceMap: ConstructorParameters[2] = { - file: 'x', - mappings: 'aACE', - names: ['x'], - sources: ['x'], - version: 3, - }; - - return sourceMap; - } - - describe('addDebugIdToSource', () => { - it('should append source snippet generated by debugIdGenerator', () => { - const expected = 'def'; - - const source = new RawSource('abc'); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - jest.spyOn(debugIdGenerator, 'generateSourceSnippet').mockReturnValue(expected); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actualSource = sourceGenerator.addDebugIdToSource(source, 'x'); - const actual = actualSource.source(); - - expect(actual).toContain('abc'); - expect(actual).toContain(expected); - }); - - it('should pass uuid to debugIdGenerator', () => { - const expected = 'def'; - - const source = new RawSource('abc'); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const spy = jest.spyOn(debugIdGenerator, 'generateSourceSnippet').mockReturnValue(expected); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdToSource(source, expected); - - expect(spy).toBeCalledWith(expected); - }); - - it('should return an instance of ConcatSource', () => { - const source = new RawSource('abc'); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actual = sourceGenerator.addDebugIdToSource(source, 'def'); - - expect(actual).toBeInstanceOf(ConcatSource); - }); - - it('should not modify original source', () => { - const expected = 'abc'; - const source = new RawSource(expected); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdToSource(source, 'def'); - - expect(source.source()).toEqual(expected); - }); - }); - - describe('addDebugIdCommentToSource', () => { - it('should append comment snippet generated by debugIdGenerator', () => { - const expected = 'def'; - - const source = new RawSource('abc'); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - jest.spyOn(debugIdGenerator, 'generateSourceComment').mockReturnValue(expected); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actualSource = sourceGenerator.addDebugIdCommentToSource(source, 'x'); - const actual = actualSource.source(); - - expect(actual).toContain('abc'); - expect(actual).toContain(expected); - }); - - it('should pass uuid to debugIdGenerator', () => { - const expected = 'def'; - - const source = new RawSource('abc'); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const spy = jest.spyOn(debugIdGenerator, 'generateSourceComment').mockReturnValue(expected); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdCommentToSource(source, expected); - - expect(spy).toBeCalledWith(expected); - }); - - it('should return an instance of ConcatSource', () => { - const source = new RawSource('abc'); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actual = sourceGenerator.addDebugIdCommentToSource(source, 'def'); - - expect(actual).toBeInstanceOf(ConcatSource); - }); - - it('should not modify original source', () => { - const expected = 'abc'; - const source = new RawSource(expected); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdCommentToSource(source, 'def'); - - expect(source.source()).toEqual(expected); - }); - }); - - // We do not support this on Webpack 4, nor it is used - if (webpack.version[0] !== '4') { - describe('addDebugIdToSourceMap', () => { - it('should append whole object generated by debugIdGenerator', () => { - const source = new SourceMapSource('abc', 'x', createTestSourceMap()); - const expected = { - ...source.map(), - debugId: '123', - newKey2: 456, - }; - - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - jest.spyOn(debugIdGenerator, 'addSourceMapKey').mockReturnValue(expected); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actualSourceMapSource = sourceGenerator.addDebugIdToSourceMap(source, 'x'); - - const { map: actualMap } = actualSourceMapSource.sourceAndMap(); - expect(actualMap).toEqual(expected); - }); - - it('should pass sourcemap to debugIdGenerator', () => { - const expected = createTestSourceMap(); - - const source = new SourceMapSource('abc', 'x', expected); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const spy = jest - .spyOn(debugIdGenerator, 'addSourceMapKey') - .mockReturnValue({ ...createTestSourceMap(), debugId: '123' }); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdToSourceMap(source, 'def'); - - expect(spy).toBeCalledWith(expected, expect.anything()); - }); - - it('should pass uuid to debugIdGenerator', () => { - const expected = 'def'; - - const source = new SourceMapSource('abc', 'x', createTestSourceMap()); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const spy = jest - .spyOn(debugIdGenerator, 'addSourceMapKey') - .mockReturnValue({ ...createTestSourceMap(), debugId: '123' }); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdToSourceMap(source, expected); - - expect(spy).toBeCalledWith(expect.anything(), expected); - }); - - it('should return an instance of SourceMapSource', () => { - const source = new SourceMapSource('abc', 'x', createTestSourceMap()); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actual = sourceGenerator.addDebugIdToSourceMap(source, 'def'); - - expect(actual).toBeInstanceOf(SourceMapSource); - }); - - it('should not modify original source', () => { - const expected = 'abc'; - const source = new SourceMapSource(expected, 'x', createTestSourceMap()); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdCommentToSource(source, 'def'); - - const { source: actualSourceMap } = source.sourceAndMap(); - expect(actualSourceMap.toString()).toEqual(expected); - }); - - it('should not modify original sourcemap', () => { - const expected = createTestSourceMap(); - const modifiedSourceMap = { - ...expected, - debugId: '123', - newKey2: 456, - }; - - const source = new SourceMapSource('abc', 'x', expected); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - jest.spyOn(debugIdGenerator, 'addSourceMapKey').mockReturnValue(modifiedSourceMap); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdToSourceMap(source, 'x'); - - const { map: actualMap } = source.sourceAndMap(); - expect(actualMap).toEqual(expected); - }); - }); - } - - describe('addDebugIdToRawSourceMap', () => { - it('should append whole object generated by debugIdGenerator', () => { - const sourceMap = createTestSourceMap(); - const expected = { - ...sourceMap, - debugId: '123', - newKey2: 456, - }; - - const source = new RawSource(JSON.stringify(sourceMap)); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - jest.spyOn(debugIdGenerator, 'addSourceMapKey').mockReturnValue(expected); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actualSource = sourceGenerator.addDebugIdToRawSourceMap(source, 'x'); - - const actual = JSON.parse(actualSource.source()); - expect(actual).toEqual(expected); - }); - - it('should pass uuid to debugIdGenerator', () => { - const expected = 'def'; - - const source = new RawSource(JSON.stringify(createTestSourceMap())); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const spy = jest - .spyOn(debugIdGenerator, 'addSourceMapKey') - .mockReturnValue({ ...createTestSourceMap(), debugId: '123' }); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdToRawSourceMap(source, expected); - - expect(spy).toBeCalledWith(expect.anything(), expected); - }); - - it('should return an instance of RawSource', () => { - const source = new RawSource(JSON.stringify(createTestSourceMap())); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - const actual = sourceGenerator.addDebugIdToRawSourceMap(source, 'def'); - - expect(actual).toBeInstanceOf(RawSource); - }); - - it('should not modify original source', () => { - const expected = JSON.stringify(createTestSourceMap()); - const source = new RawSource(expected); - const debugIdGenerator = new DebugIdGenerator(); - const contentAppender = new ContentAppender(); - - const sourceGenerator = new BacktraceWebpackSourceGenerator(debugIdGenerator, contentAppender); - sourceGenerator.addDebugIdCommentToSource(source, 'def'); - - const { source: actualSourceMap } = source.sourceAndMap(); - expect(actualSourceMap).toEqual(expected); - }); - }); -}); diff --git a/tools/webpack-plugin/webpack4.jest.config.js b/tools/webpack-plugin/webpack4.jest.config.js deleted file mode 100644 index ea8797bb..00000000 --- a/tools/webpack-plugin/webpack4.jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -/** @type {import('ts-jest').JestConfigWithTsJest} */ -module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - setupFiles: ['./tests/setupWebpackV4.ts'], - testPathIgnorePatterns: ['e2e'], -};