filesize.js is a lightweight, high-performance file size utility that converts bytes to human-readable strings. It supports multiple unit standards (SI, IEC, JEDEC), localization, and various output formats.
Key Facts:
- Single source file:
src/filesize.js(285 lines) - Helper functions:
src/helpers.js(215 lines) - Constants:
src/constants.js(81 lines) - Zero dependencies: Uses only native JavaScript APIs
- 100% test coverage: 149 tests passing
- JSDoc: Use JSDoc standard for all functions and classes
- Naming:
- Functions: camelCase (
handleZeroValue,applyPrecisionHandling) - Constants: UPPER_SNAKE_CASE (
IEC,JEDEC,BINARY_POWERS)
- Functions: camelCase (
- Testing:
- Unit tests in
tests/unit/usingnode:test+ node:assert - Use
describe()andit()fromnode:test
- Unit tests in
- Linting: oxlint (fast Rust-based linter)
- Formatting: oxfmt (fast Rust-based formatter)
- Principles: DRY, KISS, YAGNI, SOLID
- Security: OWASP best practices
// Function with JSDoc
/**
* Description
* @param {type} param - Description
* @returns {type} Description
*/
export function functionName (param) {
// Implementation
}
// Constants
export const CONSTANT_NAME = "value";
// Imports: group by source, alphabetize
import {
ARRAY,
BIT,
BYTE
} from "./constants.js";
import {
helperFunction
} from "./helpers.js";filesize.js/
├── src/
│ ├── filesize.js # Main implementation (filesize + partial)
│ ├── helpers.js # Helper functions (5 exported functions)
│ └── constants.js # Constants, symbols, lookup tables
├── tests/
│ └── unit/
│ └── filesize-helpers.test.js # Helper function tests
│ └── filesize.test.js # Main function tests
├── dist/ # Built distributions (generated)
├── types/ # TypeScript definitions
└── docs/ # Documentation
Input → Validation → Standard Normalization → Exponent Calculation
→ Value Conversion → Formatting → Output Generation
filesize(arg, options): Main conversion functionpartial(options): Creates pre-configured functionhandleZeroValue(): Special handling for zero inputcalculateOptimizedValue(): Core conversion logicapplyPrecisionHandling(): Precision + scientific notation fixapplyNumberFormatting(): Locale, separator, padding
- SI (default): Base 10, uses JEDEC symbols (kB, MB, GB)
- IEC: Base 1024, binary prefixes (KiB, MiB, GiB)
- JEDEC: Base 1024, uses traditional symbols (KB, MB, GB)
- Add constants to
src/constants.jsif needed - Implement logic in
src/filesize.jsorsrc/helpers.js - Add JSDoc documentation
- Write unit tests in
tests/unit/ - Write integration tests in
tests/integration/ - Run
npm testto verify 100% coverage maintained - Run
npm run buildto update distributions
- Write a failing test first
- Implement the fix
- Verify test passes
- Run full test suite
- Check for regressions
Important: partial() uses destructuring to freeze option values for immutability:
export function partial({
bits = false,
pad = false,
base = -1,
round = 2,
// ... other options
} = {}) {
return (arg) =>
filesize(arg, {
bits,
pad,
base,
round,
// ... same options
});
}- Destructuring extracts and freezes primitive values at creation time
- Prevents mutations to original options object from affecting created formatters
- Simpler than deep cloning while maintaining immutability for all option types
| Option | Type | Default | Description |
|---|---|---|---|
bits |
boolean | false |
Calculate bits instead of bytes |
pad |
boolean | false |
Pad decimal places |
base |
number | -1 |
Number base (2, 10, or -1 for auto) |
round |
number | 2 |
Decimal places to round |
locale |
string|boolean | "" |
Locale for formatting |
separator |
string | "" |
Custom decimal separator |
spacer |
string | " " |
Value-unit separator |
symbols |
Object | {} |
Custom unit symbols |
standard |
string | "" |
Unit standard (si, iec, jedec) |
output |
string | "string" |
Output format (string, array, object, exponent) |
fullform |
boolean | false |
Use full unit names |
fullforms |
Array | [] |
Custom full unit names |
exponent |
number | -1 |
Force specific exponent |
roundingMethod |
string | "round" |
Math.round, floor, or ceil |
precision |
number | 0 |
Significant digits |
- string:
"1.5 KB"(default) - array:
[1.5, "KB"] - object:
{value: 1.5, symbol: "KB", exponent: 1, unit: "KB"} - exponent:
1
npm test # Full test suite (lint + node:test)
npm run test:watch # Live test watchingimport assert from 'node:assert';
import { describe, it } from 'node:test';
import { filesize } from '../../src/filesize.js';
describe('Feature', () => {
it('should do something', () => {
const result = filesize(1024);
assert.strictEqual(result, "1.02 kB");
});
});- 100% statement, branch, function, and line coverage required
- No uncovered lines allowed
- Coverage reported by Node's built-in test runner with
--experimental-test-coverage
npm run build # Build all distributions
npm run dev # Development mode with live reload
npm run build:watch # Watch mode
npm run build:analyze # Bundle size analysisdist/filesize.cjs- CommonJSdist/filesize.js- ES Moduledist/filesize.min.js- Minified ES Moduledist/filesize.umd.js- UMD (browser)dist/filesize.umd.min.js- Minified UMD
- Basic conversions:
filesize(1024) - Large numbers:
filesize(1073741824) - Standard output formats
- Locale formatting:
filesize(1024, {locale: "en-US"})
- Cache
partial()formatters for reuse - Avoid locale formatting in performance-critical code
- Use
objectoutput for fastest structured data access
import { partial } from 'filesize';
const formatBinary = partial({base: 2, standard: "iec"});
const formatBits = partial({bits: true});
const formatPrecise = partial({round: 3, pad: true});try {
filesize("invalid");
} catch (error) {
// TypeError: "Invalid number"
}
try {
partial({ exponent: NaN }); // Works - NaN is preserved via destructuring
} catch (error) {
// No error - destructuring handles all primitive values
}When using pad: true with negative numbers, the decimal separator detection must skip the minus sign:
// Correct: slice(1) to skip minus sign
const x = separator || (resultStr.slice(1).match(/(\D)/g) || []).pop() || PERIOD;Without this, -1.00 would split on - instead of ., producing incorrect output like -10 kB instead of -1.00 kB.
filesize(BigInt(1024)); // "1.02 kB"
filesize(BigInt("10000000000000000000")); // Works with huge numbersWhen checking if the exponent should auto-calculate, use a named variable for clarity and coverage:
const autoExponent = exponent === -1 || isNaN(exponent);
if (result[0] === ceil && e < 8 && autoExponent) {
// Auto-increment logic
}This pattern is used in:
filesize.js: rounding-based auto-incrementfilesize.js: precision handlinghelpers.js:calculateOptimizedValue()bits auto-incrementhelpers.js:applyPrecisionHandling()scientific notation fix
When exponent is explicitly set (not -1 or NaN):
- Bits auto-increment is disabled in
calculateOptimizedValue() - Scientific notation normalization is disabled in
applyPrecisionHandling() - Rounding-based auto-increment is disabled in
filesize()
Example:
filesize(1024, { exponent: 0, bits: true }); // "8192 bit" (not auto-incremented to kbit)
filesize(1024, { exponent: 0 }); // "1024 B" (not auto-incremented to kB)The library follows OWASP best practices and is secure for production use:
Secure Patterns:
- No
eval,Functionconstructor, or command injection - No prototype pollution (read-only symbol access, deep cloning in
partial()) - No XSS (returns plain strings only, no HTML/JS generation)
- No SSRF (no network requests)
- No ReDoS (simple regex, no catastrophic backtracking)
- Input validation on all user-provided values
Security Considerations:
- The
symbolsoption allows user-controlled objects but only reads from them (safe) - On Node.js <17,
partial()uses JSON cloning which cannot serializeNaN/Infinity- throws clear error instead of silent data loss - The
symbolsoption allows user-controlled objects but only reads from them (safe)
See docs/TECHNICAL_DOCUMENTATION.md for full security details.
The ensureNewline() plugin in rollup.config.js uses generateBundle() (not renderChunk()) to add trailing newlines. This preserves sourcemaps in minified builds by modifying the bundle after sourcemap generation.
/**
* Function description
* @param {type} paramName - Parameter description
* @param {type} [paramName=default] - Optional parameter
* @returns {type} Return description
* @throws {ErrorType} When condition occurs
* @example
* // Example usage
* functionName(arg); // "result"
*/- Add/modify function parameters
- Change default values
- Add new features
- Update examples
- Create feature branch:
git checkout -b feature/name - Make changes
- Run tests:
npm test - Build:
npm run build - Commit:
git commit -m "type: description" - Push:
git push origin feature/name
feat: New featurefix: Bug fixdocs: Documentationrefactor: Code restructuringbuild: Build system changestest: Test additions/fixes
Run npm test before committing. The build process doesn't run tests automatically.
Check the c8 report for uncovered lines. Add tests for missing branches.
Run npm run lint:fix to auto-fix common issues.
Run npm run format:fix to format code with oxfmt.
Ensure output files end with newlines (configured in rollup.config.js with ensureNewline() plugin).
Files to modify:
src/filesize.js- Main logic (95% of changes)src/helpers.js- Helper functionssrc/constants.js- Constants/lookup tables
Commands:
npm test- Run everythingnpm run build- Build distributionsnpm run lint- Check code stylenpm run format:fix- Format code
Key constraints:
- No external dependencies
- 100% test coverage required
- JSDoc on all exported functions
- ES Modules only (no CommonJS in src/)