Migrate from @astrojs/image to Astro Assets and Unpic

This commit is contained in:
prototypa
2023-08-13 17:34:30 -04:00
parent 9a350af269
commit 77817aa77e
33 changed files with 508 additions and 83 deletions

View File

@ -5,7 +5,6 @@ import { defineConfig } from 'astro/config';
import tailwind from '@astrojs/tailwind';
import sitemap from '@astrojs/sitemap';
import image from '@astrojs/image';
import mdx from '@astrojs/mdx';
import icon from 'astro-icon';
import partytown from '@astrojs/partytown';
@ -37,9 +36,7 @@ export default defineConfig({
applyBaseStyles: false,
}),
sitemap(),
image({
serviceEntryPoint: '@astrojs/image/sharp',
}),
mdx(),
icon({
include: {
@ -72,6 +69,10 @@ export default defineConfig({
remarkPlugins: [readingTimeRemarkPlugin],
},
experimental:{
assets: true
},
vite: {
resolve: {
alias: {

View File

@ -13,7 +13,6 @@
"lint:eslint": "eslint . --ext .js,.ts,.astro"
},
"devDependencies": {
"@astrojs/image": "^0.17.3",
"@astrojs/mdx": "^0.19.7",
"@astrojs/partytown": "^1.2.3",
"@astrojs/rss": "^2.4.4",
@ -45,7 +44,8 @@
"svgo": "3.0.2",
"tailwind-merge": "^1.14.0",
"tailwindcss": "^3.3.3",
"typescript": "^5.1.6"
"typescript": "^5.1.6",
"unpic": "^3.10.0"
},
"engines": {
"node": ">=16.12.0"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 276 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,19 +1,19 @@
---
import { Picture } from '@astrojs/image/components';
import type { ImageMetadata } from 'astro';
import { APP_BLOG_CONFIG } from '~/utils/config';
import type { Post } from '~/types';
import Image from '~/components/common/Image.astro';
import { findImage } from '~/utils/images';
import { getPermalink } from '~/utils/permalinks';
export interface Props {
post: Post;
}
const { post } = Astro.props;
const image = (await findImage(post.image)) as ImageMetadata | undefined;
const image = (await findImage(post.image));
---
<article class="mb-6 transition">
@ -21,15 +21,15 @@ const image = (await findImage(post.image)) as ImageMetadata | undefined;
{
image && (
<a href={getPermalink(post.permalink, 'post')}>
<Picture
<Image
src={image}
class="md:object-cover w-full md:w-auto md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
class="w-full md:h-full rounded shadow-lg bg-gray-400 dark:bg-slate-700"
widths={[400, 900]}
width={400}
height={224}
sizes="(max-width: 900px) 400px, 900px"
alt={post.title}
aspectRatio="16:9"
layout="cover"
loading="lazy"
decoding="async"
/>

View File

@ -1,7 +1,7 @@
---
import { Picture } from '@astrojs/image/components';
import type { ImageMetadata } from 'astro';
import { Icon } from 'astro-icon/components';
import Image from '~/components/common/Image.astro';
import PostTags from '~/components/blog/Tags.astro';
import { APP_BLOG_CONFIG } from '~/utils/config';
@ -27,10 +27,11 @@ const link = APP_BLOG_CONFIG?.post?.isEnabled ? getPermalink(post.permalink, 'po
<a class="relative block group" href={link ?? 'javascript:void(0)'}>
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg">
{image && (
<Picture
<Image
src={image}
class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
widths={[400, 900]}
width={900}
sizes="(max-width: 900px) 400px, 900px"
alt={post.title}
aspectRatio="16:9"

View File

@ -1,7 +1,7 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import Image from '~/components/common/Image.astro';
import PostTags from '~/components/blog/Tags.astro';
import SocialShare from '~/components/common/SocialShare.astro';
@ -53,19 +53,17 @@ const Content = post?.Content || null;
{
post.image ? (
<Picture
<Image
src={post.image}
class="max-w-full lg:max-w-6xl mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
class="max-w-full lg:max-w-[900px] mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
widths={[400, 900]}
sizes="(max-width: 900px) 400px, 900px"
alt={post?.excerpt || ''}
loading="eager"
aspectRatio={16 / 9}
width={900}
height={506}
loading="eager"
decoding="async"
background={undefined}
/>
) : (
<div class="max-w-3xl mx-auto px-4 sm:px-6">

View File

@ -0,0 +1,52 @@
---
import { findImage } from '~/utils/images';
import {
getImagesOptimized,
astroAsseetsOptimizer,
unpicOptimizer,
type ImageProps,
type AttributesProps
} from '~/utils/images-optimization';
type Props = ImageProps;
type ImageType = {
src: string;
attributes: AttributesProps;
}
const props = Astro.props;
if (props.alt === undefined || props.alt === null) {
throw new Error();
}
if (typeof props.width === 'string') {
props.width = parseInt(props.width);
}
if (typeof props.height === 'string') {
props.height = parseInt(props.height);
}
if (!props.loading) {
props.loading = 'lazy';
}
if (!props.decoding) {
props.decoding = 'async';
}
const _image = await findImage(props.src);
let image: ImageType | undefined = undefined;
if (_image !== null && typeof _image === 'object') {
image = await getImagesOptimized(_image, props, astroAsseetsOptimizer);
} else if (typeof _image === 'string' && (_image.startsWith('http://') || _image.startsWith('https://'))) {
image = await getImagesOptimized(_image, props, unpicOptimizer);
} else if (_image) {
image = await getImagesOptimized(_image, props);
}
---
{!image ? <Fragment /> : <img src={image.src} {...image.attributes} />}

View File

@ -1,9 +1,9 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import type { Content } from '~/types';
import Headline from '../ui/Headline.astro';
import WidgetWrapper from '../ui/WidgetWrapper.astro';
import Image from '~/components/common/Image.astro';
const {
title = await Astro.slots.render('title'),
@ -70,13 +70,13 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg"
width={500}
height={500}
widths={[400, 768]}
sizes="(max-width: 768px) 100vw, 432px"
aspectRatio="500:500"
layout="responsive"
{...(image as any)}
/>
)}

View File

@ -1,8 +1,8 @@
---
import { Picture } from '@astrojs/image/components';
import Headline from '~/components/ui/Headline.astro';
import ItemGrid from '~/components/ui/ItemGrid.astro';
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
import Image from '~/components/common/Image.astro';
import type { Features } from '~/types';
const {
@ -39,12 +39,12 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="w-full h-80 object-cover rounded-xl mx-auto bg-gray-500 shadow-lg"
width={0}
width="auto"
height={320}
widths={[400, 768]}
aspectRatio="16:7"
layout="fullWidth"
{...(image as any)}
/>
)}

View File

@ -1,6 +1,6 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import Image from '~/components/common/Image.astro';
const {
title = await Astro.slots.render('title'),
@ -77,11 +77,10 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="mx-auto rounded-md w-full"
widths={[400, 768, 1024, 2040]}
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
aspectRatio={1024 / 576}
loading="eager"
width={1024}
height={576}

View File

@ -1,6 +1,6 @@
---
import { Icon } from 'astro-icon/components';
import { Picture } from '@astrojs/image/components';
import Image from '~/components/common/Image.astro';
import { CallToAction } from '~/types';
export interface Props {
@ -88,11 +88,10 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="mx-auto rounded-md w-full"
widths={[400, 768, 1024, 2040]}
sizes="(max-width: 767px) 400px, (max-width: 1023px) 768px, (max-width: 2039px) 1024px, 2040px"
aspectRatio={600 / 600}
loading="eager"
width={600}
height={600}

View File

@ -1,8 +1,8 @@
---
import { Picture } from '@astrojs/image/components';
import WidgetWrapper from "~/components/ui/WidgetWrapper.astro";
import Timeline from "~/components/ui/Timeline.astro";
import Headline from "~/components/ui/Headline.astro";
import Image from '~/components/common/Image.astro';
import type { Steps } from "~/types";
const {
@ -37,13 +37,13 @@ const {
(typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="inset-0 object-cover object-top w-full rounded-md shadow-lg md:absolute md:h-full bg-gray-400 dark:bg-slate-700"
widths={[400, 768]}
sizes="(max-width: 768px) 100vw, 432px"
aspectRatio="432:768"
width={432}
height={768}
layout="cover"
src={image?.src}
alt={image?.alt || ""}
/>

View File

@ -1,9 +1,10 @@
---
import Headline from '~/components/ui/Headline.astro';
import WidgetWrapper from '~/components/ui/WidgetWrapper.astro';
import CTA from '~/components/ui/CTA.astro';
import Image from '~/components/common/Image.astro';
import type { Testimonials } from '~/types';
import CTA from '../ui/CTA.astro';
import { Picture } from '@astrojs/image/components';
const {
title = '',
@ -43,12 +44,12 @@ const {
{typeof image === 'string' ? (
<Fragment set:html={image} />
) : (
<Picture
<Image
class="h-10 w-10 rounded-full border border-slate-200 dark:border-slate-600"
width={40}
height={40}
widths={[400, 768]}
aspectRatio="1:1"
layout="fixed"
{...(image as any)}
/>
)}

View File

@ -2,7 +2,7 @@
publishDate: 2023-07-17T00:00:00Z
title: AstroWind template in depth
excerpt: While easy to get started, Astrowind is quite complex internally. This page provides documentation on some of the more intricate parts.
image: ~/assets/images/stickers.jpg
image: https://images.unsplash.com/photo-1534307671554-9a6d81f4d629?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1651&q=80
category: Documentation
tags:
- astro

View File

@ -2,7 +2,7 @@
publishDate: 2023-01-12T00:00:00Z
title: Get started with AstroWind to create a website using Astro and Tailwind CSS
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
image: ~/assets/images/do-more.jpg
image: https://images.unsplash.com/photo-1516216628859-9bccecab13ca?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2069&q=80
category: Tutorials
tags:
- astro

View File

@ -2,7 +2,7 @@
publishDate: 2023-01-06T00:00:00Z
title: How to customize AstroWind template to suit your branding
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
image: ~/assets/images/colors.jpg
image: https://images.unsplash.com/photo-1546984575-757f4f7c13cf?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80
tags:
- astro
- tailwind css

View File

@ -2,7 +2,7 @@
publishDate: 2023-01-09T00:00:00Z
title: Useful tools and resources to create a professional website
excerpt: Sint sit cillum pariatur eiusmod nulla pariatur ipsum. Sit laborum anim qui mollit tempor pariatur.
image: ~/assets/images/tools.jpg
image: https://images.unsplash.com/photo-1637144113536-9c6e917be447?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80
tags:
- front-end
- tools

2
src/env.d.ts vendored
View File

@ -1,3 +1,3 @@
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference path="../.astro/types.d.ts" />
/// <reference types="@astrojs/image/client" />
/// <reference types="astro/client-image" />

View File

@ -28,7 +28,7 @@ const metadata = merge(
},
openGraph: {
type: 'article',
...(image ? { images: [{ url: image?.src, width: image?.width, height: image?.height }] } : {}),
...(image ? { images: [{ url: image, width: image?.width, height: image?.height }] } : {}),
},
},
{ ...(post?.metadata ? { ...post.metadata, canonical: post.metadata?.canonical || url } : {}) }

View File

@ -14,7 +14,7 @@ const metadata = {
<Layout metadata={metadata}>
<!-- Hero Widget ******************* -->
<Hero image={{ src: import('~/assets/images/caos.jpg'), alt: 'Caos Image' }}>
<Hero image={{ src: 'https://images.unsplash.com/photo-1559136555-9303baea8ebd?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80', alt: 'Caos Image' }}>
<Fragment slot="title">
Elevate your online presence with our <br />
<span class="text-accent dark:text-white highlight"> Beautiful Website Templates</span>
@ -112,7 +112,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/colors.jpg'),
src: '~/assets/images/colors.jpg',
alt: 'Colorful Image',
}}
/>

View File

@ -24,7 +24,7 @@ const metadata = {
<Hero
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
callToAction2={{ text: 'Learn more', href: '#features' }}
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
image={{ src: '~/assets/images/hero.png', alt: 'AstroWind Hero Image' }}
>
<Fragment slot="title">
Free template for <span class="hidden xl:inline">creating websites with</span>
@ -116,7 +116,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1517134191118-9d595e4c8c2b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Colorful Image',
}}
>
@ -157,7 +157,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/vintage.jpg'),
src: 'https://images.unsplash.com/photo-1483058712412-4245e9b90334?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Vintage Image',
}}
>
@ -195,7 +195,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/creativity.jpg'),
src: 'https://images.unsplash.com/photo-1616198814651-e71f960c3180?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80',
alt: 'Steps image',
}}
/>

View File

@ -40,7 +40,7 @@ const metadata = {
<Hero2
callToAction={{ text: 'Download App', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
callToAction2={{ text: 'Learn more', href: '#features' }}
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
image={{ src: 'https://images.unsplash.com/photo-1535303311164-664fc9ec6532?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80', alt: 'AstroWind Hero Image' }}
>
<Fragment slot="title">
Free template for <span class="hidden lg:inline">create your website <br />with</span>
@ -91,7 +91,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/colors.jpg'),
src: 'https://images.unsplash.com/photo-1521517407911-565264e7d82d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2069&q=80',
alt: 'Colorful Image',
}}
/>
@ -99,6 +99,7 @@ const metadata = {
<!-- Content Widget **************** -->
<Content
isReversed
items={[
{
title: 'High-Quality Designs',
@ -128,7 +129,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1576153192621-7a3be10b356e?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1674&q=80',
alt: 'Colorful Image',
}}
>
@ -140,7 +141,6 @@ const metadata = {
<!-- Content Widget **************** -->
<Content
isReversed
isAfterContent
items={[
{
@ -171,7 +171,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/vintage.jpg'),
src: 'https://images.unsplash.com/photo-1453738773917-9c3eff1db985?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Vintage Image',
}}
>
@ -202,7 +202,7 @@ const metadata = {
name: 'Cary Kennedy',
job: 'Film director',
image: {
src: import('~/assets/images/colors.jpg'),
src: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Cary Kennedy Image',
},
},
@ -212,7 +212,7 @@ const metadata = {
name: 'Josh Wilkinson',
job: 'Product Manager',
image: {
src: import('~/assets/images/vintage.jpg'),
src: 'https://images.unsplash.com/flagged/photo-1570612861542-284f4c12e75f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Josh Wilkinson Image',
},
},
@ -222,7 +222,7 @@ const metadata = {
name: 'Sidney Hansen',
job: 'Decorator',
image: {
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1512361436605-a484bdb34b5f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Sidney Hansen Image',
},
},

View File

@ -3,6 +3,7 @@ import Layout from '~/layouts/PageLayout.astro';
import Header from '~/components/widgets/Header.astro';
import Hero2 from '~/components/widgets/Hero2.astro';
import Features from '~/components/widgets/Features.astro';
import Steps2 from '~/components/widgets/Steps2.astro';
import Content from '~/components/widgets/Content.astro';
import CallToAction from '~/components/widgets/CallToAction.astro';
@ -39,7 +40,7 @@ const metadata = {
<Hero2
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
callToAction2={{ text: 'Learn more', href: '#features' }}
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
image={{ src: 'https://images.unsplash.com/photo-1580481072645-022f9a6dbf27?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80', alt: 'AstroWind Hero Image' }}
>
<Fragment slot="title">
Free template for <br />
@ -56,6 +57,51 @@ const metadata = {
</Fragment>
</Hero2>
<!-- Features Widget *************** -->
<Features
id="features"
tagline="Features"
title="Main features of our templates"
subtitle="Possess several key characteristics to effectively cater to the needs of startups and entrepreneurs."
columns={3}
items={[
{
title: 'Modern and Professional Design',
description:
'Have a contemporary design that reflects current design trends and gives a professional impression.',
icon: 'tabler:artboard',
},
{
title: 'Responsive and Mobile-Friendly',
description: 'Adapt seamlessly to different screen sizes and devices to ensure a consistent experience.',
icon: 'tabler:picture-in-picture',
},
{
title: 'Customizability',
description:
'Easily customizable, allowing users to adapt the design, colors, typography, and content to match their brand identity.',
icon: 'tabler:adjustments-horizontal',
},
{
title: 'Fast Loading Times',
description: 'Optimized for speed to ensure a smooth user experience and favorable search engine rankings.',
icon: 'tabler:rocket',
},
{
title: 'Search Engine Optimization (SEO)',
description:
'Incorporate SEO best practices in template structure and code to improve visibility in search engine results.',
icon: 'tabler:arrows-right-left',
},
{
title: 'Compatibility',
description: 'The templates work seamlessly across various content management systems and website builders.',
icon: 'tabler:plug-connected',
},
]}
/>
<!-- Content Widget **************** -->
<Content
@ -78,7 +124,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1620558138198-cfb9b4f3c294?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1671&q=80',
alt: 'Colorful Image',
}}
>
@ -115,7 +161,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1531973486364-5fa64260d75b?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1658&q=80',
alt: 'Colorful Image',
}}
>
@ -153,7 +199,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/caos.jpg'),
src: 'https://images.unsplash.com/photo-1635070041078-e363dbe005cb?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80',
alt: 'Colorful Image',
}}
>

View File

@ -43,14 +43,18 @@ const metadata = {
</Fragment>
<Fragment slot="image">
<div class="relative h-0 pb-[56.25%]">
<iframe
width="560"
height="315"
src="https://www.youtube.com/embed/dsTXcSeAZq8"
title="YouTube video player"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture;"
allowfullscreen
style="width:100%"></iframe>
class="absolute top-0 left-0 w-full h-full"
>
</iframe>
</div>
</Fragment>
</Hero>

View File

@ -178,7 +178,7 @@ const metadata = {
},
]}
image={{
src: import('~/assets/images/creativity.jpg'),
src: 'https://images.unsplash.com/photo-1536816579748-4ecb3f03d72a?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=987&q=80',
alt: 'Steps image',
}}
/>

View File

@ -0,0 +1,321 @@
import { getImage } from 'astro:assets';
import { transformUrl, parseUrl } from 'unpic';
import type { ImageMetadata } from 'astro';
import type { HTMLAttributes } from 'astro/types';
type Layout = 'fixed' | 'constrained' | 'fullWidth' | 'cover' | 'responsive' | 'contained';
export interface AttributesProps extends HTMLAttributes<'img'> {}
export interface ImageProps extends Omit<HTMLAttributes<'img'>, 'src'> {
src?: string | ImageMetadata | null;
width?: string | number | null;
height?: string | number | null;
alt?: string | null;
loading?: 'eager' | 'lazy' | null;
decoding?: 'sync' | 'async' | 'auto' | null;
style?: string;
srcset?: string | null;
sizes?: string | null;
fetchpriority?: 'high' | 'low' | 'auto' | null;
layout?: Layout;
widths?: number[] | null;
aspectRatio?: string | number | null;
}
export type ImagesOptimizer = (
image: ImageMetadata | string,
breakpoints: number[],
width?: number,
height?: number
) => Promise<Array<{ src: string; width: number }>>;
/* ******* */
const config = {
// FIXME: Use this when image.width is minor than deviceSizes
imageSizes: [16, 32, 48, 64, 96, 128, 256, 384],
deviceSizes: [
640, // older and lower-end phones
750, // iPhone 6-8
828, // iPhone XR/11
960, // older horizontal phones
1080, // iPhone 6-8 Plus
1280, // 720p
1668, // Various iPads
1920, // 1080p
2048, // QXGA
2560, // WQXGA
3200, // QHD+
3840, // 4K
4480, // 4.5K
5120, // 5K
6016, // 6K
],
formats: ['image/webp'],
};
const computeHeight = (width: number, aspectRatio: number) => {
return Math.floor(width / aspectRatio);
};
const parseAspectRatio = (aspectRatio: number | string | null | undefined): number | undefined => {
if (typeof aspectRatio === 'number') return aspectRatio;
if (typeof aspectRatio === 'string') {
const match = aspectRatio.match(/(\d+)\s*[/:]\s*(\d+)/);
if (match) {
const [, num, den] = match.map(Number);
if (den && !isNaN(num)) return num / den;
} else {
const numericValue = parseFloat(aspectRatio);
if (!isNaN(numericValue)) return numericValue;
}
}
return undefined;
};
/**
* Gets the `sizes` attribute for an image, based on the layout and width
*/
export const getSizes = (width?: number, layout?: Layout): string | undefined => {
if (!width || !layout) {
return undefined;
}
switch (layout) {
// If screen is wider than the max size, image width is the max size,
// otherwise it's the width of the screen
case `constrained`:
return `(min-width: ${width}px) ${width}px, 100vw`;
// Image is always the same width, whatever the size of the screen
case `fixed`:
return `${width}px`;
// Image is always the width of the screen
case `fullWidth`:
return `100vw`;
default:
return undefined;
}
};
const pixelate = (value?: number) => (value || value === 0 ? `${value}px` : undefined);
const getStyle = ({
width,
height,
aspectRatio,
layout,
objectFit = 'cover',
objectPosition = 'center',
background,
}: {
width?: number;
height?: number;
aspectRatio?: number;
objectFit?: string;
objectPosition?: string;
layout?: string;
background?: string;
}) => {
const styleEntries: Array<[prop: string, value: string | undefined]> = [
['object-fit', objectFit],
['object-position', objectPosition],
];
// If background is a URL, set it to cover the image and not repeat
if (background?.startsWith('https:') || background?.startsWith('http:') || background?.startsWith('data:')) {
styleEntries.push(['background-image', `url(${background})`]);
styleEntries.push(['background-size', 'cover']);
styleEntries.push(['background-repeat', 'no-repeat']);
} else {
styleEntries.push(['background', background]);
}
if (layout === 'fixed') {
styleEntries.push(['width', pixelate(width)]);
styleEntries.push(['height', pixelate(height)]);
styleEntries.push(['object-position', 'top left']);
}
if (layout === 'constrained') {
styleEntries.push(['max-width', pixelate(width)]);
styleEntries.push(['max-height', pixelate(height)]);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
styleEntries.push(['width', '100%']);
}
if (layout === 'fullWidth') {
styleEntries.push(['width', '100%']);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
styleEntries.push(['height', pixelate(height)]);
}
if (layout === 'responsive') {
styleEntries.push(['width', '100%']);
styleEntries.push(['height', 'auto']);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
}
if (layout === 'contained') {
styleEntries.push(['max-width', '100%']);
styleEntries.push(['max-height', '100%']);
styleEntries.push(['object-fit', 'contain']);
styleEntries.push(['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]);
}
if (layout === 'cover') {
styleEntries.push(['max-width', '100%']);
styleEntries.push(['max-height', '100%']);
}
const styles = Object.fromEntries(styleEntries.filter(([, value]) => value));
return Object.entries(styles)
.map(([key, value]) => `${key}: ${value};`)
.join(' ');
};
const getBreakpoints = ({
width,
breakpoints,
layout,
}: {
width?: number;
breakpoints?: number[];
layout: Layout;
}): number[] => {
if (layout === 'fullWidth' || layout === 'cover' || layout === 'responsive' || layout === 'contained') {
return breakpoints || config.deviceSizes;
}
if (!width) {
return [];
}
const doubleWidth = width * 2;
if (layout === 'fixed') {
return [width, doubleWidth];
}
if (layout === 'constrained') {
return [
// Always include the image at 1x and 2x the specified width
width,
doubleWidth,
// Filter out any resolutions that are larger than the double-res image
...(breakpoints || config.deviceSizes).filter((w) => w < doubleWidth),
];
}
return [];
};
/* ** */
export const astroAsseetsOptimizer: ImagesOptimizer = async (image, breakpoints) => {
if (!image || typeof image === 'string') {
return [];
}
return Promise.all(
breakpoints.map(async (w: number) => {
const url = (await getImage({ src: image, width: w })).src;
return {
src: url,
width: w,
};
})
);
};
/* ** */
export const unpicOptimizer: ImagesOptimizer = async (image, breakpoints, width, height) => {
if (!image || typeof image !== 'string') {
return [];
}
const urlParsed = parseUrl(image);
if (!urlParsed) {
return [];
}
return Promise.all(
breakpoints.map(async (w: number) => {
const url =
(await transformUrl({
url: image,
width: w,
height: width && height ? computeHeight(w, width / height) : height,
cdn: urlParsed.cdn,
})) || image;
return {
src: String(url),
width: w,
};
})
);
};
/* ** */
export async function getImagesOptimized(
image: ImageMetadata | string,
{ src: _, width, height, sizes, aspectRatio, widths, layout = 'constrained', style = '', ...rest }: ImageProps,
transform: ImagesOptimizer = () => Promise.resolve([])
): Promise<{ src: string; attributes: AttributesProps }> {
if (typeof image !== 'string') {
width ||= Number(image.width) || undefined;
height ||= typeof width === 'number' ? computeHeight(width, image.width / image.height) : undefined;
}
width = (width && Number(width)) || undefined;
height = (height && Number(height)) || undefined;
widths ||= config.deviceSizes;
sizes ||= getSizes(Number(width) || undefined, layout);
aspectRatio = parseAspectRatio(aspectRatio);
// Calculate dimensions from aspect ratio
if (aspectRatio) {
if (width) {
if (height) {
/* empty */
} else {
height = width / aspectRatio;
}
} else if (height) {
width = Number(height * aspectRatio);
} else if (layout !== 'fullWidth') {
// Fullwidth images have 100% width, so aspectRatio is applicable
console.error('When aspectRatio is set, either width or height must also be set');
console.error('Image', image);
}
} else if (width && height) {
aspectRatio = width / height;
} else if (layout !== 'fullWidth') {
// Fullwidth images don't need dimensions
console.error('Either aspectRatio or both width and height must be set');
console.error('Image', image);
}
let breakpoints = getBreakpoints({ width: width, breakpoints: widths, layout: layout });
breakpoints = [...new Set(breakpoints)].sort((a, b) => a - b);
const srcset = (await transform(image, breakpoints, Number(width) || undefined, Number(height) || undefined))
.map(({ src, width }) => `${src} ${width}w`)
.join(', ');
return {
src: typeof image === 'string' ? image : image.src,
attributes: {
width: width,
height: height,
srcset: srcset || undefined,
sizes: sizes,
style: `${getStyle({
width: width,
height: height,
aspectRatio: aspectRatio,
layout: layout,
})}${style ?? ''}`,
...rest,
},
};
}

View File

@ -1,11 +1,11 @@
import { getImage } from '@astrojs/image';
import type { OpenGraph } from '@astrolib/seo/src/types';
import { getImage } from 'astro:assets';
import type { ImageMetadata } from 'astro';
import type { OpenGraph } from '@astrolib/seo/src/types';
const load = async function () {
let images: Record<string, () => Promise<unknown>> | undefined = undefined;
try {
images = import.meta.glob('~/assets/images/**');
images = import.meta.glob('~/assets/images/**/*.{jpeg,jpg,png,tiff,webp,gif,svg,JPEG,JPG,PNG,TIFF,WEBP,GIF,SVG}');
} catch (e) {
// continue regardless of error
}
@ -21,24 +21,27 @@ export const fetchLocalImages = async () => {
};
/** */
export const findImage = async (imagePath?: string) => {
export const findImage = async (imagePath?: string | ImageMetadata | null): Promise<string | ImageMetadata | undefined | null> => {
// Not string
if (typeof imagePath !== 'string') {
return null;
return imagePath;
}
// Absolute paths
if (imagePath.startsWith('http://') || imagePath.startsWith('https://') || imagePath.startsWith('/')) {
return imagePath;
}
if (!imagePath.startsWith('~/assets')) {
return null;
} // For now only consume images using ~/assets alias (or absolute)
// Relative paths or not "~/assets/"
if (!imagePath.startsWith('~/assets/images')) {
return imagePath;
}
const images = await fetchLocalImages();
const key = imagePath.replace('~/', '/src/');
return images && typeof images[key] === 'function'
? ((await images[key]()) as { default: unknown })['default']
? ((await images[key]()) as { default: ImageMetadata })['default']
: null;
};