javascriptnode.jsangularwebpackimagemin

Can you compress angular image assets on build?


What I want

I have very big images in my assets, which slows down the site by a lot for slower networks. (you can read more about the topic on this lighthouse linked page)


What I have tried

The most promising guide I have found for that is this one here, which describes how to use the imagemin package in combination with the ngx-build-plus package.

Unfortunately, after following the tutorial, I get the following error:

[error] TypeError: Cannot assign to read only property 'main.977fe6373cfd108d.js' of object '#<Object>'
    at ImageminPlugin._callee2$ (/.../node_modules/imagemin-webpack-plugin/dist/index.js:264:48)
    at tryCatch (/.../node_modules/babel-runtime/node_modules/regenerator-runtime/runtime.js:62:40)
     // ...

Is there any way to compress asset images on build?

Angular Version: 13.1.0

Note: I do not want to know how to upload images to third party storage solutions.
I specifically want to create a compressed version of my static assets on build time.


Solution

  • You can use a gulpfile with either gulp-responsive or gulp-sharp-responsive. I personally use the latter, because it has support for the AVIF format.

    To integrate it nicely with your Angular project, you can follow these steps:

    1. Install the dependencies: npm i --save-dev gulp gulp-sharp-responsive
    2. Create a gulpfile.js in your project root with the following content
    const { src, dest } = require("gulp");
    const sharpResponsive = require("gulp-sharp-responsive");
    
    const compress = () =>
      src("images/*.{png,jpg}")
        .pipe(
          sharpResponsive({
            formats: [
              // jpeg
              { width: 256, format: "jpeg", rename: { suffix: "-256" } },
              { width: 512, format: "jpeg", rename: { suffix: "-512" } },
              { width: 1024, format: "jpeg", rename: { suffix: "-1024" } },
              // webp
              { width: 256, format: "webp", rename: { suffix: "-256" } },
              { width: 512, format: "webp", rename: { suffix: "-512" } },
              { width: 1024, format: "webp", rename: { suffix: "-1024" } },
              // avif
              { width: 256, format: "avif", rename: { suffix: "-256" } },
              { width: 512, format: "avif", rename: { suffix: "-512" } },
              { width: 1024, format: "avif", rename: { suffix: "-1024" } },
            ],
          })
        )
        .pipe(dest("src/assets/compressed"));
    
    module.exports = {
      compress,
    };
    
    1. Create a folder in your project root, where your uncompressed image files are located (In this example it is called images)
    2. Add a preinstall script to your package.js, so that your gulpfile is called on every build
    "scripts": {
      "prebuild": "gulp compress",
      // ...
    },
    

    If you call npm run build now, it will compress your images and move them in the specified assets folder, before actually running ng build.

    Now you can use the image files with a picture/source combination like in the following snippet. Keep in mind that the order of the source tags is important.

    <!-- {{image}} is the image name -->
    <picture *ngIf="image">
      <!-- avif -->
      <source
        srcset="assets/compressed/{{image}}-256.avif"
        media="(max-width: 512px)"
        type="image/avif"
      />
      <source
        srcset="assets/compressed/{{image}}-512.avif"
        media="(max-width: 1024px)"
        type="image/avif"
      />
      <source
        srcset="assets/compressed/{{image}}-1024.avif"
        media="(max-width: 2048px)"
        type="image/avif"
      />
      <!-- webp -->
      <source
        srcset="assets/compressed/{{image}}-256.webp"
        media="(max-width: 512px)"
        type="image/webp"
      />
      <source
        srcset="assets/compressed/{{image}}-512.webp"
        media="(max-width: 1024px)"
        type="image/webp"
      />
      <source
        srcset="assets/compressed/{{image}}-1024.webp"
        media="(max-width: 2048px)"
        type="image/webp"
      />
      <!-- jpeg -->
      <source
        srcset="assets/compressed/{{image}}-256.jpg"
        media="(max-width: 512px)"
        type="image/jpeg"
      />
      <source
        srcset="assets/compressed/{{image}}-512.jpg"
        media="(max-width: 1024px)"
        type="image/jpeg"
      />
      <source
        srcset="assets/compressed/{{image}}-1024.jpg"
        media="(max-width: 2048px)"
        type="image/jpeg"
      />
      <!-- original -->
      <img src="assets/compressed/{{ image }}-1024.jpg" />
    </picture>