diff --git a/.gitignore b/.gitignore
index 3c3629e6..0ca34d04 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
node_modules
+astroViteConfigs.js
+demo/dist
diff --git a/.prettierignore b/.prettierignore
index bd5535a6..2a056f33 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1 +1,2 @@
pnpm-lock.yaml
+demo/dist
diff --git a/demo/public/images/public.jpeg b/demo/public/images/public.jpeg
new file mode 100644
index 00000000..d5b648a7
Binary files /dev/null and b/demo/public/images/public.jpeg differ
diff --git a/demo/src/pages/index.md b/demo/src/pages/index.md
index 5ab76966..66d5df0d 100644
--- a/demo/src/pages/index.md
+++ b/demo/src/pages/index.md
@@ -49,20 +49,28 @@ The `placeholder` property tells the image what to show while loading.
## Internal Image
-The following is an example of a reference to an internal image.
+The following is an example of a reference to an internal image in the `src` directory (`../images/elva-800w.jpg`).
-![A father holding his beloved daughter in his arms](../images//elva-800w.jpg)
+![A father holding his beloved daughter in his arms](../images/elva-800w.jpg)
## External Image
-The following is an example of a reference to an external image.
+The following is an example of a reference to an external image (`https://picsum.photos/1024/768`).
![A random image](https://picsum.photos/1024/768)
+## Image in /public
+
+This image is in the public directory (`/images/public.jpeg`).
+
+![A random image](/images/public.jpeg)
+
+
+
## Learn More
You can optionally configure many more things!
diff --git a/packages/astro-imagetools/.npmignore b/packages/astro-imagetools/.npmignore
new file mode 100644
index 00000000..4f257ea7
--- /dev/null
+++ b/packages/astro-imagetools/.npmignore
@@ -0,0 +1,4 @@
+*.test.ts
+test-fixtures
+astroViteConfigs.js
+vitest.config.ts
diff --git a/packages/astro-imagetools/api/utils/getProcessedImage.js b/packages/astro-imagetools/api/utils/getProcessedImage.js
index 4aa9e43c..90d3170f 100644
--- a/packages/astro-imagetools/api/utils/getProcessedImage.js
+++ b/packages/astro-imagetools/api/utils/getProcessedImage.js
@@ -9,6 +9,7 @@ import {
supportedImageTypes,
} from "../../utils/runtimeChecks.js";
import { fileURLToPath } from "node:url";
+import { getSrcPath } from "./getSrcPath.js";
const { getImageDetails } = await (sharp
? import("./imagetools.js")
@@ -93,7 +94,7 @@ export default async (src, transformConfigs) => {
const path = src.replace(/\\/g, `/`);
const { image, imageWidth, imageHeight, imageFormat } = await getImageDetails(
- `./${src}`,
+ getSrcPath(src),
width,
height,
aspect
diff --git a/packages/astro-imagetools/api/utils/getSrcPath.js b/packages/astro-imagetools/api/utils/getSrcPath.js
new file mode 100644
index 00000000..fd0253b4
--- /dev/null
+++ b/packages/astro-imagetools/api/utils/getSrcPath.js
@@ -0,0 +1,30 @@
+import fs from "fs";
+import path from "path";
+
+// To strip off params when checking for file on disk.
+const paramPattern = /\?.*/;
+
+const { default: astroViteConfigs } = await import("../../astroViteConfigs.js");
+
+/**
+ * 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 function getSrcPath(src) {
+ // 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;
+}
diff --git a/packages/astro-imagetools/api/utils/getSrcPath.test.ts b/packages/astro-imagetools/api/utils/getSrcPath.test.ts
new file mode 100644
index 00000000..21166a86
--- /dev/null
+++ b/packages/astro-imagetools/api/utils/getSrcPath.test.ts
@@ -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", () => {
+ const result = getSrcPath("root.jpeg");
+ expect(result).toBe(buildPath("root.jpeg"));
+ });
+
+ it("finds a file in the public folder", () => {
+ const result = getSrcPath("out.jpeg");
+ expect(result).toBe(buildPath("out/out.jpeg"));
+ });
+
+ it("returns an absolute path unchanged, if it exists", () => {
+ const result = getSrcPath(buildPath("out/out.jpeg"));
+ expect(result).toBe(buildPath("out/out.jpeg"));
+ });
+
+ it("handles query parameters", () => {
+ const result = getSrcPath("root.jpeg?w=200");
+ expect(result).toBe(buildPath("root.jpeg?w=200"));
+ });
+
+ it("handles query parameters for public-resolved files", () => {
+ const result = 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", () => {
+ const result = 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", () => {
+ const outResult = getSrcPath("./out/out.jpeg");
+ const rootResult = getSrcPath("./root.jpeg");
+ expect(outResult).toBe(buildPath("out/out.jpeg"));
+ expect(rootResult).toBe(buildPath("root.jpeg"));
+ });
+});
diff --git a/packages/astro-imagetools/api/utils/getSrcset.js b/packages/astro-imagetools/api/utils/getSrcset.js
index 0d9141b3..a4bf1743 100644
--- a/packages/astro-imagetools/api/utils/getSrcset.js
+++ b/packages/astro-imagetools/api/utils/getSrcset.js
@@ -1,5 +1,5 @@
// @ts-check
-import { cwd } from "../../utils/runtimeChecks.js";
+import { getSrcPath } from "./getSrcPath";
export default async function getSrcset(src, breakpoints, format, options) {
options = {
@@ -23,7 +23,7 @@ export default async function getSrcset(src, breakpoints, format, options) {
const id = `${src}?${params.slice(1)}`;
if (process.env.npm_lifecycle_event !== "dev") {
- const fullPath = cwd + id;
+ const fullPath = getSrcPath(id);
const { default: load } = await import("../../plugin/hooks/load.js");
diff --git a/packages/astro-imagetools/integration/index.js b/packages/astro-imagetools/integration/index.js
index 173b46ae..351d374b 100644
--- a/packages/astro-imagetools/integration/index.js
+++ b/packages/astro-imagetools/integration/index.js
@@ -31,6 +31,8 @@ export default {
environment,
isSsrBuild,
projectBase,
+ publicDir: fileURLToPath(config.publicDir.href),
+ rootDir: fileURLToPath(config.root.href),
};
await fs.promises.writeFile(
diff --git a/packages/astro-imagetools/plugin/hooks/load.js b/packages/astro-imagetools/plugin/hooks/load.js
index f339b62f..4659c034 100644
--- a/packages/astro-imagetools/plugin/hooks/load.js
+++ b/packages/astro-imagetools/plugin/hooks/load.js
@@ -1,6 +1,7 @@
// @ts-check
import path from "node:path";
import objectHash from "object-hash";
+import { getSrcPath } from "../../api/utils/getSrcPath.js";
import { getCachedBuffer } from "../utils/cache.js";
import { getAssetPath, getConfigOptions } from "../utils/shared.js";
import { cwd, sharp, supportedImageTypes } from "../../utils/runtimeChecks.js";
@@ -31,7 +32,7 @@ export default async function load(id) {
const { environment, projectBase, assetFileNames } = astroViteConfigs;
- const src = id.startsWith(cwd) ? id : cwd + id;
+ const src = getSrcPath(id);
const config = Object.fromEntries(searchParams);
diff --git a/packages/astro-imagetools/test-fixtures/getSrcPath/out/out.jpeg b/packages/astro-imagetools/test-fixtures/getSrcPath/out/out.jpeg
new file mode 100644
index 00000000..c8b7e655
Binary files /dev/null and b/packages/astro-imagetools/test-fixtures/getSrcPath/out/out.jpeg differ
diff --git a/packages/astro-imagetools/test-fixtures/getSrcPath/root.jpeg b/packages/astro-imagetools/test-fixtures/getSrcPath/root.jpeg
new file mode 100644
index 00000000..87f9f0c7
Binary files /dev/null and b/packages/astro-imagetools/test-fixtures/getSrcPath/root.jpeg differ