From 7dbaa043b6fd3160f09b72d8bfaa9d0000015e47 Mon Sep 17 00:00:00 2001 From: Dan Egnor Date: Wed, 19 Nov 2025 01:49:36 -0800 Subject: [PATCH] fix a few problems with output-file obfuscation --- esbuild-plugin-obfuscator.js | 64 +++++++++++++++++++++++++++++------- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/esbuild-plugin-obfuscator.js b/esbuild-plugin-obfuscator.js index 172cbfe..ef5d0ab 100644 --- a/esbuild-plugin-obfuscator.js +++ b/esbuild-plugin-obfuscator.js @@ -2,6 +2,9 @@ import JavaScriptObfuscator from 'javascript-obfuscator'; import micromatch from 'micromatch'; import { promises as fs } from 'fs'; import { getBuildExtensions } from 'esbuild-extra'; +import path from 'node:path'; + +const pluginName = 'obfuscator'; export function ObfuscatorPlugin({ filter = [], @@ -9,57 +12,87 @@ export function ObfuscatorPlugin({ shouldWriteOutputSourceMap = false, shouldGenerateSourceMap = false, ignoreRequireImports = true, + debug = false, ...options } = {}) { return { - name: 'obfuscator', + name: pluginName, + async setup(build) { const { onTransform } = getBuildExtensions(build, 'obfuscator'); - + options = { ignoreRequireImports, ...options, }; if (shouldObfuscateOutput) { + // Work around esbuild issue by deferring file writes. + // See: https://github.com/evanw/esbuild/issues/2999 + const writeFlag = build.initialOptions.write; build.initialOptions.write = false; build.onEnd(async (result) => { - let tasks = []; + const outputData = {}; for (const output of result.outputFiles) { const filePath = output.path; const originalCode = output.text; + // Use micromatch to check if the OUTPUT file matches any patterns in the filter array + const shouldObfuscate = micromatch.isMatch(filePath, filter); + if (!shouldObfuscate) { + outputData[filePath] = output.contents; + continue; + } + // Obfuscate the code using javascript-obfuscator with conditional sourcemap generation const obfuscatorOptions = { ...options, sourceMap: shouldWriteOutputSourceMap }; - + if (shouldWriteOutputSourceMap) { obfuscatorOptions.sourceMapFileName = filePath + '.map'; } - + + const obfuscationResult = JavaScriptObfuscator.obfuscate(originalCode, obfuscatorOptions); const obfuscatedCode = obfuscationResult.getObfuscatedCode(); + output.contents = new TextEncoder().encode(obfuscatedCode); // Write the obfuscated code to the file - tasks.push(fs.writeFile(filePath, obfuscatedCode)); - + outputData[filePath] = obfuscatedCode; + if (shouldWriteOutputSourceMap) { const sourceMap = obfuscationResult.getSourceMap(); // Write the sourcemap file - tasks.push(fs.writeFile(filePath + '.map', sourceMap)); + outputData[obfuscatorOptions.sourceMapFileName] = sourceMap; + } + + if (debug) { + const shortPath = build.initialOptions.outdir + ? path.relative(build.initialOptions.outdir, filePath) + : path.basename(filePath); + console.log( + `[${pluginName}] processed: ${shortPath} ` + + `(${originalCode.length}b => ${obfuscatedCode.length}b)` + ); } } - await Promise.all(tasks); + // Write modified data if file writing was originally enabled. + if (writeFlag === undefined || writeFlag) { + await Promise.all(Object.entries(outputData).map(async ([p, c]) => { + await fs.mkdir(path.dirname(p), { recursive: true }); + await fs.writeFile(p, c); + })); + } }); } else { - onTransform({ + onTransform({ loaders: ['js'], namespace: 'file' }, async (args) => { @@ -76,15 +109,22 @@ export function ObfuscatorPlugin({ ...options, sourceMap: shouldGenerateSourceMap }; - + if (shouldGenerateSourceMap) { obfuscatorOptions.sourceMapFileName = args.path + '.map'; } - + const obfuscationResult = JavaScriptObfuscator.obfuscate(args.code, obfuscatorOptions); const obfuscatedCode = obfuscationResult.getObfuscatedCode(); + if (debug) { + console.log( + `[${pluginName}] processed: ${filePath} ` + + `(${args.code.length}b => ${obfuscatedCode.length}b)` + ); + } + if (shouldGenerateSourceMap) { let sourceMap = obfuscationResult.getSourceMap(); sourceMap = JSON.parse(sourceMap);