Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement RFC "A core story for images" #6344

Merged
merged 113 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from 103 commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
d714a7a
feat(assets): Add Vite plugin
Princesseuh Jan 31, 2023
1f7c4a9
feat(images): Set up Image component
Princesseuh Feb 3, 2023
21416d0
Merge branch 'main' into feat/images
Princesseuh Feb 3, 2023
af1bc13
Merge branch 'main' into feat/images
Princesseuh Feb 3, 2023
063aa27
fix(types): Attempt to fix type generation
Princesseuh Feb 3, 2023
25fc671
Revert "fix(types): Attempt to fix type generation"
Princesseuh Feb 3, 2023
bf0dfff
fix(image): Fix image types causing build to fail
Princesseuh Feb 6, 2023
f2a69bd
feat(image): Implement client side part
Princesseuh Feb 10, 2023
e5ca26e
feat(services): Allow arbitrary transforms parameters
Princesseuh Feb 13, 2023
1a9d939
fix(image): Fix paths and types
Princesseuh Feb 13, 2023
848d326
config(types): Update config types to provide completions for availab…
Princesseuh Feb 13, 2023
02295be
feat(image): Add serving in dev
Princesseuh Feb 14, 2023
bca6ebe
feat(image): Improve type error messages
Princesseuh Feb 14, 2023
b0f4dd0
refactor(image): Move sharp's parseParams to baseService
Princesseuh Feb 14, 2023
7e95c5b
refactor(image): Skip work in dev for remote servies
Princesseuh Feb 14, 2023
633a4d8
feat(image): Add support for remote images
Princesseuh Feb 15, 2023
110c56a
feat(image): Add squoosh service
Princesseuh Feb 16, 2023
cf0bc4e
chore: update export map
Princesseuh Feb 16, 2023
07f2b2e
refactor(image): Abstract attributes handling by services
Princesseuh Feb 16, 2023
092c348
config(vercel): Remove test image service
Princesseuh Feb 16, 2023
3b0d49c
feat(image): Support for relative images in Markdown (WIP)
Princesseuh Feb 16, 2023
1a0ea86
feat(images): Add support for relative images in Markdown
Princesseuh Feb 17, 2023
308a850
feat(image): Update with RFC feedback
Princesseuh Feb 23, 2023
23ac7a4
fix(image): Fix alt error on getImage
Princesseuh Feb 23, 2023
3f85ccd
feat(image): Add support for assets validation through content collec…
Princesseuh Feb 23, 2023
aac6bfc
feat(image): Remove validateTransform
Princesseuh Feb 23, 2023
ef92cca
feat(image): Move to assets folder
Princesseuh Feb 23, 2023
2614cfa
fix(image): Fix package exports
Princesseuh Feb 23, 2023
6e8c795
feat(image): Add static imports references to virtual moduel
Princesseuh Feb 23, 2023
b75cd2f
fix(image): Fix images from content collections not working when embe…
Princesseuh Feb 23, 2023
e872d8d
Merge branch 'main' into feat/images
Princesseuh Feb 23, 2023
3753445
chore: lockfile
Princesseuh Feb 23, 2023
544d6ce
fix(markdown): Fix type
Princesseuh Feb 23, 2023
b7d6f68
fix(images): Flag enhanced images behing an experimental flag
Princesseuh Feb 23, 2023
b15b666
Merge branch 'main' into feat/images
Princesseuh Feb 23, 2023
4f0cf02
config(example): Update images example conifg
Princesseuh Feb 23, 2023
08c4240
Merge branch 'main' into feat/images
Princesseuh Feb 24, 2023
52a4388
fix(image): Fix types
Princesseuh Feb 24, 2023
cf2da10
fix(image): Fix asset type for strict, allow arbritary input and outp…
Princesseuh Feb 27, 2023
87fbfc7
chore: fix example check
Princesseuh Feb 27, 2023
5f0463b
feat(image): Emit assets for ESM imported images
Princesseuh Feb 27, 2023
33238d4
Merge branch 'main' into feat/images
Princesseuh Feb 28, 2023
406eff6
Add initial core image tests (#6381)
matthewp Feb 28, 2023
4c92818
feat(images): Make frontmatter extraction more generic than images fo…
Princesseuh Feb 28, 2023
7eb51b4
feat(image): Add support for building
Princesseuh Feb 28, 2023
d1da8e2
fix(image): Fix types
Princesseuh Feb 28, 2023
0cdf5d7
fix(images): Fix compatibility with image integration
Princesseuh Feb 28, 2023
9dc6a56
feat(images): Cuter generation stats
Princesseuh Feb 28, 2023
6bb2e34
fix(images): Globals are unsafe, it turns out
Princesseuh Feb 28, 2023
e908862
fix(images): Only generate images if flag is enabled
Princesseuh Feb 28, 2023
57ae057
fix(images): Only create `addStaticImage` in build
Princesseuh Feb 28, 2023
dc87411
feat(images): Add SSR endpoint
Princesseuh Feb 28, 2023
3932c73
fix(images): Only inject route in SSR
Princesseuh Feb 28, 2023
2aec7ef
Add tests for SSR
matthewp Feb 28, 2023
baa44ae
Remove console.log
matthewp Feb 28, 2023
9526d1c
Updated lockfile
matthewp Feb 28, 2023
57a8e0e
rename to satisfy the link gods
matthewp Feb 28, 2023
8446fb3
skip build tests for now
matthewp Feb 28, 2023
4256bb5
fix(images): Fix WASM files not being copied in dev
Princesseuh Mar 1, 2023
6108458
Merge branch 'main' into feat/images
Princesseuh Mar 1, 2023
46e319b
feat(images): Add quality presets
Princesseuh Mar 1, 2023
acd56b1
fix build tests running
matthewp Mar 1, 2023
49da47c
Remove console.log
matthewp Mar 1, 2023
fab176b
Add tests for getImage
matthewp Mar 1, 2023
46d5ae2
Test local services
matthewp Mar 1, 2023
051798e
Test the content collections API
matthewp Mar 1, 2023
cdc77e2
Add tests for quality
matthewp Mar 1, 2023
caa2900
Skipping content collections test
matthewp Mar 1, 2023
b86c995
feat(image): Add support for `~/assets` alias
Princesseuh Mar 2, 2023
f29be13
test(image): Add tests for aliases in dev
Princesseuh Mar 2, 2023
f919012
Fix windows + content collections
matthewp Mar 2, 2023
15538fe
test(image): Add tests for aliased images and images in Markdown
Princesseuh Mar 2, 2023
eb20571
Fix markdown images being built
matthewp Mar 2, 2023
7ef6b27
Should be posix join
matthewp Mar 2, 2023
1ed8652
Use the optimized image
matthewp Mar 2, 2023
abb72a0
fix test
matthewp Mar 2, 2023
309dcee
Fixes windows smoke
matthewp Mar 2, 2023
87a0d6a
fix(image): Nits
Princesseuh Mar 3, 2023
4db291a
Merge branch 'main' into feat/images
Princesseuh Mar 3, 2023
edc007d
feat(images): Add automatic update for `env.d.ts` when experimental i…
Princesseuh Mar 3, 2023
f67e6c4
fix(images): Revert env.d.ts change if the user opted-out of the expe…
Princesseuh Mar 3, 2023
6bc4ce9
chore: remove bad image example project
Princesseuh Mar 3, 2023
b620671
feat(image): Rename `experimental.images` to `experimental.assets`
Princesseuh Mar 3, 2023
c88a998
fix(images): Remove unused code in MDX integration
Princesseuh Mar 3, 2023
e606a5f
chore: Remove unrelated change
Princesseuh Mar 3, 2023
3ba14ab
fix(images): Remove export from astro/components
Princesseuh Mar 3, 2023
e0fa179
Fix, esm import on Win
matthewp Mar 3, 2023
252cea8
test(images): Add test for format
Princesseuh Mar 3, 2023
d5560e2
Merge branch 'main' into feat/images
Princesseuh Mar 3, 2023
ad701ad
fix(images): Add `client-image.d.ts` to export map
Princesseuh Mar 3, 2023
aacfe61
chore: changeset
Princesseuh Mar 3, 2023
a87c7a7
fix(images): Adjust with feedback, no more automatic refine, asset() …
Princesseuh Mar 6, 2023
13a9974
fix(images): Fix types
Princesseuh Mar 6, 2023
c57a47d
fix(images): Remove unnecessary spread
Princesseuh Mar 6, 2023
0414d49
fix(images): Better types for parseUrl and transform
Princesseuh Mar 6, 2023
be5d6e5
fix(images): Fix types
Princesseuh Mar 6, 2023
7fd3eda
Merge branch 'main' into feat/images
Princesseuh Mar 6, 2023
2d54346
fix(images): Adjust from feedback
Princesseuh Mar 7, 2023
ad7a15e
fix(images): Pass width and height through getHTMLAttributes even if …
Princesseuh Mar 7, 2023
2f38e34
Merge branch 'main' into feat/images
Princesseuh Mar 7, 2023
a124a71
fix(images): Recusirsively extract frontmatter assets
Princesseuh Mar 7, 2023
84c4997
fix(images): Use a reduce instead
Princesseuh Mar 7, 2023
c1299d3
feat(images): Add support for data: URIs
Princesseuh Mar 7, 2023
8c13a6b
chore: changeset
Princesseuh Mar 7, 2023
1f7d910
docs(images): Misc docs fixes
Princesseuh Mar 7, 2023
9b53c92
Update .changeset/gold-rocks-cry.md
Princesseuh Mar 7, 2023
97f0cdd
Update .changeset/gold-rocks-cry.md
Princesseuh Mar 7, 2023
93acb22
Update packages/astro/src/@types/astro.ts
Princesseuh Mar 7, 2023
516493b
Update packages/astro/src/assets/services/service.ts
Princesseuh Mar 7, 2023
7404291
Update packages/astro/src/assets/services/service.ts
Princesseuh Mar 7, 2023
b7f9f9c
Update packages/astro/src/assets/services/service.ts
Princesseuh Mar 7, 2023
5f5a06c
Update packages/astro/src/assets/types.ts
Princesseuh Mar 7, 2023
c912ffb
Update packages/astro/src/assets/types.ts
Princesseuh Mar 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/gold-rocks-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'astro': minor
'@astrojs/mdx': minor
'@astrojs/markdown-remark': minor
---

Add a new `experimental.assets` settings to enable support for an enhanced experience with assets
23 changes: 23 additions & 0 deletions packages/astro/client-base.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
/// <reference path="./import-meta.d.ts" />

declare module 'astro:assets' {
// Exporting things one by one is a bit cumbersome, not sure if there's a better way - erika, 2023-02-03
type AstroAssets = {
getImage: typeof import('./dist/assets/index.js').getImage;
Image: typeof import('./components/Image.astro').default;
};

type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
type Simplify<T> = { [KeyType in keyof T]: T[KeyType] };
type ImgAttributes = WithRequired<
Omit<import('./types').HTMLAttributes<'img'>, 'src' | 'width' | 'height'>,
'alt'
>;

export type LocalImageProps = Simplify<
import('./dist/assets/types.js').LocalImageProps<ImgAttributes>
>;
export type RemoteImageProps = Simplify<
import('./dist/assets/types.js').RemoteImageProps<ImgAttributes>
>;
export const { getImage, Image }: AstroAssets;
}

type MD = import('./dist/@types/astro').MarkdownInstance<Record<string, any>>;
interface ExportedMarkdownModuleEntities {
frontmatter: MD['frontmatter'];
Expand Down
48 changes: 48 additions & 0 deletions packages/astro/client-image.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/// <reference path="./client-base.d.ts" />

type InputFormat = 'avif' | 'gif' | 'heic' | 'heif' | 'jpeg' | 'jpg' | 'png' | 'tiff' | 'webp';

interface ImageMetadata {
src: string;
width: number;
height: number;
format: InputFormat;
}

// images
declare module '*.avif' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.gif' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.heic' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.heif' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.jpeg' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.jpg' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.png' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.tiff' {
const metadata: ImageMetadata;
export default metadata;
}
declare module '*.webp' {
const metadata: ImageMetadata;
export default metadata;
}
28 changes: 28 additions & 0 deletions packages/astro/components/Image.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
import { getImage, type LocalImageProps, type RemoteImageProps } from 'astro:assets';
import { AstroError, AstroErrorData } from '../dist/core/errors/index.js';

// The TypeScript diagnostic for JSX props uses the last member of the union to suggest props, so it would be better for
// LocalImageProps to be last. Unfortunately, when we do this the error messages that remote images get are complete nonsense
// Not 100% sure how to fix this, seems to be a TypeScript issue. Unfortunate.
type Props = LocalImageProps | RemoteImageProps;
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved

const props = Astro.props;

if (props.alt === undefined || props.alt === null) {
throw new AstroError(AstroErrorData.ImageMissingAlt);
}

// As a convenience, allow width and height to be string with a number in them, to match HTML's native `img`.
if (typeof props.width === 'string') {
props.width = parseInt(props.width);
}

if (typeof props.height === 'string') {
props.height = parseInt(props.height);
}

const image = await getImage(props);
---

<img src={image.src} {...image.attributes} />
23 changes: 20 additions & 3 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"./types": "./types.d.ts",
"./client": "./client.d.ts",
"./client-base": "./client-base.d.ts",
"./client-image": "./client-image.d.ts",
"./import-meta": "./import-meta.d.ts",
"./astro-jsx": "./astro-jsx.d.ts",
"./tsconfigs/*.json": "./tsconfigs/*",
Expand All @@ -47,6 +48,10 @@
"./client/*": "./dist/runtime/client/*",
"./components": "./components/index.ts",
"./components/*": "./components/*",
"./assets": "./dist/assets/index.js",
"./assets/image-endpoint": "./dist/assets/image-endpoint.js",
"./assets/services/sharp": "./dist/assets/services/sharp.js",
"./assets/services/squoosh": "./dist/assets/services/squoosh.js",
"./content/internal": "./dist/content/internal.js",
"./debug": "./components/Debug.astro",
"./internal/*": "./dist/runtime/server/*",
Expand Down Expand Up @@ -77,6 +82,7 @@
"env.d.ts",
"client.d.ts",
"client-base.d.ts",
"client-image.d.ts",
"import-meta.d.ts",
"astro-jsx.d.ts",
"types.d.ts",
Expand All @@ -86,10 +92,10 @@
],
"scripts": {
"prebuild": "astro-scripts prebuild --to-string \"src/runtime/server/astro-island.ts\" \"src/runtime/client/{idle,load,media,only,visible}.ts\"",
"build": "pnpm run prebuild && astro-scripts build \"src/**/*.ts\" && tsc",
"build": "pnpm run prebuild && astro-scripts build \"src/**/*.ts\" && tsc && pnpm run postbuild",
"build:ci": "pnpm run prebuild && astro-scripts build \"src/**/*.ts\"",
"dev": "astro-scripts dev --prebuild \"src/runtime/server/astro-island.ts\" --prebuild \"src/runtime/client/{idle,load,media,only,visible}.ts\" \"src/**/*.ts\"",
"postbuild": "astro-scripts copy \"src/**/*.astro\"",
"dev": "astro-scripts dev --copy-wasm --prebuild \"src/runtime/server/astro-island.ts\" --prebuild \"src/runtime/client/{idle,load,media,only,visible}.ts\" \"src/**/*.ts\"",
"postbuild": "astro-scripts copy \"src/**/*.astro\" && astro-scripts copy \"src/**/*.wasm\"",
"test:unit": "mocha --exit --timeout 30000 ./test/units/**/*.test.js",
"test:unit:match": "mocha --exit --timeout 30000 ./test/units/**/*.test.js -g",
"test": "pnpm run test:unit && mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
Expand Down Expand Up @@ -128,6 +134,7 @@
"github-slugger": "^2.0.0",
"gray-matter": "^4.0.3",
"html-escaper": "^3.0.3",
"image-size": "^1.0.2",
"kleur": "^4.1.4",
"magic-string": "^0.27.0",
"mime": "^3.0.0",
Expand Down Expand Up @@ -173,6 +180,7 @@
"@types/rimraf": "^3.0.2",
"@types/send": "^0.17.1",
"@types/server-destroy": "^1.0.1",
"@types/sharp": "^0.31.1",
"@types/unist": "^2.0.6",
"astro-scripts": "workspace:*",
"chai": "^4.3.6",
Expand All @@ -187,10 +195,19 @@
"remark-code-titles": "^0.1.2",
"rollup": "^3.9.0",
"sass": "^1.52.2",
"sharp": "^0.31.3",
"srcset-parse": "^1.1.0",
"undici": "^5.20.0",
"unified": "^10.1.2"
},
"peerDependencies": {
"sharp": "^0.31.3"
},
"peerDependenciesMeta": {
"sharp": {
"optional": true
}
},
"engines": {
"node": ">=16.12.0",
"npm": ">=6.14.0"
Expand Down
41 changes: 40 additions & 1 deletion packages/astro/src/@types/astro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type { PageBuildData } from '../core/build/types';
import type { AstroConfigSchema } from '../core/config';
import type { AstroTimer } from '../core/config/timer';
import type { AstroCookies } from '../core/cookies';
import type { LogOptions } from '../core/logger/core';
import type { AstroComponentFactory, AstroComponentInstance } from '../runtime/server';
import { SUPPORTED_MARKDOWN_FILE_EXTENSIONS } from './../core/constants.js';
export type {
Expand All @@ -28,6 +29,8 @@ export type {
RemarkPlugins,
ShikiConfig,
} from '@astrojs/markdown-remark';
export type { ExternalImageService, LocalImageService } from '../assets/services/service';
export type { ImageTransform } from '../assets/types';
export type { SSRManifest } from '../core/app/types';
export type { AstroCookies } from '../core/cookies';

Expand Down Expand Up @@ -85,6 +88,7 @@ export interface CLIFlags {
port?: number;
config?: string;
drafts?: boolean;
experimentalAssets?: boolean;
}

export interface BuildConfig {
Expand Down Expand Up @@ -696,6 +700,16 @@ export interface AstroUserConfig {

server?: ServerConfig | ((options: { command: 'dev' | 'preview' }) => ServerConfig);

/**
* @docs
* @kind heading
* @name Image options
*/
image?: {
// eslint-disable-next-line @typescript-eslint/ban-types
service: 'astro/assets/services/sharp' | 'astro/assets/services/squoosh' | (string & {});
};

/**
* @docs
* @kind heading
Expand Down Expand Up @@ -918,7 +932,27 @@ export interface AstroUserConfig {
* Astro offers experimental flags to give users early access to new features.
* These flags are not guaranteed to be stable.
*/
experimental?: object;
experimental?: {
/**
* @docs
* @name experimental.assets
* @type {boolean}
* @default `false`
* @version 2.1.0
* @description
* Enable experimental support for enhanced images support and ability to optimize and resize images directly in Astro. With this enabled, a new `astro:assets` module will be exposed
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved
*
* To enable this feature, set `experimental.assets` to `true` in your Astro config:
*
* ```js
* {
* experimental: {
* assets: true,
* },
* }
*/
assets?: boolean;
};

// Legacy options to be removed

Expand Down Expand Up @@ -1432,6 +1466,11 @@ export interface AstroIntegration {
};
}

export interface AstroPluginOptions {
settings: AstroSettings;
logging: LogOptions;
}

export type RouteType = 'page' | 'endpoint';

export interface RoutePart {
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/src/assets/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# vite-plugin-assets

This Vite plugin powers the assets story of Astro, allowing users to import helpers from `astro:assets` to generate optimized assets
14 changes: 14 additions & 0 deletions packages/astro/src/assets/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const VIRTUAL_MODULE_ID = 'astro:assets';
export const VIRTUAL_SERVICE_ID = 'virtual:image-service';
export const VALID_INPUT_FORMATS = [
'heic',
'heif',
'avif',
'jpeg',
'jpg',
'png',
'tiff',
'webp',
'gif',
Princesseuh marked this conversation as resolved.
Show resolved Hide resolved
] as const;
export const VALID_OUTPUT_FORMATS = ['avif', 'png', 'webp', 'jpeg', 'jpg'] as const;
66 changes: 66 additions & 0 deletions packages/astro/src/assets/image-endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import mime from 'mime';
import type { APIRoute } from '../@types/astro.js';
import { isRemotePath } from '../core/path.js';
import { getConfiguredImageService } from './internal.js';
import { isLocalService } from './services/service.js';
import { etag } from './utils/etag.js';

async function loadRemoteImage(src: URL) {
try {
const res = await fetch(src);

if (!res.ok) {
return undefined;
}

return Buffer.from(await res.arrayBuffer());
} catch (err: unknown) {
return undefined;
}
}

/**
* Endpoint used in SSR to serve optimized images
*/
export const get: APIRoute = async ({ request }) => {
try {
const imageService = await getConfiguredImageService();

if (!isLocalService(imageService)) {
throw new Error('Configured image service is not a local service');
}

const url = new URL(request.url);
const transform = await imageService.parseURL(url);

if (!transform || !transform.src) {
throw new Error('Incorrect transform returned by `parseURL`');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we give a hint about why this error occurred or a clue about how to solve this error?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can definitely add more information to the message, however since this error is shown to users of the user's website, we actually need to be careful about what we say.

}

let inputBuffer: Buffer | undefined = undefined;

// TODO: handle config subpaths?
const sourceUrl = isRemotePath(transform.src)
? new URL(transform.src)
: new URL(transform.src, url.origin);
inputBuffer = await loadRemoteImage(sourceUrl);

if (!inputBuffer) {
return new Response('Not Found', { status: 404 });
}

const { data, format } = await imageService.transform(inputBuffer, transform);

return new Response(data, {
status: 200,
headers: {
'Content-Type': mime.getType(format) || '',
'Cache-Control': 'public, max-age=31536000',
ETag: etag(data.toString()),
Date: new Date().toUTCString(),
},
});
} catch (err: unknown) {
return new Response(`Server Error: ${err}`, { status: 500 });
}
};
4 changes: 4 additions & 0 deletions packages/astro/src/assets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,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';
Loading