Skip to content

feat: add support for Nuxt.js#231

Merged
z0ffy merged 2 commits intomainfrom
feature/support-nuxt
Jul 9, 2025
Merged

feat: add support for Nuxt.js#231
z0ffy merged 2 commits intomainfrom
feature/support-nuxt

Conversation

@z0ffy
Copy link
Copy Markdown
Owner

@z0ffy z0ffy commented Jul 8, 2025

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:

  • Detect Nuxt.js projects and SSR builds to conditionally apply obfuscation
  • Add generateBundle hook to obfuscate bundles in Nuxt.js environments

Enhancements:

  • Centralize chunk obfuscation into a new obfuscateAllChunks function
  • Enhance obfuscation functions to emit source maps and update worker tasks accordingly
  • Refactor utility methods to use a unified toString approach and introduce isLibMode and isNuxtProject checks
  • Introduce configResolved hook to configure source map options based on resolved Vite config

Build:

  • Configure tsup to strip legal comments from esbuild output

Tests:

  • Update plugin tests to validate source map emission and include registry state in worker messages

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Jul 8, 2025

Reviewer's Guide

This 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 detection

sequenceDiagram
    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
Loading

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
Loading

Class diagram for updated utility type checks and Nuxt detection

classDiagram
    class Utils {
        +isRegExp(input)
        +isString(input)
        +isObject(input)
        +isArray(input)
        +isFunction(input)
        +isBoolean(input)
        +isFileNameExcluded(fileName, excludes)
        +isLibMode(config)
        +isNuxtProject(config)
    }
Loading

File-Level Changes

Change Details Files
Add Nuxt.js detection and SSR build handling to plugin
  • Introduce _isNuxtProject and _isSsrBuild flags in plugin state
  • Use isNuxtProject and SSR flag to skip obfuscation in Nuxt projects or SSR builds
  • Add generateBundle hook to handle Nuxt-specific bundle obfuscation
src/index.ts
Refactor obfuscation logic into reusable function
  • Extract obfuscateAllChunks to encapsulate bundle iteration and thread pool logic
  • Replace duplicate code in transformIndexHtmlHandler and generateBundleHandler with a single call
src/index.ts
Enable source map support in obfuscation
  • Update obfuscateBundle and obfuscateLibBundle to return {code, map} and handle sourceMap options
  • Parse and attach source maps in worker tasks and bundler results
  • Adjust createWorkerTask and worker implementation to include map in results
src/utils/index.ts
src/worker/index.ts
Streamline type checks and add project mode utilities
  • Centralize Object.prototype.toString via a toString alias
  • Refactor isFileNameExcluded to use unified type guards (isArray, isString, isRegExp)
  • Add isLibMode and isNuxtProject helpers for build-mode detection
src/utils/is.ts
Update tests for source maps and worker registry state
  • Mock getSourceMap in obfuscator stub to return a JSON string
  • Assert that registryState is passed in worker.postMessage
src/__tests__/plugin.spec.ts
Configure tsup to remove legal comments
  • Add esbuildOptions in tsup.config.ts to set legalComments = 'none'
tsup.config.ts
Extend ObfuscationResult type for source maps
  • Add optional map field to ObfuscationResult interface
src/type.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/utils/index.ts Outdated
return obfuscatedCode;
return {
code: obfuscationResult.getObfuscatedCode(),
map: JSON.parse(obfuscationResult.getSourceMap() || 'null'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread src/utils/index.ts
return {
code: obfuscated.getObfuscatedCode(),
map: obfuscated.getSourceMap(),
map: JSON.parse(obfuscated.getSourceMap() || 'null'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread src/worker/index.ts
results.push({
fileName,
obfuscatedCode: obfuscated.getObfuscatedCode(),
map: JSON.parse(obfuscated.getSourceMap() || 'null'),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread src/index.ts
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 {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

issue (complexity): Consider extracting environment flag logic and obfuscation routines into helper modules to centralize checks and reduce hook complexity.

Suggested change
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

  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.

Comment thread src/index.ts
bundleItem.code = obfuscateBundle(finalConfig, fileName, bundleItem);
});
const configResolvedHandler: (resolvedConfig: ResolvedConfig) => void | Promise<void> = (resolvedConfig) => {
const sourcemap = resolvedConfig.build.sourcemap;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

suggestion (code-quality): Prefer object destructuring when accessing and using properties. (use-object-destructuring)

Suggested change
const sourcemap = resolvedConfig.build.sourcemap;
const {sourcemap} = resolvedConfig.build;


ExplanationObject destructuring can often remove an unnecessary temporary reference, as well as making your code more succinct.

From the Airbnb Javascript Style Guide

@z0ffy z0ffy mentioned this pull request Jul 9, 2025
@z0ffy z0ffy merged commit 8635cc0 into main Jul 9, 2025
4 checks passed
@z0ffy z0ffy deleted the feature/support-nuxt branch July 9, 2025 12:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant