Skip to content

Commit

Permalink
fix(image): allow usage of image from any directory (#5932)
Browse files Browse the repository at this point in the history
Currently, @astrojs/image allows *importing* images from srcDir
only. Importing images from outside srcDir fails miserably *in dev
mode* and produces incorrect src.

This happens because `path.relative(fileURLToPath(config.srcDir), id)`
resolves to "../something" and when joined with '/@astroimage' cancels
it out (`join('/@astroimage', '../../something')` => `'/something'`).

Rework /@astroimage URL scheme to be similar to "/@fs/" scheme—always
export absolute path to the target file.
  • Loading branch information
rasendubi authored Jan 30, 2023
1 parent ca91976 commit b3e6599
Show file tree
Hide file tree
Showing 14 changed files with 173 additions and 45 deletions.
5 changes: 5 additions & 0 deletions .changeset/heavy-meals-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/image': minor
---

Allow images from outside srcDir
3 changes: 1 addition & 2 deletions packages/integrations/image/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 4 additions & 10 deletions packages/integrations/image/src/vite-plugin-astro-image.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -65,22 +64,17 @@ export function createPlugin(config: AstroConfig, options: Required<IntegrationO

meta.src = `__ASTRO_IMAGE_ASSET__${handle}__`;
} else {
const relId = path.relative(fileURLToPath(config.srcDir), id);

meta.src = join('/@astroimage', relId);

// Windows compat
meta.src = slash(meta.src);
meta.src = '/@astroimage' + url.pathname;
}

return `export default ${JSON.stringify(meta)}`;
},
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
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);
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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);
---
Expand All @@ -18,6 +19,8 @@ const publicImage = new URL('./hero.jpg', Astro.url);
<br />
<Image id="no-transforms" src={socialJpg} alt="no-transforms" />
<br />
<Image id="outside-src" src={outsideSrc} alt="outside-src" />
<br />
<Image id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="webp" alt="Google" />
<br />
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -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);
---
Expand All @@ -14,6 +15,8 @@ const publicImage = new URL('./hero.jpg', Astro.url);
<br />
<Picture id="spaces" src={introJpg} sizes="100vw" widths={[384, 768]} aspectRatio={768/414} alt="spaces" />
<br />
<Picture id="outside-src" src={outsideSrc} sizes="100vw" widths={[384, 768]} aspectRatio={768/414} alt="outside-src" />
<br />
<Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Social image" />
<br />
<Picture id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} alt="Google logo" formats={["avif", "webp", "png"]} />
Expand Down
55 changes: 45 additions & 10 deletions packages/integrations/image/test/image-ssg.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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' },
},
{
Expand Down Expand Up @@ -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' },
},
{
Expand Down Expand Up @@ -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);
}
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand Down
33 changes: 26 additions & 7 deletions packages/integrations/image/test/image-ssr-dev.test.js
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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',
},
Expand Down Expand Up @@ -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',
},
Expand Down
Loading

0 comments on commit b3e6599

Please sign in to comment.