Add Vite Build System with Content Hashing for Infinite Caching #233
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Add Vite Build System with Content Hashing for Infinite Caching
🎯 Overview
This PR adds a Vite-based build system that enables infinite caching of workflow templates by using content-based hashing in filenames, while requiring zero changes to any consumers (frontend, ComfyUI server, or Cloud server).
Build Time: 0.37s for 403 files
Size Reduction: 1.5% (85.95 MB → 84.69 MB) from JSON minification
Files Changed: Only new files added, no existing files modified
Breaking Changes: None - fully backward compatible
🔥 Problems Solved
1. Cache Management Complexity
Before: Special middleware needed to serve different cache headers for index*.json (5 min TTL) vs other files (1 hour TTL)
After: Single cache rule for all files
2. Performance Issues
Before: Users must re-download templates every 5 minutes (index.json TTL)
After: Files cached infinitely, only new versions trigger downloads
3. PyPI Package Size Limit
Current: 86.3 MB / 100 MB (86% of limit)
After: 84.69 MB / 100 MB (85% of limit) with JSON minification
Future: Could reach ~65 MB with WebP recompression (35% reduction)
4. No Minification
Before: JSON files shipped with whitespace (~38% overhead)
After: JSON minified (10,829 bytes → 6,307 bytes per file on average)
🏗️ Solution Architecture
High-Level Strategy
Hash the JSON, rename the assets to match
namefield in ALL index*.json files to include the hashExample Transformation
Before (source:
templates/):After (output:
dist/):Index Update (all
index*.jsonfiles):{ "name": "01_qwen_t2i_subgraphed-214b7748", // Hash included in name "mediaType": "image", "mediaSubtype": "webp" }Why This Works With No Consumer Changes
Frontend code constructs URLs as:
Since we update
template.nameto include the hash, these URLs resolve correctly:/templates/01_qwen_t2i_subgraphed-214b7748.json✅/templates/01_qwen_t2i_subgraphed-214b7748-1.webp✅No code changes needed - just different data in index.json!
📝 Implementation Details
Files Added
vite.config.js- Vite build configuration with 2 custom pluginsscripts/build.js- Build orchestration with size measurementpackage.json- NPM dependencies (onlyviteas devDependency)package-lock.json- LockfileFiles Modified
None! All existing files remain untouched.
templates/is the source,dist/is the output.Vite Configuration Deep Dive
Plugin 1:
hash-templates-and-rename-assetsStep 1: Template Grouping
Pattern matching logic:
basename.json→ basename is template namebasename-N.ext→ extract basename via regex/^(.+)-(\d+)$/.gitignoreandindex*.jsonStep 2: JSON Hashing
Step 3: Asset Renaming
Plugin 2:
update-index-filesRuns in
closeBundlehook (after all files emitted)Step 1: Build Name Mapping
Step 2: Update All Index Files
Critical: ALL index files must be updated, not just index.json!
1. Assets Renamed Based on JSON Hash, Not Their Own Content
Scenario:
Result:
default-OLD_HASH.json→default-NEW_HASH.jsondefault-OLD_HASH-1.webp→default-NEW_HASH-1.webpImpact:
Why Acceptable:
Why Not Hash Each Asset Separately:
${name}-1.webpnamevalue per template2. Translation Files Must All Have Matching Names
Critical: The
namefield MUST be identical across all index*.json files:Why: All language versions reference the same workflow file on disk.
Handled By: The
update-index-filesplugin updates ALL index files automatically.Failure Mode: If names don't match, some languages would 404 when loading workflows.
3. Files With Unusual Naming Patterns
Standard Pattern:
basename-N.extwhere N is a digitdefault-1.webp✅default-2.webp✅Edge Cases Found (warnings in validation):
api_wan_text_to_image .json- trailing space in filename!ByteDance-Seedance_00003_.json- trailing underscorevideo_wan2_2_14B_t2v (2).json- parens in filenameHandled By: Vite plugin skips files it can't parse and logs a warning.
Action: These files should be renamed in source to follow standard patterns.
4. Build Must Happen Before Publishing
Critical Order:
templates/npm run buildto generatedist/templates/anddist/(or justtemplates/if dist/ is gitignored)dist/contentsFailure Mode: If you publish
templates/instead ofdist/:5. Hash Collisions (Theoretical)
Hash Length: 8 hex characters = 4,294,967,296 possible values
Collision Probability (Birthday paradox):
Impact If Collision Occurs:
Mitigation:
Recommendation: Monitor build output for duplicate filenames, increase hash length if we ever hit 1,000+ templates.
6. Index Schema Validation
Templates must match schema:
templates/index.schema.jsonCritical Fields:
name- String, used to construct URLsmediaType- String, determines file typemediaSubtype- String, file extension (webp, mp3, mp4)After Hashing:
namefield will contain hash suffix, but still validates as string.Validation Script:
scripts/validate_templates.pyruns automatically, still works after hashing.7. Git Diff Size
Each build changes:
Total: ~400 files changed per build
Impact:
Mitigation Options:
dist/to.gitignore, only track sourcetemplates/Recommendation: Option 2 (build in CI) is cleanest.
8. Local Development Workflow
If
dist/is gitignored:Important: Document this in README for new contributors!
9. Stale Dist Directory
Scenario:
templates/npm run builddist/Cause: Vite config has
emptyOutDir: true, but if it fails...Current Protection:
emptyOutDir: truein vite.config.jsExtra Safety: Could add to build.js:
10. Special Files Handling
Files NOT hashed:
index*.json- Updated with hashes but not renamedindex.schema.json- Copied as-is.gitignore- SkippedREADME.md- If it exists, skippedWhy: These are metadata files, not template content.
index.json Naming*: Kept as-is so frontend knows where to start:
If we renamed index.json to
index-HASH.json, frontend wouldn't know which file to request!🧪 Testing Instructions
Prerequisites
Test 1: Build Success
Expected Output:
Build should complete in < 1 second
Test 2: File Structure Validation
Expected Output:
Test 3: Manual Spot Check
Test 4: Verify Minification
Test 5: Content Integrity
Test 6: Hash Consistency
Test 7: Hash Changes With Content
🚀 Deployment Checklist
Step 1: Update CI/CD Workflow
File:
.github/workflows/publish.ymlChanges Needed:
Critical: Use
dist/*nottemplates/*!Step 2: Test in Staging
curl https://staging.api/templates/index.jsoncurl https://staging.api/templates/default-HASH.jsoncurl https://staging.api/templates/default-HASH-1.webpcurl -I https://staging.api/templates/default-HASH.jsonCache-Control: public, immutable, max-age=31536000Step 3: Update Cache Middleware (Optional)
If you have special cache logic, simplify it:
Before:
After:
Even Better (set at CDN/proxy level):
Step 4: Monitor First Deployment
Metrics to Watch:
Red Flags:
Step 5: Gradual Rollout (Recommended)
Option A: Canary Deployment
Option B: Shadow Deployment
Step 6: Verify Cache Behavior
First Load (cold cache):
Second Load (warm cache):
After Template Update:
🔙 Rollback Procedure
If Issues Found in Production
Immediate Rollback (5 minutes):
Revert publish.yml changes:
Trigger new deployment:
Wait for deployment (usually 5-10 minutes)
Verify rollback:
Why Safe:
templates/directory unchangedPartial Rollback (Serve Both)
If you want to rollback gradually:
Copy both to package:
Result: Both versions served
/templates/default.json✅/templates/default-HASH.json✅Allows:
Cleanup Later:
Rollback Cache Headers
If you updated cache middleware:
Revert to old headers:
Impact:
🔮 Future Work (Optional Enhancements)
1. WebP Recompression (High Value)
Goal: Reduce package size by ~28% (another 20 MB saved)
Implementation:
Expected Results:
Tradeoff: Build time increases to ~2-3 seconds (still acceptable)
2. Increase Hash Length
Current: 8 hex chars = 4.3 billion combinations
Proposed: 12 hex chars = 281 trillion combinations
When: If we ever reach >1,000 templates (collision risk increases)
Change:
3. Collision Detection
Add to build.js:
4. Incremental Builds
Current: Rebuilds all 403 files every time
Proposed: Only rebuild changed files
Implementation:
Benefit: Faster rebuilds during development (0.37s → 0.1s)
5. Pre-commit Hook
Auto-build before commit:
Benefit: Never forget to build before committing
Tradeoff: Commits take longer (~1 second)
6. Build Validation
Add to build.js:
7. Bundle Analyzer
See what's taking up space:
Benefit: Identify large files to optimize
8. Git LFS for Media Files
If dist/ is committed: Use Git LFS for large media files
Benefit: Keeps git repo small even with committed dist/
9. CDN Deployment Automation
Deploy dist/ directly to CDN:
Benefit: Templates served from CDN, not bundled in package
10. Source Maps for Templates
Add metadata about source:
Benefit: Debugging, tracing issues back to source file
❓ FAQ
Q: Do I need to change any frontend code?
A: No! The frontend already constructs URLs as
${template.name}.json. We just changed whattemplate.namecontains (now includes hash).Q: What if users have cached old index.json?
A: The old index.json has a 5-minute TTL, so it expires quickly. Once refreshed, they'll get the new index with hashed names.
Q: Can I mix hashed and non-hashed files?
A: Yes! You can serve both. The frontend will use whatever
nameis in index.json, so both work simultaneously.Q: What happens to old cached files?
A: They stay cached until TTL expires or user clears cache. They won't be accessed anymore (new index.json points to new hashed names).
Q: How do I test this locally?
A: Run
npm run build, then point your local ComfyUI todist/instead oftemplates/. No other changes needed.Q: What if build fails in CI?
A: CI will exit with error code 1, deployment won't proceed. Same as any build failure.
Q: Do translation files need special handling?
A: No, the build script automatically updates ALL
index*.jsonfiles (10 languages). Just ensure they all exist intemplates/.Q: Can I still manually edit templates?
A: Yes! Edit files in
templates/, then runnpm run build. Never editdist/directly (it gets overwritten).Q: How do I add a new template?
A: Same as before:
templates/templates/templates/index.jsonnpm run buildtemplates/anddist/if tracking dist/)Q: What if I delete a template?
A: Delete from
templates/, remove from index.json, runnpm run build. The build emptiesdist/first so deleted templates won't carry over.Q: How big can hash collisions be?
A: With 8-char hashes and 176 templates, collision probability is 0.00000036%. Practically impossible. Could increase hash length if worried.
Q: Does this work with ComfyUI's template browser?
A: Yes! The browser loads
index.jsonand constructs URLs from thenamefield. No code changes needed.Q: Can I preview changes before deploying?
A: Yes:
Q: What if Vite breaks in the future?
A: Vite is stable (v5.x), but if needed:
Q: Do I need Node.js in production?
A: No! Node.js only needed for building. Production just serves static files from
dist/.Q: How often should I rebuild?
A: Only when templates change. Build in CI on every commit, or manually before release.
Q: Can this work with incremental updates?
A: Yes! Only changed templates get new hashes. Unchanged templates keep same hash and filename, so caches remain valid.
Q: What about browser cache limits?
A: Browsers have cache limits (~50-100MB typically), but:
Q: Does this affect ComfyUI Cloud?
A: No changes needed! Cloud serves from same package, just with different cache headers.
Q: Can I A/B test hashed vs non-hashed?
A: Yes! Serve both versions, use different index files:
/templates/index.json- Non-hashed names/templates/index-hashed.json- Hashed namesQ: What if I want to revert a specific template?
A:
git checkout HEAD~1 templates/old-template.jsonnpm run buildQ: How do I debug mismatched hashes?
A:
📊 Metrics to Track Post-Deployment
Success Metrics
Cache Hit Rate
Bandwidth Usage
Template Load Time
Package Size
ls -lh dist/Build Time
Warning Metrics
404 Rate on /templates/*
Frontend Errors
CI Build Failures
📚 Additional Resources
✅ Pre-Merge Checklist
npm run build && python3 test_dist_structure.py)🎓 Learning from This Implementation
Key Insights
What Almost Went Wrong
What Worked Well
Ready to merge when you are! All edge cases documented for when you return in a few months. 🚀