Skip to content

Commit cf4229d

Browse files
committed
fix: buffer reading in the browser environment
rewrite typescript to mjs with jsdocs to make bundle smaller add missing engines compatibility info to package.json
1 parent 8d33939 commit cf4229d

File tree

14 files changed

+126
-87
lines changed

14 files changed

+126
-87
lines changed

README.md

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</a>
1414
</p>
1515

16-
<h1 align="center">is-animated - checks if the image is animated 🎞</h1>
16+
<h1 align="center">is-animated - check if the image is animated 🎞</h1>
1717

1818
<p align="center">
1919
<a href="#quick-start">Getting Started</a>
@@ -31,8 +31,8 @@
3131
<i>A simple library for detecting animated images.
3232
<br>Works under Node and Browser environments!
3333
<br>Performant & with small bundle size
34-
<br>Supports GIF, APNG and WebP
35-
<br>Written completely in <a href="https://www.typescriptlang.org">typescript</a>
34+
<br>Supports GIF, PNG, APNG and WebP
35+
<br>Fully typed in <a href="https://www.typescriptlang.org">Typescript</a>
3636
<br>Published under <a href="https://opensource.org/licenses/MIT" target="_blank">MIT</a> license</i>
3737
<br>
3838
<br>
@@ -57,27 +57,34 @@ pnpm add @frsource/is-animated
5757
```
5858

5959
```ts
60+
import isAnimated from '@frsource/is-animated';
61+
6062
const input = document.querySelector('input[type="file"]');
6163

6264
input.addEventListener('change', async function () {
6365
const arrayBuffer = await this.files[0].arrayBuffer();
64-
const answer = isAnimated(arrayBuffer) ? 'Yes' : 'No';
65-
alert(`Is "${this.files[0].name}" animated? ${answer}.`);
66+
const answer = isAnimated(arrayBuffer) ? 'IS' : 'IS NOT';
67+
alert(`File "${this.files[0].name}" ${answer} animated.`);
6668
});
6769
```
6870

71+
>If you prefer, you can import this library using unpkg:
72+
>`<script defer src="https://unpkg.com/@frsource/is-animated"></script>`
73+
6974
### Usage in Node.js
7075

7176
```ts
7277
import { readFileSync } from 'fs';
7378
import isAnimated from '@frsource/is-animated';
7479

7580
readFileSync('my-test-file.png', (err, buffer) => {
76-
const answer = isAnimated(buffer) ? 'Yes' : 'No';
77-
console.log(`Is "my-test-file.png" animated? ${answer}.`);
81+
const answer = isAnimated(buffer) ? 'IS' : 'IS NOT';
82+
console.log(`File "my-test-file.png" ${answer} animated.`);
7883
});
7984
```
8085

86+
## Demo
87+
8188
For a working example, check out [our demo](https://www.frsource.org/is-animated#demo).
8289

8390
## Questions

eslint.config.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import globals from 'globals';
22
import { typescript } from '@frsource/eslint-config';
33

4-
/** @type {import("eslint").Linter.FlatConfig[]} */
4+
/** @type {import("eslint").Linter.Config[]} */
55
export default [
66
...typescript,
7-
{ ignores: ['**/dist', '**/coverage', '**/node_modules'] },
7+
{ ignores: ['**/dist', '**/coverage', '**/node_modules', '**/docs'] },
88
{ languageOptions: { globals: globals.node } },
99
{
1010
files: ['scripts/index.tpl.js'],

package.json

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,29 +3,26 @@
33
"version": "1.0.2",
44
"description": "Detects animated images in browser & node. Supports GIT, APNG & WebP",
55
"sideEffects": false,
6-
"source": "src/index.ts",
76
"exports": {
8-
"require": "./dist/index.js",
9-
"types": "./dist/index.d.ts",
10-
"default": "./dist/index.modern.mjs"
7+
"default": "./src/index.mjs"
118
},
12-
"main": "./dist/index.js",
13-
"module": "./dist/index.modern.mjs",
14-
"unpkg": "./dist/index.umd.js",
15-
"types": "./dist/index.d.ts",
9+
"main": "./src/index.mjs",
10+
"module": "./src/index.mjs",
11+
"unpkg": "./dist/is-animated.umd.js",
1612
"scripts": {
17-
"start": "pnpm clean && concurrently \"microbundle watch\" \"node ./scripts/build-docs.mjs --watch\" \"serve docs\"",
18-
"build": "pnpm clean && microbundle --compress && node ./scripts/build-docs.mjs",
13+
"typecheck": "tsc --project tsconfig.json",
14+
"start": "pnpm clean && concurrently \"microbundle watch -i ./src/index.mjs -o docs -f umd\" \"node ./scripts/build-docs.mjs --watch\" \"serve docs\"",
15+
"build": "pnpm clean && microbundle -i ./src/index.mjs -o dist -f umd && microbundle -i ./src/index.mjs -o docs -f umd && node ./scripts/build-docs.mjs",
1916
"lint": "eslint . --max-warnings 0 && prettier --check .",
2017
"fix": "eslint . --fix && prettier --write .",
2118
"release": "semantic-release",
2219
"release:ci": "pnpm release --yes",
2320
"release:test": "pnpm release --no-git-tag-version --no-push --skip-npm",
24-
"clean": "pnpm rimraf \"dist/**/*\"",
21+
"clean": "pnpm rimraf dist docs",
2522
"test": "pnpm vitest",
2623
"coverage": "pnpm vitest --coverage"
2724
},
28-
"homepage": "https://github.com/FRSOURCE/is-animated/",
25+
"homepage": "https://www.frsource.org/is-animated",
2926
"bugs": {
3027
"url": "https://github.com/FRSOURCE/is-animated/issues"
3128
},
@@ -78,7 +75,12 @@
7875
"packageManager": "pnpm@8.15.8",
7976
"files": [
8077
"dist",
78+
"src/*.mjs",
79+
"src/*/*.mjs",
8180
"package.json"
8281
],
82+
"engines": {
83+
"node": ">=20.0.0"
84+
},
8385
"funding": "https://buymeacoffee.com/frsource"
8486
}

scripts/index.tpl.html

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,14 @@ <h4>In browser</h4>
123123

124124
input.addEventListener('change', async function () {
125125
const arrayBuffer = await this.files[0].arrayBuffer();
126-
const answer = isAnimated(arrayBuffer) ? 'Yes' : 'No';
127-
alert(`Is "${this.files[0].name}" animated? ${answer}.`);
126+
const answer = isAnimated(arrayBuffer) ? 'IS' : 'IS NOT';
127+
alert(`File "${this.files[0].name}" ${answer} animated.`);
128128
});</code></pre>
129+
130+
<blockquote>
131+
<p>If you prefer, you can import this library using unpkg:</p>
132+
<pre><code data-lang="html">&lt;script defer src="https://unpkg.com/@frsource/is-animated"&gt;&lt;/script&gt;</code></pre>
133+
</blockquote>
129134
</section>
130135
<section>
131136
<h4>In Node.js</h4>
@@ -134,8 +139,8 @@ <h4>In Node.js</h4>
134139
import isAnimated from '@frsource/is-animated';
135140

136141
const buffer = readFileSync('my-test-file.png');
137-
const answer = isAnimated(buffer) ? 'Yes' : 'No';
138-
console.log(`Is "my-test-file.png" animated? ${answer}.`);</code></pre>
142+
const answer = isAnimated(buffer) ? 'IS' : 'IS NOT';
143+
console.log(`File "my-test-file.png" ${answer} animated.`);</code></pre>
139144
</section>
140145
</main>
141146
<hr />
@@ -150,8 +155,7 @@ <h4>In Node.js</h4>
150155
>
151156
</small>
152157
</footer>
153-
<script defer src="https://unpkg.com/@frsource/is-animated"></script>
154-
<script defer src="../dist/index.umd.js"></script>
155-
<script defer src="./index.js"></script>
158+
<script defer src="is-animated.umd.js"></script>
159+
<script defer src="index.js"></script>
156160
</body>
157161
</html>

scripts/index.tpl.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ const input = document.querySelector('input[type="file"]');
22

33
input.addEventListener('change', async function () {
44
const arrayBuffer = await this.files[0].arrayBuffer();
5-
const answer = isAnimated(arrayBuffer) ? 'Yes' : 'No';
6-
alert(`Is "${this.files[0].name}" animated? ${answer}.`);
5+
const answer = isAnimated(arrayBuffer) ? 'IS' : 'IS NOT';
6+
alert(`File "${this.files[0].name}" ${answer} animated.`);
77
});

src/index.ts renamed to src/index.mjs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
import * as gif from './utils/gif';
2-
import * as webp from './utils/webp';
3-
import * as png from './utils/png';
4-
import type { NodeBufferOrArrayBuffer, StandardisedBuffer } from './types';
1+
import * as gif from './utils/gif.mjs';
2+
import * as webp from './utils/webp.mjs';
3+
import * as png from './utils/png.mjs';
54

6-
const toStandardisedBuffer = (
7-
buffer: NodeBufferOrArrayBuffer,
8-
): StandardisedBuffer => {
5+
/** @typedef {import('./types.mjs').NodeBufferOrArrayBuffer} NodeBufferOrArrayBuffer */
6+
/** @typedef {import('./types.mjs').StandardisedBuffer} StandardisedBuffer */
7+
8+
/**
9+
* Converts Node.js/Web buffer into an unified one with a standardized API
10+
* @param {NodeBufferOrArrayBuffer} buffer
11+
* @returns {StandardisedBuffer}
12+
*/
13+
const toStandardisedBuffer = (buffer) => {
914
if ('subarray' in buffer) {
1015
return {
1116
read: (begin, end, { encoding = 'utf8' } = {}) =>
@@ -17,18 +22,21 @@ const toStandardisedBuffer = (
1722
}
1823

1924
const decoder = new TextDecoder();
20-
const array = new Uint8Array(buffer as ArrayBuffer);
25+
const array = new Uint8Array(/** @type {ArrayBuffer} */ (buffer));
2126
return {
2227
read: (begin, end, { encoding = 'utf8' } = {}) => {
2328
if (encoding === 'utf8')
24-
return decoder.decode(buffer.slice(begin, end) as ArrayBuffer);
25-
return [...array.slice(begin, end)]
29+
return decoder.decode(
30+
/** @type {ArrayBuffer} */ (buffer.slice(begin, end)),
31+
);
32+
return Array.from(array.slice(begin, end))
2633
.map((x) => x.toString(16).padStart(2, '0'))
2734
.join('');
2835
},
2936
readUInt32BE: (offset) =>
3037
new DataView(
31-
buffer as ArrayBuffer,
38+
/** @type {ArrayBuffer} */
39+
(buffer),
3240
array.byteOffset,
3341
array.byteLength,
3442
).getUint32(offset),
@@ -39,8 +47,9 @@ const toStandardisedBuffer = (
3947

4048
/**
4149
* Checks if buffer contains animated image
50+
* @param {NodeBufferOrArrayBuffer} buffer
4251
*/
43-
export default function isAnimated(buffer: NodeBufferOrArrayBuffer) {
52+
export default function isAnimated(buffer) {
4453
const standardisedBuffer = toStandardisedBuffer(buffer);
4554
if (gif.isGIF(standardisedBuffer)) return gif.isAnimated(standardisedBuffer);
4655
if (png.isPNG(standardisedBuffer)) return png.isAnimated(standardisedBuffer);

src/index.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { promises as fs } from 'node:fs';
44
import { join, extname, basename } from 'node:path';
55
import { File } from 'happy-dom';
66
import { it, describe } from 'vitest';
7-
import isAnimated from './index';
7+
import isAnimated from './index.mjs';
88

99
const types = ['gif', 'png', 'webp'];
1010
const environmentTypes = ['browser', 'nodejs'];

src/types.mjs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @typedef {{ slice(begin: number, end?: number): unknown }} ArrayBufferLike
3+
* @typedef {{
4+
* subarray(begin: number, end?: number): NodeBufferLike;
5+
* readUInt32BE(offset: number): number;
6+
* toString(encoding: 'hex' | 'utf8'): string;
7+
* [index: number]: number;
8+
* readonly length: number;
9+
* }} NodeBufferLike
10+
* @typedef {ArrayBufferLike | NodeBufferLike} NodeBufferOrArrayBuffer
11+
* @typedef {{
12+
* read(
13+
* begin: number,
14+
* end?: number,
15+
* opts?: {
16+
* encoding?: 'utf8' | 'hex';
17+
* },
18+
* ): string;
19+
* readUInt32BE(offset: number): number;
20+
* at(index: number): number;
21+
* length: number;
22+
* }} StandardisedBuffer
23+
*/

src/types.ts

Lines changed: 0 additions & 24 deletions
This file was deleted.

src/utils/gif.ts renamed to src/utils/gif.mjs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
import type { StandardisedBuffer } from '../types';
1+
/** @typedef {import('../types.mjs').StandardisedBuffer} StandardisedBuffer */
22

33
/**
44
* Returns total length of data blocks sequence
5+
* @param {StandardisedBuffer} buffer
6+
* @param {number} offset
57
*/
6-
function getDataBlocksLength(buffer: StandardisedBuffer, offset: number) {
8+
function getDataBlocksLength(buffer, offset) {
79
let length = 0;
810

911
while (buffer.at(offset + length)) {
@@ -15,14 +17,15 @@ function getDataBlocksLength(buffer: StandardisedBuffer, offset: number) {
1517

1618
/**
1719
* Checks if buffer contains GIF image
20+
* @param {StandardisedBuffer} buffer
1821
*/
19-
export const isGIF = (buffer: StandardisedBuffer) =>
20-
buffer.read(0, 3) === 'GIF';
22+
export const isGIF = (buffer) => buffer.read(0, 3) === 'GIF';
2123

2224
/**
2325
* Checks if buffer contains animated GIF image
26+
* @param {StandardisedBuffer} buffer
2427
*/
25-
export const isAnimated = (buffer: StandardisedBuffer) => {
28+
export const isAnimated = (buffer) => {
2629
let hasColorTable, colorTableSize;
2730
let offset = 0;
2831
let imagesCount = 0;

0 commit comments

Comments
 (0)