@divriots/jampack 0.2.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/optimize.js CHANGED
@@ -1,30 +1,32 @@
1
- import { globby } from 'globby';
2
- import * as path from 'path';
3
- import * as fs from 'fs/promises';
4
- import cheerio from 'cheerio';
5
- import { isLocal, isNumeric, translateSrc } from './utils.js';
6
- import sharp from "sharp";
7
- import config from './config.js';
8
- import { compressImage } from './compress.js';
9
- import svgToMiniDataURI from 'mini-svg-data-uri';
10
- import $state from './state.js';
11
- import kleur from 'kleur';
12
- import ora from 'ora';
1
+ import { globby } from "globby";
2
+ import * as path from "path";
3
+ import * as fs from "fs/promises";
4
+ import cheerio from "cheerio";
5
+ import { isNumeric } from "./utils.js";
6
+ import config from "./config.js";
7
+ import { compressImage } from "./compress.js";
8
+ import svgToMiniDataURI from "mini-svg-data-uri";
9
+ import $state from "./state.js";
10
+ import kleur from "kleur";
11
+ import ora from "ora";
12
+ import { isLocal, Resource, translateSrc } from "./utils/resource.js";
13
13
  async function analyse(file) {
14
- console.log('▶ ' + file);
14
+ console.log("▶ " + file);
15
15
  const html = (await fs.readFile(path.join($state.dir, file))).toString();
16
- const $ = cheerio.load(html, {});
17
- const imgs = $('img');
16
+ const $ = cheerio.load(html, { withStartIndices: true });
17
+ const theFold = getTheFold($);
18
+ const imgs = $("img");
18
19
  const imgsArray = [];
19
20
  imgs.each(async (index, imgElement) => {
20
21
  imgsArray.push(imgElement);
21
22
  });
22
23
  // Process images sequentially
23
- const spinnerImg = ora({ prefixText: ' ' }).start();
24
+ const spinnerImg = ora({ prefixText: " " }).start();
24
25
  for (let i = 0; i < imgsArray.length; i++) {
25
26
  const imgElement = imgsArray[i];
26
- spinnerImg.text = kleur.dim(`<img> [${i + 1}/${imgsArray.length}] ${$(imgElement).attr('src')} `);
27
- await processImage(file, $, imgElement);
27
+ spinnerImg.text = kleur.dim(`<img> [${i + 1}/${imgsArray.length}] ${$(imgElement).attr("src")} `);
28
+ const isAboveTheFold = imgElement.startIndex < theFold;
29
+ await processImage(file, $, imgElement, isAboveTheFold);
28
30
  }
29
31
  // Reset spinner
30
32
  spinnerImg.text = kleur.dim(`<img> [${imgsArray.length}/${imgsArray.length}]`);
@@ -32,105 +34,137 @@ async function analyse(file) {
32
34
  const issues = $state.issues.get(file);
33
35
  if (issues) {
34
36
  spinnerImg.fail();
35
- console.log(kleur.red(` ${issues.length} issue${issues.length > 1 ? 's' : ''}`));
37
+ console.log(kleur.red(` ${issues.length} issue${issues.length > 1 ? "s" : ""}`));
36
38
  }
37
39
  else {
38
40
  spinnerImg.succeed();
39
41
  }
42
+ // Remove the fold
43
+ if (theFold) {
44
+ $("the-fold").remove();
45
+ }
40
46
  if (!$state.args.nowrite) {
41
47
  await fs.writeFile(path.join($state.dir, file), $.html());
42
48
  }
43
49
  }
44
- async function processImage(htmlfile, $, imgElement) {
50
+ function getTheFold($) {
51
+ const theFolds = $("the-fold");
52
+ if (!theFolds[0]) {
53
+ return 0;
54
+ }
55
+ // @ts-ignore
56
+ return theFolds[0].startIndex;
57
+ }
58
+ async function processImage(htmlfile, $, imgElement, isAboveTheFold) {
45
59
  try {
46
60
  const img = $(imgElement);
47
61
  /*
48
- * Attribute 'src'
49
- */
50
- const attrib_src = img.attr('src');
62
+ * Attribute 'src'
63
+ */
64
+ const attrib_src = img.attr("src");
51
65
  if (!attrib_src) {
52
- $state.reportIssue(htmlfile, { msg: `Missing [src] on img - processing skipped.` });
66
+ $state.reportIssue(htmlfile, {
67
+ type: "warn",
68
+ msg: `Missing [src] on img - processing skipped.`,
69
+ });
53
70
  return;
54
71
  }
55
72
  /*
56
- * Attribute 'alt'
57
- */
58
- const attrib_alt = img.attr('alt');
73
+ * Attribute 'alt'
74
+ */
75
+ const attrib_alt = img.attr("alt");
59
76
  if (attrib_alt === undefined) {
60
- $state.reportIssue(htmlfile, { msg: `Missing [alt] on img src="${attrib_src}" - Adding alt="" meanwhile.` });
61
- img.attr('alt', "");
77
+ $state.reportIssue(htmlfile, {
78
+ type: "a11y",
79
+ msg: `Missing [alt] on img src="${attrib_src}" - Adding alt="" meanwhile.`,
80
+ });
81
+ img.attr("alt", "");
62
82
  }
63
- if (attrib_src.startsWith('data:')) {
83
+ if (attrib_src.startsWith("data:")) {
64
84
  // Data URI image
65
85
  // TODO: try to compress it
66
86
  return;
67
87
  }
88
+ /*
89
+ * Attribute 'loading'
90
+ */
91
+ const attr_loading = img.attr("loading");
92
+ if (isAboveTheFold) {
93
+ img.removeAttr("loading");
94
+ img.attr("fetchpriority", "high");
95
+ }
96
+ else {
97
+ switch (attr_loading) {
98
+ case undefined:
99
+ // Go lazy by default
100
+ img.attr("loading", "lazy");
101
+ break;
102
+ case "eager":
103
+ img.removeAttr("loading");
104
+ break;
105
+ case "lazy":
106
+ // Don't touch it
107
+ break;
108
+ default:
109
+ $state.reportIssue(htmlfile, {
110
+ type: "invalid",
111
+ msg: `Invalid [loading]="${attr_loading}" on img src="${attrib_src}"`,
112
+ });
113
+ }
114
+ }
115
+ /*
116
+ * Attribute 'decoding'
117
+ */
118
+ img.attr("decoding", "async");
68
119
  if (!isLocal(attrib_src)) {
69
120
  // Image not local, don't touch it
70
121
  return;
71
122
  }
72
123
  /*
73
- * Attribute 'loading'
74
- */
75
- const attr_loading = img.attr('loading');
76
- switch (attr_loading) {
77
- case undefined:
78
- // Go lazy by default
79
- img.attr('loading', 'lazy');
80
- break;
81
- case 'eager':
82
- img.removeAttr('loading');
83
- break;
84
- case 'lazy':
85
- // Don't touch it
86
- break;
87
- default:
88
- $state.reportIssue(htmlfile, { msg: `Invalid [loading]="${attr_loading}" on img src="${attrib_src}"` });
89
- }
90
- /*
91
- * Attribute 'decoding'
92
- */
93
- img.attr('decoding', 'async');
94
- /*
95
- * Loading image
96
- */
97
- const absoluteImgPath = translateSrc($state.dir, path.dirname(htmlfile), attrib_src);
98
- // file exists?
99
- try {
100
- await fs.stat(absoluteImgPath);
101
- }
102
- catch (e) {
103
- $state.reportIssue(htmlfile, { msg: `Can't find img src="${attrib_src}" in ${absoluteImgPath}` });
124
+ * Loading image
125
+ */
126
+ const originalImage = await Resource.loadResource($state.dir, htmlfile, attrib_src);
127
+ // No file -> give up
128
+ if (!originalImage) {
129
+ $state.reportIssue(htmlfile, {
130
+ type: "erro",
131
+ msg: `Can't find img on disk src="${attrib_src}"`,
132
+ });
104
133
  return;
105
134
  }
106
- // Read file
107
- const originalData = await fs.readFile(absoluteImgPath);
108
- const sharpFile = sharp(absoluteImgPath);
109
- const meta = await sharpFile.metadata();
110
135
  /*
111
136
  * Compress image to WebP
112
137
  */
113
138
  let newImage;
114
- if (!$state.optimizedFiles.has(absoluteImgPath)) {
139
+ if (!$state.optimizedFiles.has(originalImage.filePathAbsolute)) {
115
140
  // Let's avoid to optimize same images twice
116
- $state.optimizedFiles.add(absoluteImgPath);
117
- newImage = await compressImage(originalData, {}, true);
118
- if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) && newImage.data.length < originalData.length) {
141
+ $state.optimizedFiles.add(originalImage.filePathAbsolute);
142
+ newImage = await compressImage(await originalImage.getData(), {
143
+ toFormat: isAboveTheFold ? "pjpg" : "webp",
144
+ });
145
+ if ((newImage === null || newImage === void 0 ? void 0 : newImage.data) &&
146
+ (newImage.data.length < (await originalImage.getLen()) ||
147
+ isAboveTheFold) // Progressive jpg above the fold should get replaced even if bigger
148
+ ) {
119
149
  // Do we need to add an new extension?
120
150
  const newExtension = `.${newImage.format}`;
121
- const additionalExtension = path.extname(absoluteImgPath) === newExtension ? '' : newExtension;
122
- const newFilename = absoluteImgPath + additionalExtension;
151
+ const additionalExtension = path.extname(originalImage.filePathAbsolute) === newExtension
152
+ ? ""
153
+ : newExtension;
154
+ const newFilename = originalImage.filePathAbsolute + additionalExtension;
123
155
  if (!$state.args.nowrite) {
124
156
  fs.writeFile(newFilename, newImage.data);
125
157
  }
126
158
  $state.compressedFiles.add(newFilename);
127
159
  // Report compression result
128
160
  $state.reportSummary({
129
- action: (newFilename !== absoluteImgPath) ? `${meta.format}->${newImage.format}` : path.extname(absoluteImgPath),
130
- originalSize: originalData.length,
131
- compressedSize: newImage.data.length
161
+ action: newFilename !== originalImage.filePathAbsolute
162
+ ? `${await originalImage.getExt()}->${newImage.format}`
163
+ : path.extname(originalImage.filePathAbsolute),
164
+ originalSize: await originalImage.getLen(),
165
+ compressedSize: newImage.data.length,
132
166
  });
133
- img.attr('src', attrib_src + additionalExtension);
167
+ img.attr("src", attrib_src + additionalExtension);
134
168
  }
135
169
  }
136
170
  /*
@@ -144,33 +178,33 @@ async function processImage(htmlfile, $, imgElement) {
144
178
  if (newImage && newImage.data.length <= config.image.embed_size) {
145
179
  let datauri = undefined;
146
180
  switch (newImage.format) {
147
- case 'svg':
181
+ case "svg":
148
182
  datauri = svgToMiniDataURI(newImage.data.toString());
149
183
  break;
150
- case 'webp':
151
- datauri = `data:image/webp;base64,${newImage.data.toString('base64')}`;
184
+ case "webp":
185
+ datauri = `data:image/webp;base64,${newImage.data.toString("base64")}`;
152
186
  break;
153
- case 'jpg':
154
- case 'png':
187
+ case "jpg":
188
+ case "png":
155
189
  // TODO but not possible in current code
156
190
  break;
157
191
  }
158
192
  if (datauri) {
159
193
  isEmbed = true;
160
- img.attr('src', datauri);
161
- img.removeAttr('loading');
162
- img.removeAttr('decoding');
163
- $state.reportSummary({ action: `${newImage.format}->embed`, originalSize: originalData.length, compressedSize: newImage.data.length });
194
+ img.attr("src", datauri);
195
+ img.removeAttr("loading");
196
+ img.removeAttr("decoding");
197
+ $state.reportSummary({
198
+ action: `${newImage.format}->embed`,
199
+ originalSize: await originalImage.getLen(),
200
+ compressedSize: newImage.data.length,
201
+ });
164
202
  }
165
203
  }
166
204
  /*
167
205
  * Attribute 'width' & 'height'
168
206
  */
169
- const [w, h] = setImageSize(img, meta);
170
- if (w < 0 || h < 0) {
171
- $state.reportIssue(htmlfile, { msg: `Unexpected error in image size calculation src="${attrib_src}" - can't performance some optmizations.` });
172
- return;
173
- }
207
+ const [w, h] = await setImageSize(img, originalImage);
174
208
  if (isEmbed) {
175
209
  // Image is embed, no need for more processing
176
210
  return;
@@ -178,12 +212,13 @@ async function processImage(htmlfile, $, imgElement) {
178
212
  //
179
213
  // Stop here if svg
180
214
  //
181
- if (meta.format === 'svg')
215
+ if ((await originalImage.getExt()) === "svg") {
182
216
  return;
217
+ }
183
218
  /*
184
219
  * Attribute 'srcset'
185
220
  */
186
- const attr_srcset = img.attr('srcset');
221
+ const attr_srcset = img.attr("srcset");
187
222
  if (attr_srcset) {
188
223
  // If srcset is set, don't touch it.
189
224
  // The compress pass will compress the images
@@ -192,10 +227,10 @@ async function processImage(htmlfile, $, imgElement) {
192
227
  else {
193
228
  // Generate image set
194
229
  const ext = path.extname(attrib_src);
195
- const fullbasename = attrib_src.slice(0, -(ext.length));
230
+ const fullbasename = attrib_src.slice(0, -ext.length);
196
231
  const imageSrc = (addition) => `${fullbasename}${addition}.webp`;
197
232
  // Start from original image
198
- let new_srcset = '';
233
+ let new_srcset = "";
199
234
  // Start reduction
200
235
  const step = 300; //px
201
236
  const ratio = w / h;
@@ -208,7 +243,7 @@ async function processImage(htmlfile, $, imgElement) {
208
243
  if (!$state.compressedFiles.has(absoluteFilename)) {
209
244
  // Add file to list avoid recompression
210
245
  $state.compressedFiles.add(absoluteFilename);
211
- const compressedImage = await compressImage(originalData, { width: valueW, height: valueH }, true);
246
+ const compressedImage = await compressImage(await originalImage.getData(), { resize: { width: valueW, height: valueH }, toFormat: "webp" });
212
247
  if ((compressedImage === null || compressedImage === void 0 ? void 0 : compressedImage.data) && !$state.args.nowrite) {
213
248
  fs.writeFile(absoluteFilename, compressedImage.data);
214
249
  }
@@ -219,17 +254,21 @@ async function processImage(htmlfile, $, imgElement) {
219
254
  valueH = Math.trunc(valueW / ratio);
220
255
  }
221
256
  if (new_srcset) {
222
- img.attr('srcset', `${img.attr('src')} ${w}w` + new_srcset);
257
+ img.attr("srcset", `${img.attr("src")} ${w}w` + new_srcset);
223
258
  }
224
259
  }
225
260
  }
226
261
  catch (e) {
227
- $state.reportIssue(htmlfile, { msg: `Unexpected error while processing image: ${JSON.stringify(e)}` });
262
+ $state.reportIssue(htmlfile, {
263
+ type: "erro",
264
+ msg: e.message ||
265
+ `Unexpected error while processing image: ${JSON.stringify(e)}`,
266
+ });
228
267
  }
229
268
  }
230
- function setImageSize(img, meta) {
231
- let width = img.attr('width');
232
- let height = img.attr('height');
269
+ async function setImageSize(img, image) {
270
+ let width = img.attr("width");
271
+ let height = img.attr("height");
233
272
  let width_new = undefined;
234
273
  let height_new = undefined;
235
274
  // Check valid values
@@ -246,8 +285,12 @@ function setImageSize(img, meta) {
246
285
  }
247
286
  }
248
287
  // If we don't have the metadata, we can't do much more
249
- if (meta.width === undefined || meta.height === undefined) {
250
- return [-1, -1];
288
+ const meta = await image.getImageMeta();
289
+ if (!meta) {
290
+ throw new Error(`Can't get image meta information of "${image.src}" - some optimizations are not possible without this information.`);
291
+ }
292
+ if (meta.width === undefined && meta.height === undefined) {
293
+ throw new Error(`Can't get image width and height of "${image.src}" - some optimizations are not possible without this information.`);
251
294
  }
252
295
  const originalRatio = meta.width / meta.height;
253
296
  if (width !== undefined && height !== undefined) {
@@ -255,7 +298,7 @@ function setImageSize(img, meta) {
255
298
  const w = parseInt(width, 10);
256
299
  const h = parseInt(height, 10);
257
300
  // Is ratio equal?
258
- const providedRatio = Math.round(w / h * 10) / 10;
301
+ const providedRatio = Math.round((w / h) * 10) / 10;
259
302
  const imageRatio = Math.round(originalRatio * 10) / 10;
260
303
  if (providedRatio !== imageRatio) {
261
304
  //console.warn(`Image aspect ratio in HTML (${providedRatio}) differs from image aspect ratio (${imageRatio}) - fix width and height or let jampack fill them.`);
@@ -274,26 +317,81 @@ function setImageSize(img, meta) {
274
317
  }
275
318
  else {
276
319
  // No width or height provided - set both to image size
277
- width_new = meta.width;
278
- height_new = meta.height;
320
+ if ((await image.getExt()) === "svg") {
321
+ // svg with no height and width has special sizing by browsers
322
+ // They size it inside 300x150 unless they have width and height
323
+ // attributes
324
+ // Load svg
325
+ const c = cheerio.load(await image.getData(), {});
326
+ const svg = c("svg").first();
327
+ const svg_viewbox = svg.attr("viewbox"); // bug in cheerio here, should be "viewBox"
328
+ const svg_width = svg.attr("width");
329
+ const svg_height = svg.attr("height");
330
+ // Calculate aspect ratio from viewbox
331
+ let svg_aspectratio_from_viewbox = undefined;
332
+ if (svg_viewbox) {
333
+ const box = svg_viewbox.split(" ");
334
+ const w = parseInt(box[2], 10);
335
+ const h = parseInt(box[3], 10);
336
+ svg_aspectratio_from_viewbox = w / h;
337
+ }
338
+ // Set size
339
+ //
340
+ if (svg_width &&
341
+ isNumeric(svg_width) &&
342
+ svg_height &&
343
+ isNumeric(svg_height)) {
344
+ // height and width are present
345
+ // use them
346
+ width_new = parseInt(svg_width, 10);
347
+ height_new = parseInt(svg_height, 10);
348
+ }
349
+ else if (svg_width === undefined &&
350
+ svg_height === undefined &&
351
+ svg_aspectratio_from_viewbox) {
352
+ // no height and no width but viewbox is present
353
+ // fit it in default browser box 300x150
354
+ if (svg_aspectratio_from_viewbox >= 2) {
355
+ // fit width
356
+ width_new = 300;
357
+ height_new = width_new / svg_aspectratio_from_viewbox;
358
+ }
359
+ else {
360
+ // fit height
361
+ height_new = 150;
362
+ width_new = height_new * svg_aspectratio_from_viewbox;
363
+ }
364
+ }
365
+ else {
366
+ // no width and no height and no viewbox
367
+ // default browser values
368
+ width_new = 300;
369
+ height_new = 150;
370
+ }
371
+ }
372
+ else {
373
+ width_new = meta.width;
374
+ height_new = meta.height;
375
+ }
279
376
  }
280
377
  // New sizes
281
378
  if (width_new !== undefined && height_new !== undefined) {
282
- img.attr('width', width_new.toFixed(0));
283
- img.attr('height', height_new.toFixed(0));
284
- return [Math.round(width_new), Math.round(height_new)];
379
+ const result_w = Math.round(width_new);
380
+ const result_h = Math.round(height_new);
381
+ img.attr("width", result_w.toFixed(0));
382
+ img.attr("height", result_h.toFixed(0));
383
+ return [result_w, result_h];
285
384
  }
286
385
  // Something when wrong
287
- throw new Error('Unexpected issue when resolving image sizes');
386
+ throw new Error(`Unexpected issue when resolving image size "${image.src}"`);
288
387
  }
289
388
  export async function optimize(exclude) {
290
- const glob = ['**/*.{htm,html}'];
389
+ const glob = ["**/*.{htm,html}"];
291
390
  if (exclude)
292
- glob.push('!' + exclude);
391
+ glob.push("!" + exclude);
293
392
  const paths = await globby(glob, { cwd: $state.dir });
294
393
  // Sequential async
295
394
  for (const file of paths) {
296
395
  await analyse(file);
297
396
  }
298
- ;
299
397
  }
package/dist/state.d.ts CHANGED
@@ -1,18 +1,20 @@
1
- export declare type Args = {
1
+ export type Args = {
2
2
  nowrite?: boolean;
3
+ nocache?: boolean;
3
4
  };
4
- export declare type ReportItem = {
5
+ export type ReportItem = {
5
6
  action: string;
6
7
  originalSize: number;
7
8
  compressedSize: number;
8
9
  };
9
- declare type Summary = {
10
+ type Summary = {
10
11
  nbFiles: number;
11
12
  nbFilesCompressed: number;
12
13
  dataLenUncompressed: number;
13
14
  dataLenCompressed: number;
14
15
  };
15
- declare type Issue = {
16
+ type Issue = {
17
+ type: 'invalid' | 'a11y' | 'perf' | 'erro' | 'warn';
16
18
  msg: string;
17
19
  };
18
20
  export declare class GlobalState {
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /// <reference types="node" resolution-mode="require"/>
2
- export declare type ImageFormat = 'webp' | 'svg' | 'jpg' | 'png' | undefined;
3
- export declare type Image = {
2
+ export type ImageFormat = 'webp' | 'svg' | 'jpg' | 'png' | undefined;
3
+ export type Image = {
4
4
  format: ImageFormat;
5
5
  data: Buffer;
6
6
  };
@@ -0,0 +1,31 @@
1
+ /// <reference types="node" resolution-mode="require"/>
2
+ import { FileExtension, MimeType } from 'file-type';
3
+ type ImageMeta = {
4
+ width: number | undefined;
5
+ height: number | undefined;
6
+ isProgressive: boolean;
7
+ hasAlpha: boolean;
8
+ };
9
+ /**
10
+ * File extension, mime and data are loaded only on demand, then cached.
11
+ */
12
+ export declare class Resource {
13
+ readonly src: string;
14
+ readonly filePathAbsolute: string;
15
+ private ext;
16
+ private mime;
17
+ private data;
18
+ private image_meta;
19
+ constructor(src: string, filePathAbsolute: string);
20
+ getData(): Promise<Buffer>;
21
+ getImageMeta(): Promise<ImageMeta | null | undefined>;
22
+ getLen(): Promise<number>;
23
+ getExt(): Promise<FileExtension | 'svg'>;
24
+ getMime(): Promise<MimeType | 'image/svg+xml'>;
25
+ private loadFileType;
26
+ static loadResource(projectRoot: string, relativeFile: string, src: string): Promise<Resource | undefined>;
27
+ }
28
+ export declare function isLocal(src: string): boolean;
29
+ export declare function fileExists(path: string): Promise<boolean>;
30
+ export declare function translateSrc(projectRoot: string, htmlRelativePath: string, src: string): string;
31
+ export {};
@@ -0,0 +1,110 @@
1
+ import * as url from 'url';
2
+ import * as path from 'path';
3
+ import * as fs from 'fs/promises';
4
+ import { fileTypeFromBuffer } from 'file-type';
5
+ import sharp from 'sharp';
6
+ /**
7
+ * File extension, mime and data are loaded only on demand, then cached.
8
+ */
9
+ export class Resource {
10
+ constructor(src, filePathAbsolute) {
11
+ this.src = src;
12
+ this.filePathAbsolute = filePathAbsolute;
13
+ }
14
+ async getData() {
15
+ if (this.data === undefined) {
16
+ this.data = await fs.readFile(this.filePathAbsolute);
17
+ }
18
+ return this.data;
19
+ }
20
+ async getImageMeta() {
21
+ if (this.image_meta === undefined) {
22
+ const ext = await this.getExt();
23
+ switch (ext) {
24
+ case 'svg':
25
+ case 'gif':
26
+ case 'jpg':
27
+ case 'png':
28
+ case 'webp':
29
+ case 'tif':
30
+ let sharpFile = await sharp(await this.getData(), { animated: true });
31
+ const meta = await sharpFile.metadata();
32
+ this.image_meta = {
33
+ width: meta.width,
34
+ height: meta.height,
35
+ isProgressive: meta.isProgressive || false,
36
+ hasAlpha: meta.hasAlpha || false
37
+ };
38
+ break;
39
+ }
40
+ }
41
+ if (!this.image_meta) {
42
+ console.log(await this.getExt());
43
+ }
44
+ return this.image_meta;
45
+ }
46
+ async getLen() {
47
+ return (await this.getData()).length;
48
+ }
49
+ async getExt() {
50
+ if (this.ext === undefined) {
51
+ await this.loadFileType();
52
+ }
53
+ return this.ext;
54
+ }
55
+ async getMime() {
56
+ if (this.mime === undefined) {
57
+ await this.loadFileType();
58
+ }
59
+ return this.mime;
60
+ }
61
+ async loadFileType() {
62
+ const fileType = await fileTypeFromBuffer(await this.getData());
63
+ if (this.filePathAbsolute.endsWith('.svg')) {
64
+ this.ext = 'svg';
65
+ this.mime = 'image/svg+xml';
66
+ }
67
+ else if (fileType) {
68
+ this.ext = fileType.ext;
69
+ this.mime = fileType.mime;
70
+ }
71
+ else {
72
+ throw new Error(`Unknown file type "${this.src}"`);
73
+ }
74
+ }
75
+ static async loadResource(projectRoot, relativeFile, src) {
76
+ if (!isLocal(src)) {
77
+ throw new Error('src should be local');
78
+ }
79
+ const u = url.parse(src);
80
+ if (!u.pathname) {
81
+ throw new Error(`Invalid src format "${src}"`);
82
+ }
83
+ const relativePath = path.join(projectRoot, src.startsWith('/') ? '' : path.dirname(relativeFile), u.pathname);
84
+ let absolutePath = path.resolve(relativePath);
85
+ if (await fileExists(absolutePath)) {
86
+ return new Resource(src, absolutePath);
87
+ }
88
+ return undefined;
89
+ }
90
+ }
91
+ export function isLocal(src) {
92
+ const u = url.parse(src);
93
+ return !u.host;
94
+ }
95
+ export async function fileExists(path) {
96
+ try {
97
+ await fs.stat(path);
98
+ }
99
+ catch (e) {
100
+ return false;
101
+ }
102
+ return true;
103
+ }
104
+ export function translateSrc(projectRoot, htmlRelativePath, src) {
105
+ if (!isLocal(src)) {
106
+ throw new Error('Source should be local');
107
+ }
108
+ const srcAbsolutePath = path.join(projectRoot, src.startsWith('/') ? '' : htmlRelativePath, src);
109
+ return path.resolve(srcAbsolutePath);
110
+ }
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,2 @@
1
1
  export declare function formatBytes(bytes: number, decimals?: number): string;
2
- export declare function isLocal(src: string): boolean;
3
- export declare function translateSrc(projectRoot: string, htmlRelativePath: string, src: string): string;
4
2
  export declare function isNumeric(value: string): boolean;