init from astro-components
This commit is contained in:
parent
5303c638ce
commit
745d1061df
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
/node_modules
|
||||
/coverage
|
||||
*.log
|
||||
.DS_Store
|
||||
@ -1,4 +0,0 @@
|
||||
./docs
|
||||
./scripts
|
||||
./tests
|
||||
./incoming
|
||||
9
LICENSE
9
LICENSE
@ -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.
|
||||
45
package.json
45
package.json
@ -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"
|
||||
]
|
||||
}
|
||||
17
packages/imagetools/.eslintrc.js
Normal file
17
packages/imagetools/.eslintrc.js
Normal 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",
|
||||
},
|
||||
};
|
||||
65
packages/imagetools/.github/workflows/ci.yml
vendored
Normal file
65
packages/imagetools/.github/workflows/ci.yml
vendored
Normal 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
20
packages/imagetools/.gitignore
vendored
Normal 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
|
||||
4
packages/imagetools/.npmignore
Normal file
4
packages/imagetools/.npmignore
Normal file
@ -0,0 +1,4 @@
|
||||
*.test.ts
|
||||
test-fixtures
|
||||
astroViteConfigs.js
|
||||
vitest.config.ts
|
||||
2
packages/imagetools/.npmrc
Normal file
2
packages/imagetools/.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
## force pnpm to hoist
|
||||
shamefully-hoist = true
|
||||
2
packages/imagetools/.prettierignore
Normal file
2
packages/imagetools/.prettierignore
Normal file
@ -0,0 +1,2 @@
|
||||
pnpm-lock.yaml
|
||||
demo/dist
|
||||
9
packages/imagetools/.prettierrc
Normal file
9
packages/imagetools/.prettierrc
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"overrides": [
|
||||
{
|
||||
"files": "**/*.astro",
|
||||
"options": { "parser": "astro" }
|
||||
}
|
||||
],
|
||||
"plugins": ["prettier-plugin-astro"]
|
||||
}
|
||||
21
packages/imagetools/LICENSE
Normal file
21
packages/imagetools/LICENSE
Normal 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.
|
||||
39
packages/imagetools/README.md
Normal file
39
packages/imagetools/README.md
Normal 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. 🧑🚀
|
||||
1
packages/imagetools/api/importImage.d.ts
vendored
Normal file
1
packages/imagetools/api/importImage.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
export default function importImage(url: string): Promise<string>;
|
||||
23
packages/imagetools/api/importImage.js
Normal file
23
packages/imagetools/api/importImage.js
Normal 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;
|
||||
}
|
||||
}
|
||||
6
packages/imagetools/api/index.js
Normal file
6
packages/imagetools/api/index.js
Normal 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"
|
||||
8
packages/imagetools/api/renderBackgroundImage.d.ts
vendored
Normal file
8
packages/imagetools/api/renderBackgroundImage.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import type {
|
||||
BackgroundImageConfigOptions,
|
||||
BackgroundImageHTMLData,
|
||||
} from "../types";
|
||||
|
||||
export default function renderBackgroundImage(
|
||||
config: BackgroundImageConfigOptions
|
||||
): Promise<BackgroundImageHTMLData>;
|
||||
159
packages/imagetools/api/renderBackgroundImage.js
Normal file
159
packages/imagetools/api/renderBackgroundImage.js
Normal 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 };
|
||||
}
|
||||
8
packages/imagetools/api/renderBackgroundPicture.d.ts
vendored
Normal file
8
packages/imagetools/api/renderBackgroundPicture.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import type {
|
||||
BackgroundPictureConfigOptions,
|
||||
BackgroundPictureHTMLData,
|
||||
} from "../types";
|
||||
|
||||
export default function renderBackgroundPicture(
|
||||
config: BackgroundPictureConfigOptions
|
||||
): Promise<BackgroundPictureHTMLData>;
|
||||
127
packages/imagetools/api/renderBackgroundPicture.js
Normal file
127
packages/imagetools/api/renderBackgroundPicture.js
Normal 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 };
|
||||
}
|
||||
5
packages/imagetools/api/renderImg.d.ts
vendored
Normal file
5
packages/imagetools/api/renderImg.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import type { ImgConfigOptions, ImgHTMLData } from "../types";
|
||||
|
||||
export default function renderImg(
|
||||
config: ImgConfigOptions
|
||||
): Promise<ImgHTMLData>;
|
||||
93
packages/imagetools/api/renderImg.js
Normal file
93
packages/imagetools/api/renderImg.js
Normal 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 }
|
||||
}
|
||||
5
packages/imagetools/api/renderPicture.d.ts
vendored
Normal file
5
packages/imagetools/api/renderPicture.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
import type { PictureConfigOptions, PictureHTMLData } from "../types";
|
||||
|
||||
export default function renderPicture(
|
||||
config: PictureConfigOptions
|
||||
): Promise<PictureHTMLData>;
|
||||
111
packages/imagetools/api/renderPicture.js
Normal file
111
packages/imagetools/api/renderPicture.js
Normal 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 };
|
||||
}
|
||||
38
packages/imagetools/api/utils/codecs.js
Normal file
38
packages/imagetools/api/utils/codecs.js
Normal 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,
|
||||
};
|
||||
}
|
||||
137
packages/imagetools/api/utils/getArtDirectedImages.js
Normal file
137
packages/imagetools/api/utils/getArtDirectedImages.js
Normal 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;
|
||||
}
|
||||
27
packages/imagetools/api/utils/getAttributesString.js
Normal file
27
packages/imagetools/api/utils/getAttributesString.js
Normal 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;
|
||||
}
|
||||
97
packages/imagetools/api/utils/getBackgroundStyles.js
Normal file
97
packages/imagetools/api/utils/getBackgroundStyles.js
Normal 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;
|
||||
}
|
||||
77
packages/imagetools/api/utils/getBreakpoints.js
Normal file
77
packages/imagetools/api/utils/getBreakpoints.js
Normal 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)];
|
||||
}
|
||||
34
packages/imagetools/api/utils/getConfigOptions.js
Normal file
34
packages/imagetools/api/utils/getConfigOptions.js
Normal 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,
|
||||
};
|
||||
}
|
||||
48
packages/imagetools/api/utils/getContainerElement.js
Normal file
48
packages/imagetools/api/utils/getContainerElement.js
Normal 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;
|
||||
}
|
||||
58
packages/imagetools/api/utils/getFallbackImage.js
Normal file
58
packages/imagetools/api/utils/getFallbackImage.js
Normal 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;
|
||||
}
|
||||
}
|
||||
138
packages/imagetools/api/utils/getFilteredProps.js
Normal file
138
packages/imagetools/api/utils/getFilteredProps.js
Normal 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,
|
||||
};
|
||||
}
|
||||
49
packages/imagetools/api/utils/getFilteredProps.test.ts
Normal file
49
packages/imagetools/api/utils/getFilteredProps.test.ts
Normal 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: "",
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
107
packages/imagetools/api/utils/getImage.js
Normal file
107
packages/imagetools/api/utils/getImage.js
Normal 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;
|
||||
}
|
||||
}
|
||||
91
packages/imagetools/api/utils/getImageSources.js
Normal file
91
packages/imagetools/api/utils/getImageSources.js
Normal 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 };
|
||||
}
|
||||
}
|
||||
80
packages/imagetools/api/utils/getImgElement.js
Normal file
80
packages/imagetools/api/utils/getImgElement.js
Normal 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;
|
||||
}
|
||||
16
packages/imagetools/api/utils/getLayoutStyles.js
Normal file
16
packages/imagetools/api/utils/getLayoutStyles.js
Normal 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;";
|
||||
}
|
||||
34
packages/imagetools/api/utils/getLinkElement.js
Normal file
34
packages/imagetools/api/utils/getLinkElement.js
Normal 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;
|
||||
}
|
||||
14
packages/imagetools/api/utils/getLinkElement.test.ts
Normal file
14
packages/imagetools/api/utils/getLinkElement.test.ts
Normal 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("");
|
||||
});
|
||||
});
|
||||
43
packages/imagetools/api/utils/getPictureElement.js
Normal file
43
packages/imagetools/api/utils/getPictureElement.js
Normal 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;
|
||||
}
|
||||
63
packages/imagetools/api/utils/getProcessedImage.js
Normal file
63
packages/imagetools/api/utils/getProcessedImage.js
Normal 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,
|
||||
};
|
||||
}
|
||||
50
packages/imagetools/api/utils/getResolvedSrc.js
Normal file
50
packages/imagetools/api/utils/getResolvedSrc.js
Normal 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 };
|
||||
}
|
||||
32
packages/imagetools/api/utils/getSrcPath.js
Normal file
32
packages/imagetools/api/utils/getSrcPath.js
Normal 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;
|
||||
}
|
||||
67
packages/imagetools/api/utils/getSrcPath.test.ts
Normal file
67
packages/imagetools/api/utils/getSrcPath.test.ts
Normal 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"));
|
||||
});
|
||||
});
|
||||
39
packages/imagetools/api/utils/getSrcset.js
Normal file
39
packages/imagetools/api/utils/getSrcset.js
Normal 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;
|
||||
}
|
||||
15
packages/imagetools/api/utils/getStyleElement.js
Normal file
15
packages/imagetools/api/utils/getStyleElement.js
Normal 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;
|
||||
}
|
||||
40
packages/imagetools/api/utils/imagetools.js
Normal file
40
packages/imagetools/api/utils/imagetools.js
Normal 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 };
|
||||
}
|
||||
14
packages/imagetools/api/utils/throwErrorIfUnsupported.js
Normal file
14
packages/imagetools/api/utils/throwErrorIfUnsupported.js
Normal 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`
|
||||
);
|
||||
}
|
||||
}
|
||||
12
packages/imagetools/astroViteConfigs.js
Normal file
12
packages/imagetools/astroViteConfigs.js
Normal 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]"
|
||||
}
|
||||
46
packages/imagetools/components/BackgroundImage.astro
Normal file
46
packages/imagetools/components/BackgroundImage.astro
Normal 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>
|
||||
19
packages/imagetools/components/BackgroundPicture.astro
Normal file
19
packages/imagetools/components/BackgroundPicture.astro
Normal 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} />
|
||||
10
packages/imagetools/components/Image.astro
Normal file
10
packages/imagetools/components/Image.astro
Normal 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} />
|
||||
@ -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>
|
||||
10
packages/imagetools/components/Img.astro
Normal file
10
packages/imagetools/components/Img.astro
Normal 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} />
|
||||
10
packages/imagetools/components/Picture.astro
Normal file
10
packages/imagetools/components/Picture.astro
Normal 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} />
|
||||
5
packages/imagetools/components/index.js
Normal file
5
packages/imagetools/components/index.js
Normal 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
3
packages/imagetools/config.d.ts
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
import type { GlobalConfigOptions } from "./types";
|
||||
|
||||
export function defineConfig(config: GlobalConfigOptions): GlobalConfigOptions;
|
||||
3
packages/imagetools/config.mjs
Normal file
3
packages/imagetools/config.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
export function defineConfig(config) {
|
||||
return config;
|
||||
}
|
||||
2
packages/imagetools/demo/.npmrc
Normal file
2
packages/imagetools/demo/.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
## force pnpm to hoist
|
||||
shamefully-hoist = true
|
||||
6
packages/imagetools/demo/.stackblitzrc
Normal file
6
packages/imagetools/demo/.stackblitzrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"startCommand": "npm start",
|
||||
"env": {
|
||||
"ENABLE_CJS_IMPORTS": true
|
||||
}
|
||||
}
|
||||
5
packages/imagetools/demo/README.md
Normal file
5
packages/imagetools/demo/README.md
Normal 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.
|
||||
3
packages/imagetools/demo/astro-imagetools.config.mjs
Normal file
3
packages/imagetools/demo/astro-imagetools.config.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
import { defineConfig } from "astro-imagetools/config";
|
||||
|
||||
export default defineConfig({});
|
||||
23
packages/imagetools/demo/astro.config.mjs
Normal file
23
packages/imagetools/demo/astro.config.mjs
Normal 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],
|
||||
});
|
||||
23
packages/imagetools/demo/package.json
Normal file
23
packages/imagetools/demo/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
packages/imagetools/demo/public/favicon.ico
Normal file
BIN
packages/imagetools/demo/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
BIN
packages/imagetools/demo/public/images/public.jpeg
Normal file
BIN
packages/imagetools/demo/public/images/public.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 95 KiB |
11
packages/imagetools/demo/sandbox.config.json
Normal file
11
packages/imagetools/demo/sandbox.config.json
Normal 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
1
packages/imagetools/demo/src/env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="astro/client" />
|
||||
BIN
packages/imagetools/demo/src/images/elva-480w-close-portrait.jpg
Normal file
BIN
packages/imagetools/demo/src/images/elva-480w-close-portrait.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
packages/imagetools/demo/src/images/elva-800w.jpg
Normal file
BIN
packages/imagetools/demo/src/images/elva-800w.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 111 KiB |
15
packages/imagetools/demo/src/layouts/LayoutsLayout.astro
Normal file
15
packages/imagetools/demo/src/layouts/LayoutsLayout.astro
Normal 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>
|
||||
57
packages/imagetools/demo/src/layouts/MainLayout.astro
Normal file
57
packages/imagetools/demo/src/layouts/MainLayout.astro
Normal 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>
|
||||
18
packages/imagetools/demo/src/layouts/PlaceholderLayout.astro
Normal file
18
packages/imagetools/demo/src/layouts/PlaceholderLayout.astro
Normal 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
17
packages/imagetools/demo/src/pages/api/renderImg.astro
Normal file
17
packages/imagetools/demo/src/pages/api/renderImg.astro
Normal 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>
|
||||
17
packages/imagetools/demo/src/pages/api/renderPicture.astro
Normal file
17
packages/imagetools/demo/src/pages/api/renderPicture.astro
Normal 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>
|
||||
@ -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>
|
||||
@ -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>
|
||||
12
packages/imagetools/demo/src/pages/components/Img.astro
Normal file
12
packages/imagetools/demo/src/pages/components/Img.astro
Normal 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>
|
||||
12
packages/imagetools/demo/src/pages/components/Picture.astro
Normal file
12
packages/imagetools/demo/src/pages/components/Picture.astro
Normal 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>
|
||||
78
packages/imagetools/demo/src/pages/index.md
Normal file
78
packages/imagetools/demo/src/pages/index.md
Normal 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`).
|
||||
|
||||

|
||||
|
||||
<hr />
|
||||
|
||||
## External Image
|
||||
|
||||
The following is an example of a reference to an external image (`https://picsum.photos/1024/768`).
|
||||
|
||||

|
||||
|
||||
<hr />
|
||||
|
||||
## Image in /public
|
||||
|
||||
This image is in the public directory (`/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.
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
|
||||
---
|
||||
|
||||
<LayoutsLayout layout="constrained" importMetaUrl={import.meta.url} />
|
||||
5
packages/imagetools/demo/src/pages/layout/fill.astro
Normal file
5
packages/imagetools/demo/src/pages/layout/fill.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
|
||||
---
|
||||
|
||||
<LayoutsLayout layout="fill" importMetaUrl={import.meta.url} />
|
||||
5
packages/imagetools/demo/src/pages/layout/fixed.astro
Normal file
5
packages/imagetools/demo/src/pages/layout/fixed.astro
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
|
||||
---
|
||||
|
||||
<LayoutsLayout layout="fixed" importMetaUrl={import.meta.url} />
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
import LayoutsLayout from "../../layouts/LayoutsLayout.astro";
|
||||
---
|
||||
|
||||
<LayoutsLayout layout="fullWidth" importMetaUrl={import.meta.url} />
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
|
||||
---
|
||||
|
||||
<PlaceholderLayout placeholder="blurred" importMetaUrl={import.meta.url} />
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
|
||||
---
|
||||
|
||||
<PlaceholderLayout placeholder="dominantColor" importMetaUrl={import.meta.url} />
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
|
||||
---
|
||||
|
||||
<PlaceholderLayout placeholder="none" importMetaUrl={import.meta.url} />
|
||||
@ -0,0 +1,5 @@
|
||||
---
|
||||
import PlaceholderLayout from "../../layouts/PlaceholderLayout.astro";
|
||||
---
|
||||
|
||||
<PlaceholderLayout placeholder="tracedSVG" importMetaUrl={import.meta.url} />
|
||||
19
packages/imagetools/demo/src/styles/index.css
Normal file
19
packages/imagetools/demo/src/styles/index.css
Normal 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;
|
||||
}
|
||||
2
packages/imagetools/docs/.npmrc
Normal file
2
packages/imagetools/docs/.npmrc
Normal file
@ -0,0 +1,2 @@
|
||||
# Expose Astro dependencies for `pnpm` users
|
||||
shamefully-hoist=true
|
||||
6
packages/imagetools/docs/.stackblitzrc
Normal file
6
packages/imagetools/docs/.stackblitzrc
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"startCommand": "npm start",
|
||||
"env": {
|
||||
"ENABLE_CJS_IMPORTS": true
|
||||
}
|
||||
}
|
||||
97
packages/imagetools/docs/README.md
Normal file
97
packages/imagetools/docs/README.md
Normal 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 you’re 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
|
||||
```
|
||||
|
||||
That’s 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)
|
||||
25
packages/imagetools/docs/astro.config.mjs
Normal file
25
packages/imagetools/docs/astro.config.mjs
Normal 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",
|
||||
}),
|
||||
],
|
||||
},
|
||||
});
|
||||
7
packages/imagetools/docs/auto-imports.d.ts
vendored
Normal file
7
packages/imagetools/docs/auto-imports.d.ts
vendored
Normal 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"];
|
||||
}
|
||||
28
packages/imagetools/docs/package.json
Normal file
28
packages/imagetools/docs/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
BIN
packages/imagetools/docs/public/default-og-image.png
Normal file
BIN
packages/imagetools/docs/public/default-og-image.png
Normal file
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
Loading…
Reference in New Issue
Block a user