fix: replace Function constructor with JSON.parse in cache reading
Fix: Replace Function constructor with JSON.parse in cache reading
Summary
This PR replaces the current approach that evaluates cache files using the Function constructor with a safer, simpler JSON-based format and reader. It provides a seamless migration path from the existing export default {...}; format to JSON, and adds comprehensive tests to cover read/write and migration scenarios.
Fixes #1266
Motivation
- Using
new Function(...)to evaluate a cache file is unnecessarily risky and harder to maintain - A plain JSON cache improves safety, tooling compatibility, and clarity
- Migration can be implemented without breaking users by auto-detecting and converting the old format
Changes Made
1. Core Implementation
Changed cache file format to JSON
- File format: Changed from
dictionary.js(withexport default {...}) todictionary.json(pure JSON) - Reading: Now uses
JSON.parse()instead ofnew Function(...) - Writing: Outputs pure JSON without JavaScript wrapper
- Parser: Updated Prettier parser from
typescripttojson
Backward-compatible migration
- Auto-detects legacy format that starts with
export default - Safely parses legacy content by stripping the wrapper and using
JSON.parse()- no code execution - Automatically re-writes cache in new JSON format after first read
- Cleans up legacy
dictionary.jsfiles after successful migration - Logs migration with optional debug flag (
process.env.DEBUG)
Error handling
- Gracefully handles corrupted JSON cache files
- Returns empty cache on parse failures with warning message
- Creates directories recursively if they don't exist
2. Updated Constants
- Changed
LCP_DICTIONARY_FILE_NAMEfrom"dictionary.js"to"dictionary.json" - Added
LCP_DICTIONARY_LEGACY_FILE_NAMEconstant for backward compatibility
3. Demo Projects
Migrated all demo dictionary files from legacy JS format to pure JSON:
- ✅
demo/adonisjs/inertia/lingo/dictionary.js→dictionary.json - ✅
demo/next-app/src/lingo/dictionary.js→dictionary.json - ✅
demo/react-router-app/app/lingo/dictionary.js→dictionary.json - ✅
demo/vite-project/src/lingo/dictionary.js→dictionary.json
4. Comprehensive Test Coverage
Added 14 tests covering all scenarios:
JSON format tests (5)
- ✅ Writing cache in JSON format
- ✅ Reading cache from JSON format
- ✅ Returning empty cache when file doesn't exist
- ✅ Ensuring dictionary file with empty JSON object
- ✅ Maintaining stable key ordering
Legacy migration tests (3)
- ✅ Migrating from legacy JS format to JSON
- ✅ Handling legacy format without 'export default' prefix
- ✅ Handling corrupted legacy cache gracefully
Multiple locales tests (2)
- ✅ Handling multiple locales in cache
- ✅ Sorting locales alphabetically in cache entries
Hash validation tests (2)
- ✅ Invalidating cached entries when hash changes
- ✅ Reusing cached entries when hash matches
Error handling tests (2)
- ✅ Handling corrupted JSON cache gracefully
- ✅ Creating directory if it doesn't exist
Files Changed
Modified (5 files)
packages/compiler/src/_const.ts- Updated cache filename constantspackages/compiler/src/lib/lcp/cache.ts- Complete refactor with JSON support and migrationpackages/compiler/src/lib/lcp/cache.spec.ts- Added 14 comprehensive testspackages/compiler/src/_utils.spec.ts- Updated tests to expect.jsonextensionpackages/compiler/src/_loader-utils.spec.ts- Updated tests to usedictionary.json
Deleted (4 files)
demo/adonisjs/inertia/lingo/dictionary.jsdemo/next-app/src/lingo/dictionary.jsdemo/react-router-app/app/lingo/dictionary.jsdemo/vite-project/src/lingo/dictionary.js
Added (4 files)
demo/adonisjs/inertia/lingo/dictionary.jsondemo/next-app/src/lingo/dictionary.jsondemo/react-router-app/app/lingo/dictionary.jsondemo/vite-project/src/lingo/dictionary.json
Testing
All tests pass successfully:
✓ src/lib/lcp/cache.spec.ts (14)
✓ LCPCache (14)
✓ JSON format (5)
✓ Legacy format migration (3)
✓ Multiple locales (2)
✓ Hash validation (2)
✓ Error handling (2)
Test Files 1 passed (1)
Tests 14 passed (14)
Migration Path for Users
Existing projects with dictionary.js:
- The cache reader automatically detects the old format
- Reads the legacy file safely (no code execution)
- On next write, saves in new JSON format
- Legacy
dictionary.jsfile is automatically deleted
New projects:
- Will use
dictionary.jsonfrom the start - No migration needed
Security Improvements
Before:
// UNSAFE - executes code from file
const obj = new Function(`return (${cache})`)();
After:
// SAFE - just parses JSON
return JSON.parse(content);
Checklist
- [x] Removed unsafe
new Function()constructor - [x] Implemented JSON-based cache with
JSON.parse() - [x] Added backward-compatible migration
- [x] Added 14 comprehensive tests (all passing)
- [x] Migrated all demo projects to new format
- [x] Updated constants and file references
- [x] Graceful error handling for corrupted files
- [x] Automatic cleanup of legacy files
Also, @manasdutta04 have you joined our discord? I'd love to go over something with you. Can you DM me there please?
Hey @sumitsaurabh927, I DM you on Discord, and yes, I’ve joined the Discord server!
@manasdutta04 there's a failing check on this PR and your branch is out of date.
Also, still waiting for the DM 🙂
Hey, I have already sent you DM over discord @sumitsaurabh927 My Discord user name: manasdutta.in
I am working on the fix
closing the PR till the discussion finishes in #1266. feel free to recreate later.