Conversation
Reviewer's GuideThis PR adds first-class support for Nuxt.js by detecting Nuxt projects and SSR builds, skips obfuscation when appropriate, refactors obfuscation into a shared function, introduces source map handling throughout the bundling and worker pipelines, enhances utility type checks, updates tests for the new behavior, and tweaks tsup configuration. Sequence diagram for plugin initialization and Nuxt/SSR detectionsequenceDiagram
participant Plugin as viteBundleObfuscator
participant Utils as isLibMode/isNuxtProject
participant Vite as ViteConfig
Plugin->>Vite: config(modifyConfigHandler)
Vite-->>Plugin: config, env
Plugin->>Utils: isLibMode(config)
Plugin->>Utils: isNuxtProject(config)
Plugin->>Plugin: set _isLibMode, _isNuxtProject, _isSsrBuild
Plugin->>Vite: return if obfuscation should be skipped
Sequence diagram for obfuscation with source map handling (shared for main and worker)sequenceDiagram
participant Plugin
participant Obfuscator as javascriptObfuscator
participant Registry as ObfuscatedFilesRegistry
Plugin->>Registry: isObfuscated(fileName)?
alt Not obfuscated
Plugin->>Obfuscator: obfuscate(code, options)
Obfuscator-->>Plugin: obfuscatedCode, sourceMap
Plugin->>Registry: markAsObfuscated(fileName)
Plugin-->>Plugin: return { code, map }
else Already obfuscated
Plugin-->>Plugin: return original code and map
end
Class diagram for updated utility type checks and Nuxt detectionclassDiagram
class Utils {
+isRegExp(input)
+isString(input)
+isObject(input)
+isArray(input)
+isFunction(input)
+isBoolean(input)
+isFileNameExcluded(fileName, excludes)
+isLibMode(config)
+isNuxtProject(config)
}
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey @z0ffy - I've reviewed your changes - here's some feedback:
- Consider caching or memoizing the isNuxtProject file-system checks in utils/is.ts to avoid running expensive IO on every config resolution.
- Double-check that using both transformIndexHtml and generateBundle for obfuscation doesn’t lead to the same chunks being processed twice in Nuxt client builds.
- You could simplify the manual SSR/ Nuxt flags by leveraging Vite’s built-in apply/enforce options or dedicated hooks instead of tracking _isSsrBuild and _isNuxtProject yourself.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider caching or memoizing the isNuxtProject file-system checks in utils/is.ts to avoid running expensive IO on every config resolution.
- Double-check that using both transformIndexHtml and generateBundle for obfuscation doesn’t lead to the same chunks being processed twice in Nuxt client builds.
- You could simplify the manual SSR/ Nuxt flags by leveraging Vite’s built-in apply/enforce options or dedicated hooks instead of tracking _isSsrBuild and _isNuxtProject yourself.
## Individual Comments
### Comment 1
<location> `src/utils/index.ts:179` </location>
<code_context>
- return obfuscatedCode;
+ return {
+ code: obfuscationResult.getObfuscatedCode(),
+ map: JSON.parse(obfuscationResult.getSourceMap() || 'null'),
+ };
}
</code_context>
<issue_to_address>
Parsing getSourceMap() with 'null' fallback may cause issues if the result is not valid JSON.
If getSourceMap() returns invalid or empty JSON, JSON.parse will throw. Use try-catch or a safer fallback to prevent runtime errors.
</issue_to_address>
### Comment 2
<location> `src/utils/index.ts:208` </location>
<code_context>
return {
code: obfuscated.getObfuscatedCode(),
- map: obfuscated.getSourceMap(),
+ map: JSON.parse(obfuscated.getSourceMap() || 'null'),
};
}
</code_context>
<issue_to_address>
Same JSON.parse risk as above for obfuscateLibBundle.
Parsing getSourceMap() without validation can throw if the input is not valid JSON. Please add error handling.
</issue_to_address>
### Comment 3
<location> `src/worker/index.ts:43` </location>
<code_context>
return {
code: obfuscated.getObfuscatedCode(),
- map: obfuscated.getSourceMap(),
+ map: JSON.parse(obfuscated.getSourceMap() || 'null'),
};
}
</code_context>
<issue_to_address>
Potential unhandled exception when parsing getSourceMap() in worker.
Wrap JSON.parse in a try-catch or validate the input to prevent worker crashes from invalid or empty source maps.
</issue_to_address>
### Comment 4
<location> `src/index.ts:21` </location>
<code_context>
+import { isArray, isFunction, isLibMode, isNuxtProject, isObject } from './utils/is';
import { defaultConfig, LOG_COLOR, NODE_MODULES, VENDOR_MODULES } from './utils/constants';
export default function viteBundleObfuscator(config?: Partial<Config>): PluginOption {
const finalConfig = { ...defaultConfig, ...config };
const _log = new Log(finalConfig.log);
</code_context>
<issue_to_address>
Consider extracting environment flag logic and obfuscation routines into helper modules to centralize checks and reduce hook complexity.
```suggestion
// 1) Extract environment‐flags and “shouldObfuscate” into a small helper module.
// src/utils/flags.ts
export interface Flags {
enable: boolean
isLib: boolean
isNuxt: boolean
isSSR: boolean
}
export function makeFlags(finalConfig, env, config): Flags {
return {
enable: finalConfig.enable,
isLib: isLibMode(config),
isNuxt: isNuxtProject(config),
isSSR: !!env?.isSsrBuild,
}
}
export function shouldObfuscateHtml(f: Flags) {
return f.enable && !f.isNuxt && !f.isSSR
}
export function shouldObfuscateBundle(f: Flags) {
return f.enable && !f.isLib && f.isNuxt && !f.isSSR
}
export function shouldObfuscateLib(f: Flags) {
return f.enable && f.isLib && !f.isSSR
}
// 2) Factor out the common obfuscation logic.
// src/utils/obfuscator.ts
import { CodeSizeAnalyzer, createWorkerTask, obfuscateBundle } from './…'
export async function obfuscateChunks(finalConfig, log, bundle) {
log.forceLog('\nstarting obfuscation process…')
const analyzer = new CodeSizeAnalyzer(log)
const list = getValidBundleList(finalConfig, bundle)
analyzer.start(list)
if (isEnableThreadPool(finalConfig)) {
const poolSize = Math.min(getThreadPoolSize(finalConfig), list.length)
const chunkSize = Math.ceil(list.length / poolSize)
await Promise.all(
Array.from({ length: poolSize }, (_, i) => {
const slice = list.slice(i*chunkSize, (i+1)*chunkSize)
return createWorkerTask(finalConfig, slice)
})
)
}
else {
list.forEach(([name,item]) => {
const { code, map } = obfuscateBundle(finalConfig, name, item)
item.code = code; item.map = map as any
})
}
analyzer.end(list)
}
// 3) Simplify your hooks in src/index.ts:
import { makeFlags, shouldObfuscateHtml, shouldObfuscateBundle, shouldObfuscateLib } from './utils/flags'
import { obfuscateChunks } from './utils/obfuscator'
export default function viteBundleObfuscator(cfg?) {
const finalConfig = { …defaultConfig, …cfg }
const log = new Log(finalConfig.log)
let flags: Flags
const configHook: ViteConfigFn = (config, env) => {
flags = makeFlags(finalConfig, env, config)
// existing manual‐chunk logic…
}
const transformIndexHtml: IndexHtmlTransformHook = async (html, { bundle }) => {
if (!bundle || !shouldObfuscateHtml(flags)) return html
await obfuscateChunks(finalConfig, log, bundle)
return html
}
const generateBundle: Rollup.Plugin['generateBundle'] = async (_, bundle) => {
if (!bundle || !shouldObfuscateBundle(flags)) return
await obfuscateChunks(finalConfig, log, bundle)
}
const renderChunk: Rollup.RenderChunkHook = (code, chunk) => {
if (!shouldObfuscateLib(flags)) return null
// you can also factor `obfuscateLibBundle` + analyzer into a helper if wanted
const analyzer = new CodeSizeAnalyzer(log)
analyzer.start([[chunk.name,{code}]])
const { code: obf, map } = obfuscateLibBundle(finalConfig, chunk.name, code)
analyzer.end([[chunk.name,{code}]])
return { code: obf, map }
}
return {
name: 'vite-plugin-bundle-obfuscator',
config: configHook,
configResolved: configResolvedHandler,
transformIndexHtml,
generateBundle,
renderChunk,
/* … */
}
}
```
These changes
1) centralize all flag‐building and stage checks in one small module
2) unify the “thread vs single” obfuscation logic into `obfuscateChunks`
3) shrink each hook down to a single `if (!shouldXxx(flags))` + a one‐liner call, preserving all behavior.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| return obfuscatedCode; | ||
| return { | ||
| code: obfuscationResult.getObfuscatedCode(), | ||
| map: JSON.parse(obfuscationResult.getSourceMap() || 'null'), |
There was a problem hiding this comment.
issue (bug_risk): Parsing getSourceMap() with 'null' fallback may cause issues if the result is not valid JSON.
If getSourceMap() returns invalid or empty JSON, JSON.parse will throw. Use try-catch or a safer fallback to prevent runtime errors.
| return { | ||
| code: obfuscated.getObfuscatedCode(), | ||
| map: obfuscated.getSourceMap(), | ||
| map: JSON.parse(obfuscated.getSourceMap() || 'null'), |
There was a problem hiding this comment.
issue (bug_risk): Same JSON.parse risk as above for obfuscateLibBundle.
Parsing getSourceMap() without validation can throw if the input is not valid JSON. Please add error handling.
| results.push({ | ||
| fileName, | ||
| obfuscatedCode: obfuscated.getObfuscatedCode(), | ||
| map: JSON.parse(obfuscated.getSourceMap() || 'null'), |
There was a problem hiding this comment.
issue (bug_risk): Potential unhandled exception when parsing getSourceMap() in worker.
Wrap JSON.parse in a try-catch or validate the input to prevent worker crashes from invalid or empty source maps.
| import { isArray, isFunction, isLibMode, isNuxtProject, isObject } from './utils/is'; | ||
| import { defaultConfig, LOG_COLOR, NODE_MODULES, VENDOR_MODULES } from './utils/constants'; | ||
|
|
||
| export default function viteBundleObfuscator(config?: Partial<Config>): PluginOption { |
There was a problem hiding this comment.
issue (complexity): Consider extracting environment flag logic and obfuscation routines into helper modules to centralize checks and reduce hook complexity.
| export default function viteBundleObfuscator(config?: Partial<Config>): PluginOption { | |
| // 1) Extract environment‐flags and “shouldObfuscate” into a small helper module. | |
| // src/utils/flags.ts | |
| export interface Flags { | |
| enable: boolean | |
| isLib: boolean | |
| isNuxt: boolean | |
| isSSR: boolean | |
| } | |
| export function makeFlags(finalConfig, env, config): Flags { | |
| return { | |
| enable: finalConfig.enable, | |
| isLib: isLibMode(config), | |
| isNuxt: isNuxtProject(config), | |
| isSSR: !!env?.isSsrBuild, | |
| } | |
| } | |
| export function shouldObfuscateHtml(f: Flags) { | |
| return f.enable && !f.isNuxt && !f.isSSR | |
| } | |
| export function shouldObfuscateBundle(f: Flags) { | |
| return f.enable && !f.isLib && f.isNuxt && !f.isSSR | |
| } | |
| export function shouldObfuscateLib(f: Flags) { | |
| return f.enable && f.isLib && !f.isSSR | |
| } | |
| // 2) Factor out the common obfuscation logic. | |
| // src/utils/obfuscator.ts | |
| import { CodeSizeAnalyzer, createWorkerTask, obfuscateBundle } from './…' | |
| export async function obfuscateChunks(finalConfig, log, bundle) { | |
| log.forceLog('\nstarting obfuscation process…') | |
| const analyzer = new CodeSizeAnalyzer(log) | |
| const list = getValidBundleList(finalConfig, bundle) | |
| analyzer.start(list) | |
| if (isEnableThreadPool(finalConfig)) { | |
| const poolSize = Math.min(getThreadPoolSize(finalConfig), list.length) | |
| const chunkSize = Math.ceil(list.length / poolSize) | |
| await Promise.all( | |
| Array.from({ length: poolSize }, (_, i) => { | |
| const slice = list.slice(i*chunkSize, (i+1)*chunkSize) | |
| return createWorkerTask(finalConfig, slice) | |
| }) | |
| ) | |
| } | |
| else { | |
| list.forEach(([name,item]) => { | |
| const { code, map } = obfuscateBundle(finalConfig, name, item) | |
| item.code = code; item.map = map as any | |
| }) | |
| } | |
| analyzer.end(list) | |
| } | |
| // 3) Simplify your hooks in src/index.ts: | |
| import { makeFlags, shouldObfuscateHtml, shouldObfuscateBundle, shouldObfuscateLib } from './utils/flags' | |
| import { obfuscateChunks } from './utils/obfuscator' | |
| export default function viteBundleObfuscator(cfg?) { | |
| const finalConfig = { …defaultConfig, …cfg } | |
| const log = new Log(finalConfig.log) | |
| let flags: Flags | |
| const configHook: ViteConfigFn = (config, env) => { | |
| flags = makeFlags(finalConfig, env, config) | |
| // existing manual‐chunk logic… | |
| } | |
| const transformIndexHtml: IndexHtmlTransformHook = async (html, { bundle }) => { | |
| if (!bundle || !shouldObfuscateHtml(flags)) return html | |
| await obfuscateChunks(finalConfig, log, bundle) | |
| return html | |
| } | |
| const generateBundle: Rollup.Plugin['generateBundle'] = async (_, bundle) => { | |
| if (!bundle || !shouldObfuscateBundle(flags)) return | |
| await obfuscateChunks(finalConfig, log, bundle) | |
| } | |
| const renderChunk: Rollup.RenderChunkHook = (code, chunk) => { | |
| if (!shouldObfuscateLib(flags)) return null | |
| // you can also factor `obfuscateLibBundle` + analyzer into a helper if wanted | |
| const analyzer = new CodeSizeAnalyzer(log) | |
| analyzer.start([[chunk.name,{code}]]) | |
| const { code: obf, map } = obfuscateLibBundle(finalConfig, chunk.name, code) | |
| analyzer.end([[chunk.name,{code}]]) | |
| return { code: obf, map } | |
| } | |
| return { | |
| name: 'vite-plugin-bundle-obfuscator', | |
| config: configHook, | |
| configResolved: configResolvedHandler, | |
| transformIndexHtml, | |
| generateBundle, | |
| renderChunk, | |
| /* … */ | |
| } | |
| } |
These changes
- centralize all flag‐building and stage checks in one small module
- unify the “thread vs single” obfuscation logic into
obfuscateChunks - shrink each hook down to a single
if (!shouldXxx(flags))+ a one‐liner call, preserving all behavior.
| bundleItem.code = obfuscateBundle(finalConfig, fileName, bundleItem); | ||
| }); | ||
| const configResolvedHandler: (resolvedConfig: ResolvedConfig) => void | Promise<void> = (resolvedConfig) => { | ||
| const sourcemap = resolvedConfig.build.sourcemap; |
There was a problem hiding this comment.
suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)
| const sourcemap = resolvedConfig.build.sourcemap; | |
| const {sourcemap} = resolvedConfig.build; |
Explanation
Object destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.From the Airbnb Javascript Style Guide
Summary by Sourcery
Enable Nuxt.js and SSR support in the Vite bundle obfuscator plugin, refactor obfuscation workflow into a shared handler, add source map generation, and adjust utility, hook, and test logic accordingly.
New Features:
Enhancements:
Build:
Tests: