Imported from mattiasw/ExifReader (
AGENTS.md). Install upstream withnpx skills add mattiasw/ExifReader. Copyright stays with the author.
ExifReader - Agent Guide
JavaScript library that parses Exif/IPTC/XMP/ICC/MPF metadata from JPEG, TIFF, PNG, HEIC, AVIF, JPEG XL, WebP, and GIF images. Published on npm as exifreader.
Project Structure
src/- ES module source (entry:exif-reader.js)test/unit/- Mocha+Chai unit tests (*-spec.js), run as native ES modulestest/types/- TypeScript type tests forexif-reader.d.tstest/integration/- Integration tests with fixture imagestest/build/- Tests verifying the built outputdist/- Webpack UMD bundle (committed - required by Bower)bin/- Build scriptsexif-reader.d.ts- TypeScript type definitions (root level)
Tag definitions live in src/tag-names-*.js (keyed by hex tag ID). Each image format has its own *-tags.js parser. src/constants.js has feature flags for tree-shaking.
Custom Builds
Users can configure custom builds (via package.json "exifreader" key) to include/exclude specific formats and tag groups, reducing bundle size. When adding a new image format or metadata group:
- Add a
USE_<NAME>flag tosrc/constants.js - Add the module name to the
modulesarray inbin/parse-config.js - Add the source filename to the string-replace regex in
webpack.config.js(soConstants.USE_*gets replaced) - Add test entries in
test/build/custom-builds.json - Document the module in the README custom build table
Commands
| Task | Command |
|---|---|
| Lint | npm run lint |
| Unit tests | npm test |
| Type tests | npm run test:types |
| Coverage | npm run coverage |
| Build | npm run build |
| Pre-commit suite | npm run pre-commit (lint + types + coverage + build test) |
All tests (E2Es need the local server running with npm run start) |
npm run test:all |
Before Committing
- Run
npm run build- thedist/files must be committed (Bower consumes them from the repo). - The pre-commit hook (
npm run pre-commit) runs automatically via Husky and must pass.
Pushing and Pull Requests
Make changes on a topic branch, not on main.
Do not push a branch or commits, and do not open a pull request, unless asked to in the current conversation. Preparing a branch for one is fine. Nothing here should reach the remote without the person you are working with knowing about it.
Coding Style
Enforced by ESLint (eslint.config.mjs). Key rules:
- ES modules (
import/export), no CommonJS insrc/ - 4-space indent, single quotes, semicolons always
const/letonly (novar),prefer-const, strict equality (===)object-shorthand, noconsole, no TODO/FIXME comments- No curly-brace spacing:
{a, b}not{ a, b } - Arrow parens always:
(x) => x - Space before anonymous function paren, not named:
function foo()vsfunction () - kebab-case filenames, camelCase variables/functions, PascalCase constructors/class-like exports
- Place functions in the order they are used. That means the definition of a function is placed after the location from where it is called.
Every .js and .ts file in src/, bin/ and test/, and exif-reader.d.ts, starts with the MPL 2.0 license header, after the shebang line if there is one. The linter does not check it, so copy the three-line version from an existing file such as src/tags.js.
Older runtime support (src/ only)
The browserslist target includes IE10 and other older runtimes. The
build pipeline transpiles down to a low ECMAScript baseline, and a few
modern constructs balloon the output. Avoid them in src/:
- No
async/awaitkeywords. They transpile to a regenerator runtime that adds significant bundle size. Use explicit promise chains (Promise.resolve(...).then(...)) instead. - No object spread (
{...obj, key: value}) and noObject.assign. UseobjectAssign(target, ...sources)fromsrc/utils.jsinstead. Example:const next = objectAssign({}, options, {async: true});.
test/, bin/, and webpack.config.js run only in modern Node on
maintainer machines, so they may use any modern syntax.
Comments
Prefer self-explaining code (clear names, small functions) over comments. Add a comment only when the code cannot be made obvious on its own, for example a non-trivial spec reference, a workaround for a known quirk, or a subtle invariant.
When a comment is warranted, keep it short and to the point. One or two lines is usually enough.
When an exported function warrants documentation, use JSDoc (not a plain comment) to describe its contract: parameters and return shape. Keep it concise and skip restating what the signature already says.
Type Definitions
This is not a TypeScript project, but exif-reader.d.ts provides types for consumers. When adding or changing public API surface (new tags, options, return types):
- Update
exif-reader.d.ts - Update
test/types/exif-reader.tswith type-level tests (including@ts-expect-errorfor invalid usage) - Verify with
npm run test:types
Tests
Every source file has a corresponding test/unit/*-spec.js. New code needs tests. Coverage thresholds are enforced (via c8, configured in package.json).
Unit tests run as native ES modules with no transpilation. Develop on Node 24.7+ to be able to run the full suite: the integration and build test suites parse JPEG XL fixtures that need its Brotli support, and they require() the ES module source which needs at least 22.12. Relative imports in test files must carry explicit .js extensions, and named imports from src/ modules must actually exist (most src/ modules only have a default export).
There is no module-mocking library, and none should be introduced. To isolate a module from its dependencies:
- Swap properties on a shared default-export object (
Constants,TagNames, module API objects likeTagsorImageHeader, orglobalThis) withswapPropertiesfromtest-utils.js, and call the returned restore function inafterEach. - Where the dependency is a named function import (immutable binding), do not mock it. Craft real binary input with
getDataViewso the real function runs, or test one level up through the module's public API.
Use npm run test to run the unit tests and make use of describe.only and it.only for focused testing.
Mocha defaults to the quiet dot reporter locally (spec on CI), configured in .mocharc.cjs; failures always print in full. Set EXIFREADER_MOCHA_REPORTER=spec for per-test output.
TDD: red-green-refactor
Write code test-first. For each new behavior:
- Red — write the smallest failing test that captures the next bit of behavior. Run it and watch it fail for the expected reason (missing function, wrong return, etc.).
- Green — write the smallest change in
src/that makes the test pass. Resist adding anything the test doesn't demand. - Refactor — with tests green, tidy names, extract helpers, remove duplication. Re-run the suite after each change.
Cycle one behavior at a time, not one feature. A feature like "parse iloc constructionMethod 1" is many cycles (parse the idat box; resolve a single-extent cm-1 offset; handle a missing idat box; etc.), each one a red-green-refactor turn.
Changelog
When making notable changes (new features, bug fixes, breaking changes, etc.), add an entry to the Unreleased section in CHANGELOG.md. Use the Keep a Changelog format with the appropriate change type: Added, Changed, Deprecated, Removed, Fixed, or Security.
After updating, read through the whole Unreleased section again. Each change type must appear at most once, in the Keep a Changelog order above, with all its entries under that one heading. Duplicate headings (a second Security, for example) have slipped in several times.
Adding Tags
- Add hex-keyed entry in the appropriate
src/tag-names-*-ifd.js - Add description function if needed (see
src/tag-names-common.jsfor helpers) - Add unit test in corresponding
test/unit/tag-names-*-spec.js - Update
exif-reader.d.tsandtest/types/exif-reader.tsif the tag is user-facing