diff --git a/.changeset/heavy-meals-complain.md b/.changeset/heavy-meals-complain.md new file mode 100644 index 000000000000..2314150ffc6c --- /dev/null +++ b/.changeset/heavy-meals-complain.md @@ -0,0 +1,5 @@ +--- +'@astrojs/image': minor +--- + +Allow images from outside srcDir diff --git a/packages/integrations/image/package.json b/packages/integrations/image/package.json index 521dcc4c50f9..1c82a2beb93f 100644 --- a/packages/integrations/image/package.json +++ b/packages/integrations/image/package.json @@ -47,8 +47,7 @@ "image-size": "^1.0.2", "kleur": "^4.1.5", "magic-string": "^0.27.0", - "mime": "^3.0.0", - "slash": "^4.0.0" + "mime": "^3.0.0" }, "devDependencies": { "@types/http-cache-semantics": "^4.0.1", diff --git a/packages/integrations/image/src/vite-plugin-astro-image.ts b/packages/integrations/image/src/vite-plugin-astro-image.ts index 04f2303414d7..410d9bbd78cd 100644 --- a/packages/integrations/image/src/vite-plugin-astro-image.ts +++ b/packages/integrations/image/src/vite-plugin-astro-image.ts @@ -1,10 +1,9 @@ import type { AstroConfig } from 'astro'; import MagicString from 'magic-string'; import fs from 'node:fs/promises'; -import path, { basename, extname, join } from 'node:path'; +import { basename, extname, join } from 'node:path'; import { Readable } from 'node:stream'; import { fileURLToPath, pathToFileURL } from 'node:url'; -import slash from 'slash'; import type { Plugin, ResolvedConfig } from 'vite'; import type { IntegrationOptions } from './index.js'; import type { InputFormat } from './loaders/index.js'; @@ -65,12 +64,7 @@ export function createPlugin(config: AstroConfig, options: Required { if (req.url?.startsWith('/@astroimage/')) { - const [, id] = req.url.split('/@astroimage/'); + // Reconstructing URL to get rid of query parameters in path + const url = new URL(req.url.slice('/@astroimage'.length), 'file:'); - const url = new URL(id, config.srcDir); const file = await fs.readFile(url); const meta = await metadata(url); diff --git a/packages/integrations/image/test/fixtures/basic-image/social.png b/packages/integrations/image/test/fixtures/basic-image/social.png new file mode 100644 index 000000000000..1399856f1f31 Binary files /dev/null and b/packages/integrations/image/test/fixtures/basic-image/social.png differ diff --git a/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro b/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro index 4a808e0acd70..ba492576cf96 100644 --- a/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro +++ b/packages/integrations/image/test/fixtures/basic-image/src/pages/index.astro @@ -1,6 +1,7 @@ --- import socialJpg from '../assets/social.jpg'; import introJpg from '../assets/blog/introducing astro.jpg'; +import outsideSrc from '../../social.png'; import { Image } from '@astrojs/image/components'; const publicImage = new URL('./hero.jpg', Astro.url); --- @@ -18,6 +19,8 @@ const publicImage = new URL('./hero.jpg', Astro.url);
no-transforms
+ outside-src +
Google
inline diff --git a/packages/integrations/image/test/fixtures/basic-picture/social.png b/packages/integrations/image/test/fixtures/basic-picture/social.png new file mode 100644 index 000000000000..1399856f1f31 Binary files /dev/null and b/packages/integrations/image/test/fixtures/basic-picture/social.png differ diff --git a/packages/integrations/image/test/fixtures/basic-picture/src/pages/index.astro b/packages/integrations/image/test/fixtures/basic-picture/src/pages/index.astro index 086cea3880d5..39e997ba8dbb 100644 --- a/packages/integrations/image/test/fixtures/basic-picture/src/pages/index.astro +++ b/packages/integrations/image/test/fixtures/basic-picture/src/pages/index.astro @@ -1,6 +1,7 @@ --- import socialJpg from '../assets/social.jpg'; import introJpg from '../assets/blog/introducing astro.jpg'; +import outsideSrc from '../../social.png'; import { Picture } from '@astrojs/image/components'; const publicImage = new URL('./hero.jpg', Astro.url); --- @@ -14,6 +15,8 @@ const publicImage = new URL('./hero.jpg', Astro.url);

+ +

diff --git a/packages/integrations/image/test/image-ssg.test.js b/packages/integrations/image/test/image-ssg.test.js index ce91c00ea411..d342db05dc6f 100644 --- a/packages/integrations/image/test/image-ssg.test.js +++ b/packages/integrations/image/test/image-ssg.test.js @@ -2,9 +2,13 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; import sizeOf from 'image-size'; import fs from 'fs/promises'; -import { fileURLToPath } from 'url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { join } from 'node:path'; import { loadFixture } from './test-utils.js'; +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const toAstroImage = (relpath) => '/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-image', relpath)).pathname; + describe('SSG images - dev', function () { let fixture; let devServer; @@ -25,25 +29,32 @@ describe('SSG images - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, }, { title: 'Local image no transforms', id: '#no-transforms', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: {}, }, { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { f: 'webp', w: '768', h: '414' }, }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '2024', h: '1012' }, + contentType: 'image/png', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, }, { @@ -123,19 +134,32 @@ describe('SSG images with subpath - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, }, + { + title: 'Local image no transforms', + id: '#no-transforms', + url: toAstroImage('src/assets/social.jpg'), + query: {}, + }, { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { f: 'webp', w: '768', h: '414' }, }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '2024', h: '1012' }, + contentType: 'image/png', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, }, { @@ -210,8 +234,7 @@ describe('SSG images - build', function () { }); function verifyImage(pathname, expected) { - const url = new URL('./fixtures/basic-image/dist/' + pathname, import.meta.url); - const dist = fileURLToPath(url); + const dist = join(fileURLToPath(new URL('.', import.meta.url)), 'fixtures/basic-image/dist', pathname); const result = sizeOf(dist); expect(result).to.deep.equal(expected); } @@ -229,6 +252,12 @@ describe('SSG images - build', function () { regex: /^\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/, size: { width: 768, height: 414, type: 'webp' }, }, + { + title: 'File outside src', + id: '#outside-src', + regex: /^\/_astro\/social.\w{8}_\w{4,10}.png/, + size: { type: 'png', width: 2024, height: 1012 }, + }, { title: 'Inline imports', id: '#inline', @@ -311,6 +340,12 @@ describe('SSG images with subpath - build', function () { regex: /^\/docs\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/, size: { width: 768, height: 414, type: 'webp' }, }, + { + title: 'File outside src', + id: '#outside-src', + regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.png/, + size: { type: 'png', width: 2024, height: 1012 }, + }, { title: 'Inline imports', id: '#inline', diff --git a/packages/integrations/image/test/image-ssr-dev.test.js b/packages/integrations/image/test/image-ssr-dev.test.js index e6883ce036e5..7f0e0229ef8c 100644 --- a/packages/integrations/image/test/image-ssr-dev.test.js +++ b/packages/integrations/image/test/image-ssr-dev.test.js @@ -1,8 +1,13 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { join } from 'node:path'; import { loadFixture } from './test-utils.js'; import testAdapter from '../../../astro/test/test-adapter.js'; +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const toAstroImage = (relpath) => '/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-image', relpath)).pathname; + describe('SSR images - dev', function () { let fixture; let devServer; @@ -28,28 +33,35 @@ describe('SSR images - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', }, { title: 'Local image no transforms', id: '#no-transforms', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: {}, contentType: 'image/jpeg', }, { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { f: 'webp', w: '768', h: '414' }, contentType: 'image/webp', }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '2024', h: '1012' }, + contentType: 'image/png', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', }, @@ -150,21 +162,28 @@ describe('SSR images with subpath - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', }, { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { f: 'webp', w: '768', h: '414' }, contentType: 'image/webp', }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '2024', h: '1012' }, + contentType: 'image/png', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', }, diff --git a/packages/integrations/image/test/picture-ssg.test.js b/packages/integrations/image/test/picture-ssg.test.js index e5da7ba1aa27..be6d05506f18 100644 --- a/packages/integrations/image/test/picture-ssg.test.js +++ b/packages/integrations/image/test/picture-ssg.test.js @@ -3,9 +3,13 @@ import * as cheerio from 'cheerio'; import fs from 'fs'; import sizeOf from 'image-size'; import path from 'path'; -import { fileURLToPath } from 'url'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { join } from 'node:path'; import { loadFixture } from './test-utils.js'; +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const toAstroImage = (relpath) => '/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-picture', relpath)).pathname; + describe('SSG pictures - dev', function () { let fixture; let devServer; @@ -26,21 +30,28 @@ describe('SSG pictures - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, alt: 'Social image', }, { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { f: 'jpg', w: '768', h: '414' }, alt: 'spaces', }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '768', h: '414' }, + alt: 'outside-src', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, alt: 'Inline social image', }, @@ -120,21 +131,28 @@ describe('SSG pictures with subpath - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, alt: 'Social image', }, { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { f: 'jpg', w: '768', h: '414' }, alt: 'spaces', }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '768', h: '414' }, + alt: 'outside-src', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, alt: 'Inline social image', }, @@ -222,6 +240,13 @@ describe('SSG pictures - build', function () { size: { width: 768, height: 414, type: 'jpg' }, alt: 'spaces', }, + { + title: 'File outside src', + id: '#outside-src', + regex: /^\/_astro\/social.\w{8}_\w{4,10}.png/, + size: { type: 'png', width: 768, height: 414 }, + alt: 'outside-src', + }, { title: 'Inline images', id: '#inline', @@ -322,6 +347,13 @@ describe('SSG pictures with subpath - build', function () { size: { width: 506, height: 253, type: 'jpg' }, alt: 'Social image', }, + { + title: 'File outside src', + id: '#outside-src', + regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.png/, + size: { type: 'png', width: 768, height: 414 }, + alt: 'outside-src', + }, { title: 'Inline images', id: '#inline', diff --git a/packages/integrations/image/test/picture-ssr-build.test.js b/packages/integrations/image/test/picture-ssr-build.test.js index 8da476166f13..5e760d0b4349 100644 --- a/packages/integrations/image/test/picture-ssr-build.test.js +++ b/packages/integrations/image/test/picture-ssr-build.test.js @@ -30,6 +30,13 @@ describe('SSR pictures - build', function () { query: { w: '768', h: '414', f: 'jpg', href: /^\/_astro\/introducing astro.\w{8}.jpg/ }, alt: 'spaces', }, + { + title: 'File outside src', + id: '#outside-src', + url: '/_image', + query: { w: '768', h: '414', f: 'png', href: /^\/_astro\/social.\w{8}.png/ }, + alt: 'outside-src', + }, { title: 'Inline imports', id: '#inline', @@ -141,6 +148,13 @@ describe('SSR pictures with subpath - build', function () { query: { w: '768', h: '414', f: 'jpg', href: /^\/docs\/_astro\/introducing astro.\w{8}.jpg/ }, alt: 'spaces', }, + { + title: 'File outside src', + id: '#outside-src', + url: '/_image', + query: { w: '768', h: '414', f: 'png', href: /^\/docs\/_astro\/social.\w{8}.png/ }, + alt: 'outside-src', + }, { title: 'Inline imports', id: '#inline', diff --git a/packages/integrations/image/test/picture-ssr-dev.test.js b/packages/integrations/image/test/picture-ssr-dev.test.js index 7ddae9424a55..e3e1d60b04fb 100644 --- a/packages/integrations/image/test/picture-ssr-dev.test.js +++ b/packages/integrations/image/test/picture-ssr-dev.test.js @@ -1,8 +1,13 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { join } from 'node:path'; import { loadFixture } from './test-utils.js'; import testAdapter from '../../../astro/test/test-adapter.js'; +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const toAstroImage = (relpath) => '/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-picture', relpath)).pathname; + describe('SSR pictures - dev', function () { let fixture; let devServer; @@ -28,7 +33,7 @@ describe('SSR pictures - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', alt: 'Social image', @@ -36,15 +41,23 @@ describe('SSR pictures - dev', function () { { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { w: '768', h: '414', f: 'jpg' }, contentType: 'image/jpeg', alt: 'spaces', }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '768', h: '414' }, + contentType: 'image/png', + alt: 'outside-src', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', alt: 'Inline social image', @@ -157,7 +170,7 @@ describe('SSR pictures with subpath - dev', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', alt: 'Social image', @@ -165,15 +178,23 @@ describe('SSR pictures with subpath - dev', function () { { title: 'Filename with spaces', id: '#spaces', - url: '/@astroimage/assets/blog/introducing astro.jpg', + url: toAstroImage('src/assets/blog/introducing astro.jpg'), query: { w: '768', h: '414', f: 'jpg' }, contentType: 'image/jpeg', alt: 'spaces', }, + { + title: 'File outside src', + id: '#outside-src', + url: toAstroImage('social.png'), + query: { f: 'png', w: '768', h: '414' }, + contentType: 'image/png', + alt: 'outside-src', + }, { title: 'Inline imports', id: '#inline', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, contentType: 'image/jpeg', alt: 'Inline social image', diff --git a/packages/integrations/image/test/squoosh-service.test.js b/packages/integrations/image/test/squoosh-service.test.js index a067dd117ce7..2e7c6d10920d 100644 --- a/packages/integrations/image/test/squoosh-service.test.js +++ b/packages/integrations/image/test/squoosh-service.test.js @@ -1,7 +1,12 @@ import { expect } from 'chai'; import * as cheerio from 'cheerio'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { join } from 'node:path'; import { loadFixture } from './test-utils.js'; +const __dirname = fileURLToPath(new URL('.', import.meta.url)); +const toAstroImage = (relpath) => '/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/squoosh-service', relpath)).pathname; + describe('Squoosh service', function () { let fixture; let devServer; @@ -22,7 +27,7 @@ describe('Squoosh service', function () { { title: 'Local images', id: '#social-jpg', - url: '/@astroimage/assets/social.jpg', + url: toAstroImage('src/assets/social.jpg'), query: { f: 'jpg', w: '506', h: '253' }, }, { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d39f3f79bd1..c800e4641627 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2706,7 +2706,6 @@ importers: mocha: ^9.2.2 rollup-plugin-copy: ^3.4.0 sharp: ^0.31.0 - slash: ^4.0.0 vite: ^4.0.3 dependencies: '@altano/tiny-async-pool': 1.0.2 @@ -2715,7 +2714,6 @@ importers: kleur: 4.1.5 magic-string: 0.27.0 mime: 3.0.0 - slash: 4.0.0 devDependencies: '@types/http-cache-semantics': 4.0.1 '@types/mime': 2.0.3