Skip to content

Commit e4c56de

Browse files
authored
Merge pull request #291 from mat1jaczyyy/feature/compose-sourcemaps
Compose sourcemaps
2 parents b6136ab + 1f70038 commit e4c56de

5 files changed

Lines changed: 117 additions & 21 deletions

File tree

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,16 @@
5151
"version": "auto-changelog"
5252
},
5353
"dependencies": {
54+
"@jridgewell/remapping": "^2.3.5",
5455
"javascript-obfuscator": "^4.1.1"
5556
},
5657
"devDependencies": {
5758
"@commitlint/cli": "^19.8.1",
5859
"@commitlint/config-conventional": "^19.8.1",
5960
"@commitlint/types": "^19.8.1",
6061
"@eslint/js": "^9.32.0",
62+
"@jridgewell/sourcemap-codec": "^1.5.5",
63+
"@jridgewell/trace-mapping": "^0.3.31",
6164
"@stylistic/eslint-plugin": "^5.2.2",
6265
"@types/node": "^24.2.0",
6366
"@vitest/coverage-v8": "^3.2.4",
@@ -88,4 +91,4 @@
8891
"esbuild"
8992
]
9093
}
91-
}
94+
}

pnpm-lock.yaml

Lines changed: 30 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/__tests__/plugin.spec.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
getThreadPoolSize,
99
getValidBundleList,
1010
Log,
11+
composeSourcemaps,
1112
isEnabledFeature,
1213
isEnableThreadPool,
1314
isEnableAutoExcludesNodeModules,
@@ -19,6 +20,8 @@ import {
1920
import {BundleList, Config} from "../type";
2021
import {isArray, isFunction, isFileNameExcluded} from '../utils/is';
2122
import { Worker } from 'node:worker_threads';
23+
import { encode } from '@jridgewell/sourcemap-codec';
24+
import { TraceMap, originalPositionFor, sourceContentFor } from '@jridgewell/trace-mapping';
2225

2326
// Mock WORKER_FILE_PATH
2427
vi.stubGlobal('WORKER_FILE_PATH', './worker.js');
@@ -237,6 +240,58 @@ describe('getChunkName', () => {
237240
});
238241
});
239242

243+
describe('composeSourcemaps', () => {
244+
it('should compose maps and preserve the original source content', () => {
245+
const originalSource = 'const message = "hi";\nconsole.log(message);\n';
246+
const intermediateSource = 'const message="hi";\nconsole.log(message);\n';
247+
248+
const bundlerMap: Rollup.SourceMapInput = {
249+
version: 3,
250+
file: 'bundle.js',
251+
sources: ['original.ts'],
252+
sourcesContent: [originalSource],
253+
names: [],
254+
mappings: encode([
255+
[[0, 0, 0, 0]],
256+
[[0, 0, 1, 0]],
257+
]),
258+
};
259+
260+
const obfuscatorMap: Rollup.SourceMapInput = {
261+
version: 3,
262+
file: 'bundle-obf.js',
263+
sources: ['bundle.js'],
264+
sourcesContent: [intermediateSource],
265+
names: [],
266+
mappings: encode([
267+
[[0, 0, 0, 0]],
268+
[[0, 0, 1, 0]],
269+
]),
270+
};
271+
272+
const logSpy = vi.fn();
273+
274+
const composed = composeSourcemaps(bundlerMap, obfuscatorMap, logSpy);
275+
276+
expect(logSpy).toHaveBeenCalledWith('composing source maps...');
277+
expect(composed).not.toBeNull();
278+
279+
const trace = new TraceMap(composed as any);
280+
281+
const firstLine = originalPositionFor(trace, { line: 1, column: 0 });
282+
expect(firstLine.source).toBe('original.ts');
283+
expect(firstLine.line).toBe(1);
284+
expect(firstLine.column).toBe(0);
285+
286+
const secondLine = originalPositionFor(trace, { line: 2, column: 0 });
287+
expect(secondLine.source).toBe('original.ts');
288+
expect(secondLine.line).toBe(2);
289+
290+
const recoveredSource = sourceContentFor(trace, firstLine.source as string);
291+
expect(recoveredSource).toBe(originalSource);
292+
});
293+
});
294+
240295
describe('CodeSizeAnalyzer', () => {
241296
let analyzer: CodeSizeAnalyzer;
242297
const mockLog = new Log(true);

src/utils/index.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import path from 'node:path';
55
import os from 'node:os';
66
import { gzipSync } from 'node:zlib';
77
import javascriptObfuscator from 'javascript-obfuscator';
8+
import remapping from '@jridgewell/remapping';
89

910
import type { BundleList, Config, FormatSizeResult, ObfuscationResult, SizeResult } from '../type';
1011
import { isBoolean, isFileNameExcluded, isObject } from './is';
@@ -105,6 +106,20 @@ export function getChunkName(id: string, manualChunks: string[]): string {
105106
return VENDOR_MODULES;
106107
}
107108

109+
export function composeSourcemaps(map1: Rollup.SourceMapInput | null, map2: Rollup.SourceMapInput | null, log?: (msg: string) => void): Rollup.SourceMapInput | null {
110+
if (!map1) return map2;
111+
if (!map2) return map1;
112+
113+
log?.('composing source maps...');
114+
115+
const composed = remapping(
116+
[map2 as remapping.SourceMapInput, map1 as remapping.SourceMapInput],
117+
() => null,
118+
);
119+
120+
return composed as Rollup.SourceMapInput;
121+
}
122+
108123
export class ObfuscatedFilesRegistry {
109124
private static instance: ObfuscatedFilesRegistry;
110125
private obfuscatedFiles: Set<string> = new Set();
@@ -167,17 +182,19 @@ export function obfuscateBundle(finalConfig: Config, fileName: string, bundleIte
167182
sourceMapFileName: `${fileName}.map`,
168183
}
169184
: finalConfig.options;
170-
const obfuscationResult = javascriptObfuscator.obfuscate(bundleItem.code, fileSpecificOptions);
185+
const obfuscated = javascriptObfuscator.obfuscate(bundleItem.code, fileSpecificOptions);
171186
_log.info(`obfuscation complete for ${fileName}.`);
172187

173188
registry.markAsObfuscated(fileName);
174189
_log.info(`added ${fileName} to obfuscated files registry`);
175190

176-
const sourceMap = obfuscationResult.getSourceMap();
177-
178191
return {
179-
code: obfuscationResult.getObfuscatedCode(),
180-
map: sourceMap ? JSON.parse(sourceMap) : null,
192+
code: obfuscated.getObfuscatedCode(),
193+
map: composeSourcemaps(
194+
JSON.parse(JSON.stringify(bundleItem.map) || 'null'), // strip methods
195+
JSON.parse(obfuscated.getSourceMap() || 'null'),
196+
_log.info.bind(_log),
197+
),
181198
};
182199
}
183200

src/worker/index.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { parentPort } from 'node:worker_threads';
22
import javascriptObfuscator, { ObfuscatorOptions } from 'javascript-obfuscator';
3-
import { Log, ObfuscatedFilesRegistry } from '../utils';
3+
import { composeSourcemaps, Log, ObfuscatedFilesRegistry } from '../utils';
44
import type { ObfuscationResult, WorkerMessage } from '../type';
55

66
if (parentPort) {
@@ -40,7 +40,11 @@ if (parentPort) {
4040
results.push({
4141
fileName,
4242
obfuscatedCode: obfuscated.getObfuscatedCode(),
43-
map: JSON.parse(obfuscated.getSourceMap() || 'null'),
43+
map: composeSourcemaps(
44+
JSON.parse(JSON.stringify(bundleItem.map) || 'null'), // strip methods
45+
JSON.parse(obfuscated.getSourceMap() || 'null'),
46+
_log.info.bind(_log),
47+
),
4448
});
4549
}
4650

0 commit comments

Comments
 (0)