1+ import fs from 'fs-extra' ;
2+ import path from 'path' ;
3+ import { fileURLToPath } from 'url' ;
4+ import { program } from 'commander' ;
5+ import logger from '@docusaurus/logger' ;
6+ import sharp from 'sharp' ;
7+ import imageSize from 'image-size' ;
8+
9+ // You can use it as:
10+ //
11+ // # Resize all images in showcase (which is most likely)
12+ // node admin/scripts/resizeImage.js
13+ //
14+ // # Resize specified images / all images in a folder
15+ // # This does not read folders recursively as of now
16+ // node admin/scripts/resizeImage.js image1.png some-folder ...
17+ //
18+ // By default, showcase images are resized to 640×320; everything else is
19+ // resized to width 1000. You can explicitly give a width/height as arguments.
20+ // node admin/scripts/resizeImage.js --width 640 --height 320 image1.png
21+
22+ function maybeParseInt ( n ) {
23+ const res = Number . parseInt ( n , 10 ) ;
24+ if ( Number . isNaN ( res ) ) {
25+ return undefined ;
26+ }
27+ return res ;
28+ }
29+
30+ const showcasePath = 'src/data/showcase' ;
31+
32+ program
33+ . arguments ( '[imagePaths...]' )
34+ . option ( '-w, --width <width>' , 'Image width' , maybeParseInt )
35+ . option ( '-h, --height <height>' , 'Image height' , maybeParseInt )
36+ . action ( async ( imagePaths , options ) => {
37+ if ( imagePaths . length === 0 ) {
38+ imagePaths . push ( showcasePath ) ;
39+ }
40+ const rootDir = fileURLToPath ( new URL ( '../..' , import . meta. url ) ) ;
41+ const images = (
42+ await Promise . all (
43+ imagePaths . map ( async ( p ) =>
44+ path . extname ( p )
45+ ? [ path . resolve ( rootDir , p ) ]
46+ : ( await fs . readdir ( p ) ) . map ( ( f ) => path . resolve ( rootDir , p , f ) ) ,
47+ ) ,
48+ )
49+ )
50+ . flat ( )
51+ . filter ( ( p ) => [ '.png' , 'jpg' , '.jpeg' ] . includes ( path . extname ( p ) ) ) ;
52+
53+ const stats = {
54+ skipped : 0 ,
55+ resized : 0 ,
56+ } ;
57+
58+ await Promise . all (
59+ images . map ( async ( imgPath ) => {
60+ const { width, height} = imageSize ( imgPath ) ;
61+ const targetWidth =
62+ options . width ?? ( imgPath . includes ( showcasePath ) ? 640 : 1000 ) ;
63+ const targetHeight =
64+ options . height ?? ( imgPath . includes ( showcasePath ) ? 320 : undefined ) ;
65+ if (
66+ width <= targetWidth &&
67+ ( ! targetHeight || height <= targetHeight ) &&
68+ imgPath . endsWith ( '.png' )
69+ ) {
70+ // Do not emit if not resized. Important because we can't guarantee
71+ // idempotency during resize -> optimization
72+ stats . skipped += 1 ;
73+ return ;
74+ }
75+ logger . info `Resized path=${ imgPath } : before number=${ width } ×number=${ height } ; now number=${ targetWidth } ×number=${
76+ targetHeight ?? Math . floor ( ( height / width ) * targetWidth )
77+ } `;
78+ const data = await sharp ( imgPath )
79+ . resize ( targetWidth , targetHeight , { fit : 'cover' , position : 'top' } )
80+ . png ( )
81+ . toBuffer ( ) ;
82+ await fs . writeFile ( imgPath . replace ( / j p e ? g / , 'png' ) , data ) ;
83+ stats . resized += 1 ;
84+ } ) ,
85+ ) ;
86+
87+ logger . info `Images resizing complete.
88+ resized: number=${ stats . resized }
89+ skipped: number=${ stats . skipped } ` ;
90+ } ) ;
91+
92+ program . parse ( process . argv ) ;
0 commit comments