[Markdoc] Support automatic image optimization with experimental.assets
(#6630)
* wip: scrappy implementation. It works! 🥳
* chore: add code comments on inline utils
* fix: code cleanup, run on experimental.assets
* feat: support ~/assets alias
* fix: spoof `astro:assets` when outside experimental
* test: image paths in dev and prod
* feat: support any vite alias with ctx.resolve
* fix: avoid trying to process absolute paths
* fix: raise helpful error for invalid vite paths
* refactor: revert URL support on emitAsset
* chore: lint
* refactor: expose emitESMImage from assets base
* wip: why doesn't assets exist
* scary chore: make @astrojs/markdoc truly depend on astro
* fix: import emitESMImage straight from dist
* chore: remove type def from assets package
* chore: screw it, just ts ignore
* deps: rollup types
* refactor: optimize images during parse step
* chore: remove unneeded `.flat()`
* fix: use file-based relative paths
* fix: add back helpful error
* chore: changeset
* deps: move astro back to dev dep
* fix: put emit assets behind flag
* chore: change to markdoc patch
This commit is contained in:
parent
dfbd09b711
commit
cfcf2e2ffd
18 changed files with 368 additions and 21 deletions
13
.changeset/big-rice-rest.md
Normal file
13
.changeset/big-rice-rest.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
'@astrojs/markdoc': patch
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Support automatic image optimization for Markdoc images when using `experimental.assets`. You can [follow our Assets guide](https://docs.astro.build/en/guides/assets/#enabling-assets-in-your-project) to enable this feature in your project. Then, start using relative or aliased image sources in your Markdoc files for automatic optimization:
|
||||
|
||||
```md
|
||||
<!--Relative paths-->
|
||||
![The Milky Way Galaxy](../assets/galaxy.jpg)
|
||||
<!--Or configured aliases-->
|
||||
![Houston smiling and looking cute](~/assets/houston-smiling.jpg)
|
||||
```
|
|
@ -1053,9 +1053,12 @@ export interface ContentEntryType {
|
|||
fileUrl: URL;
|
||||
contents: string;
|
||||
}): GetEntryInfoReturnType | Promise<GetEntryInfoReturnType>;
|
||||
getRenderModule?(params: {
|
||||
entry: ContentEntryModule;
|
||||
}): rollup.LoadResult | Promise<rollup.LoadResult>;
|
||||
getRenderModule?(
|
||||
this: rollup.PluginContext,
|
||||
params: {
|
||||
entry: ContentEntryModule;
|
||||
}
|
||||
): rollup.LoadResult | Promise<rollup.LoadResult>;
|
||||
contentModuleTypes?: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,3 +2,4 @@ export { getConfiguredImageService, getImage } from './internal.js';
|
|||
export { baseService } from './services/service.js';
|
||||
export { type LocalImageProps, type RemoteImageProps } from './types.js';
|
||||
export { imageMetadata } from './utils/metadata.js';
|
||||
export { emitESMImage } from './utils/emitAsset.js';
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import type { AstroSettings } from '../../@types/astro';
|
||||
import { rootRelativePath } from '../../core/util.js';
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url';
|
||||
import slash from 'slash';
|
||||
import type { AstroSettings, AstroConfig } from '../../@types/astro';
|
||||
import { imageMetadata } from './metadata.js';
|
||||
|
||||
export async function emitESMImage(
|
||||
id: string,
|
||||
watchMode: boolean,
|
||||
fileEmitter: any,
|
||||
settings: AstroSettings
|
||||
settings: Pick<AstroSettings, 'config'>
|
||||
) {
|
||||
const url = pathToFileURL(id);
|
||||
const meta = await imageMetadata(url);
|
||||
|
@ -41,3 +41,29 @@ export async function emitESMImage(
|
|||
|
||||
return meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utilities inlined from `packages/astro/src/core/util.ts`
|
||||
* Avoids ESM / CJS bundling failures when accessed from integrations
|
||||
* due to Vite dependencies in core.
|
||||
*/
|
||||
|
||||
function rootRelativePath(config: Pick<AstroConfig, 'root'>, url: URL) {
|
||||
const basePath = fileURLToNormalizedPath(url);
|
||||
const rootPath = fileURLToNormalizedPath(config.root);
|
||||
return prependForwardSlash(basePath.slice(rootPath.length));
|
||||
}
|
||||
|
||||
function prependForwardSlash(filePath: string) {
|
||||
return filePath[0] === '/' ? filePath : '/' + filePath;
|
||||
}
|
||||
|
||||
function fileURLToNormalizedPath(filePath: URL): string {
|
||||
// Uses `slash` package instead of Vite's `normalizePath`
|
||||
// to avoid CJS bundling issues.
|
||||
return slash(fileURLToPath(filePath) + filePath.search).replace(/\\/g, '/');
|
||||
}
|
||||
|
||||
export function emoji(char: string, fallback: string) {
|
||||
return process.platform !== 'win32' ? char : fallback;
|
||||
}
|
||||
|
|
|
@ -139,7 +139,7 @@ export const _internal = {
|
|||
});
|
||||
}
|
||||
|
||||
return contentRenderer({ entry });
|
||||
return contentRenderer.bind(this)({ entry });
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import type { AstroInstance } from 'astro';
|
||||
import type { RenderableTreeNode } from '@markdoc/markdoc';
|
||||
import { createComponent, renderComponent, render } from 'astro/runtime/server/index.js';
|
||||
// @ts-expect-error Cannot find module 'astro:markdoc-assets' or its corresponding type declarations
|
||||
import { Image } from 'astro:markdoc-assets';
|
||||
import Markdoc from '@markdoc/markdoc';
|
||||
import { MarkdocError, isCapitalized } from '../dist/utils.js';
|
||||
|
||||
|
@ -45,10 +47,16 @@ export const ComponentNode = createComponent({
|
|||
propagation: 'none',
|
||||
});
|
||||
|
||||
const builtInComponents: Record<string, AstroInstance['default']> = {
|
||||
Image,
|
||||
};
|
||||
|
||||
export function createTreeNode(
|
||||
node: RenderableTreeNode,
|
||||
components: Record<string, AstroInstance['default']> = {}
|
||||
userComponents: Record<string, AstroInstance['default']> = {}
|
||||
): TreeNode {
|
||||
const components = { ...userComponents, ...builtInComponents };
|
||||
|
||||
if (typeof node === 'string' || typeof node === 'number') {
|
||||
return { type: 'text', content: String(node) };
|
||||
} else if (node === null || typeof node !== 'object' || !Markdoc.Tag.isTag(node)) {
|
||||
|
|
|
@ -35,16 +35,20 @@
|
|||
"gray-matter": "^4.0.3",
|
||||
"zod": "^3.17.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "workspace:*",
|
||||
"@types/chai": "^4.3.1",
|
||||
"@types/html-escaper": "^3.0.0",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"astro": "workspace:*",
|
||||
"astro-scripts": "workspace:*",
|
||||
"chai": "^4.3.6",
|
||||
"devalue": "^4.2.0",
|
||||
"linkedom": "^0.14.12",
|
||||
"mocha": "^9.2.2",
|
||||
"rollup": "^3.20.1",
|
||||
"vite": "^4.0.3"
|
||||
},
|
||||
"engines": {
|
||||
|
|
|
@ -1,9 +1,23 @@
|
|||
import type { Config } from '@markdoc/markdoc';
|
||||
import type {
|
||||
Config as ReadonlyMarkdocConfig,
|
||||
ConfigType as MarkdocConfig,
|
||||
Node,
|
||||
} from '@markdoc/markdoc';
|
||||
import Markdoc from '@markdoc/markdoc';
|
||||
import type { AstroConfig, AstroIntegration, ContentEntryType, HookParameters } from 'astro';
|
||||
import fs from 'node:fs';
|
||||
import type * as rollup from 'rollup';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { getAstroConfigPath, MarkdocError, parseFrontmatter } from './utils.js';
|
||||
import {
|
||||
getAstroConfigPath,
|
||||
isValidUrl,
|
||||
MarkdocError,
|
||||
parseFrontmatter,
|
||||
prependForwardSlash,
|
||||
} from './utils.js';
|
||||
// @ts-expect-error Cannot find module 'astro/assets' or its corresponding type declarations.
|
||||
import { emitESMImage } from 'astro/assets';
|
||||
import type { Plugin as VitePlugin } from 'vite';
|
||||
|
||||
type SetupHookParams = HookParameters<'astro:config:setup'> & {
|
||||
// `contentEntryType` is not a public API
|
||||
|
@ -11,12 +25,24 @@ type SetupHookParams = HookParameters<'astro:config:setup'> & {
|
|||
addContentEntryType: (contentEntryType: ContentEntryType) => void;
|
||||
};
|
||||
|
||||
export default function markdoc(markdocConfig: Config = {}): AstroIntegration {
|
||||
export default function markdocIntegration(
|
||||
userMarkdocConfig: ReadonlyMarkdocConfig = {}
|
||||
): AstroIntegration {
|
||||
return {
|
||||
name: '@astrojs/markdoc',
|
||||
hooks: {
|
||||
'astro:config:setup': async (params) => {
|
||||
const { updateConfig, config, addContentEntryType } = params as SetupHookParams;
|
||||
const {
|
||||
updateConfig,
|
||||
config: astroConfig,
|
||||
addContentEntryType,
|
||||
} = params as SetupHookParams;
|
||||
|
||||
updateConfig({
|
||||
vite: {
|
||||
plugins: [safeAssetsVirtualModulePlugin({ astroConfig })],
|
||||
},
|
||||
});
|
||||
|
||||
function getEntryInfo({ fileUrl, contents }: { fileUrl: URL; contents: string }) {
|
||||
const parsed = parseFrontmatter(contents, fileURLToPath(fileUrl));
|
||||
|
@ -30,16 +56,44 @@ export default function markdoc(markdocConfig: Config = {}): AstroIntegration {
|
|||
addContentEntryType({
|
||||
extensions: ['.mdoc'],
|
||||
getEntryInfo,
|
||||
getRenderModule({ entry }) {
|
||||
validateRenderProperties(markdocConfig, config);
|
||||
async getRenderModule({ entry }) {
|
||||
validateRenderProperties(userMarkdocConfig, astroConfig);
|
||||
const ast = Markdoc.parse(entry.body);
|
||||
const content = Markdoc.transform(ast, {
|
||||
...markdocConfig,
|
||||
const pluginContext = this;
|
||||
const markdocConfig: MarkdocConfig = {
|
||||
...userMarkdocConfig,
|
||||
variables: {
|
||||
...markdocConfig.variables,
|
||||
...userMarkdocConfig.variables,
|
||||
entry,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
if (astroConfig.experimental?.assets) {
|
||||
await emitOptimizedImages(ast.children, {
|
||||
astroConfig,
|
||||
pluginContext,
|
||||
filePath: entry._internal.filePath,
|
||||
});
|
||||
|
||||
markdocConfig.nodes ??= {};
|
||||
markdocConfig.nodes.image = {
|
||||
...Markdoc.nodes.image,
|
||||
transform(node, config) {
|
||||
const attributes = node.transformAttributes(config);
|
||||
const children = node.transformChildren(config);
|
||||
|
||||
if (node.type === 'image' && '__optimizedSrc' in node.attributes) {
|
||||
const { __optimizedSrc, ...rest } = node.attributes;
|
||||
return new Markdoc.Tag('Image', { ...rest, src: __optimizedSrc }, children);
|
||||
} else {
|
||||
return new Markdoc.Tag('img', attributes, children);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const content = Markdoc.transform(ast, markdocConfig);
|
||||
|
||||
return {
|
||||
code: `import { jsx as h } from 'astro/jsx-runtime';\nimport { Renderer } from '@astrojs/markdoc/components';\nconst transformedContent = ${JSON.stringify(
|
||||
content
|
||||
|
@ -56,7 +110,54 @@ export default function markdoc(markdocConfig: Config = {}): AstroIntegration {
|
|||
};
|
||||
}
|
||||
|
||||
function validateRenderProperties(markdocConfig: Config, astroConfig: AstroConfig) {
|
||||
/**
|
||||
* Emits optimized images, and appends the generated `src` to each AST node
|
||||
* via the `__optimizedSrc` attribute.
|
||||
*/
|
||||
async function emitOptimizedImages(
|
||||
nodeChildren: Node[],
|
||||
ctx: {
|
||||
pluginContext: rollup.PluginContext;
|
||||
filePath: string;
|
||||
astroConfig: AstroConfig;
|
||||
}
|
||||
) {
|
||||
for (const node of nodeChildren) {
|
||||
if (
|
||||
node.type === 'image' &&
|
||||
typeof node.attributes.src === 'string' &&
|
||||
shouldOptimizeImage(node.attributes.src)
|
||||
) {
|
||||
// Attempt to resolve source with Vite.
|
||||
// This handles relative paths and configured aliases
|
||||
const resolved = await ctx.pluginContext.resolve(node.attributes.src, ctx.filePath);
|
||||
|
||||
if (resolved?.id && fs.existsSync(new URL(prependForwardSlash(resolved.id), 'file://'))) {
|
||||
const src = await emitESMImage(
|
||||
resolved.id,
|
||||
ctx.pluginContext.meta.watchMode,
|
||||
ctx.pluginContext.emitFile,
|
||||
{ config: ctx.astroConfig }
|
||||
);
|
||||
node.attributes.__optimizedSrc = src;
|
||||
} else {
|
||||
throw new MarkdocError({
|
||||
message: `Could not resolve image ${JSON.stringify(
|
||||
node.attributes.src
|
||||
)} from ${JSON.stringify(ctx.filePath)}. Does the file exist?`,
|
||||
});
|
||||
}
|
||||
}
|
||||
await emitOptimizedImages(node.children, ctx);
|
||||
}
|
||||
}
|
||||
|
||||
function shouldOptimizeImage(src: string) {
|
||||
// Optimize anything that is NOT external or an absolute path to `public/`
|
||||
return !isValidUrl(src) && !src.startsWith('/');
|
||||
}
|
||||
|
||||
function validateRenderProperties(markdocConfig: ReadonlyMarkdocConfig, astroConfig: AstroConfig) {
|
||||
const tags = markdocConfig.tags ?? {};
|
||||
const nodes = markdocConfig.nodes ?? {};
|
||||
|
||||
|
@ -105,3 +206,37 @@ function validateRenderProperty({
|
|||
function isCapitalized(str: string) {
|
||||
return str.length > 0 && str[0] === str[0].toUpperCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: remove when `experimental.assets` is baselined.
|
||||
*
|
||||
* `astro:assets` will fail to resolve if the `experimental.assets` flag is not enabled.
|
||||
* This ensures a fallback for the Markdoc renderer to safely import at the top level.
|
||||
* @see ../components/TreeNode.ts
|
||||
*/
|
||||
function safeAssetsVirtualModulePlugin({
|
||||
astroConfig,
|
||||
}: {
|
||||
astroConfig: Pick<AstroConfig, 'experimental'>;
|
||||
}): VitePlugin {
|
||||
const virtualModuleId = 'astro:markdoc-assets';
|
||||
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||
|
||||
return {
|
||||
name: 'astro:markdoc-safe-assets-virtual-module',
|
||||
resolveId(id) {
|
||||
if (id === virtualModuleId) {
|
||||
return resolvedVirtualModuleId;
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (id !== resolvedVirtualModuleId) return;
|
||||
|
||||
if (astroConfig.experimental?.assets) {
|
||||
return `export { Image } from 'astro:assets';`;
|
||||
} else {
|
||||
return `export const Image = () => { throw new Error('Cannot use the Image component without the \`experimental.assets\` flag.'); }`;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -145,3 +145,12 @@ const componentsPropValidator = z.record(
|
|||
export function isCapitalized(str: string) {
|
||||
return str.length > 0 && str[0] === str[0].toUpperCase();
|
||||
}
|
||||
|
||||
export function isValidUrl(str: string): boolean {
|
||||
try {
|
||||
new URL(str);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
10
packages/integrations/markdoc/test/fixtures/image-assets/astro.config.mjs
vendored
Normal file
10
packages/integrations/markdoc/test/fixtures/image-assets/astro.config.mjs
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import markdoc from '@astrojs/markdoc';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
experimental: {
|
||||
assets: true,
|
||||
},
|
||||
integrations: [markdoc()],
|
||||
});
|
9
packages/integrations/markdoc/test/fixtures/image-assets/package.json
vendored
Normal file
9
packages/integrations/markdoc/test/fixtures/image-assets/package.json
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@test/image-assets",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@astrojs/markdoc": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
BIN
packages/integrations/markdoc/test/fixtures/image-assets/src/assets/alias/cityscape.jpg
vendored
Normal file
BIN
packages/integrations/markdoc/test/fixtures/image-assets/src/assets/alias/cityscape.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
BIN
packages/integrations/markdoc/test/fixtures/image-assets/src/assets/relative/oar.jpg
vendored
Normal file
BIN
packages/integrations/markdoc/test/fixtures/image-assets/src/assets/relative/oar.jpg
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
7
packages/integrations/markdoc/test/fixtures/image-assets/src/content/docs/intro.mdoc
vendored
Normal file
7
packages/integrations/markdoc/test/fixtures/image-assets/src/content/docs/intro.mdoc
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Image assets
|
||||
|
||||
![Favicon](/favicon.svg) {% #public %}
|
||||
|
||||
![Oar](../../assets/relative/oar.jpg) {% #relative %}
|
||||
|
||||
![Gray cityscape arial view](~/assets/alias/cityscape.jpg) {% #alias %}
|
19
packages/integrations/markdoc/test/fixtures/image-assets/src/pages/index.astro
vendored
Normal file
19
packages/integrations/markdoc/test/fixtures/image-assets/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
import { getEntryBySlug } from 'astro:content';
|
||||
|
||||
const intro = await getEntryBySlug('docs', 'intro');
|
||||
const { Content } = await intro.render();
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
<Content />
|
||||
</body>
|
||||
</html>
|
9
packages/integrations/markdoc/test/fixtures/image-assets/src/public/favicon.svg
vendored
Normal file
9
packages/integrations/markdoc/test/fixtures/image-assets/src/public/favicon.svg
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||
<style>
|
||||
path { fill: #000; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
path { fill: #FFF; }
|
||||
}
|
||||
</style>
|
||||
</svg>
|
After Width: | Height: | Size: 749 B |
76
packages/integrations/markdoc/test/image-assets.test.js
Normal file
76
packages/integrations/markdoc/test/image-assets.test.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
import { parseHTML } from 'linkedom';
|
||||
import { expect } from 'chai';
|
||||
import { loadFixture } from '../../../astro/test/test-utils.js';
|
||||
|
||||
const root = new URL('./fixtures/image-assets/', import.meta.url);
|
||||
|
||||
describe('Markdoc - Image assets', () => {
|
||||
let baseFixture;
|
||||
|
||||
before(async () => {
|
||||
baseFixture = await loadFixture({
|
||||
root,
|
||||
});
|
||||
});
|
||||
|
||||
describe('dev', () => {
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
devServer = await baseFixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
it('uses public/ image paths unchanged', async () => {
|
||||
const res = await baseFixture.fetch('/');
|
||||
const html = await res.text();
|
||||
const { document } = parseHTML(html);
|
||||
expect(document.querySelector('#public > img')?.src).to.equal('/favicon.svg');
|
||||
});
|
||||
|
||||
it('transforms relative image paths to optimized path', async () => {
|
||||
const res = await baseFixture.fetch('/');
|
||||
const html = await res.text();
|
||||
const { document } = parseHTML(html);
|
||||
expect(document.querySelector('#relative > img')?.src).to.equal(
|
||||
'/_image?href=%2Fsrc%2Fassets%2Frelative%2Foar.jpg%3ForigWidth%3D420%26origHeight%3D630%26origFormat%3Djpg&f=webp'
|
||||
);
|
||||
});
|
||||
|
||||
it('transforms aliased image paths to optimized path', async () => {
|
||||
const res = await baseFixture.fetch('/');
|
||||
const html = await res.text();
|
||||
const { document } = parseHTML(html);
|
||||
expect(document.querySelector('#alias > img')?.src).to.equal(
|
||||
'/_image?href=%2Fsrc%2Fassets%2Falias%2Fcityscape.jpg%3ForigWidth%3D420%26origHeight%3D280%26origFormat%3Djpg&f=webp'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('build', () => {
|
||||
before(async () => {
|
||||
await baseFixture.build();
|
||||
});
|
||||
|
||||
it('uses public/ image paths unchanged', async () => {
|
||||
const html = await baseFixture.readFile('/index.html');
|
||||
const { document } = parseHTML(html);
|
||||
expect(document.querySelector('#public > img')?.src).to.equal('/favicon.svg');
|
||||
});
|
||||
|
||||
it('transforms relative image paths to optimized path', async () => {
|
||||
const html = await baseFixture.readFile('/index.html');
|
||||
const { document } = parseHTML(html);
|
||||
expect(document.querySelector('#relative > img')?.src).to.match(/^\/_astro\/oar.*\.webp$/);
|
||||
});
|
||||
|
||||
it('transforms aliased image paths to optimized path', async () => {
|
||||
const html = await baseFixture.readFile('/index.html');
|
||||
const { document } = parseHTML(html);
|
||||
expect(document.querySelector('#alias > img')?.src).to.match(/^\/_astro\/cityscape.*\.webp$/);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -3080,6 +3080,7 @@ importers:
|
|||
gray-matter: ^4.0.3
|
||||
linkedom: ^0.14.12
|
||||
mocha: ^9.2.2
|
||||
rollup: ^3.20.1
|
||||
vite: ^4.0.3
|
||||
zod: ^3.17.3
|
||||
dependencies:
|
||||
|
@ -3096,6 +3097,7 @@ importers:
|
|||
devalue: 4.2.3
|
||||
linkedom: 0.14.21
|
||||
mocha: 9.2.2
|
||||
rollup: 3.20.1
|
||||
vite: 4.1.2
|
||||
|
||||
packages/integrations/markdoc/test/fixtures/content-collections:
|
||||
|
@ -3119,6 +3121,14 @@ importers:
|
|||
'@astrojs/markdoc': link:../../..
|
||||
astro: link:../../../../../astro
|
||||
|
||||
packages/integrations/markdoc/test/fixtures/image-assets:
|
||||
specifiers:
|
||||
'@astrojs/markdoc': workspace:*
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
'@astrojs/markdoc': link:../../..
|
||||
astro: link:../../../../../astro
|
||||
|
||||
packages/integrations/mdx:
|
||||
specifiers:
|
||||
'@astrojs/markdown-remark': ^2.1.2
|
||||
|
@ -14921,6 +14931,14 @@ packages:
|
|||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
/rollup/3.20.1:
|
||||
resolution: {integrity: sha512-sz2w8cBJlWQ2E17RcpvHuf4sk2BQx4tfKDnjNPikEpLEevrbIAR7CH3PGa2hpPwWbNgPaA9yh9Jzljds5bc9zg==}
|
||||
engines: {node: '>=14.18.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/run-parallel/1.2.0:
|
||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||
dependencies:
|
||||
|
|
Loading…
Reference in a new issue