Skip to content

Commit

Permalink
Merge branch 'main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
natemoo-re authored Mar 9, 2023
2 parents 8589e62 + 3f6a088 commit e51d87c
Show file tree
Hide file tree
Showing 19 changed files with 229 additions and 69 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-coats-tie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix images defined in content collections schemas not working
5 changes: 5 additions & 0 deletions .changeset/lucky-shoes-mate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Remove use of createRequire breaking non-Node hosts.
5 changes: 5 additions & 0 deletions .changeset/tall-taxis-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Fix `@astrojs/prism` edgecase with strict package managers
7 changes: 7 additions & 0 deletions .github/ISSUE_TEMPLATE/---01-bug-report.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ body:
placeholder: Mac, Windows, Linux
validations:
required: true
- type: input
id: browser
attributes:
label: What browser are you using?
placeholder: Chrome, Firefox, Safari
validations:
required: true
- type: textarea
id: bug-description
attributes:
Expand Down
42 changes: 42 additions & 0 deletions packages/astro/src/assets/internal.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import { AstroSettings } from '../@types/astro.js';
import { StaticBuildOptions } from '../core/build/types.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { rootRelativePath } from '../core/util.js';
import { ImageService, isLocalService, LocalImageService } from './services/service.js';
import type { ImageMetadata, ImageTransform } from './types.js';
import { imageMetadata } from './utils/metadata.js';

export function isESMImportedImage(src: ImageMetadata | string): src is ImageMetadata {
return typeof src === 'object';
Expand Down Expand Up @@ -115,3 +120,40 @@ export async function generateImage(
},
};
}

export async function emitESMImage(
id: string,
watchMode: boolean,
fileEmitter: any,
settings: AstroSettings
) {
const url = pathToFileURL(id);
const meta = await imageMetadata(url);

if (!meta) {
return;
}

// Build
if (!watchMode) {
const pathname = decodeURI(url.pathname);
const filename = path.basename(pathname, path.extname(pathname) + `.${meta.format}`);

const handle = fileEmitter({
name: filename,
source: await fs.promises.readFile(url),
type: 'asset',
});

meta.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
} else {
// Pass the original file information through query params so we don't have to load the file twice
url.searchParams.append('origWidth', meta.width.toString());
url.searchParams.append('origHeight', meta.height.toString());
url.searchParams.append('origFormat', meta.format);

meta.src = rootRelativePath(settings.config, url);
}

return meta;
}
4 changes: 1 addition & 3 deletions packages/astro/src/assets/utils/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { createRequire } from 'module';
import sizeOf from 'image-size';
import fs from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { ImageMetadata, InputFormat } from '../types.js';
const require = createRequire(import.meta.url);
const sizeOf = require('image-size');

export interface Metadata extends ImageMetadata {
orientation?: number;
Expand Down
35 changes: 3 additions & 32 deletions packages/astro/src/assets/vite-plugin-assets.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import MagicString from 'magic-string';
import mime from 'mime';
import fs from 'node:fs/promises';
import path from 'node:path';
import { Readable } from 'node:stream';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { fileURLToPath } from 'node:url';
import type * as vite from 'vite';
import { normalizePath } from 'vite';
import { AstroPluginOptions, ImageTransform } from '../@types/astro';
import { error } from '../core/logger/core.js';
import { joinPaths, prependForwardSlash } from '../core/path.js';
import { rootRelativePath } from '../core/util.js';
import { VIRTUAL_MODULE_ID, VIRTUAL_SERVICE_ID } from './consts.js';
import { isESMImportedImage } from './internal.js';
import { emitESMImage, isESMImportedImage } from './internal.js';
import { isLocalService } from './services/service.js';
import { copyWasmFiles } from './services/vendor/squoosh/copy-wasm.js';
import { imageMetadata } from './utils/metadata.js';
Expand Down Expand Up @@ -202,34 +200,7 @@ export default function assets({
},
async load(id) {
if (/\.(jpeg|jpg|png|tiff|webp|gif|svg)$/.test(id)) {
const url = pathToFileURL(id);
const meta = await imageMetadata(url);

if (!meta) {
return;
}

// Build
if (!this.meta.watchMode) {
const pathname = decodeURI(url.pathname);
const filename = path.basename(pathname, path.extname(pathname) + `.${meta.format}`);

const handle = this.emitFile({
name: filename,
source: await fs.readFile(url),
type: 'asset',
});

meta.src = `__ASTRO_ASSET_IMAGE__${handle}__`;
} else {
// Pass the original file information through query params so we don't have to load the file twice
url.searchParams.append('origWidth', meta.width.toString());
url.searchParams.append('origHeight', meta.height.toString());
url.searchParams.append('origFormat', meta.format);

meta.src = rootRelativePath(settings.config, url);
}

const meta = await emitESMImage(id, this.meta.watchMode, this.emitFile, settings);
return `export default ${JSON.stringify(meta)}`;
}
},
Expand Down
2 changes: 1 addition & 1 deletion packages/astro/src/content/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ async function render({
};
}

export function createImage(options: { assetsDir: string }) {
export function createImage(options: { assetsDir: string; relAssetsDir: string }) {
return () => {
if (options.assetsDir === 'undefined') {
throw new Error('Enable `experimental.assets` in your Astro config to use image()');
Expand Down
37 changes: 23 additions & 14 deletions packages/astro/src/content/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import matter from 'gray-matter';
import fsMod from 'node:fs';
import path from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
import type { EmitFile } from 'rollup';
import { ErrorPayload as ViteErrorPayload, normalizePath, ViteDevServer } from 'vite';
import { z } from 'zod';
import { AstroConfig, AstroSettings } from '../@types/astro.js';
import type { ImageMetadata } from '../assets/types.js';
import { emitESMImage } from '../assets/internal.js';
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { CONTENT_TYPES_FILE } from './consts.js';

Expand Down Expand Up @@ -43,21 +44,29 @@ export const msg = {
`${collection} does not have a config. We suggest adding one for type safety!`,
};

export function extractFrontmatterAssets(data: Record<string, any>): string[] {
function findAssets(potentialAssets: Record<string, any>): ImageMetadata[] {
return Object.values(potentialAssets).reduce((acc, curr) => {
if (typeof curr === 'object') {
if (curr.__astro === true) {
acc.push(curr);
} else {
acc.push(...findAssets(curr));
}
/**
* Mutate (arf) the entryData to reroute assets to their final paths
*/
export async function patchAssets(
frontmatterEntry: Record<string, any>,
watchMode: boolean,
fileEmitter: EmitFile,
astroSettings: AstroSettings
) {
for (const key of Object.keys(frontmatterEntry)) {
if (typeof frontmatterEntry[key] === 'object' && frontmatterEntry[key] !== null) {
if (frontmatterEntry[key]['__astro_asset']) {
frontmatterEntry[key] = await emitESMImage(
frontmatterEntry[key].src,
watchMode,
fileEmitter,
astroSettings
);
} else {
await patchAssets(frontmatterEntry[key], watchMode, fileEmitter, astroSettings);
}
return acc;
}, []);
}
}

return findAssets(data).map((asset) => asset.src);
}

export function getEntrySlug({
Expand Down
13 changes: 3 additions & 10 deletions packages/astro/src/content/vite-plugin-content-imports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@ import type fsMod from 'node:fs';
import { extname } from 'node:path';
import { pathToFileURL } from 'url';
import type { Plugin } from 'vite';
import { normalizePath } from 'vite';
import { AstroSettings, ContentEntryType } from '../@types/astro.js';
import { AstroErrorData } from '../core/errors/errors-data.js';
import { AstroError } from '../core/errors/errors.js';
import { escapeViteEnvReferences, getFileInfo } from '../vite-plugin-utils/index.js';
import { CONTENT_FLAG } from './consts.js';
import {
ContentConfig,
extractFrontmatterAssets,
getContentEntryExts,
getContentPaths,
getEntryData,
getEntryInfo,
getEntrySlug,
getEntryType,
globalContentConfigObserver,
patchAssets,
} from './utils.js';

function isContentFlagImport(viteId: string, contentEntryExts: string[]) {
const { searchParams, pathname } = new URL(viteId, 'file://');
return searchParams.has(CONTENT_FLAG) && contentEntryExts.some((ext) => pathname.endsWith(ext));
Expand Down Expand Up @@ -106,25 +104,20 @@ export function astroContentImportPlugin({
const slug = getEntrySlug({ ...generatedInfo, unvalidatedSlug: info.slug });

const collectionConfig = contentConfig?.collections[generatedInfo.collection];
const data = collectionConfig
let data = collectionConfig
? await getEntryData(
{ ...generatedInfo, _internal, unvalidatedData: info.data },
collectionConfig
)
: info.data;

const images = extractFrontmatterAssets(data).map(
(image) => `'${image}': await import('${normalizePath(image)}'),`
);
await patchAssets(data, this.meta.watchMode, this.emitFile, settings);

const code = escapeViteEnvReferences(`
export const id = ${JSON.stringify(generatedInfo.id)};
export const collection = ${JSON.stringify(generatedInfo.collection)};
export const slug = ${JSON.stringify(slug)};
export const body = ${JSON.stringify(info.body)};
const frontmatterImages = {
${images.join('\n')}
}
export const data = ${devalue.uneval(data) /* TODO: reuse astro props serializer */};
export const _internal = {
filePath: ${JSON.stringify(_internal.filePath)},
Expand Down
15 changes: 11 additions & 4 deletions packages/astro/src/core/create-vite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ const ALWAYS_NOEXTERNAL = [
'@fontsource/*',
];

// These specifiers are usually dependencies written in CJS, but loaded through Vite's transform
// pipeline, which Vite doesn't support in development time. This hardcoded list temporarily
// fixes things until Vite can properly handle them, or when they support ESM.
const ONLY_DEV_EXTERNAL = [
// Imported by `<Code/>` which is processed by Vite
'shiki',
// Imported by `@astrojs/prism` which exposes `<Prism/>` that is processed by Vite
'prismjs/components/index.js',
];

/** Return a common starting point for all Vite actions */
export async function createVite(
commandConfig: vite.InlineConfig,
Expand Down Expand Up @@ -162,10 +172,7 @@ export async function createVite(
},
ssr: {
noExternal: [...ALWAYS_NOEXTERNAL, ...astroPkgsConfig.ssr.noExternal],
// shiki is imported by Code.astro, which is no-externalized (processed by Vite).
// However, shiki's deps are in CJS and trips up Vite's dev SSR transform, externalize
// shiki to load it with node instead.
external: [...(mode === 'dev' ? ['shiki'] : []), ...astroPkgsConfig.ssr.external],
external: [...(mode === 'dev' ? ONLY_DEV_EXTERNAL : []), ...astroPkgsConfig.ssr.external],
},
};

Expand Down
1 change: 1 addition & 0 deletions packages/astro/src/core/sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export async function sync(
{
server: { middlewareMode: true, hmr: false },
optimizeDeps: { entries: [] },
ssr: { external: ['image-size'] },
logLevel: 'silent',
},
{ settings, logging, mode: 'build', command: 'build', fs }
Expand Down
44 changes: 42 additions & 2 deletions packages/astro/test/core-image.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,34 @@ describe('astro:image', () => {
$ = cheerio.load(html);
});

it('Adds the <img> tag', () => {
it('Adds the <img> tags', () => {
let $img = $('img');
expect($img).to.have.a.lengthOf(1);
expect($img).to.have.a.lengthOf(4);
});

it('has proper source for directly used image', () => {
let $img = $('#direct-image img');
expect($img.attr('src').startsWith('/src/')).to.equal(true);
});

it('has proper attributes for optimized image through getImage', () => {
let $img = $('#optimized-image-get-image img');
expect($img.attr('src').startsWith('/_image')).to.equal(true);
expect($img.attr('width')).to.equal('207');
expect($img.attr('height')).to.equal('243');
});

it('has proper attributes for optimized image through Image component', () => {
let $img = $('#optimized-image-component img');
expect($img.attr('src').startsWith('/_image')).to.equal(true);
expect($img.attr('width')).to.equal('207');
expect($img.attr('height')).to.equal('243');
expect($img.attr('alt')).to.equal('A penguin!');
});

it('properly handles nested images', () => {
let $img = $('#nested-image img');
expect($img.attr('src').startsWith('/src/')).to.equal(true);
});
});

Expand Down Expand Up @@ -306,6 +330,22 @@ describe('astro:image', () => {
expect(data).to.be.an.instanceOf(Buffer);
});

it('output files for content collections images', async () => {
const html = await fixture.readFile('/blog/one/index.html');

const $ = cheerio.load(html);
let $img = $('img');
expect($img).to.have.a.lengthOf(2);

const srcdirect = $('#direct-image img').attr('src');
const datadirect = await fixture.readFile(srcdirect, null);
expect(datadirect).to.be.an.instanceOf(Buffer);

const srcnested = $('#nested-image img').attr('src');
const datanested = await fixture.readFile(srcnested, null);
expect(datanested).to.be.an.instanceOf(Buffer);
});

it('quality attribute produces a different file', async () => {
const html = await fixture.readFile('/quality/index.html');
const $ = cheerio.load(html);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
title: One
image: penguin2.jpg
cover:
image: penguin1.jpg
---

# A post

text here
15 changes: 15 additions & 0 deletions packages/astro/test/fixtures/core-image-ssg/src/content/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineCollection, image, z } from "astro:content";

const blogCollection = defineCollection({
schema: z.object({
title: z.string(),
image: image(),
cover: z.object({
image: image()
})
}),
});

export const collections = {
blog: blogCollection
};
Loading

0 comments on commit e51d87c

Please sign in to comment.