cyrilwanner / next-optimized-images

🌅 next-optimized-images automatically optimizes images used in next.js projects (jpeg, png, svg, webp and gif).

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

How would one dynamically inline an image based on a prop?

jrhager84 opened this issue · comments

Here is my Image wrapper. I would like to inline above-the-fold images so they don't pop in. I can't pass the prop in - it doesn't work.

Here's the structure I have:

import { AspectRatio } from 'react-aspect-ratio'

export default function Image({ alt = "decorative image", inline = 'false', lazy = true, src, divClasses, ratio = 1, ...rest }) {
  const imageName = src.slice(src.lastIndexOf('/'), src.lastIndexOf('.'))
  const extension = src.slice(src.lastIndexOf('.'))
  // slicing extension is for webpack compatibility
  
  return (
    <div className={divClasses} style={{ lineHeight: '0px' }}>
      <picture>
        <source srcSet={require(`../public/images${imageName}${extension}?webp`)} type="image/webp" />
        <source srcSet={require(`../public/images${imageName}${extension}`)} type={extension == '.jpg' || extension == '.jpeg' ? 'image/jpeg' : `image/${extension.slice(1)}`} />
        <AspectRatio ratio={ratio}>
          <img src={require(`../public/images${imageName}${extension}`)} alt={alt} {...rest} />
        </AspectRatio>
      </picture>
    </div>
  )
}

Here's what I would like to have:

import { AspectRatio } from 'react-aspect-ratio'

export default function Image({ alt = "decorative image", inline = 'false', lazy = true, src, divClasses, ratio = 1, ...rest }) {
  const imageName = src.slice(src.lastIndexOf('/'), src.lastIndexOf('.'))
  const extension = src.slice(src.lastIndexOf('.'))
  // slicing extension is for webpack compatibility
  
  return (
    <div className={divClasses} style={{ lineHeight: '0px' }}>
      <picture>
        <source srcSet={require(`../public/images${imageName}${extension}?webp${inline && '&inline'}`)} type="image/webp" />
        <source srcSet={require(`../public/images${imageName}${extension}${inline && '?inline'}`)} type={extension == '.jpg' || extension == '.jpeg' ? 'image/jpeg' : `image/${extension.slice(1)}`} />
        <AspectRatio ratio={ratio}>
          <img src={require(`../public/images${imageName}${extension}${inline && '?inline'}`)} alt={alt} {...rest} />
        </AspectRatio>
      </picture>
    </div>
  )
}
commented

Same problem. Any solution?

commented

You need to use babel compiler for replace param of image at the place of the component call

babel-plugin-replace-path-images.js

var babelParser = require('@babel/parser')

const nestedVisitor = {
    StringLiteral(path, opts) {
      try{
      
      var pathFile = path.node.value;

      if(pathFile && /\_\_load\_images\_\_/g.test(pathFile)){

        console.log('optimize image', pathFile)

        var newPath = pathFile.replace('__load_images__', ''),
            originPath = newPath;

        var pathRequest = newPath.match(/\?.*/g);

        if(pathRequest){
            pathRequest = pathRequest[0];
            newPath = newPath.replace(pathRequest, '')
        }

        if( !newPath ) return match;

        var resizeMode = pathRequest && /\\?resize/.test(pathRequest),
            webpMode = pathRequest && /\\?webp/.test(pathRequest),
            images = [], imagesString = '[';

        if(resizeMode){
          images.push(`require('public/static/${newPath}?resize&format=webp')`)

          if(/\.png/g.test(newPath)){
              images.push(`require('public/static/${newPath}?resize&format=png')`);
          }

          if(/\.jpg/g.test(newPath)){
              images.push(`require('public/static/${newPath}?resize&format=jpg')`);
          }
        }


        if(webpMode){
            images.push(`{path: require('public/static/${newPath}?webp')}`)
            images.push(`{path: require('public/static/${newPath}')}`)
        }

        if(!webpMode && !resizeMode){
          images.push(`{path: require('public/static/${originPath}')}`)
        }

        for(var item of images){
          imagesString += item + ','
        }

        imagesString += ']';

        var node = babelParser.parse(imagesString)

        path.replaceWith(
          node.program.body[0]
        );
        
    }

    }catch(err){
      console.log('err', err)
    }
  }

}

const nestedVisitor2 = {
  JSXExpressionContainer(path, opts){
    path.traverse(nestedVisitor, opts);
  }
}

module.exports = function({ types: t }) {
  return {
    visitor: {
      JSXAttribute(path) {

        path.traverse(nestedVisitor2, t);

      }
    }
  };
}

then apply plugin in babel.config.js

require('dotenv').config();

var DEV_VAR = process.env.DEV_VAR == 'true' ? false : true;

module.exports = function (api) {
  api.cache(DEV_VAR);

  return {
    presets: [
      ["next/babel"]
    ],
    "plugins": [
      ["./babel-plugin-replace-path-images.js"]
    ]
  }
}

It also works correctly on devices that do not support the webp image format.

My realization of image wrapper component -

import { Component } from 'react';
class Img extends Component {
    componentDidMount() {
        if (this.img.complete) {
            this.img.style.background = 'initial';
        }
    }
    render() {
        return(
            <img
                ref={el => this.img = el}

                onLoad={ e => {
                    e.target.style.background = 'initial'
                }}

                {...this.props}
            />
        )
    }
}

const Image = (props) => {

    if(!props.src) return null;

    var newProps = JSON.parse(JSON.stringify(props))

    var images = [], image, placeholder, addExtra = false;

    for(var item of props.src){
        if(item.images){
            if(!addExtra){
                addExtra = true;
                image = item.image;
                placeholder = item.placeholder;
            }
            for(var item2 of item.images){
                images.push(item2)
            }
        }else{
            images.push(item)
        }
    }

    var reversed = images.reverse();

    delete newProps.src;
    delete newProps.require;
    delete newProps.priority;

    if(placeholder){
        newProps.style = {background: `url(${placeholder})`}
    }

    if(!props.priority){
        newProps.loading = "lazy"
    }

    return(
        <picture>
            {reversed.map(item => {
                var sourceProps = {
                    srcSet: item.path,
                    type: 'image/'
                }

                if(/(\.png|data\:image\/png\;base64)/g.test(item.path)){
                    sourceProps.type += 'png'
                }else if(/(\.webp|data\:image\/webp\;base64)/g.test(item.path)){
                    sourceProps.type += 'webp'
                }else if(/(\.jpg|data\:image\/jpg\;base64)/g.test(item.path)){
                    sourceProps.type += 'jpeg'
                }else{
                   delete sourceProps.type;
                }

                if(item.width){
                    sourceProps.media = `(min-width: ${item.width - 10}px)`
                }
                return(
                    <source {...sourceProps} />
                )
            })}

            <Img
                src={image}
                {...newProps}
            />
        </picture>
    )
};

export default Image;

finally example how you can use it

<Image draggable="false" src={"__load_images__happyDeveloper.png?resize"}  alt="Счастливый разработчик" />

is there a easy solution to this ? the answer is not so clear to me