Skip to content

fix (webpack-image-sizes-plugin): optimizes image generation and proc… #464

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

firestar300
Copy link
Contributor

@firestar300 firestar300 commented Jun 5, 2025

…essing

Improves the image generation process by adding change detection based on timestamps to avoid unnecessary rebuilds.

Refactors TPL parsing to extract comprehensive size and crop information, ensuring accurate image configuration.

Enhances default image generation by skipping existing images and updating only outdated ones.

Cleans up image locations data for output by removing internal properties.

Summary by Sourcery

Optimize WebpackImageSizesPlugin by adding timestamp-based rebuild skipping, enhancing TPL parsing with crop mapping, cleaning output data, and streamlining default image generation and build hooks

Enhancements:

  • Add change detection to skip image generation when no source or template files have changed
  • Refactor image sizes and locations generation to conditionally process JSON and TPL directories with improved error handling
  • Introduce parseTPLContent to extract comprehensive size and crop information from TPL files
  • Clean up output imageLocations by stripping internal properties via cleanImageLocationsForOutput
  • Optimize default image generation to skip existing images, update only outdated ones, and report generated vs skipped counts
  • Remove redundant watchRun hook and rely on the emit hook for builds

…essing

Improves the image generation process by adding change detection based on timestamps to avoid unnecessary rebuilds.

Refactors TPL parsing to extract comprehensive size and crop information, ensuring accurate image configuration.

Enhances default image generation by skipping existing images and updating only outdated ones.

Cleans up image locations data for output by removing internal properties.
Copy link

sourcery-ai bot commented Jun 5, 2025

Reviewer's Guide

Optimizes the image generation process by adding timestamp-based change detection, refactoring TPL parsing for accurate size and crop extraction, enhancing default image generation to skip or update files based on timestamps, cleaning up output image location data, and unifying directory existence checks with improved error handling.

Sequence Diagram for Image Generation Skip Logic

sequenceDiagram
    participant C as Compiler
    participant P as WebpackImageSizesPlugin
    participant FS as FileSystem

    C->>P: emit hook (triggers runGeneration)
    P->>P: shouldSkipGeneration(confImgPath, sizesPath, tplPath)
    alt lastGenerationTime is 0
        P-->>P: false (don't skip)
    else
        P->>FS: statSync(sizesPath / tplPath / files in dirs)
        FS-->>P: file/dir stats (mtime)
        P->>P: Compare mtime with lastGenerationTime
        alt any file/dir modified since lastGenerationTime
            P-->>P: false (don't skip)
        else no changes
            P-->>P: true (skip)
        end
    end
    P-->>P: result from shouldSkipGeneration()
    alt result is true (skip generation)
        P->>P: log("No changes detected, skipping generation")
        P->>C: callback() (generation skipped)
    else result is false (proceed with generation)
        P->>P: log("Starting WebpackImageSizesPlugin generation...")
        P->>P: Perform image locations and sizes generation
        P->>P: lastGenerationTime = Date.now()
        P->>P: cleanImageLocationsForOutput()
        P->>FS: writeFileSync(imageLocationsPath, cleanedImageLocations)
        P->>FS: writeFileSync(imageSizesPath, imageSizes)
        opt generateDefaultImages is true
          P->>P: generateDefaultImages(compiler.context, imageSizes)
        end
        P->>C: callback() (generation done)
    end
Loading

Sequence Diagram for Optimized Default Image Generation Process

sequenceDiagram
    participant P as WebpackImageSizesPlugin
    participant FS as FileSystem
    participant Sharp as SharpLibrary

    P->>P: generateDefaultImages(context, imageSizes)
    loop for each sizeKey in imageSizes
        P->>P: Determine outputPath for image
        P->>FS: existsSync(outputPath)
        alt outputPath exists
            FS-->>P: true
            P->>FS: statSync(sourceImagePath)
            FS-->>P: sourceStats (mtime)
            P->>FS: statSync(outputPath)
            FS-->>P: outputStats (mtime)
            alt sourceStats.mtime <= outputStats.mtime (image is up-to-date)
                P->>P: log("Skipped existing image")
            else source is newer or timestamp check failed (image outdated)
                P->>P: log("Updating/Regenerating outdated image")
                P->>Sharp: sharp(sourceImagePath).resize().toFile(outputPath)
                Sharp-->>P: Image processed
                P->>P: log("Generated/Updated image")
            end
        else outputPath does not exist (new image)
            FS-->>P: false
            P->>P: log("Generating new image")
            P->>Sharp: sharp(sourceImagePath).resize().toFile(outputPath)
            Sharp-->>P: Image processed
            P->>P: log("Generated new image")
        end
    end
    P->>P: log("Default image generation completed!")
Loading

Updated Class Diagram for WebpackImageSizesPlugin

classDiagram
  class WebpackImageSizesPlugin {
    +options: Object
    +lastGenerationTime: number
    +constructor(options)
    +apply(compiler): void
    +shouldSkipGeneration(confImgPath, sizesPath, tplPath): boolean
    +generateImageSizes(sizesPath, tplPath, imageLocations): Array
    +extractSizesFromTPLFiles(tplPath, imageLocations, imageSizesObj, allSizes): void
    +parseTPLContent(tplContent): Object
    +generateImageLocations(sizesPath, tplPath): Array
    +cleanImageLocationsForOutput(imageLocations): Array
    +generateDefaultImages(context, imageSizes): Promise~void~
    +log(level, message, ...args): void
    +deleteRemovedImages(outputDir, currentFiles): void
    +getSharpFormatOptions(format): Object
    +checkForRemovedFiles(confImgPath, imageLocationsPath, imageSizesPath): void
  }
Loading

File-Level Changes

Change Details Files
Add change detection to avoid unnecessary rebuilds
  • Initialize lastGenerationTime
  • Implement shouldSkipGeneration logic
  • Update compiler hooks to use skip logic
  • Update lastGenerationTime after generation
config/webpack-image-sizes-plugin.js
Refactor TPL parsing to extract comprehensive size and crop information
  • Extract crop values in existing TPL extraction
  • Add parseTPLContent method for unified parsing
  • Store sizeCropMapping for TPL-extracted sizes
config/webpack-image-sizes-plugin.js
Enhance default image generation to skip unchanged and update outdated images
  • Compare modification times before generating default images
  • Track and log generated vs skipped counts
  • Improve default image generation logging
config/webpack-image-sizes-plugin.js
Clean up output image locations by removing internal properties
  • Add cleanImageLocationsForOutput method
  • Use cleaned data for writing image-locations.json
config/webpack-image-sizes-plugin.js
Improve directory existence checks and error handling
  • Throw error if both sizes and tpl directories missing
  • Log warnings when one directory is missing
  • Unify existence checks in generateImageSizes and generateImageLocations
config/webpack-image-sizes-plugin.js

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @firestar300 - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines -120 to -121
compiler.hooks.watchRun.tapAsync('WebpackImageSizesPlugin', (compiler, callback) => {
this.log('log', '👀 Watch mode: checking for conf-img changes...')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Removal of watchRun hook may break incremental builds in watch mode.

If removal is intentional, verify that the emit hook reliably triggers on every rebuild in watch mode, or consider restoring watchRun to ensure updates are not missed.

Comment on lines +398 to +405
const result = {
srcsets: srcsets,
default_img: `default-${largestSize.size.replace('img-', '')}.${this.options.defaultImageFormat}`,
img_base: largestSize.size,
sizeCropMap: sizeCropMap, // Include crop mapping
}

return result
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const result = {
srcsets: srcsets,
default_img: `default-${largestSize.size.replace('img-', '')}.${this.options.defaultImageFormat}`,
img_base: largestSize.size,
sizeCropMap: sizeCropMap, // Include crop mapping
}
return result
return {
srcsets: srcsets,
default_img: `default-${largestSize.size.replace('img-', '')}.${this.options.defaultImageFormat}`,
img_base: largestSize.size,
sizeCropMap: sizeCropMap, // Include crop mapping
};


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

Comment on lines +654 to +665
const cleanedLocation = {
srcsets: location.srcsets.map((srcset) => ({
srcset: srcset.srcset,
size: srcset.size,
// Remove crop property from srcsets
})),
default_img: location.default_img,
img_base: location.img_base,
// Remove sizeCropMap property
}

return cleanedLocation
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Inline variable that is immediately returned (inline-immediately-returned-variable)

Suggested change
const cleanedLocation = {
srcsets: location.srcsets.map((srcset) => ({
srcset: srcset.srcset,
size: srcset.size,
// Remove crop property from srcsets
})),
default_img: location.default_img,
img_base: location.img_base,
// Remove sizeCropMap property
}
return cleanedLocation
return {
srcsets: location.srcsets.map((srcset) => ({
srcset: srcset.srcset,
size: srcset.size,
// Remove crop property from srcsets
})),
default_img: location.default_img,
img_base: location.img_base,
// Remove sizeCropMap property
};


ExplanationSomething that we often see in people's code is assigning to a result variable
and then immediately returning it.

Returning the result directly shortens the code and removes an unnecessary
variable, reducing the mental load of reading the function.

Where intermediate variables can be useful is if they then get used as a
parameter or a condition, and the name can act like a comment on what the
variable represents. In the case where you're returning it from a function, the
function name is there to tell you what the result is, so the variable name
is unnecessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant