fix(images): Simpler logic for collecting images in Markdown (#6744)
This commit is contained in:
parent
366decbe33
commit
a1a4f45b51
7 changed files with 54 additions and 42 deletions
6
.changeset/green-turtles-jump.md
Normal file
6
.changeset/green-turtles-jump.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
'@astrojs/markdown-remark': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix remote images in Markdown throwing errors when using `experimental.assets`
|
|
@ -86,18 +86,17 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu
|
||||||
|
|
||||||
let html = renderResult.code;
|
let html = renderResult.code;
|
||||||
const { headings } = renderResult.metadata;
|
const { headings } = renderResult.metadata;
|
||||||
|
|
||||||
|
// Resolve all the extracted images from the content
|
||||||
let imagePaths: { raw: string; resolved: string }[] = [];
|
let imagePaths: { raw: string; resolved: string }[] = [];
|
||||||
if (settings.config.experimental.assets) {
|
if (settings.config.experimental.assets && renderResult.vfile.data.imagePaths) {
|
||||||
let paths = (renderResult.vfile.data.imagePaths as string[]) ?? [];
|
for (let imagePath of renderResult.vfile.data.imagePaths.values()) {
|
||||||
imagePaths = await Promise.all(
|
imagePaths.push({
|
||||||
paths.map(async (imagePath) => {
|
raw: imagePath,
|
||||||
return {
|
resolved:
|
||||||
raw: imagePath,
|
(await this.resolve(imagePath, id))?.id ?? path.join(path.dirname(id), imagePath),
|
||||||
resolved:
|
});
|
||||||
(await this.resolve(imagePath, id))?.id ?? path.join(path.dirname(id), imagePath),
|
}
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const astroData = safelyGetAstroData(renderResult.vfile.data);
|
const astroData = safelyGetAstroData(renderResult.vfile.data);
|
||||||
|
|
|
@ -218,6 +218,19 @@ describe('astro:image', () => {
|
||||||
let $img = $('img');
|
let $img = $('img');
|
||||||
expect($img.attr('src').startsWith('/_image')).to.equal(true);
|
expect($img.attr('src').startsWith('/_image')).to.equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('properly handles remote images', async () => {
|
||||||
|
let res = await fixture.fetch('/httpImage');
|
||||||
|
let html = await res.text();
|
||||||
|
$ = cheerio.load(html);
|
||||||
|
|
||||||
|
let $img = $('img');
|
||||||
|
expect($img).to.have.a.lengthOf(2);
|
||||||
|
const remoteUrls = ['https://example.com/image.png', '/image.png'];
|
||||||
|
$img.each((index, element) => {
|
||||||
|
expect(element.attribs['src']).to.equal(remoteUrls[index]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getImage', () => {
|
describe('getImage', () => {
|
||||||
|
|
2
packages/astro/test/fixtures/core-image/src/pages/httpImage.md
vendored
Normal file
2
packages/astro/test/fixtures/core-image/src/pages/httpImage.md
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
![Remote image](https://example.com/image.png)
|
||||||
|
![/public image](/image.png)
|
|
@ -9,9 +9,7 @@ export function rehypeImages() {
|
||||||
if (node.tagName !== 'img') return;
|
if (node.tagName !== 'img') return;
|
||||||
|
|
||||||
if (node.properties?.src) {
|
if (node.properties?.src) {
|
||||||
if (file.dirname) {
|
if (file.data.imagePaths?.has(node.properties.src)) {
|
||||||
if (!isRelativePath(node.properties.src) && !isAliasedPath(node.properties.src)) return;
|
|
||||||
|
|
||||||
node.properties['__ASTRO_IMAGE_'] = node.properties.src;
|
node.properties['__ASTRO_IMAGE_'] = node.properties.src;
|
||||||
delete node.properties.src;
|
delete node.properties.src;
|
||||||
}
|
}
|
||||||
|
@ -19,24 +17,3 @@ export function rehypeImages() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAliasedPath(path: string) {
|
|
||||||
return path.startsWith('~/assets');
|
|
||||||
}
|
|
||||||
|
|
||||||
function isRelativePath(path: string) {
|
|
||||||
return startsWithDotDotSlash(path) || startsWithDotSlash(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
function startsWithDotDotSlash(path: string) {
|
|
||||||
const c1 = path[0];
|
|
||||||
const c2 = path[1];
|
|
||||||
const c3 = path[2];
|
|
||||||
return c1 === '.' && c2 === '.' && c3 === '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
function startsWithDotSlash(path: string) {
|
|
||||||
const c1 = path[0];
|
|
||||||
const c2 = path[1];
|
|
||||||
return c1 === '.' && c2 === '/';
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
import type { Image } from 'mdast';
|
import type { Image } from 'mdast';
|
||||||
import { visit } from 'unist-util-visit';
|
import { visit } from 'unist-util-visit';
|
||||||
import type { VFile } from 'vfile';
|
import type { MarkdownVFile } from './types';
|
||||||
|
|
||||||
export default function toRemarkCollectImages() {
|
export default function toRemarkCollectImages() {
|
||||||
return () =>
|
return () =>
|
||||||
async function (tree: any, vfile: VFile) {
|
async function (tree: any, vfile: MarkdownVFile) {
|
||||||
if (typeof vfile?.path !== 'string') return;
|
if (typeof vfile?.path !== 'string') return;
|
||||||
|
|
||||||
const imagePaths = new Set<string>();
|
const imagePaths = new Set<string>();
|
||||||
visit(tree, 'image', function raiseError(node: Image) {
|
visit(tree, 'image', (node: Image) => {
|
||||||
imagePaths.add(node.url);
|
if (shouldOptimizeImage(node.url)) imagePaths.add(node.url);
|
||||||
});
|
});
|
||||||
|
|
||||||
vfile.data.imagePaths = Array.from(imagePaths);
|
vfile.data.imagePaths = imagePaths;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function shouldOptimizeImage(src: string) {
|
||||||
|
// Optimize anything that is NOT external or an absolute path to `public/`
|
||||||
|
return !isValidUrl(src) && !src.startsWith('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidUrl(str: string): boolean {
|
||||||
|
try {
|
||||||
|
new URL(str);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import type * as hast from 'hast';
|
import type * as hast from 'hast';
|
||||||
import type * as mdast from 'mdast';
|
import type * as mdast from 'mdast';
|
||||||
import type {
|
import type {
|
||||||
all as Handlers,
|
|
||||||
one as Handler,
|
one as Handler,
|
||||||
|
all as Handlers,
|
||||||
Options as RemarkRehypeOptions,
|
Options as RemarkRehypeOptions,
|
||||||
} from 'remark-rehype';
|
} from 'remark-rehype';
|
||||||
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
|
import type { ILanguageRegistration, IThemeRegistration, Theme } from 'shiki';
|
||||||
|
@ -85,11 +85,12 @@ export interface MarkdownMetadata {
|
||||||
export interface MarkdownVFile extends VFile {
|
export interface MarkdownVFile extends VFile {
|
||||||
data: {
|
data: {
|
||||||
__astroHeadings?: MarkdownHeading[];
|
__astroHeadings?: MarkdownHeading[];
|
||||||
|
imagePaths?: Set<string>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MarkdownRenderingResult {
|
export interface MarkdownRenderingResult {
|
||||||
metadata: MarkdownMetadata;
|
metadata: MarkdownMetadata;
|
||||||
vfile: VFile;
|
vfile: MarkdownVFile;
|
||||||
code: string;
|
code: string;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue