redwoodjs / redwood

The App Framework for Startups
https://redwoodjs.com
MIT License
17.18k stars 984 forks source link

[RFC]: Univerzal image component #8291

Open vanam opened 1 year ago

vanam commented 1 year ago

Summary

A universal image component for generic way of dealing with images without worrying whether it is a PNG, JPEG or SVG, ideally with more features such as optimization, loaders etc.

    <Image
      src="/profile.png"
      width={500}
      height={500}
      alt="Picture of the author"
    />

Inspiration: https://nextjs.org/docs/app/building-your-application/optimizing/images

Motivation

When I experimented with RedwoodJS I tried to use a SVG logo. I went to the Assets and Files docs page. Copied an example and replaced "png" with "svg" and boom...it didn't work.

import logo from './logo.svg'

const Header = () => {
  return (
    <header>
      {/* ... */}
      <img src={logo} alt="Logo" />
    </header>
  )
}

export default Header

I did some research and discovered several pitfalls:

import { MetaTags } from '@redwoodjs/web'

import Favicon from '/public/favicon.png'
import Logo from '/public/logo.svg'
import LogoNoXml from '/public/logo-no-xml.svg'
import LogoSimple from '/public/logo-simple.svg'

const HomePage = () => {
  return (
    <>
      <MetaTags title="Home" description="Home page" />

      <h1>HomePage</h1>
      <main className="flex min-h-screen flex-row items-center justify-between p-24">
        <img
          src={Favicon}
          width={30}
          height={15}
        />

        <img
          src='/logo.svg'
          width={30}
          height={15}
        />
        {/* Unexpected token (1:1) */}
        {/* <Logo /> */}
        <img
          src='/logo-no-xml.svg'
          width={30}
          height={15}
        />
        {/* name.name.toLowerCase is not a function */}
        {/* <LogoNoXml /> */}
        <img
          src='/logo-simple.svg'
          width={30}
          height={15}
        />
        {/* Type 'FC<SVGProps<SVGSVGElement>>' is not assignable to type 'string' */}
        {/* index.d.ts(2203, 9): The expected type comes from property 'src' which is declared here on type 'DetailedHTMLProps<ImgHTMLAttributes<HTMLImageElement>, HTMLImageElement>' */}
        <img
          src={LogoSimple}
          width={30}
          height={15}
        />
        <LogoSimple />
    </main>
    </>
  )
}

export default HomePage

SVGs are very fragile.

Standard SVG with <?xml> tag

import Logo from '/public/logo.svg'

fails with Unexpected token (1:1). Cannot inline SVG with <?xml tag.

SVG without <?xml> tag

import Logo from '/public/logo-no-xml.svg'

is the same logo but without the <?xml tag and it fails with name.name.toLowerCase is not a function. I guess it has some issue with the complexity of the SVG file.

Simple SVG

import Logo from '/public/logo-simple.svg'

is the simple logo and it works.

Of course, I can always just use plain ol' <img>

<img
          src='/logo.svg'
          width={30}
          height={15}
        />

Detailed proposal

I propose having an <Image> component which gives me single API when dealing with images.

Minimal version

Support for imported images or path.

import logo from '@/public/logo.svg'

...
      <Image
        src="/logo.svg"
        alt="logo"
      />
      <Image
        src={logo}
        alt="logo"
      />

Advanced features

Inspiration from Next.js

Are you interested in working on this?

dthyresson commented 1 year ago

Might a RedwoodJS version of https://github.com/ascorbic/unpic-img work?

thedavidprice commented 1 year ago

@vanam I can't stop thinking about this idea since you posted. Do you have experience with building components like this? Is there interest and/or availability on your end with helping work on this in some capacity?

vanam commented 1 year ago

I would love to participate, however, I am a backend developer (Java/PHP) with almost no experience with Typescript/React. I am currently at a level where I mostly copy&paste code from tutorials as I am rapid prototyping a tiny app :( Sorry

dthyresson commented 1 year ago

@vanam Could you try out just the React version of https://unpic.pics/img/react/ and see if those features match what you're looking for?

If so, perhaps can contribute a Redwood version or add the React version to the @redwoodjs/web package as an image component.

dthyresson commented 1 year ago

Also, @vanam please see the docs regarding svg's here: https://redwoodjs.com/docs/prerender#images-and-assets

re-exporting the SVG as a component requires a small change

image

vanam commented 1 year ago

Also, @vanam please see the docs regarding svg's here: https://redwoodjs.com/docs/prerender#images-and-assets

re-exporting the SVG as a component requires a small change

image

I tried that (https://github.com/vanam/svg-demo/blob/master/redwoodjs/web/src/components/LogoComponent/LogoComponent.tsx) but the result is the same. It seems to struggle with XML header and some other XML features when using svg's react component.

I also looked at https://unpic.pics/img/react/ and it has a fallback to regular <img src="logo.svg"> when not using a CDN. And I also cannot feed it with React.FC<React.SVGProps<SVGSVGElement>>. It is very nice library, though.

I understand why one cannot just place XML header at random place in DOM. I would love the image to be transformed so it can be safely used as a react component. That is just layman opinion. Might be too much work for little benefit, though.

The more I think about, the more I think I want to spoil myself. It would need some preprocessing of an image. I guess just a warning in docs would be enough to keep me from going down this rabbit hole. I can just avoid inlining SVGs without cleaning them up first.

dthyresson commented 1 year ago

You may want to look at https://imagekit.io and https://imgix.com as they offer CDN hosting for speedy image retrieval and caching and lots of transformations.

Supabase storage also has some transforms (and more will come): https://supabase.com/docs/guides/storage/image-transformations

pantheredeye commented 1 year ago

@dthyresson @vanam I am curious, after following along somewhat, is this issue and proposed solution still valid for RW, should it be enhanced, or should it be closed?

Benjamin-Lee commented 1 year ago

Just adding that I was looking for exactly this to use on the landing page of my build competition submission. My ideal API would be the same as Next's. I don't want to set up an image CDN to get decent performance on my landing page. For now, I'll just manually create a webp and fall back to that but I would love to see some DX improvements here.