Skip to content

Builder-Vite: Use djb2 hash to prevent variable name collisions in builder-vite#34274

Merged
valentinpalkovic merged 4 commits into
storybookjs:nextfrom
chida09:fix/34270-builder-vite-hash-collision
Mar 25, 2026
Merged

Builder-Vite: Use djb2 hash to prevent variable name collisions in builder-vite#34274
valentinpalkovic merged 4 commits into
storybookjs:nextfrom
chida09:fix/34270-builder-vite-hash-collision

Conversation

@chida09
Copy link
Copy Markdown
Contributor

@chida09 chida09 commented Mar 23, 2026

Closes #34270

What I did

Replaced the naive character-code-sum hash function in codegen-project-annotations.ts with the djb2 hash algorithm.

Background

The existing implementation summed character codes without considering position:

value.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0);

This means different strings containing the same characters (anagrams) produce identical hashes.

With npm/yarn, preview paths are short and collisions don't occur. However, with pnpm, peer dependency hash suffixes (hex strings) are included in paths, causing different packages to produce the same hash value — resulting in duplicate preview_XXXX variable names and a build error:

[vite:build-html] The symbol "preview_19913" has already been declared

Fix

Replaced with djb2 hash (seed 5381, hash * 33 + c), which incorporates character position into the computation, eliminating the collision.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

  1. Create a pnpm project with multiple addons that produce long paths with hex suffixes
  2. Run storybook build
  3. Verify no The symbol "preview_XXXX" has already been declared error occurs

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update MIGRATION.MD

Summary by CodeRabbit

  • Refactor
    • Updated internal preview-annotation generation. This changes how generated preview identifiers are computed and emitted, affecting the naming of generated preview artifacts while preserving public APIs and behavior.

…ns with pnpm

Replace the naive character-code-sum hash with djb2 to prevent collisions
when pnpm peer dependency hash suffixes produce paths with identical sums.

Closes storybookjs#34270

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 23, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3908812f-5879-4bf3-aec2-ced65b5e4a9f

📥 Commits

Reviewing files that changed from the base of the PR and between 262d541 and 8863f78.

📒 Files selected for processing (1)
  • code/builders/builder-vite/src/codegen-project-annotations.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/builders/builder-vite/src/codegen-project-annotations.ts

📝 Walkthrough

Walkthrough

Updated the internal hash used when generating preview annotation variable name suffixes: the implementation switched from summing character codes to a djb2-style rolling hash (starting at 5381) that returns an unsigned 32-bit integer, changing emitted identifier values in generated code.

Changes

Cohort / File(s) Summary
Hash Function Implementation
code/builders/builder-vite/src/codegen-project-annotations.ts
Replaced simple character-code summation with a djb2-style rolling hash (initial value 5381, using ((acc << 5) + acc + charCode) >>> 0) which alters the computed variable name suffixes used in generated preview/project annotation code.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@code/builders/builder-vite/src/codegen-project-annotations.ts`:
- Around line 108-115: The djb2 implementation in function hash currently
applies >>> 0 only on return; change the loop so the accumulator is forced to
32-bit unsigned on each iteration by updating acc as ((acc << 5) + acc +
value.charCodeAt(i)) >>> 0 inside the for loop (use the acc variable and
value.charCodeAt(i) as shown) and keep the final return as acc (no extra >>> 0
needed).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dcead538-6884-4184-9e4f-0b56522b6cdf

📥 Commits

Reviewing files that changed from the base of the PR and between 7801015 and 262d541.

📒 Files selected for processing (1)
  • code/builders/builder-vite/src/codegen-project-annotations.ts

Comment thread code/builders/builder-vite/src/codegen-project-annotations.ts
chida09 and others added 2 commits March 23, 2026 22:35
Move the unsigned right shift inside the loop so the accumulator stays
within 32-bit range on every iteration, avoiding precision loss when
hashing long file paths (common with pnpm).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@valentinpalkovic valentinpalkovic moved this to Empathy Queue (prioritized) in Core Team Projects Mar 24, 2026
@valentinpalkovic valentinpalkovic self-assigned this Mar 24, 2026
@valentinpalkovic valentinpalkovic moved this from Empathy Queue (prioritized) to In Progress in Core Team Projects Mar 24, 2026
@valentinpalkovic
Copy link
Copy Markdown
Contributor

Thank you so much for your contribution!

I am pasting some AI-generated explanation here for future reference:

How the Fix Works

The Problem: Position-Blind Hashing

The old hash sums character codes without caring about order:

"abc" → 97 + 98 + 99 = 294
"bca" → 98 + 99 + 97 = 294  ← same hash! collision
"cab" → 99 + 97 + 98 = 294  ← same hash! collision

In the pnpm case, package paths contain hex suffixes like:

node_modules/.pnpm/package-a_DEADBEEF/preview.js
node_modules/.pnpm/package-b_BEEFABCD/preview.js

These paths are anagrams of each other (same hex characters, different order) → same hash → preview_19913 declared twice → Vite build error.

The Fix: djb2 — Position Matters

djb2 uses the recurrence: acc = acc * 33 + charCode

Starting from seed 5381, each character multiplies the accumulator before adding the next character code. This means the same characters in different positions produce different outputs.

Step-by-step for "ab":

Step Operation Value
init acc = 5381 5381
'a' (97) acc = 5381 * 33 + 97 177,590
'b' (98) acc = 177590 * 33 + 98 5,860,568

Step-by-step for "ba":

Step Operation Value
init acc = 5381 5381
'b' (98) acc = 5381 * 33 + 98 177,591
'a' (97) acc = 177591 * 33 + 97 5,860,600

"ab" → 5,860,568 vs "ba" → 5,860,600 — no collision.

Why * 33 and seed 5381?
These are empirically chosen constants. 33 = 32 + 1 = 2⁵ + 1, which is why the code uses (acc << 5) + acc (a fast bit-shift version of acc * 33).

(acc << 5) + acc  ===  acc * 32 + acc  ===  acc * 33

Applied to the Real pnpm Case:

path1 = ".../package-a_DEADBEEF/preview.js"
path2 = ".../package-b_BEEFABCD/preview.js"

Old hash: both paths contain the same hex characters → same sum → collision.

djb2: D appearing at position 20 vs position 24 contributes completely different amounts to the final accumulator → different hashes → distinct variable names like preview_2847361 and preview_9134822.

@storybook-app-bot
Copy link
Copy Markdown

storybook-app-bot Bot commented Mar 25, 2026

Package Benchmarks

Commit: f4adf64, ran on 25 March 2026 at 18:39:48 UTC

No significant changes detected, all good. 👏

@valentinpalkovic valentinpalkovic added the patch:yes Bugfix & documentation PR that need to be picked to main branch label Mar 25, 2026
@valentinpalkovic valentinpalkovic changed the title Fix: Use djb2 hash to prevent variable name collisions in builder-vite Builder-Vite: Use djb2 hash to prevent variable name collisions in builder-vite Mar 25, 2026
@valentinpalkovic valentinpalkovic merged commit 1c38a36 into storybookjs:next Mar 25, 2026
120 of 121 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Core Team Projects Mar 25, 2026
valentinpalkovic added a commit that referenced this pull request Apr 2, 2026
…llision

Builder-Vite: Use djb2 hash to prevent variable name collisions in builder-vite
(cherry picked from commit 1c38a36)
@github-actions github-actions Bot mentioned this pull request Apr 2, 2026
19 tasks
@github-actions github-actions Bot added the patch:done Patch/release PRs already cherry-picked to main/release branch label Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug ci:normal core patch:done Patch/release PRs already cherry-picked to main/release branch patch:yes Bugfix & documentation PR that need to be picked to main branch

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

[Bug]: builder-vite hash collision causes duplicate variable declaration with pnpm

3 participants