Search code examples
javascriptnode.jsgulpsharp

A better way to round a list of const variables in Javascript for gulpfile.js?


In my gulpfile.js, I want to be able to calculate widths based on the approximate percentage of the screen that they will occupy for an image sizing/compression task using the gulp plugin "gulp-sharp-responsive".

For example, one of my sizes for a full-screen image is 1200. For images that are 1/4 that width, I want it to output a 300px image? I wanted to avoid having to manually calculate the new width and to be able to set the divisor as a command-line option, so this is the solution I came up with the below approach following this tutorial, https://www.sitepoint.com/pass-parameters-gulp-tasks/.

At the top of gulpfile.js, I added the following code:

// fetch command line arguments
const arg = (argList => {
    let arg = {}, a, opt, thisOpt, curOpt;
    for (a = 0; a < argList.length; a++) {
        thisOpt = argList[a].trim();
        opt = thisOpt.replace(/^\-+/, '');
        if (opt === thisOpt) {
            // argument value
            if (curOpt) arg[curOpt] = opt;
            curOpt = null;
        }
        else {

            // argument name
            curOpt = opt;
            arg[curOpt] = true;
        }
    }
    return arg;
})(process.argv);

I assigned div as arg.d and provided a fallback if arg is null to 1 (i.e., div = arg.d || 1). Note, since I am mainly going to show featured images at full screen at widths 576px and below (mobile screens), I am not dividing the xs size by a divisor. Also since the gulp-sharp-responsive is not able to process non-integer widths, I had to round the quotient with the round function. My question is, how would I make my code less redundant--for example, so I am not repeating math.round() for each const variable. If you have any suggestions to make my code more succinct please let me know, I am just a beginner. Thanks!

function sharpImg() {
    const div = arg.d || 1, xs = (Math.round(576 / div)), sm = (Math.round(769 / div)), md = (Math.round(992 / div)), lg = (Math.round(1200 / div)), xl = (Math.round(1400 / div)), xxl = (Math.round(2048 / div));
    return src(['_images/original/process/**/*.{jpeg,jpg,png,tiff,webp}', '!_images/original/raw/**'])
        .pipe($.rename(function (path) {
            path.dirname += "/" + path.basename;
        }))
        .pipe($.sharpResponsive({
            formats: [
                // jpeg
                { width: xs, format: "jpeg", rename: { suffix: "-xs" }, jpegOptions: { quality: 50, progressive: true } },
                { width: sm, format: "jpeg", rename: { suffix: "-sm" }, jpegOptions: { quality: 50, progressive: true } },
                { width: md, format: "jpeg", rename: { suffix: "-md" }, jpegOptions: { quality: 50, progressive: true } },
                { width: lg, format: "jpeg", rename: { suffix: "-lg" }, jpegOptions: { quality: 50, progressive: true } },
                { width: xl, format: "jpeg", rename: { suffix: "-xl" }, jpegOptions: { quality: 50, progressive: true } },
                { width: xxl, format: "jpeg", rename: { suffix: "-xxl" }, jpegOptions: { quality: 50, progressive: true } },
                // webp
                { width: xs, format: "webp", rename: { suffix: "-xs" }, webpOptions: { quality: 50 } },
                { width: sm, format: "webp", rename: { suffix: "-sm" }, webpOptions: { quality: 50 } },
                { width: md, format: "webp", rename: { suffix: "-md" }, webpOptions: { quality: 50 } },
                { width: lg, format: "webp", rename: { suffix: "-lg" }, webpOptions: { quality: 50 } },
                { width: xl, format: "webp", rename: { suffix: "-xl" }, webpOptions: { quality: 50 } },
                { width: xxl, format: "webp", rename: { suffix: "-xxl" }, webpOptions: { quality: 50 } },
                // avif
                { width: xs, format: "avif", rename: { suffix: "-xs" }, avifOptions: { quality: 50 } },
                { width: sm, format: "avif", rename: { suffix: "-sm" }, avifOptions: { quality: 50 } },
                { width: md, format: "avif", rename: { suffix: "-md" }, avifOptions: { quality: 50 } },
                { width: lg, format: "avif", rename: { suffix: "-lg" }, avifOptions: { quality: 50 } },
                { width: xl, format: "avif", rename: { suffix: "-xl" }, avifOptions: { quality: 50 } },
                { width: xxl, format: "avif", rename: { suffix: "-xxl" }, avifOptions: { quality: 50 } },
            ]
        }))
        .pipe(dest('_images/processed'))
}

export task:

exports.sharpImg = sharpImg;

The result: Running "gulp sharpImg" results in the default const widths defined, whereas running "gulp sharpImg --d 4" results in images 1/4 their default width.


Solution

  • You could create a breakpoints object, a function to do the repetitive math.floor of the division, then with some modern JS majicks, I think your code can be a lot shorter, less repetitive, yet just as readable, and, importantly, easy to change the breakpoints for example

    function sharpImg() {
        const BREAKPOINTS = {
            xs: 576,
            sm: 769,
            md: 992,
            lg: 1200,
            xl: 1400,
            xxl: 2048,
        };
        const onDiv = div => Object.entries(BREAKPOINTS).map(([bp, value]) => [Math.round(value / div), `-${bp}`]);
        // creates an array of [[1, "-xs"], [2, "-sm"], ... ] (obviously the values are 576/div etc)
        
        const div = arg.d || 1, bps = onDiv(div);
        
        const jpegOptions = { quality: 50, progressive: true };
        const webpOptions = { quality: 50 };
        const avifOptions = { quality: 50 };
        
        return src(['_images/original/process/**/*.{jpeg,jpg,png,tiff,webp}', '!_images/original/raw/**'])
            .pipe($.rename(function (path) {
                path.dirname += "/" + path.basename;
            }))
            .pipe($.sharpResponsive({
                formats: [
                    // jpeg
                    ...bps.map(([width, suffix]) => ({ width, format: "jpeg", rename: { suffix }, jpegOptions })),
                    // webp
                    ...bps.map(([width, suffix]) => ({ width, format: "webp", rename: { suffix }, webpOptions })),
                    // avif
                    ...bps.map(([width, suffix]) => ({ width, format: "avif", rename: { suffix }, avifOptions })),
                ]
            }))
            .pipe(dest('_images/processed'))
    }
    

    A snippet that outputs format to check if it's right

    function sharpImg() {
        const BREAKPOINTS = {
            xs: 576,
            sm: 769,
            md: 992,
            lg: 1200,
            xl: 1400,
            xxl: 2048,
        };
        const onDiv = div => Object.entries(BREAKPOINTS).map(([bp, value]) => [Math.round(value / div), `-${bp}`]);
        // creates an array of [[1, "-xs"], [2, "-sm"], ... ] (obviously the values are 576/div etc)
        
        const div = 1, bps = onDiv(div);
        
        const jpegOptions = { quality: 50, progressive: true };
        const webpOptions = { quality: 50 };
        const avifOptions = { quality: 50 };
        const formats = [
            // jpeg
            ...bps.map(([width, suffix]) => ({ width, format: "jpeg", rename: { suffix }, jpegOptions })),
            // webp
            ...bps.map(([width, suffix]) => ({ width, format: "webp", rename: { suffix }, webpOptions })),
            // avif
            ...bps.map(([width, suffix]) => ({ width, format: "avif", rename: { suffix }, avifOptions })),
        ];
        return formats;
    }
    console.log(JSON.stringify(sharpImg(), null, 4));