Skip to content

Commit

Permalink
feat: Export as ESM.
Browse files Browse the repository at this point in the history
  • Loading branch information
ShogunPanda committed Jan 4, 2021
1 parent 08710dd commit e73f9c4
Show file tree
Hide file tree
Showing 18 changed files with 277 additions and 9 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 3 additions & 2 deletions lib/models.js → dist/cjs/models.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultOptions = exports.FastImageError = void 0;
// The version is dynamically generated via build script in order not rely on require in the ESM case.
const version_1 = require("./version");
class FastImageError extends Error {
constructor(message, code, url, httpResponseCode) {
super(message);
Expand All @@ -13,6 +15,5 @@ exports.FastImageError = FastImageError;
exports.defaultOptions = {
timeout: 30000,
threshold: 4096,
// eslint-disable-next-line @typescript-eslint/no-var-requires
userAgent: `fastimage/${require('../package.json').version}`
userAgent: `fastimage/${version_1.version}`
};
File renamed without changes.
4 changes: 4 additions & 0 deletions dist/cjs/version.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.version = void 0;
exports.version = '3.0.2';
19 changes: 19 additions & 0 deletions dist/mjs/callback.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export function ensurePromiseCallback(callback) {
if (typeof callback === 'function') {
return [callback];
}
let promiseResolve, promiseReject;
const promise = new Promise((resolve, reject) => {
promiseResolve = resolve;
promiseReject = reject;
});
return [
(err, info) => {
if (err) {
return promiseReject(err);
}
return promiseResolve(info);
},
promise
];
}
54 changes: 54 additions & 0 deletions dist/mjs/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { ensurePromiseCallback } from "./callback.mjs";
import { handleData, handleError, toStream } from "./internals.mjs";
import { defaultOptions, FastImageError } from "./models.mjs";
import { FastImageStream } from "./stream.mjs";
export function info(source, options, cb) {
// Normalize arguments
if (typeof options === 'function') {
cb = options;
options = {};
}
const { timeout, threshold, userAgent } = { ...defaultOptions, ...options };
// Prepare execution
let finished = false;
let response;
let buffer = Buffer.alloc(0);
const [callback, promise] = ensurePromiseCallback(cb);
const start = process.hrtime.bigint();
// Make sure the source is always a Stream
let stream;
let url;
try {
;
[stream, url] = toStream(source, timeout, threshold, userAgent);
}
catch (e) {
callback(e);
return promise;
}
// When dealing with URLs, save the response to extract data later
stream.on('response', (r) => {
response = r;
});
stream.on('data', (chunk) => {
if (finished) {
return;
}
buffer = Buffer.concat([buffer, chunk]);
finished = handleData(buffer, response, threshold, start, callback);
});
stream.on('error', (error) => {
callback(handleError(error, url));
});
stream.on('end', () => {
if (finished) {
return;
}
// We have reached the end without figuring the image type. Just give up
callback(new FastImageError('Unsupported data.', 'UNSUPPORTED'));
});
return promise;
}
export function stream(options) {
return new FastImageStream(options !== null && options !== void 0 ? options : {});
}
112 changes: 112 additions & 0 deletions dist/mjs/internals.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { createReadStream } from 'fs';
import got from 'got';
import imageSize from 'image-size';
import { Readable } from 'stream';
import { FastImageError } from "./models.mjs";
export function toStream(source, timeout, threshold, userAgent) {
let url;
const highWaterMark = threshold > 0 ? Math.floor(threshold / 10) : 1024;
// If the source is a buffer, get it as stream
if (Buffer.isBuffer(source)) {
source = Readable.from(source, { highWaterMark });
}
else if (typeof source === 'string') {
// Try to parse the source as URL - If it succeeds, we will fetch it
try {
const parsedUrl = new URL(source);
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
throw new FastImageError('Invalid URL.', 'URL_ERROR', parsedUrl.toString());
}
url = source;
source = got.stream(parsedUrl.toString(), {
headers: { 'user-agent': userAgent },
followRedirect: true,
timeout
});
}
catch (e) {
if (e.code === 'FASTIMAGE_URL_ERROR') {
throw e;
}
// Parsing failed. Treat as local file
source = createReadStream(source, { highWaterMark });
}
}
return [source, url];
}
export function handleData(buffer, response, threshold, start, callback) {
try {
const info = imageSize(buffer);
const data = {
width: info.width,
height: info.height,
type: info.type,
time: Number(process.hrtime.bigint() - start) / 1e6,
analyzed: buffer.length
};
// Add URL informations
if (response) {
data.realUrl = response.url;
/* istanbul ignore else */
if ('content-length' in response.headers) {
data.size = parseInt(response.headers['content-length'], 10);
}
}
// Close the URL if possible
if (response) {
response.destroy();
}
callback(null, data);
return true;
}
catch (e) {
// Check threshold
if (threshold > 0 && buffer.length > threshold) {
if (response) {
response.destroy();
}
callback(new FastImageError('Unsupported data.', 'UNSUPPORTED'));
return true;
}
return false;
}
}
export function handleError(error, url) {
let message = null;
let code = 'NETWORK_ERROR';
switch (error.code) {
case 'EISDIR':
code = 'FS_ERROR';
message = 'Source is a directory.';
break;
case 'ENOENT':
code = 'FS_ERROR';
message = 'Source not found.';
break;
case 'EACCES':
code = 'FS_ERROR';
message = 'Source is not readable.';
break;
case 'ENOTFOUND':
message = 'Invalid remote host requested.';
break;
case 'ECONNRESET':
case 'EPIPE':
message = 'Connection with the remote host interrupted.';
break;
case 'ECONNREFUSED':
message = 'Connection refused from the remote host.';
break;
case 'ETIMEDOUT':
message = 'Connection to the remote host timed out.';
break;
}
if (error.response) {
message = `Remote host replied with HTTP ${error.response.statusCode}.`;
}
/* istanbul ignore else */
if (message) {
error = new FastImageError(message, code, url);
}
return error;
}
15 changes: 15 additions & 0 deletions dist/mjs/models.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// The version is dynamically generated via build script in order not rely on require in the ESM case.
import { version } from "./version.mjs";
export class FastImageError extends Error {
constructor(message, code, url, httpResponseCode) {
super(message);
this.code = `FASTIMAGE_${code}`;
this.url = url;
this.httpResponseCode = httpResponseCode;
}
}
export const defaultOptions = {
timeout: 30000,
threshold: 4096,
userAgent: `fastimage/${version}`
};
44 changes: 44 additions & 0 deletions dist/mjs/stream.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Writable } from 'stream';
import { handleData } from "./internals.mjs";
import { defaultOptions, FastImageError } from "./models.mjs";
export class FastImageStream extends Writable {
constructor(options) {
var _a;
super(options);
this.threshold = (_a = options.threshold) !== null && _a !== void 0 ? _a : defaultOptions.threshold;
this.buffer = Buffer.alloc(0);
this.start = process.hrtime.bigint();
this.finished = false;
}
analyze(chunk) {
this.buffer = Buffer.concat([this.buffer, chunk]);
this.finished = handleData(this.buffer, undefined, this.threshold, this.start, (error, data) => {
if (error) {
this.emit('error', error);
}
else {
this.emit('info', data);
}
this.destroy();
});
}
_write(chunk, _e, cb) {
this.analyze(chunk);
cb();
}
/* istanbul ignore next */
_writev(chunks, cb) {
for (const { chunk } of chunks) {
this.analyze(chunk);
}
cb();
}
_final(cb) {
/* istanbul ignore if */
if (this.finished) {
cb();
return;
}
cb(new FastImageError('Unsupported data.', 'UNSUPPORTED'));
}
}
1 change: 1 addition & 0 deletions dist/mjs/version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const version = '3.0.2';
13 changes: 9 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,26 @@
"license": "ISC",
"private": false,
"files": [
"lib",
"dist",
"types",
"CHANGELOG.md",
"LICENSE.md",
"README.md"
],
"main": "lib/index.js",
"main": "dist/cjs/index.js",
"exports": {
"require": "./dist/cjs/index.js",
"import": "./dist/mjs/index.mjs"
},
"typings": "types/index.d.ts",
"types": "types/index.d.ts",
"scripts": {
"lint": "eslint src/*.ts test/*.ts",
"test": "tap --reporter=spec --coverage-report=html --coverage-report=text --no-browser test/*.test.ts",
"test:ci": "tap --no-color --reporter=spec --coverage-report=json --coverage-report=text --branches 90 --functions 90 --lines 90 --statements 90 test/*.test.ts",
"ci": "yarn lint && yarn test:ci",
"prebuild": "rm -rf lib/* types/* && yarn lint",
"build": "tsc -p .",
"prebuild": "rm -rf dist types && yarn lint && node -e \"console.log('export const version = \\'' + require('./package').version + '\\'')\" > src/version.ts",
"build": "tsc -p . && tsc -p tsconfig.modules.json && renamer --find js --replace mjs dist/mjs/** >> /dev/null && jscodeshift -s --extensions=mjs -t node_modules/@cowtech/esm-package-utils dist/mjs/**",
"prepublishOnly": "yarn ci",
"postpublish": "git push origin && git push origin -f --tags"
},
Expand All @@ -54,6 +58,7 @@
},
"devDependencies": {
"@cowtech/eslint-config": "^7.14.0",
"@cowtech/esm-package-utils": "^0.2.0",
"@types/node": "^14.14.19",
"@types/tap": "^14.10.1",
"prettier": "^2.2.1",
Expand Down
6 changes: 4 additions & 2 deletions src/models.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// The version is dynamically generated via build script in order not rely on require in the ESM case.
import { version } from './version'

export interface ImageInfo {
width: number
height: number
Expand Down Expand Up @@ -30,6 +33,5 @@ export class FastImageError extends Error {
export const defaultOptions: Options = {
timeout: 30000,
threshold: 4096,
// eslint-disable-next-line @typescript-eslint/no-var-requires
userAgent: `fastimage/${require('../package.json').version}`
userAgent: `fastimage/${version}`
}
1 change: 1 addition & 0 deletions src/version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const version = '3.0.2'
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"module": "commonjs",
"moduleResolution": "node",
"jsx": "preserve",
"outDir": "lib",
"outDir": "dist/cjs",
"declarationDir": "types",
"allowJs": false,
"allowSyntheticDefaultImports": true,
Expand Down
9 changes: 9 additions & 0 deletions tsconfig.modules.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"declaration": false,
"declarationDir": null,
"outDir": "dist/mjs"
}
}
1 change: 1 addition & 0 deletions types/version.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export declare const version = "3.0.2";

0 comments on commit e73f9c4

Please sign in to comment.