init from astro-components

This commit is contained in:
babayaga 2025-08-15 20:42:02 +02:00
parent 5303c638ce
commit 745d1061df
244 changed files with 20546 additions and 65 deletions

4
.gitignore vendored
View File

@ -1,4 +0,0 @@
/node_modules
/coverage
*.log
.DS_Store

View File

@ -1,4 +0,0 @@
./docs
./scripts
./tests
./incoming

View File

@ -1,9 +0,0 @@
Copyright (c) <year> <owner> All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,3 +0,0 @@
# osr-package-template
Package basics

View File

@ -1,45 +0,0 @@
{
"name": "@plastichub/template",
"description": "",
"version": "0.3.1",
"main": "main.js",
"typings": "index.d.ts",
"publishConfig": {
"access": "public"
},
"bin": {
"osr-bin": "main.js"
},
"dependencies": {
"@types/node": "^14.17.5",
"@types/yargs": "^17.0.2",
"chalk": "^2.4.1",
"convert-units": "^2.3.4",
"env-var": "^7.0.1",
"typescript": "^4.3.5",
"yargs": "^14.2.3",
"yargs-parser": "^15.0.3"
},
"scripts": {
"test": "tsc; mocha --full-trace mocha \"spec/**/*.spec.js\"",
"test-with-coverage": "istanbul cover node_modules/.bin/_mocha -- 'spec/**/*.spec.js'",
"lint": "tslint --project=./tsconfig.json",
"build": "tsc -p .",
"dev": "tsc -p . --declaration -w",
"typings": "tsc --declaration",
"docs": "npx typedoc src/index.ts",
"dev-test-watch": "mocha-typescript-watch"
},
"homepage": "https://git.osr-plastic.org/plastichub/lib-content",
"repository": {
"type": "git",
"url": "https://git.osr-plastic.org/plastichub/lib-content.git"
},
"engines": {
"node": ">= 14.0.0"
},
"license": "BSD-3-Clause",
"keywords": [
"typescript"
]
}

View File

@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true,
browser: true,
es2020: true,
},
parserOptions: {
ecmaVersion: 2022,
sourceType: "module",
},
plugins: ["unicorn"],
extends: ["eslint:recommended"],
rules: {
"unicorn/prefer-node-protocol": "error",
},
};

View File

@ -0,0 +1,65 @@
name: CI
on:
pull_request:
push:
branches:
- main
jobs:
lint:
env:
ASTRO_TELEMETRY_DISABLED: true
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PNPM
uses: pnpm/action-setup@v2.2.1
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: 16
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Prettier
run: pnpm run format:check
- name: ESLint
run: pnpm run lint
test:
name: "Test: ${{ matrix.os }} (node@${{ matrix.node_version }})"
env:
ASTRO_TELEMETRY_DISABLED: true
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
node_version: [14, 16]
include:
- os: windows-latest
node_version: 16
- os: macos-latest
node_version: 16
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup PNPM
uses: pnpm/action-setup@v2.2.1
- name: Setup node@${{ matrix.node_version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node_version }}
cache: "pnpm"
- name: Install dependencies
run: pnpm install
- name: Test
run: pnpm --filter astro-imagetools run test

20
packages/imagetools/.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# dependencies
node_modules/
# build output
dist/
# logs
*.log
# npm
package-lock.json
# macOS-specific files
.DS_Store
# env
*.env
# astro-imagetools
packages/astro-imagetools/astroViteConfigs.js

View File

@ -0,0 +1,4 @@
*.test.ts
test-fixtures
astroViteConfigs.js
vitest.config.ts

View File

@ -0,0 +1,2 @@
## force pnpm to hoist
shamefully-hoist = true

View File

@ -0,0 +1,2 @@
pnpm-lock.yaml
demo/dist

View File

@ -0,0 +1,9 @@
{
"overrides": [
{
"files": "**/*.astro",
"options": { "parser": "astro" }
}
],
"plugins": ["prettier-plugin-astro"]
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Rafid Muhymin Wafi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,39 @@
# **Astro ImageTools**
**Astro ImageTools** is a collection of tools for optimizing images, background images, and generating responsive images for the **Astro JS** framework.
## Features
Below is a short list of features that **Astro ImageTools** offers. For more information, please see component-specific or API-specific documentation.
- ✅ **Regular Image Optimization** (`<img>` and `<picture>`)
- ✅ **Background Image Optimization**
- ✅ **Responsive Images**
- ✅ **Simple and intuitive Art Direction API**
- ✅ **Lazy Loading**
- ✅ **Programmatic APIs**
- ✅ **Asynchronous Decoding**
- ✅ **Unique Breakpoints Calculation**
- ✅ **Preloading for urgent images**
- ✅ **SVG Tracing and Posterization**
- ✅ **100% Scoped CSS**
- ✅ **Four kind of Layouts: `constrained`, `fixed`, `fullWidth` & `fill`**
- ✅ **Three kind of Placeholder Images: `blurred`, `dominantColor` & `tracedSVG`**
- ✅ **Long list of supported Image Formats**
- ✅ **Long List of supported Configuration Options**
- ✅ **Supports Remote Images and Data URIs too**
- ✅ **Support for _`sharp`less_ Environments**
- ✅ **Both Memory-based and FS-based Caching for better Performance**
- ✅ **Respects to _Semantics of HTML_ as much as possible**
## Getting Started
To get started with **Astro ImageTools**, first check out the [Installation](https://astro-imagetools-docs.vercel.app/en/installation) documentation for instructions on how to install the `astro-imagetools` package.
If you are looking for the available components and APIs, please check out the [Components and APIs](https://astro-imagetools-docs.vercel.app/en/components-and-apis) documentation.
If you want to view live examples of the components, APIs, layouts, and placeholder images, check out the [Astro ImageTools Demo](https://astro-imagetools-demo.vercel.app/) website.
If you want to report any issues or have found a missing feature, please report it on [GitHub](https://github.com/RafidMuhymin/astro-imagetools/)!
Good luck out there, Astronaut. 🧑‍🚀

View File

@ -0,0 +1 @@
export default function importImage(url: string): Promise<string>;

View File

@ -0,0 +1,23 @@
import load from "../plugin/hooks/load.js";
import { getSrcPath } from "./utils/getSrcPath.js";
import getResolvedSrc from "./utils/getResolvedSrc.js";
export default async function importImage(path) {
try {
const { search, protocol, pathname } = new URL(path);
const { src: id, base } = await getResolvedSrc(
protocol === "data:" ? protocol + pathname : path
);
const src = (await load(id + search, base)).slice(16, -1);
return src;
} catch (error) {
const id = await getSrcPath(path);
const src = (await load(id)).slice(16, -1);
return src;
}
}

View File

@ -0,0 +1,6 @@
export { default as renderImg } from "./renderImg.js";
export { default as renderPicture } from "./renderPicture.js";
export { default as renderBackgroundImage } from "./renderBackgroundImage.js";
export { default as renderBackgroundPicture } from "./renderBackgroundPicture.js";
export { default as importImage } from "./importImage.js";
export { getImageDetails, loadImage } from "./utils/imagetools.js"

View File

@ -0,0 +1,8 @@
import type {
BackgroundImageConfigOptions,
BackgroundImageHTMLData,
} from "../types";
export default function renderBackgroundImage(
config: BackgroundImageConfigOptions
): Promise<BackgroundImageHTMLData>;

View File

@ -0,0 +1,159 @@
// @ts-check
import crypto from "node:crypto";
import getImage from "./utils/getImage.js";
import getLinkElement from "./utils/getLinkElement.js";
import getStyleElement from "./utils/getStyleElement.js";
import getFilteredProps from "./utils/getFilteredProps.js";
import getContainerElement from "./utils/getContainerElement.js";
export default async function renderBackgroundImage(props) {
const type = "BackgroundImage";
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
const {
src,
tag,
content,
preload,
attributes,
placeholder,
breakpoints,
backgroundSize,
backgroundPosition,
format,
fallbackFormat,
includeSourceFormat,
formatOptions,
artDirectives,
} = filteredProps;
const {
link: linkAttributes = {},
style: styleAttributes = {},
container: containerAttributes = {},
} = attributes;
const sizes = "";
const { uuid, images } = await getImage({
src,
type,
sizes,
format,
breakpoints,
placeholder,
artDirectives,
fallbackFormat,
includeSourceFormat,
formatOptions,
transformConfigs,
});
const className = `astro-imagetools-background-image-${uuid}`;
const { imagesizes } = images[images.length - 1];
const link = getLinkElement({ images, preload, imagesizes, linkAttributes });
const backgroundImageStylesArray = images.map(({ media, sources }) => {
const uuid = crypto.randomBytes(4).toString("hex").toUpperCase();
const fallbackUrlCustomVariable = `--astro-imagetools-background-image-fallback-url${uuid}`;
const newSources = {};
sources.forEach(({ src, format, srcset }) => {
const sources = srcset
.split(", ")
.map((source) => [
source.slice(0, source.lastIndexOf(" ")),
source.slice(source.lastIndexOf(" ") + 1, -1),
]);
sources.forEach(([path, width]) => {
if (!newSources[width]) {
newSources[width] = [];
}
newSources[width].push({ src, format, path });
});
});
const widths = Object.keys(newSources)
.map((width) => parseInt(width))
.reverse();
const maxWidth = Math.max(...widths);
const styles = widths
.map((width) => {
const sources = newSources[width];
const styles = sources
.map(
({ format, path }, i) =>
`
${i !== sources.length - 1 ? `.${format} ` : ""}.${className} {
background-repeat: no-repeat;
background-image: url(${path}),
var(${fallbackUrlCustomVariable});
background-size: ${backgroundSize};
background-position: ${backgroundPosition};
}
`
)
.reverse()
.join("");
return width === maxWidth
? styles
: `
@media screen and (max-width: ${width}px) {
${styles}
}
`;
})
.join("");
return {
fallbackUrlCustomVariable,
styles: media
? `
@media ${media} {
${styles}
}
`
: styles,
};
});
const containerStyles = `
.${className} {
position: relative;
${images
.map(({ fallback }, i) => {
const fallbackUrlCustomVariable =
backgroundImageStylesArray[i].fallbackUrlCustomVariable;
return `${fallbackUrlCustomVariable}: url("${encodeURI(fallback)}");`;
})
.join("\n")}
}
`;
const backgroundStyles =
backgroundImageStylesArray.map(({ styles }) => styles).join("\n") +
containerStyles;
const style = getStyleElement({ styleAttributes, backgroundStyles });
const htmlElement = getContainerElement({
tag,
content,
className,
containerAttributes,
});
return { link, style, htmlElement };
}

View File

@ -0,0 +1,8 @@
import type {
BackgroundPictureConfigOptions,
BackgroundPictureHTMLData,
} from "../types";
export default function renderBackgroundPicture(
config: BackgroundPictureConfigOptions
): Promise<BackgroundPictureHTMLData>;

View File

@ -0,0 +1,127 @@
// @ts-check
import getImage from "./utils/getImage.js";
import getImgElement from "./utils/getImgElement.js";
import getLinkElement from "./utils/getLinkElement.js";
import getStyleElement from "./utils/getStyleElement.js";
import getLayoutStyles from "./utils/getLayoutStyles.js";
import getFilteredProps from "./utils/getFilteredProps.js";
import getPictureElement from "./utils/getPictureElement.js";
import getBackgroundStyles from "./utils/getBackgroundStyles.js";
import getContainerElement from "./utils/getContainerElement.js";
export default async function renderBackgroundPicture(props) {
const type = "BackgroundPicture";
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
const {
src,
tag,
content,
sizes,
preload,
loading,
decoding,
attributes,
placeholder,
breakpoints,
objectFit,
objectPosition,
format,
fallbackFormat,
includeSourceFormat,
formatOptions,
fadeInTransition,
artDirectives,
} = filteredProps;
const {
img: imgAttributes = {},
link: linkAttributes = {},
style: styleAttributes = {},
picture: pictureAttributes = {},
container: containerAttributes = {},
} = attributes;
const { uuid, images } = await getImage({
src,
type,
sizes,
format,
breakpoints,
placeholder,
artDirectives,
fallbackFormat,
includeSourceFormat,
formatOptions,
transformConfigs,
});
const className = `astro-imagetools-picture-${uuid}`,
containerClassName = `astro-imagetools-background-picture-${uuid}`;
const { imagesizes } = images[images.length - 1];
const backgroundStyles = getBackgroundStyles(
images,
className,
objectFit,
objectPosition,
fadeInTransition,
{ isBackgroundPicture: true, containerClassName }
);
const style = getStyleElement({ styleAttributes, backgroundStyles });
const link = getLinkElement({ images, preload, imagesizes, linkAttributes });
const layoutStyles = getLayoutStyles({ isBackgroundImage: true });
// Background Images shouldn't convey important information
const alt = "";
const sources = images.flatMap(({ media, sources, sizes, imagesizes }) =>
sources.map(({ format, src, srcset }) =>
src
? getImgElement({
src,
alt,
sizes,
style,
srcset,
loading,
decoding,
imagesizes,
fadeInTransition,
layoutStyles,
imgAttributes,
})
: `<source
srcset="${srcset}"
sizes="${imagesizes}"
width="${sizes.width}"
height="${sizes.height}"
type="${`image/${format}`}"
${media ? `media="${media}"` : ""}
/>`
)
);
const picture = getPictureElement({
sources,
className,
layoutStyles,
pictureAttributes,
isBackgroundPicture: true,
});
const htmlElement = getContainerElement({
tag,
content: picture + content,
containerAttributes,
isBackgroundPicture: true,
containerClassName,
});
return { link, style, htmlElement };
}

View File

@ -0,0 +1,5 @@
import type { ImgConfigOptions, ImgHTMLData } from "../types";
export default function renderImg(
config: ImgConfigOptions
): Promise<ImgHTMLData>;

View File

@ -0,0 +1,93 @@
// @ts-check
import getImage from "./utils/getImage.js";
import getImgElement from "./utils/getImgElement.js";
import getLinkElement from "./utils/getLinkElement.js";
import getStyleElement from "./utils/getStyleElement.js";
import getLayoutStyles from "./utils/getLayoutStyles.js";
import getFilteredProps from "./utils/getFilteredProps.js";
import getBackgroundStyles from "./utils/getBackgroundStyles.js";
export default async function renderImg(props) {
const type = "Img";
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
const {
src,
alt,
sizes,
preload,
loading,
decoding,
attributes,
layout,
breakpoints,
placeholder,
objectFit,
objectPosition,
format,
formatOptions,
} = filteredProps;
const artDirectives = [],
fallbackFormat = format,
fadeInTransition = false,
includeSourceFormat = false;
const {
img: imgAttributes = {},
link: linkAttributes = {},
style: styleAttributes = {},
} = attributes;
const { uuid, images } = await getImage({
src,
type,
sizes,
format,
breakpoints,
placeholder,
artDirectives,
fallbackFormat,
includeSourceFormat,
formatOptions,
transformConfigs,
});
const className = `astro-imagetools-img-${uuid}`;
const { imagesizes } = images[images.length - 1];
const backgroundStyles = getBackgroundStyles(
images,
className,
objectFit,
objectPosition,
fadeInTransition,
{ isImg: true }
);
const style = getStyleElement({ styleAttributes, backgroundStyles })
const link = getLinkElement({ images, preload, imagesizes, linkAttributes })
const layoutStyles = getLayoutStyles({ layout })
const sources = images.flatMap(({ sources, sizes, imagesizes }) =>
sources.map(({ src, srcset }) =>
getImgElement({
src,
alt,
sizes,
style,
srcset,
loading,
decoding,
imagesizes,
fadeInTransition,
layoutStyles,
imgAttributes,
imgClassName: className,
})
)
)
const [img] = sources
return { link, style, img }
}

View File

@ -0,0 +1,5 @@
import type { PictureConfigOptions, PictureHTMLData } from "../types";
export default function renderPicture(
config: PictureConfigOptions
): Promise<PictureHTMLData>;

View File

@ -0,0 +1,111 @@
// @ts-check
import getImage from "./utils/getImage.js";
import getImgElement from "./utils/getImgElement.js";
import getLinkElement from "./utils/getLinkElement.js";
import getStyleElement from "./utils/getStyleElement.js";
import getLayoutStyles from "./utils/getLayoutStyles.js";
import getFilteredProps from "./utils/getFilteredProps.js";
import getPictureElement from "./utils/getPictureElement.js";
import getBackgroundStyles from "./utils/getBackgroundStyles.js";
export default async function renderPicture(props) {
const type = "Picture";
const { filteredProps, transformConfigs } = getFilteredProps(type, props);
const {
src,
alt,
sizes,
preload,
loading,
decoding,
attributes,
layout,
placeholder,
breakpoints,
objectFit,
objectPosition,
format,
fallbackFormat,
includeSourceFormat,
formatOptions,
fadeInTransition,
artDirectives,
} = filteredProps;
const {
img: imgAttributes = {},
link: linkAttributes = {},
style: styleAttributes = {},
picture: pictureAttributes = {},
} = attributes;
const { uuid, images } = await getImage({
src,
type,
sizes,
format,
breakpoints,
placeholder,
fallbackFormat,
includeSourceFormat,
formatOptions,
artDirectives,
transformConfigs,
});
const className = `astro-imagetools-picture-${uuid}`;
const { imagesizes } = images[images.length - 1];
const backgroundStyles = getBackgroundStyles(
images,
className,
objectFit,
objectPosition,
fadeInTransition
);
const style = getStyleElement({ styleAttributes, backgroundStyles });
const link = getLinkElement({ images, preload, imagesizes, linkAttributes });
const layoutStyles = getLayoutStyles({ layout });
const sources = images.flatMap(({ media, sources, sizes, imagesizes }) =>
sources.map(({ format, src, srcset }) =>
src
? getImgElement({
src,
alt,
sizes,
style,
srcset,
loading,
decoding,
imagesizes,
fadeInTransition,
layoutStyles,
imgAttributes,
})
: `<source
srcset="${srcset}"
sizes="${imagesizes}"
width="${sizes.width}"
height="${sizes.height}"
type="${`image/${format}`}"
${media ? `media="${media}"` : ""}
/>`
)
);
const picture = getPictureElement({
sources,
className,
layoutStyles,
pictureAttributes,
});
return { link, style, picture };
}

View File

@ -0,0 +1,38 @@
// @ts-check
import fs from "node:fs";
import { extname } from "node:path";
import * as codecs from "@astropub/codecs";
export async function getImageDetails(path, width, height, aspect) {
const extension = extname(path).slice(1);
const imageFormat = extension === "jpeg" ? "jpg" : extension;
const buffer = fs.readFileSync(path);
const decodedImage = await codecs.jpg.decode(buffer);
if (aspect && !width && !height) {
if (!width && !height) {
({ width } = decodedImage);
}
if (width) {
height = width / aspect;
}
if (height) {
width = height * aspect;
}
}
const image = await decodedImage.resize({ width, height });
const { width: imageWidth, height: imageHeight } = image;
return {
image,
imageWidth,
imageHeight,
imageFormat,
};
}

View File

@ -0,0 +1,137 @@
// @ts-check
import getSrcset from "./getSrcset.js";
import getConfigOptions from "./getConfigOptions.js";
import getFallbackImage from "./getFallbackImage.js";
import getProcessedImage from "./getProcessedImage.js";
export default async function getArtDirectedImages(
artDirectives = [],
placeholder,
format,
imagesizes,
breakpoints,
fallbackFormat,
includeSourceFormat,
formatOptions,
rest
) {
const images = await Promise.all(
artDirectives.map(
async ({
src,
media,
sizes: directiveImagesizes,
placeholder: directivePlaceholder,
breakpoints: directiveBreakpoints,
objectFit,
objectPosition,
backgroundSize,
backgroundPosition,
format: directiveFormat,
fallbackFormat: directiveFallbackFormat,
includeSourceFormat: directiveIncludeSourceFormat,
formatOptions: directiveFormatOptions = {},
...configOptions
}) => {
const {
path,
base,
rest: rest2,
image,
imageWidth,
imageHeight,
imageFormat,
} = await getProcessedImage(src, configOptions);
rest2.aspect = `${imageWidth / imageHeight}`;
const calculatedConfigs = getConfigOptions(
imageWidth,
directiveImagesizes || imagesizes,
directiveBreakpoints || breakpoints,
directiveFormat || format,
imageFormat,
directiveFallbackFormat || fallbackFormat,
directiveIncludeSourceFormat || includeSourceFormat
);
const { formats, requiredBreakpoints } = calculatedConfigs;
imagesizes = calculatedConfigs.imagesizes;
const maxWidth = requiredBreakpoints[requiredBreakpoints.length - 1];
const sources = await Promise.all(
formats.map(async (format) => {
const srcset = await getSrcset(
path,
base,
requiredBreakpoints,
format,
{
...rest,
...rest2,
...formatOptions[format],
...directiveFormatOptions[format],
}
);
return {
format,
srcset,
};
})
);
const sizes = {
width: maxWidth,
height: Math.round(maxWidth / rest2.aspect),
};
const object = {
fit: objectFit,
position: objectPosition,
};
const background = {
size: backgroundSize,
position: backgroundPosition,
};
const fallback = await getFallbackImage(
path,
directivePlaceholder || placeholder,
image,
imageFormat,
{ ...formatOptions, ...directiveFormatOptions },
{ ...rest, ...rest2 }
);
const returnValue = {
media,
sources,
sizes,
fallback,
imagesizes,
};
const isBackgroundImage = !!backgroundSize || !!backgroundPosition;
isBackgroundImage
? (returnValue.background = background)
: (returnValue.object = object);
return {
media,
sources,
sizes,
object,
fallback,
imagesizes,
};
}
)
);
return images;
}

View File

@ -0,0 +1,27 @@
// @ts-check
import printWarning from "../../utils/printWarning.js";
export default function getAttributesString({
attributes,
element = "",
excludeArray = [],
}) {
const attributesString = Object.keys(attributes)
.filter((key) => {
if (excludeArray.includes(key)) {
printWarning({
key,
element,
});
return false;
}
return true;
})
.map((key) => `${key}="${attributes[key]}"`)
.join(" ");
return attributesString;
}

View File

@ -0,0 +1,97 @@
// @ts-check
export default function getBackgroundStyles(
images,
className,
objectFit,
objectPosition,
fadeInTransition,
{ isImg = false, isBackgroundPicture = false, containerClassName = "" } = {}
) {
const sourcesWithFallback = images.filter(({ fallback }) => fallback);
if (sourcesWithFallback.length === 0) return "";
const staticStyles = !fadeInTransition
? ""
: `
${
isBackgroundPicture
? `
.${containerClassName} * {
z-index: 1;
position: relative;
}
`
: ""
}
.${className} {
--opacity: 1;
--z-index: 0;
}
${
!isBackgroundPicture
? `
.${className} img {
z-index: 1;
position: relative;
}
`
: ""
}
.${className}::after {
inset: 0;
content: "";
left: 0;
width: 100%;
height: 100%;
position: absolute;
pointer-events: none;
transition: opacity ${
typeof fadeInTransition !== "object"
? "1s"
: (() => {
const {
delay = "0s",
duration = "1s",
timingFunction = "ease",
} = fadeInTransition;
return `${duration} ${timingFunction} ${delay}`;
})()
};
opacity: var(--opacity);
z-index: var(--z-index);
}
`;
const dynamicStyles = images
.map(({ media, fallback, object }) => {
const elementSelector = className + (!isImg ? " img" : ""),
backgroundElementSelector =
className + (fadeInTransition ? "::after" : "");
const style = `
.${elementSelector} {
object-fit: ${object?.fit || objectFit};
object-position: ${object?.position || objectPosition};
}
.${backgroundElementSelector} {
background-size: ${object?.fit || objectFit};
background-image: url("${encodeURI(fallback)}");
background-position: ${object?.position || objectPosition};
}
`;
return media ? `@media ${media} { ${style} }` : style;
})
.reverse();
const backgroundStyles = [staticStyles, ...dynamicStyles].join("");
return backgroundStyles;
}

View File

@ -0,0 +1,77 @@
// @ts-check
import printWarning from "../../utils/printWarning.js";
export default function getBreakpoints(breakpoints, imageWidth) {
if (Array.isArray(breakpoints)) {
return breakpoints.sort((a, b) => a - b);
}
const { count, minWidth = 320 } = breakpoints || {};
const maxWidth = (() => {
if (breakpoints?.maxWidth) return breakpoints.maxWidth;
if (imageWidth > 3840) {
printWarning({
message:
"The width of the source image is greater than 3840px. The generated breakpoints will be capped at 3840px. If you need breakpoints larger than this, please pass the maxWidth option to the breakpoints property.",
});
return 3840;
}
return imageWidth;
})();
const breakPoints = [];
const diff = maxWidth - minWidth;
const n =
count ||
(maxWidth <= 400
? 1
: maxWidth <= 640
? 2
: maxWidth <= 800
? 3
: maxWidth <= 1024
? 4
: maxWidth <= 1280
? 5
: maxWidth <= 1440
? 6
: maxWidth <= 1920
? 7
: maxWidth <= 2560
? 8
: maxWidth <= 2880
? 9
: maxWidth <= 3840
? 10
: 11);
let currentWidth = minWidth;
n > 1 && breakPoints.push(currentWidth);
let steps = 0;
for (let i = 1; i < n; i++) {
steps += i;
}
const pixelsPerStep = diff / steps;
for (let i = 1; i < n - 1; i++) {
const next = pixelsPerStep * (n - i) + currentWidth;
breakPoints.push(Math.round(next));
currentWidth = next;
}
breakPoints.push(maxWidth);
return [...new Set(breakPoints)];
}

View File

@ -0,0 +1,34 @@
// @ts-check
import getBreakpoints from "./getBreakpoints.js";
export default function getConfigOptions(
imageWidth,
imagesizes,
breakpoints,
format,
imageFormat,
fallbackFormat,
includeSourceFormat
) {
const formats = [
...new Set(
[format, includeSourceFormat && imageFormat]
.flat()
.filter((f) => f && f !== fallbackFormat)
),
fallbackFormat,
];
const requiredBreakpoints = getBreakpoints(breakpoints, imageWidth);
imagesizes =
typeof imagesizes === "string"
? imagesizes
: imagesizes(requiredBreakpoints);
return {
formats,
imagesizes,
requiredBreakpoints,
};
}

View File

@ -0,0 +1,48 @@
// @ts-check
import getAttributesString from "./getAttributesString.js";
export default function getContainerElement({
tag,
content,
className = "",
containerAttributes,
isBackgroundPicture = false,
containerClassName = "",
}) {
const {
class: customClasses = "",
style: customInlineStyles = "",
...restContainerAttributes
} = containerAttributes;
const attributesString = getAttributesString({
attributes: restContainerAttributes,
});
const classAttribute = [
isBackgroundPicture
? "astro-imagetools-background-picture"
: "astro-imagetools-background-image",
isBackgroundPicture ? containerClassName : className,
customClasses,
]
.join(" ")
.trim();
const styleAttribute = [
isBackgroundPicture ? "position: relative;" : "",
customInlineStyles + (customInlineStyles.endsWith(";") ? "" : ";"),
]
.join(" ")
.trim();
const containerElement = `<${tag}
${attributesString}
class="${classAttribute}"
style="${styleAttribute}"
>
${content}
</${tag}>`;
return containerElement;
}

View File

@ -0,0 +1,58 @@
// @ts-check
import util from "node:util";
import potrace from "potrace";
import getSrcset from "./getSrcset.js";
import { sharp } from "../../utils/runtimeChecks.js";
export default async function getFallbackImage(
src,
placeholder,
image,
format,
formatOptions,
rest
) {
const base = null;
switch (placeholder) {
case "blurred": {
const dataUri = await getSrcset(src, base, [20], format, {
inline: true,
...rest,
...formatOptions[format],
});
return dataUri;
}
case "tracedSVG": {
const { function: fn, options } = formatOptions.tracedSVG;
const traceSVG = util.promisify(potrace[fn]);
const imageBuffer = sharp
? await image.toBuffer()
: Buffer.from(
(await image.encode(`image/${format === "jpg" ? "jpeg" : format}`))
.data
);
const tracedSVG = await traceSVG(imageBuffer, options);
return `data:image/svg+xml;utf8,${tracedSVG}`;
}
case "dominantColor": {
if (sharp) {
var { r, g, b } = (await image.stats()).dominant;
} else {
[r, g, b] = image.color;
}
const svg = `<svg xmlns="http://www.w3.org/2000/svg" style="background: rgb(${r},${g},${b})"></svg>`;
return `data:image/svg+xml;utf8,${svg}`;
}
default:
return null;
}
}

View File

@ -0,0 +1,138 @@
// @ts-check
import filterConfigs from "../../utils/filterConfigs.js";
import {
supportedConfigs,
GlobalConfigOptions,
} from "../../utils/runtimeChecks.js";
const GlobalOnlyProperties = ["cacheDir", "assetFileNames"];
const NonGlobalSupportedConfigs = supportedConfigs.filter(
(key) => !GlobalOnlyProperties.includes(key)
);
const NonProperties = {
Img: [
"tag",
"content",
"backgroundSize",
"backgroundPosition",
"fallbackFormat",
"includeSourceFormat",
"fadeInTransition",
"artDirectives",
],
Picture: ["tag", "content", "backgroundSize", "backgroundPosition"],
BackgroundImage: [
"alt",
"loading",
"decoding",
"layout",
"objectFit",
"objectPosition",
"fadeInTransition",
],
BackgroundPicture: ["alt", "backgroundSize", "backgroundPosition"],
};
const ImgProperties = NonGlobalSupportedConfigs.filter(
(key) => !NonProperties.Img.includes(key)
),
PictureProperties = NonGlobalSupportedConfigs.filter(
(key) => !NonProperties.Picture.includes(key)
),
BackgroundImageProperties = NonGlobalSupportedConfigs.filter(
(key) => !NonProperties.BackgroundImage.includes(key)
),
BackgroundPictureProperties = NonGlobalSupportedConfigs.filter(
(key) => !NonProperties.BackgroundPicture.includes(key)
);
const SupportedProperties = {
Img: ImgProperties,
Picture: PictureProperties,
BackgroundImage: BackgroundImageProperties,
BackgroundPicture: BackgroundPictureProperties,
};
export default function getFilteredProps(type, props) {
const filteredGlobalConfigs = filterConfigs(
"Global",
GlobalConfigOptions,
SupportedProperties[type],
{ warn: false }
);
const { search, searchParams } = new URL(props.src, "file://");
props.src = props.src.replace(search, "");
const paramOptions = Object.fromEntries(searchParams);
const filteredLocalProps = filterConfigs(
type,
{
...paramOptions,
...props,
},
SupportedProperties[type]
);
const resolvedProps = {
...filteredGlobalConfigs,
...filteredLocalProps,
};
const {
src,
alt,
tag = "section",
content = "",
sizes = function (breakpoints) {
const maxWidth = breakpoints[breakpoints.length - 1];
return `(min-width: ${maxWidth}px) ${maxWidth}px, 100vw`;
},
preload,
loading = preload ? "eager" : "lazy",
decoding = "async",
attributes = {},
layout = "constrained",
placeholder = "blurred",
breakpoints,
objectFit = "cover",
objectPosition = "50% 50%",
backgroundSize = "cover",
backgroundPosition = "50% 50%",
format = type === "Img" ? undefined : ["avif", "webp"],
fallbackFormat,
includeSourceFormat = true,
formatOptions = {
tracedSVG: {
function: "trace",
},
},
fadeInTransition = true,
artDirectives,
...transformConfigs
} = resolvedProps;
// prettier-ignore
const allProps = {
src, alt, tag, content, sizes, preload, loading, decoding, attributes, layout, placeholder,
breakpoints, objectFit, objectPosition, backgroundSize, backgroundPosition, format,
fallbackFormat, includeSourceFormat, formatOptions, fadeInTransition, artDirectives,
...transformConfigs,
};
const filteredProps = filterConfigs(
type,
allProps,
SupportedProperties[type],
{ warn: false }
);
return {
filteredProps,
transformConfigs,
};
}

View File

@ -0,0 +1,49 @@
import { describe, expect, it } from "vitest";
import getFilteredProps from "./getFilteredProps";
describe("getFilteredProps", () => {
it("should should merge in default props", () => {
const result = getFilteredProps("Img", { src: "/img.jpeg", alt: "alt" });
expect(result).toEqual({
filteredProps: {
alt: "alt",
attributes: {},
breakpoints: undefined,
decoding: "async",
format: undefined,
formatOptions: {
tracedSVG: {
function: "trace",
},
},
layout: "constrained",
loading: "lazy",
objectFit: "cover",
objectPosition: "50% 50%",
placeholder: "blurred",
preload: undefined,
sizes: expect.any(Function),
src: "/img.jpeg",
},
transformConfigs: {},
});
});
it("should accept empty string for `alt` prop on Img", () => {
const result = getFilteredProps("Img", { src: "/img.jpeg", alt: "" });
expect(result).toMatchObject({
filteredProps: {
alt: "",
},
});
});
it("should accept empty string for `alt` prop on Picture", () => {
const result = getFilteredProps("Picture", { src: "/img.jpeg", alt: "" });
expect(result).toMatchObject({
filteredProps: {
alt: "",
},
});
});
});

View File

@ -0,0 +1,107 @@
// @ts-check
import crypto from "node:crypto";
import objectHash from "object-hash";
import getImageSources from "./getImageSources.js";
import getProcessedImage from "./getProcessedImage.js";
import getArtDirectedImages from "./getArtDirectedImages.js";
import pMap from "p-map";
const imagesData = new Map();
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
export default async function ({
src,
type,
sizes: imagesizes,
format,
breakpoints,
placeholder,
fallbackFormat,
includeSourceFormat,
formatOptions,
artDirectives,
transformConfigs,
}) {
try {
const args = Array.from(arguments);
const hash = objectHash(args);
if (imagesData.has(hash)) {
return imagesData.get(hash);
}
const start = performance.now();
const { path, base, rest, image, imageWidth, imageHeight, imageFormat } =
await getProcessedImage(src, transformConfigs);
await delay(250);
src = path;
rest.aspect = `${imageWidth / imageHeight}`;
if (!fallbackFormat) {
fallbackFormat = imageFormat;
}
// Fetch both image sources and art-directed images
const [mainImage, artDirectedImages] = await pMap(
[
async () =>
await getImageSources(
src,
base,
image,
format,
imageWidth,
imagesizes,
breakpoints,
placeholder,
imageFormat,
formatOptions,
fallbackFormat,
includeSourceFormat,
rest
),
async () => {
await delay(250);
return await getArtDirectedImages(
artDirectives,
placeholder,
format,
imagesizes,
breakpoints,
fallbackFormat,
includeSourceFormat,
formatOptions,
rest
);
},
],
async (task) => await task(),
{ concurrency: 1 }
);
// Ensure artDirectedImages is an array
const images = Array.isArray(artDirectedImages) ? [...artDirectedImages, mainImage] : [mainImage];
const uuid = crypto.randomBytes(4).toString("hex").toUpperCase();
const returnObject = {
uuid,
images,
};
imagesData.set(hash, returnObject);
const end = performance.now();
console.log(
`Responsive Image sets generated for ${type} at ${args[0].src} in ${end - start}ms`
);
return returnObject;
} catch (error) {
console.error("Error processing images:", error);
throw error;
}
}

View File

@ -0,0 +1,91 @@
// @ts-check
import getSrcset from "./getSrcset.js";
import getConfigOptions from "./getConfigOptions.js";
import getFallbackImage from "./getFallbackImage.js";
import pMap from "p-map";
function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
export default async function getImageSources(
src,
base,
image,
format,
imageWidth,
imagesizes,
breakpoints,
placeholder,
imageFormat,
formatOptions,
fallbackFormat,
includeSourceFormat,
rest
) {
try {
const calculatedConfigs = getConfigOptions(
imageWidth,
imagesizes,
breakpoints,
format,
imageFormat,
fallbackFormat,
includeSourceFormat
);
const { formats, requiredBreakpoints } = calculatedConfigs;
imagesizes = calculatedConfigs.imagesizes;
const maxWidth = requiredBreakpoints[requiredBreakpoints.length - 1];
const sliceLength = -(maxWidth.toString().length + 2);
const sources = await pMap(
formats,
async (format) => {
try {
await delay(250);
const srcset = await getSrcset(src, base, requiredBreakpoints, format, {
...rest,
...formatOptions[format],
});
const srcsets = srcset.split(", ");
const srcObject =
format === fallbackFormat
? { src: srcsets[srcsets.length - 1].slice(0, sliceLength) }
: {};
return {
...srcObject,
format,
srcset,
};
} catch (error) {
console.error(`Error processing format ${format}:`, error);
return null;
}
},
{ concurrency: 1 }
);
const filteredSources = sources.filter(Boolean);
const sizes = {
width: maxWidth,
height: Math.round(maxWidth / rest.aspect),
};
const fallback = await getFallbackImage(
src,
placeholder,
image,
fallbackFormat,
formatOptions,
rest
)
return { sources: filteredSources, sizes, fallback, imagesizes };
} catch (error) {
console.error("Error in getImageSources:", error);
return { sources: [], sizes: {}, fallback: null, imagesizes: null };
}
}

View File

@ -0,0 +1,80 @@
// @ts-check
import getAttributesString from "./getAttributesString.js";
export default function getImgElement({
src,
alt,
sizes,
style,
srcset,
loading,
decoding,
imagesizes,
fadeInTransition,
layoutStyles,
imgAttributes,
imgClassName = "",
}) {
const {
class: customClasses = "",
style: customInlineStyles = "",
onload: customOnload = "",
...restImgAttributes
} = imgAttributes;
const attributesString = getAttributesString({
attributes: restImgAttributes,
element: "img",
excludeArray: [
"src",
"alt",
"srcset",
"sizes",
"width",
"height",
"loading",
"decoding",
],
});
const classAttribute = ["astro-imagetools-img", imgClassName, customClasses]
.join(" ")
.trim();
const styleAttribute = [
"display: inline-block; overflow: hidden; vertical-align: middle;",
customInlineStyles + (customInlineStyles.endsWith(";") ? "" : ";"),
layoutStyles,
]
.join(" ")
.trim();
const onloadAttribute = [
!imgClassName && style
? fadeInTransition
? `parentElement.style.setProperty('--z-index', 1); parentElement.style.setProperty('--opacity', 0);`
: `parentElement.style.backgroundImage = 'unset';`
: "",
customOnload,
]
.join(" ")
.trim();
const imgElement = `<img
${attributesString}
src="${src}"
${typeof alt === "string" ? `alt="${alt}"` : ""}
srcset="${srcset}"
sizes="${imagesizes}"
width="${sizes.width}"
height="${sizes.height}"
${loading ? `loading="${loading}"` : ""}
${decoding ? `decoding="${decoding}"` : ""}
class="${classAttribute}"
style="${styleAttribute}"
onload="${onloadAttribute}"
/>`;
return imgElement;
}

View File

@ -0,0 +1,16 @@
// @ts-check
export default function getLayoutStyles({
layout = null,
isBackgroundImage = false,
}) {
return isBackgroundImage
? "width: 100%; height: 100%;"
: layout === "fill"
? `width: 100%; height: 100%;`
: layout === "fullWidth"
? `width: 100%; height: auto;`
: layout === "fixed"
? ""
: "max-width: 100%; height: auto;";
}

View File

@ -0,0 +1,34 @@
// @ts-check
import getAttributesString from "./getAttributesString.js";
export default function getLinkElement({
images = [],
preload = "",
imagesizes = "",
linkAttributes,
}) {
const imagesrcset =
preload &&
images[images.length - 1]?.sources.find(
({ format: fmt }) => fmt === preload
)?.srcset;
const attributesString = getAttributesString({
element: "link",
attributes: linkAttributes,
excludeArray: ["as", "rel", "imagesizes", "imagesrcset"],
});
const linkElement =
preload && images.length
? `<link
${attributesString}
as="image"
rel="preload"
imagesizes="${imagesizes}"
imagesrcset="${imagesrcset}"
/>`
: "";
return linkElement;
}

View File

@ -0,0 +1,14 @@
import { describe, expect, it } from "vitest";
import getLinkElement from "./getLinkElement";
describe("getLinkElement", () => {
it("returns an empty string if preload is not set", () => {
const result = getLinkElement({ linkAttributes: {} });
expect(result).toBe("");
});
it("returns an empty string if no images are provided", () => {
const result = getLinkElement({ linkAttributes: {}, preload: "webp" });
expect(result).toBe("");
});
});

View File

@ -0,0 +1,43 @@
// @ts-check
import getAttributesString from "./getAttributesString.js";
export default function getPictureElement({
sources,
className,
layoutStyles,
pictureAttributes,
isBackgroundPicture = false,
}) {
const {
class: customClasses = "",
style: customInlineStyles = "",
...restPictureAttributes
} = pictureAttributes;
const attributesString = getAttributesString({
attributes: restPictureAttributes,
});
const classAttribute = ["astro-imagetools-picture", className, customClasses]
.join(" ")
.trim();
const styleAttribute = [
isBackgroundPicture
? `position: absolute; z-index: 0; width: 100%; height: 100%; display: inline-block;`
: `position: relative; display: inline-block;`,
customInlineStyles + (customInlineStyles.endsWith(";") ? "" : ";"),
layoutStyles,
]
.join(" ")
.trim();
const pictureElement = `<picture
${attributesString}
class="${classAttribute}"
style="${styleAttribute}"
>${sources.join("\n")}
</picture>`;
return pictureElement;
}

View File

@ -0,0 +1,63 @@
// @ts-check
import { fileURLToPath } from "node:url";
import { extname, relative, resolve } from "node:path";
import { getSrcPath } from "./getSrcPath.js";
import getResolvedSrc from "./getResolvedSrc.js";
import { cwd, sharp } from "../../utils/runtimeChecks.js";
import throwErrorIfUnsupported from "./throwErrorIfUnsupported.js";
const { getImageDetails } = await (sharp
? import("./imagetools.js")
: import("./codecs.js"));
export default async function getProcessedImage(src, transformConfigs) {
throwErrorIfUnsupported(src, extname(src).slice(1));
let base;
if (src.match("(http://|https://|data:image/).*")) {
({ src, base } = await getResolvedSrc(src));
} else {
const {
default: { isSsrBuild },
} = await import("../../astroViteConfigs.js");
if (isSsrBuild) {
const filename = fileURLToPath(import.meta.url);
const assetPath = resolve(filename, "../../client") + src;
src = "/" + relative(cwd, assetPath);
}
}
const {
w,
h,
ar,
width = w,
height = h,
aspect = ar,
...rest
} = transformConfigs;
const path = src.replace(/\\/g, `/`);
const { image, imageWidth, imageHeight, imageFormat } = await getImageDetails(
await getSrcPath(src),
width,
height,
aspect
);
return {
path,
base,
rest,
image,
imageWidth,
imageHeight,
imageFormat,
};
}

View File

@ -0,0 +1,50 @@
// @ts-check
import fs from "node:fs";
import crypto from "node:crypto";
import { join, parse, relative } from "node:path";
import throwErrorIfUnsupported from "./throwErrorIfUnsupported.js";
import {
cwd,
fsCachePath,
supportedImageTypes,
} from "../../utils/runtimeChecks.js";
const { fileTypeFromBuffer } = await import("file-type");
export default async function getResolvedSrc(src) {
const token = crypto.createHash("md5").update(src).digest("hex");
let filepath = fsCachePath + token;
const fileExists = (() => {
for (const type of supportedImageTypes) {
const fileExists = fs.existsSync(filepath + `.${type}`);
if (fileExists) {
filepath += `.${type}`;
return true;
}
}
})();
if (!fileExists) {
const buffer = Buffer.from(await (await fetch(src)).arrayBuffer());
const { ext } = (await fileTypeFromBuffer(buffer)) || {};
throwErrorIfUnsupported(src, ext);
filepath += `.${ext}`;
fs.writeFileSync(filepath, buffer);
}
const base = /^https?:/.test(src)
? parse(new URL(src).pathname).name
: undefined;
src = join("/", relative(cwd, filepath));
return { src, base };
}

View File

@ -0,0 +1,32 @@
import fs from "node:fs";
import path from "node:path";
// To strip off params when checking for file on disk.
const paramPattern = /\?.*/;
/**
* getSrcPath allows the use of `src` attributes relative to either the public folder or project root.
*
* It first checks to see if the src is a file relative to the project root.
* If the file isn't found, it will look in the public folder.
* Finally, if it still can't be found, the original input will be returned.
*/
export async function getSrcPath(src) {
const { default: astroViteConfigs } = await import(
"../../astroViteConfigs.js"
);
// If this is already resolved to a file, return it.
if (fs.existsSync(src.replace(paramPattern, ""))) return src;
const rootPath = path.join(astroViteConfigs.rootDir, src);
const rootTest = rootPath.replace(paramPattern, "");
if (fs.existsSync(rootTest)) return rootPath;
const publicPath = path.join(astroViteConfigs.publicDir, src);
const publicTest = publicPath.replace(paramPattern, "");
if (fs.existsSync(publicTest)) return publicPath;
// Fallback
return src;
}

View File

@ -0,0 +1,67 @@
import path from "node:path";
import { describe, expect, it, afterAll, vi } from "vitest";
import { getSrcPath } from "./getSrcPath";
vi.mock("../../astroViteConfigs.js", () => {
return {
default: {
rootDir: buildPath(),
// Custom publicDir
publicDir: buildPath("out"),
},
};
});
/**
* Build an absolute path to the target in the fixture directory
*/
function buildPath(target = "") {
return path.resolve(__dirname, "../../test-fixtures/getSrcPath", target);
}
describe("getLinkElement", () => {
afterAll(() => {
vi.unmock("../../astroViteConfigs.js");
});
it("finds a file in the root of the project", async () => {
const result = await getSrcPath("root.jpeg");
expect(result).toBe(buildPath("root.jpeg"));
});
it("finds a file in the public folder", async () => {
const result = await getSrcPath("out.jpeg");
expect(result).toBe(buildPath("out/out.jpeg"));
});
it("returns an absolute path unchanged, if it exists", async () => {
const result = await getSrcPath(buildPath("out/out.jpeg"));
expect(result).toBe(buildPath("out/out.jpeg"));
});
it("handles query parameters", async () => {
const result = await getSrcPath("root.jpeg?w=200");
expect(result).toBe(buildPath("root.jpeg?w=200"));
});
it("handles query parameters for public-resolved files", async () => {
const result = await getSrcPath("out.jpeg?w=200");
expect(result).toBe(buildPath("out/out.jpeg?w=200"));
});
it("returns the original input if the file is not found", async () => {
const result = await getSrcPath(
"https://cdn.nedis.com/images/products_high_res/TVRC2080BK_P30.JPG"
);
expect(result).toBe(
"https://cdn.nedis.com/images/products_high_res/TVRC2080BK_P30.JPG"
);
});
it("finds relative paths correctly", async () => {
const outResult = await getSrcPath("./out/out.jpeg");
const rootResult = await getSrcPath("./root.jpeg");
expect(outResult).toBe(buildPath("out/out.jpeg"));
expect(rootResult).toBe(buildPath("root.jpeg"));
});
});

View File

@ -0,0 +1,39 @@
// @ts-check
import { getSrcPath } from "./getSrcPath.js";
export default async function getSrcset(
src,
base,
breakpoints,
format,
options
) {
options = {
format,
w: breakpoints,
...options,
};
const keys = Object.keys(options);
const params = keys.length
? keys
.map((key) =>
Array.isArray(options[key])
? `&${key}=${options[key].join(";")}`
: `&${key}=${options[key]}`
)
.join("")
: "";
const id = `${src}?${params.slice(1)}`;
const fullPath = await getSrcPath(id);
const { default: load } = await import("../../plugin/hooks/load.js");
// @ts-ignore
const srcset = (await load(fullPath, base)).slice(16, -1);
return srcset;
}

View File

@ -0,0 +1,15 @@
// @ts-check
import getAttributesString from "./getAttributesString.js";
export default function getStyleElement({
styleAttributes,
backgroundStyles = "",
}) {
const attributesString = getAttributesString({
attributes: styleAttributes,
});
const styleElement = `<style ${attributesString}>${backgroundStyles}</style>`;
return styleElement;
}

View File

@ -0,0 +1,40 @@
// @ts-check
import {
builtins,
loadImage,
applyTransforms,
generateTransforms,
} from "imagetools-core";
export {
loadImage
} from "imagetools-core";
export async function getImageDetails(path, width, height, aspect) {
const loadedImage = loadImage(path);
if (aspect && !width && !height) {
if (!width && !height) {
({ width } = await loadedImage.metadata());
}
if (width) {
height = width / aspect;
}
if (height) {
width = height * aspect;
}
}
const { image, metadata } = await applyTransforms(
generateTransforms({ width, height }, builtins).transforms,
loadedImage
);
const {
width: imageWidth,
height: imageHeight,
format: imageFormat,
} = metadata;
return { image, imageWidth, imageHeight, imageFormat };
}

View File

@ -0,0 +1,14 @@
// @ts-check
import { supportedImageTypes } from "../../utils/runtimeChecks.js";
export default function throwErrorIfUnsupported(src, ext) {
if (!ext && typeof ext !== "string") {
throw new Error(`Failed to load ${src}; Invalid image format`);
}
if (ext && !supportedImageTypes.includes(ext.toLowerCase())) {
throw new Error(
`Failed to load ${src}; Invalid image format ${ext} or the format is not supported by astro-imagetools`
);
}
}

View File

@ -0,0 +1,12 @@
export default {
"environment": "dev",
"isSsrBuild": false,
"projectBase": "",
"publicDir": "C:\\Users\\zx\\Desktop\\polymech\\site2\\public\\",
"rootDir": "C:\\Users\\zx\\Desktop\\polymech\\site2\\",
"mode": "dev",
"outDir": "dist",
"assetsDir": "/_astro",
"sourcemap": false,
"assetFileNames": "/_astro/[name]@[width].[hash][extname]"
}

View File

@ -0,0 +1,46 @@
---
import renderBackgroundImage from "../api/renderBackgroundImage.js";
import type { BackgroundImageConfigOptions } from "../types.d";
const content = await Astro.slots.render("default");
declare interface Props
extends Pick<
BackgroundImageConfigOptions,
Exclude<keyof BackgroundImageConfigOptions, "content">
> {}
const { link, style, htmlElement } = await renderBackgroundImage({
content,
...(Astro.props as Props),
});
---
<Fragment set:html={link + style + htmlElement} />
<script>
const { classList } = document.documentElement;
const addClass = classList.add.bind(classList);
addClass("jpeg");
addClass("png");
const isFormatSupported = (format, dataUri) => {
const image = new Image();
image.src = `data:image/${format};base64,${dataUri}`;
image.onload = addClass(format);
};
// TODO: Check support for JXL images
// isFormatSupported("jxl", "/woAEBAJCAQBACwASxLFgoUJEP3D/wA=");
isFormatSupported("webp", "UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==");
isFormatSupported(
"avif",
"AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A="
);
</script>

View File

@ -0,0 +1,19 @@
---
import renderBackgroundPicture from "../api/renderBackgroundPicture.js";
import { BackgroundPictureConfigOptions } from "../types.d";
declare interface Props
extends Pick<
BackgroundPictureConfigOptions,
Exclude<keyof BackgroundPictureConfigOptions, "content">
> {}
const content = await Astro.slots.render("default");
const { link, style, htmlElement } = await renderBackgroundPicture({
content,
...(Astro.props as Props),
});
---
<Fragment set:html={link + style + htmlElement} />

View File

@ -0,0 +1,10 @@
---
import renderImage from "../api/renderImage.js";
import type { PictureConfigOptions as ImageConfigOptions } from "../types.d";
const { link, style, image } = await renderImage(
Astro.props as ImageConfigOptions
);
---
<Fragment set:html={link + style + image} />

View File

@ -0,0 +1,4 @@
<!-- prettier-ignore -->
<script is:inline>
const{classList:e}=document.documentElement,A=e.add.bind(e);A("jpeg");A("png");const g=(B,d)=>{const a=new Image;a.src=`data:image/${B};base64,${d}`,a.onload=A(B)};g("webp","UklGRhoAAABXRUJQVlA4TA0AAAAvAAAAEAcQERGIiP4HAA==");g("avif","AAAAIGZ0eXBhdmlmAAAAAGF2aWZtaWYxbWlhZk1BMUIAAADybWV0YQAAAAAAAAAoaGRscgAAAAAAAAAAcGljdAAAAAAAAAAAAAAAAGxpYmF2aWYAAAAADnBpdG0AAAAAAAEAAAAeaWxvYwAAAABEAAABAAEAAAABAAABGgAAAB0AAAAoaWluZgAAAAAAAQAAABppbmZlAgAAAAABAABhdjAxQ29sb3IAAAAAamlwcnAAAABLaXBjbwAAABRpc3BlAAAAAAAAAAIAAAACAAAAEHBpeGkAAAAAAwgICAAAAAxhdjFDgQ0MAAAAABNjb2xybmNseAACAAIAAYAAAAAXaXBtYQAAAAAAAAABAAEEAQKDBAAAACVtZGF0EgAKCBgANogQEAwgMg8f8D///8WfhwB8+ErK42A=");
</script>

View File

@ -0,0 +1,10 @@
---
import renderImg from "../api/renderImg.js";
import type { ImgConfigOptions } from "../types.d";
declare interface Props extends ImgConfigOptions {}
const { link, style, img } = await renderImg(Astro.props as Props);
---
<Fragment set:html={link + style + img} />

View File

@ -0,0 +1,10 @@
---
import renderPicture from "../api/renderPicture.js";
import type { PictureConfigOptions } from "../types.d";
declare interface Props extends PictureConfigOptions {}
const { link, style, picture } = await renderPicture(Astro.props as Props);
---
<Fragment set:html={link + style + picture} />

View File

@ -0,0 +1,5 @@
export { default as Img } from "./Img.astro";
export { default as Picture } from "./Picture.astro";
export { default as BackgroundImage } from "./BackgroundImage.astro";
export { default as BackgroundPicture } from "./BackgroundPicture.astro";
export { default as ImageSupportDetection } from "./ImageSupportDetection.astro";

3
packages/imagetools/config.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import type { GlobalConfigOptions } from "./types";
export function defineConfig(config: GlobalConfigOptions): GlobalConfigOptions;

View File

@ -0,0 +1,3 @@
export function defineConfig(config) {
return config;
}

View File

@ -0,0 +1,2 @@
## force pnpm to hoist
shamefully-hoist = true

View File

@ -0,0 +1,6 @@
{
"startCommand": "npm start",
"env": {
"ENABLE_CJS_IMPORTS": true
}
}

View File

@ -0,0 +1,5 @@
# Astro ImageTools Live Examples
This repository contains source code for the [**Astro ImageTools Demo**](https://astro-imagetools-demo.vercel.app) website.
The demo displays live examples of the components and APIs provided by the [**Astro ImageTools**](https://npmjs.com/package/astro-imagetools) library and the **Layouts** and **Placeholders** supported by the library.

View File

@ -0,0 +1,3 @@
import { defineConfig } from "astro-imagetools/config";
export default defineConfig({});

View File

@ -0,0 +1,23 @@
import { defineConfig } from "astro/config";
import { astroImageTools } from "astro-imagetools";
// https://astro.build/config
export default defineConfig({
vite: {
plugins: [
{
name: "import.meta.url-transformer",
transform: (code, id) => {
if (id.endsWith(".astro"))
return code.replace(/import.meta.url/g, `"${id}"`);
},
},
],
},
experimental: {
integrations: true,
},
integrations: [astroImageTools],
});

View File

@ -0,0 +1,23 @@
{
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"astro-spa": "^1.3.9",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@astrojs/lit": "^1.1.2",
"@astrojs/preact": "^2.0.2",
"@astrojs/react": "^2.0.2",
"@astrojs/solid-js": "^2.0.2",
"@astrojs/svelte": "^2.0.1",
"@astrojs/vue": "^2.0.1",
"astro": "^2.0.6",
"astro-imagetools": "workspace:^0.9.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

View File

@ -0,0 +1,11 @@
{
"infiniteLoopProtection": true,
"hardReloadOnChange": false,
"view": "browser",
"template": "node",
"container": {
"port": 3000,
"startScript": "start",
"node": "16"
}
}

1
packages/imagetools/demo/src/env.d.ts vendored Normal file
View File

@ -0,0 +1 @@
/// <reference types="astro/client" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

View File

@ -0,0 +1,15 @@
---
import { Picture } from "astro-imagetools/components";
import MainLayout from "./MainLayout.astro";
const { layout, importMetaUrl } = Astro.props;
---
<MainLayout {importMetaUrl}>
<h1><code>{layout}</code> Layout Example</h1>
<br />
<Picture src="https://picsum.photos/1024/768/" alt="A random image" {layout}
/>
</MainLayout>

View File

@ -0,0 +1,57 @@
---
import { Spa } from "astro-spa";
import "../styles/index.css";
const { importMetaUrl } = Astro.props as { importMetaUrl: string };
const path =
Astro.props.content?.path ||
importMetaUrl?.slice(importMetaUrl.indexOf("/pages/") + 7);
const GitHubURL = `https://github.com/RafidMuhymin/astro-imagetools/tree/main/demo/src/pages/${path}`;
---
<html>
<head>
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap-reboot.min.css"
crossorigin="anonymous"
/>
<!-- Unset bootstrap image resets -->
<style is:global>
img, svg {
vertical-align: unset;
}
</style>
</head>
<body>
<main>
<svg
xmlns="http://www.w3.org/2000/svg"
width="100"
height="100"
viewBox="0 0 250 250"
fill="#151513"
class="view-source"
>
<a title="View Source" href={GitHubURL}>
<path d="M0 0l115 115h15l12 27 108 108V0z" fill="#fff"></path>
<path
class="view-source_arm___1DMT"
d="M128 109c-15-9-9-19-9-19 3-7 2-11 2-11-1-7 3-2 3-2 4 5 2 11 2 11-3 10 5 15 9 16"
></path>
<path
d="M115 115s4 2 5 0l14-14c3-2 6-3 8-3-8-11-15-24 2-41 5-5 10-7 16-7 1-2 3-7 12-11 0 0 5 3 7 16 4 2 8 5 12 9s7 8 9 12c14 3 17 7 17 7-4 8-9 11-11 11 0 6-2 11-7 16-16 16-30 10-41 2 0 3-1 7-5 11l-12 11c-1 1 1 5 1 5z"
></path>
</a>
</svg>
<slot />
</main>
<Spa />
</body>
</html>

View File

@ -0,0 +1,18 @@
---
import { Picture } from "astro-imagetools/components";
import MainLayout from "./MainLayout.astro";
const { placeholder, importMetaUrl } = Astro.props;
---
<MainLayout {importMetaUrl}>
<h1><code>{placeholder}</code> Placeholder Example</h1>
<br />
<Picture
src="https://picsum.photos/1024/768/"
alt="A random image"
{placeholder}
/>
</MainLayout>

View File

@ -0,0 +1,48 @@
---
import { ImageSupportDetection } from "astro-imagetools/components";
import { renderBackgroundImage } from "astro-imagetools/api";
import MainLayout from "../../layouts/MainLayout.astro";
const { link, style, htmlElement } = await renderBackgroundImage({
src: "https://picsum.photos/1024/768/",
content: `<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet, nisi!
Commodi, dolor hic omnis debitis natus eaque nisi magni corrupti earum
aliquam at quidem dolorum?
</p>
<p>
Eos facere amet nesciunt! Vero, culpa eaque quasi ullam atque alias
repudiandae facere placeat ipsam recusandae quam minus nemo officia
reiciendis dicta quaerat maiores omnis.
</p>
<p>
Pariatur doloribus, facilis enim accusamus velit, amet ducimus dolorum
unde numquam doloremque eveniet eum et error, quod quas fugit commodi
suscipit sequi. At, deleniti nihil.
</p>
<p>
Laborum voluptate maxime dicta alias minus nam, doloribus accusantium
veritatis perferendis, expedita eaque. Deleniti deserunt, iure dolorum
itaque fugiat assumenda ullam amet asperiores soluta ipsam!
</p>
<p>
Veniam qui ad, illo fuga autem voluptatibus iusto cum reprehenderit error.
Nemo nisi laborum blanditiis, accusamus dolores harum labore perspiciatis.
Nesciunt, repellat mollitia. Rem, labore?
</p>`,
});
---
<ImageSupportDetection />
<MainLayout importMetaUrl={import.meta.url}>
<h1>Astro ImageTools <code>{"renderBackgroundImage"}</code> API Example</h1>
<br />
<Fragment set:html={link + style + htmlElement} />
</MainLayout>

View File

@ -0,0 +1,45 @@
---
import { renderBackgroundPicture } from "astro-imagetools/api";
import MainLayout from "../../layouts/MainLayout.astro";
const { link, style, htmlElement } = await renderBackgroundPicture({
src: "https://picsum.photos/1024/768/",
content: `<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet, nisi!
Commodi, dolor hic omnis debitis natus eaque nisi magni corrupti earum
aliquam at quidem dolorum?
</p>
<p>
Eos facere amet nesciunt! Vero, culpa eaque quasi ullam atque alias
repudiandae facere placeat ipsam recusandae quam minus nemo officia
reiciendis dicta quaerat maiores omnis.
</p>
<p>
Pariatur doloribus, facilis enim accusamus velit, amet ducimus dolorum
unde numquam doloremque eveniet eum et error, quod quas fugit commodi
suscipit sequi. At, deleniti nihil.
</p>
<p>
Laborum voluptate maxime dicta alias minus nam, doloribus accusantium
veritatis perferendis, expedita eaque. Deleniti deserunt, iure dolorum
itaque fugiat assumenda ullam amet asperiores soluta ipsam!
</p>
<p>
Veniam qui ad, illo fuga autem voluptatibus iusto cum reprehenderit error.
Nemo nisi laborum blanditiis, accusamus dolores harum labore perspiciatis.
Nesciunt, repellat mollitia. Rem, labore?
</p>`,
});
---
<MainLayout importMetaUrl={import.meta.url}>
<h1>Astro ImageTools <code>{"renderBackgroundPicture"}</code> API Example</h1>
<br />
<Fragment set:html={link + style + htmlElement} />
</MainLayout>

View File

@ -0,0 +1,17 @@
---
import { renderImg } from "astro-imagetools/api";
import MainLayout from "../../layouts/MainLayout.astro";
const { link, style, img } = await renderImg({
src: "https://picsum.photos/1024/768/",
alt: "A random image",
});
---
<MainLayout importMetaUrl={import.meta.url}>
<h1>Astro ImageTools <code>{"renderImg"}</code> API Example</h1>
<br />
<Fragment set:html={link + style + img} />
</MainLayout>

View File

@ -0,0 +1,17 @@
---
import { renderPicture } from "astro-imagetools/api";
import MainLayout from "../../layouts/MainLayout.astro";
const { link, style, picture } = await renderPicture({
src: "https://picsum.photos/1024/768/",
alt: "A random image",
});
---
<MainLayout importMetaUrl={import.meta.url}>
<h1>Astro ImageTools <code>{"renderPicture"}</code> API Example</h1>
<br />
<Fragment set:html={link + style + picture} />
</MainLayout>

View File

@ -0,0 +1,45 @@
---
import {
BackgroundImage,
ImageSupportDetection,
} from "astro-imagetools/components";
import MainLayout from "../../layouts/MainLayout.astro";
---
<ImageSupportDetection />
<MainLayout importMetaUrl={import.meta.url}>
<h1>
Astro ImageTools <code>{"<BackgroundImage />"}</code> Component Example
</h1>
<br />
<BackgroundImage src="https://picsum.photos/1024/768/">
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet, nisi!
Commodi, dolor hic omnis debitis natus eaque nisi magni corrupti earum
aliquam at quidem dolorum?
</p>
<p>
Eos facere amet nesciunt! Vero, culpa eaque quasi ullam atque alias
repudiandae facere placeat ipsam recusandae quam minus nemo officia
reiciendis dicta quaerat maiores omnis.
</p>
<p>
Pariatur doloribus, facilis enim accusamus velit, amet ducimus dolorum
unde numquam doloremque eveniet eum et error, quod quas fugit commodi
suscipit sequi. At, deleniti nihil.
</p>
<p>
Laborum voluptate maxime dicta alias minus nam, doloribus accusantium
veritatis perferendis, expedita eaque. Deleniti deserunt, iure dolorum
itaque fugiat assumenda ullam amet asperiores soluta ipsam!
</p>
<p>
Veniam qui ad, illo fuga autem voluptatibus iusto cum reprehenderit error.
Nemo nisi laborum blanditiis, accusamus dolores harum labore perspiciatis.
Nesciunt, repellat mollitia. Rem, labore?
</p>
</BackgroundImage>
</MainLayout>

View File

@ -0,0 +1,40 @@
---
import { BackgroundPicture } from "astro-imagetools/components";
import MainLayout from "../../layouts/MainLayout.astro";
---
<MainLayout importMetaUrl={import.meta.url}>
<h1>
Astro ImageTools <code>{"<BackgroundPicture />"}</code> Component Example
</h1>
<br />
<BackgroundPicture src="https://picsum.photos/1024/768/">
<p>
Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eveniet, nisi!
Commodi, dolor hic omnis debitis natus eaque nisi magni corrupti earum
aliquam at quidem dolorum?
</p>
<p>
Eos facere amet nesciunt! Vero, culpa eaque quasi ullam atque alias
repudiandae facere placeat ipsam recusandae quam minus nemo officia
reiciendis dicta quaerat maiores omnis.
</p>
<p>
Pariatur doloribus, facilis enim accusamus velit, amet ducimus dolorum
unde numquam doloremque eveniet eum et error, quod quas fugit commodi
suscipit sequi. At, deleniti nihil.
</p>
<p>
Laborum voluptate maxime dicta alias minus nam, doloribus accusantium
veritatis perferendis, expedita eaque. Deleniti deserunt, iure dolorum
itaque fugiat assumenda ullam amet asperiores soluta ipsam!
</p>
<p>
Veniam qui ad, illo fuga autem voluptatibus iusto cum reprehenderit error.
Nemo nisi laborum blanditiis, accusamus dolores harum labore perspiciatis.
Nesciunt, repellat mollitia. Rem, labore?
</p>
</BackgroundPicture>
</MainLayout>

View File

@ -0,0 +1,12 @@
---
import { Img } from "astro-imagetools/components";
import MainLayout from "../../layouts/MainLayout.astro";
---
<MainLayout importMetaUrl={import.meta.url}>
<h1>Astro ImageTools <code>{"<Img />"}</code> Component Example</h1>
<br />
<Img src="https://picsum.photos/1024/768/" alt="A random image" />
</MainLayout>

View File

@ -0,0 +1,12 @@
---
import { Picture } from "astro-imagetools/components";
import MainLayout from "../../layouts/MainLayout.astro";
---
<MainLayout importMetaUrl={import.meta.url}>
<h1>Astro ImageTools <code>{"<Picture />"}</code> Component Example</h1>
<br />
<Picture src="https://picsum.photos/1024/768/" alt="A random image" />
</MainLayout>

View File

@ -0,0 +1,78 @@
---
path: index.md
layout: ../layouts/MainLayout.astro
---
# Image Optimization in Astro with Astro ImageTools
This page demonstrates the usage of the [astro-imagetools](https://www.npmjs.com/package/astro-imagetools) library with live examples.
<hr />
## Components
- [`<Img />` Component](/components/Img)
- [`<Picture />` Component](/components/Picture)
- [`<BackgroundImage />` Component](/components/BackgroundImage)
- [`<BackgroundPicture />` Component](/components/BackgroundPicture)
## APIs
- [`renderImg` API](/api/renderImg)
- [`renderPicture` API](/api/renderPicture)
- [`renderBackgroundImage` API](/api/renderBackgroundImage)
- [`renderBackgroundPicture` API](/api/renderBackgroundPicture)
## Layout
The `layout` property tells the image to respond differently depending on the device size or the container size.
Select a layout below and try resizing the window or rotating your device to see how the image reacts.
- [Constrained Layout](/layout/constrained)
- [Fixed Layout](/layout/fixed)
- [Full Width Layout](/layout/fullWidth)
- [Fill Layout](/layout/fill)
<hr />
## Placeholder
The `placeholder` property tells the image what to show while loading.
- [Blurred Placeholder](/placeholder/blurred)
- [Dominant Color Placeholder](/placeholder/dominantColor)
- [Traced SVG Placeholder](/placeholder/tracedSVG)
- [No Placeholder](/placeholder/none)
<hr />
## Internal Image
The following is an example of a reference to an internal image in the `src` directory (`../images/elva-800w.jpg`).
![A father holding his beloved daughter in his arms](../images/elva-800w.jpg)
<hr />
## External Image
The following is an example of a reference to an external image (`https://picsum.photos/1024/768`).
![A random image](https://picsum.photos/1024/768)
<hr />
## Image in /public
This image is in the public directory (`/images/public.jpeg`).
![A random image](/images/public.jpeg)
<hr />
## Learn More
You can optionally configure many more things!
Checkout the [Astro ImageTools](https://astro-imagetools-docs.vercel.app/) documentation to learn more.

View File

@ -0,0 +1,5 @@
---
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
---
<LayoutsLayout layout="constrained" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,5 @@
---
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
---
<LayoutsLayout layout="fill" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,5 @@
---
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
---
<LayoutsLayout layout="fixed" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,5 @@
---
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
---
<LayoutsLayout layout="fullWidth" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,5 @@
---
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
---
<PlaceholderLayout placeholder="blurred" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,5 @@
---
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
---
<PlaceholderLayout placeholder="dominantColor" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,5 @@
---
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
---
<PlaceholderLayout placeholder="none" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,5 @@
---
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
---
<PlaceholderLayout placeholder="tracedSVG" importMetaUrl={import.meta.url} />

View File

@ -0,0 +1,19 @@
main {
padding: 1.5rem;
background-color: black;
color: white;
}
code::before {
content: "`";
}
code::after {
content: "`";
}
.view-source {
position: fixed;
top: 0;
right: 0;
}

View File

@ -0,0 +1,2 @@
# Expose Astro dependencies for `pnpm` users
shamefully-hoist=true

View File

@ -0,0 +1,6 @@
{
"startCommand": "npm start",
"env": {
"ENABLE_CJS_IMPORTS": true
}
}

View File

@ -0,0 +1,97 @@
# Astro ImageTools Docs <img align="right" valign="center" height="52" width="39" src="https://raw.githubusercontent.com/withastro/astro/main/assets/brand/logo.svg" alt="Astro logo" />
To all who come to this happy place: welcome.
This is the repo for [https://astro-imagetools-docs.vercel.app/](https://astro-imagetools-docs.vercel.app/).
This repo contains all the source code to build the docs site.
## We are Astro
Astro is a site builder for the web platform.
We want everyone to be successful building sites, and that means helping everyone understand how Astro works.
## You are Awesome
You can also help make the docs awesome.
Your feedback is welcome.
Your writing, editing, translating, designing, and developing skills are welcome.
You being a part of our community is welcome.
## Chat with Us
You can learn more about Astro, get support, and meet other devs in [our Discord community](https://astro.build/chat).
## Raise an Issue
Is something missing?
Is something wrong?
Could something be better?
Issues are a quick way for you to offer us feedback about the docs.
Before you share, please [see if your issue has already been reported](https://github.com/RafidMuhymin/astro-imagetools/issues).
## Edit a Page
Every page on [https://astro-imagetools-docs.vercel.app/](https://astro-imagetools-docs.vercel.app/) has an **Edit this page** button in the sidebar.
You can click that button to edit the source code for that page in **GitHub**.
After you make your changes, click **Commit changes**.
This will automatically create a [fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks) of the docs in your GitHub account with the changes.
Once your edits are ready in GitHub, follow the prompts to **create a pull request** and submit your changes for review.
## Develop
To begin developing locally, checkout this project from your machine.
```shell
git clone git@github.com:RafidMuhymin/astro-imagetools.git
```
You can install and run the project locally using [pnpm](https://pnpm.io/). Head to [the pnpm installation guide](https://pnpm.io/installation) to get that set up. Then, run the following from your terminal:
```shell
pnpm install
pnpm dev
```
If youre copying these instructions, remember to [configure this project as a fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/configuring-a-remote-for-a-fork).
```shell
git remote add upstream git@github.com:RafidMuhymin/astro-imagetools.git
```
At any point, create a branch for your contribution.
We are not strict about branch names.
```shell
git checkout -b add/klingon-language
```
Thats it.
As you [open a pull request](https://github.com/withastro/astro/compare), please include a clear title and description.
```markdown
# Add Klingon language to Getting Started page
This adds the Klingon language and also updates the sidebar and language selection components.
```
Thank you for helping make the docs awesome.
And please, [come chat with us](https://astro.build/chat) if you have any questions.
## Deploy
Every pull request generates a preview using **Vercel** for anyone to see.
Use the **Deploy Preview** of your pull request to review and share your changes.
The docs site will be automatically updated whenever pull requests are merged.
## Next Steps
- [Read the docs](https://astro-imagetools-docs.vercel.app/)
- [Fork the project](https://github.com/RafidMuhymin/astro-imagetools/fork)
- [Raise an issue](https://github.com/RafidMuhymin/astro-imagetools/issues/new)
- [Discuss the docs](https://discord.gg/cZDZU3hJHc)

View File

@ -0,0 +1,25 @@
import mdx from "@astrojs/mdx";
import react from "@astrojs/react";
import preact from "@astrojs/preact";
import { defineConfig } from "astro/config";
import AutoImport from "unplugin-auto-import/vite";
// https://astro.build/config
export default defineConfig({
site: "https://astro-imagetools-docs.vercel.app/",
integrations: [preact(), react(), mdx()],
vite: {
plugins: [
AutoImport({
include: [/\.astro$/, /\.mdx$/],
imports: [
{
"@astrojs/markdown-component": [["default", "Markdown"]],
"/src/components/CodeExample.astro": [["default", "CodeExample"]],
},
],
dts: "./auto-imports.d.ts",
}),
],
},
});

View File

@ -0,0 +1,7 @@
// Generated by 'unplugin-auto-import'
export {};
declare global {
const CodeExample: typeof import("../../../src/components/CodeExample.astro")["default"];
const Markdown: typeof import("@astrojs/markdown-component")["default"];
}

View File

@ -0,0 +1,28 @@
{
"name": "@example/docs",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview"
},
"dependencies": {
"@algolia/client-search": "^4.14.3",
"@astrojs/markdown-component": "^1.0.2",
"@astrojs/mdx": "^0.14.0",
"@docsearch/css": "^3.3.2",
"@docsearch/react": "^3.3.2",
"@types/react": "^18.0.26",
"preact": "^10.11.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"unplugin-auto-import": "^0.9.5"
},
"devDependencies": {
"@astrojs/preact": "^0.2.0",
"@astrojs/react": "^0.2.1",
"astro": "^1.9.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 731 KiB

Some files were not shown because too many files have changed in this diff Show More