From b76c166bdd8e28683f62806aef968d1e0c3b06d9 Mon Sep 17 00:00:00 2001 From: Erika <3019731+Princesseuh@users.noreply.github.com> Date: Wed, 16 Aug 2023 10:21:05 +0200 Subject: [PATCH] feat: unflag experimental.assets (#7921) Co-authored-by: Sarah Rainsberger Co-authored-by: Emanuele Stoppa --- .changeset/warm-weeks-yell.md | 13 + packages/astro/client-base.d.ts | 427 ----------------- packages/astro/client-image.d.ts | 41 -- packages/astro/client.d.ts | 450 +++++++++++++++++- .../view-transitions/astro.config.mjs | 1 - packages/astro/env.d.ts | 4 +- packages/astro/package.json | 4 - packages/astro/src/@types/astro.ts | 22 - packages/astro/src/assets/image-endpoint.ts | 11 +- packages/astro/src/assets/internal.ts | 1 + packages/astro/src/cli/flags.ts | 3 - packages/astro/src/content/types-generator.ts | 3 +- packages/astro/src/content/utils.ts | 15 +- .../content/vite-plugin-content-imports.ts | 18 +- packages/astro/src/core/build/generate.ts | 12 +- packages/astro/src/core/build/index.ts | 4 +- .../src/core/build/plugins/plugin-pages.ts | 18 +- .../core/build/plugins/plugin-prerender.ts | 2 +- packages/astro/src/core/config/config.ts | 2 - packages/astro/src/core/config/schema.ts | 2 - packages/astro/src/core/config/settings.ts | 1 - packages/astro/src/core/create-vite.ts | 2 +- packages/astro/src/core/dev/container.ts | 6 +- .../astro/src/core/routing/manifest/create.ts | 7 +- .../src/vite-plugin-inject-env-ts/index.ts | 26 +- .../astro/src/vite-plugin-markdown/index.ts | 5 +- packages/astro/test/core-image.test.js | 21 - .../astro-assets-prefix/astro.config.mjs | 5 +- .../astro-assets/src/pages/index.astro | 4 +- .../astro.config.mjs | 3 - .../astro/test/ssr-split-manifest.test.js | 2 +- .../get-entry-type.test.js | 21 +- .../cloudflare/test/prerender.test.js | 4 +- .../cloudflare/test/routesJson.js | 6 +- packages/integrations/markdoc/package.json | 2 +- .../markdoc/src/content-entry-type.ts | 26 +- ...ets-config.ts => runtime-assets-config.ts} | 5 +- packages/integrations/markdoc/src/runtime.ts | 3 +- .../fixtures/image-assets/astro.config.mjs | 5 +- packages/integrations/mdx/src/index.ts | 2 +- packages/integrations/mdx/src/plugins.ts | 10 +- .../test/fixtures/mdx-images/astro.config.ts | 3 - .../netlify/test/functions/prerender.test.js | 17 +- .../netlify/test/functions/redirects.test.js | 8 +- .../test/functions/redirects.test.js.snap | 1 + .../test/functions/split-support.test.js | 22 +- .../hosted-astro-project/astro.config.mjs | 3 - packages/integrations/node/test/image.test.js | 3 - .../integrations/vercel/src/image/shared.ts | 11 +- .../vercel/src/serverless/adapter.ts | 8 +- .../integrations/vercel/src/static/adapter.ts | 8 +- .../vercel/test/edge-middleware.test.js | 12 +- .../test/fixtures/image/astro.config.mjs | 3 - .../hosted-astro-project/astro.config.mjs | 3 - .../vercel/test/serverless-prerender.test.js | 5 +- .../integrations/vercel/test/split.test.js | 4 +- packages/markdown/remark/src/index.ts | 10 +- packages/markdown/remark/src/types.ts | 1 - 58 files changed, 589 insertions(+), 752 deletions(-) create mode 100644 .changeset/warm-weeks-yell.md delete mode 100644 packages/astro/client-base.d.ts delete mode 100644 packages/astro/client-image.d.ts rename packages/integrations/markdoc/src/{experimental-assets-config.ts => runtime-assets-config.ts} (77%) diff --git a/.changeset/warm-weeks-yell.md b/.changeset/warm-weeks-yell.md new file mode 100644 index 000000000000..9c0ca21daa02 --- /dev/null +++ b/.changeset/warm-weeks-yell.md @@ -0,0 +1,13 @@ +--- +"astro": major +--- + +`astro:assets` is now enabled by default. If you were previously using the `experimental.assets` flag, please remove it from your config. Also note that the previous `@astrojs/image` integration is incompatible, and must be removed. + +This also brings two important changes to using images in Astro: + +- New ESM shape: importing an image will now return an object with different properties describing the image such as its path, format and dimensions. This is a breaking change and may require you to update your existing images. +- In Markdown, MDX, and Markdoc, the `![]()` syntax will now resolve relative images located anywhere in your project in addition to remote images and images stored in the `public/` folder. This notably unlocks storing images next to your content. + +Please see our existing [Assets page in Docs](https://docs.astro.build/en/guides/assets/) for more information about using `astro:assets`. + diff --git a/packages/astro/client-base.d.ts b/packages/astro/client-base.d.ts deleted file mode 100644 index 3b0ee490167d..000000000000 --- a/packages/astro/client-base.d.ts +++ /dev/null @@ -1,427 +0,0 @@ -/// - -// eslint-disable-next-line @typescript-eslint/no-namespace -declare namespace App { - // eslint-disable-next-line @typescript-eslint/no-empty-interface - export interface Locals {} -} - -interface ImportMetaEnv { - /** - * The prefix for Astro-generated asset links if the build.assetsPrefix config option is set. This can be used to create asset links not handled by Astro. - */ - readonly ASSETS_PREFIX: string; - /** - * This is set to the site option specified in your project’s Astro config file. - */ - readonly SITE: string; -} - -interface ImportMeta { - /** - * Astro and Vite expose environment variables through `import.meta.env`. For a complete list of the environment variables available, see the two references below. - * - * - [Astro reference](https://docs.astro.build/en/guides/environment-variables/#default-environment-variables) - * - [Vite reference](https://vitejs.dev/guide/env-and-mode.html#env-variables) - */ - readonly env: ImportMetaEnv; -} - -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's type here is different from the internal function since the Vite module implicitly pass the service config - /** - * Get an optimized image and the necessary attributes to render it. - * - * **Example** - * ```astro - * --- - * import { getImage } from 'astro:assets'; - * import originalImage from '../assets/image.png'; - * - * const optimizedImage = await getImage({src: originalImage, width: 1280 }); - * --- - * - * ``` - * - * This is functionally equivalent to using the `` component, as the component calls this function internally. - */ - getImage: ( - options: - | import('./dist/assets/types.js').ImageTransform - | import('./dist/assets/types.js').UnresolvedImageTransform - ) => Promise; - getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService; - Image: typeof import('./components/Image.astro').default; - }; - - type WithRequired = T & { [P in K]-?: T[P] }; - type Simplify = { [KeyType in keyof T]: T[KeyType] }; - type ImgAttributes = WithRequired< - Omit, 'src' | 'width' | 'height'>, - 'alt' - >; - - export type LocalImageProps = Simplify< - import('./dist/assets/types.js').LocalImageProps - >; - export type RemoteImageProps = Simplify< - import('./dist/assets/types.js').RemoteImageProps - >; - export const { getImage, getConfiguredImageService, Image }: AstroAssets; -} - -declare module 'astro:transitions' { - type TransitionModule = typeof import('./dist/transitions/index.js'); - export const slide: TransitionModule['slide']; - export const fade: TransitionModule['fade']; - - type ViewTransitionsModule = typeof import('./components/ViewTransitions.astro'); - export const ViewTransitions: ViewTransitionsModule['default']; -} - -type MD = import('./dist/@types/astro').MarkdownInstance>; -interface ExportedMarkdownModuleEntities { - frontmatter: MD['frontmatter']; - file: MD['file']; - url: MD['url']; - getHeadings: MD['getHeadings']; - /** @deprecated Renamed to `getHeadings()` */ - getHeaders: () => void; - Content: MD['Content']; - rawContent: MD['rawContent']; - compiledContent: MD['compiledContent']; - load: MD['default']; -} - -declare module '*.md' { - const { load }: ExportedMarkdownModuleEntities; - export const { - frontmatter, - file, - url, - getHeadings, - getHeaders, - Content, - rawContent, - compiledContent, - }: ExportedMarkdownModuleEntities; - export default load; -} - -declare module '*.markdown' { - const { load }: ExportedMarkdownModuleEntities; - export const { - frontmatter, - file, - url, - getHeadings, - getHeaders, - Content, - rawContent, - compiledContent, - }: ExportedMarkdownModuleEntities; - export default load; -} - -declare module '*.mkdn' { - const { load }: ExportedMarkdownModuleEntities; - export const { - frontmatter, - file, - url, - getHeadings, - getHeaders, - Content, - rawContent, - compiledContent, - }: ExportedMarkdownModuleEntities; - export default load; -} - -declare module '*.mkd' { - const { load }: ExportedMarkdownModuleEntities; - export const { - frontmatter, - file, - url, - getHeadings, - getHeaders, - Content, - rawContent, - compiledContent, - }: ExportedMarkdownModuleEntities; - export default load; -} - -declare module '*.mdwn' { - const { load }: ExportedMarkdownModuleEntities; - export const { - frontmatter, - file, - url, - getHeadings, - getHeaders, - Content, - rawContent, - compiledContent, - }: ExportedMarkdownModuleEntities; - export default load; -} - -declare module '*.mdown' { - const { load }: ExportedMarkdownModuleEntities; - export const { - frontmatter, - file, - url, - getHeadings, - getHeaders, - Content, - rawContent, - compiledContent, - }: ExportedMarkdownModuleEntities; - export default load; -} - -declare module '*.mdx' { - type MDX = import('./dist/@types/astro').MDXInstance>; - - export const frontmatter: MDX['frontmatter']; - export const file: MDX['file']; - export const url: MDX['url']; - export const getHeadings: MDX['getHeadings']; - export const Content: MDX['Content']; - - const load: MDX['default']; - export default load; -} - -declare module 'astro:ssr-manifest' { - export const manifest: import('./dist/@types/astro').SSRManifest; -} - -// Everything below are Vite's types (apart from image types, which are in `client.d.ts`) - -// CSS modules -type CSSModuleClasses = { readonly [key: string]: string }; - -declare module '*.module.css' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.scss' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.sass' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.less' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.styl' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.stylus' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.pcss' { - const classes: CSSModuleClasses; - export default classes; -} -declare module '*.module.sss' { - const classes: CSSModuleClasses; - export default classes; -} - -// CSS -declare module '*.css' { - const css: string; - export default css; -} -declare module '*.scss' { - const css: string; - export default css; -} -declare module '*.sass' { - const css: string; - export default css; -} -declare module '*.less' { - const css: string; - export default css; -} -declare module '*.styl' { - const css: string; - export default css; -} -declare module '*.stylus' { - const css: string; - export default css; -} -declare module '*.pcss' { - const css: string; - export default css; -} -declare module '*.sss' { - const css: string; - export default css; -} - -// Built-in asset types -// see `src/node/constants.ts` - -// images -declare module '*.jfif' { - const src: string; - export default src; -} -declare module '*.pjpeg' { - const src: string; - export default src; -} -declare module '*.pjp' { - const src: string; - export default src; -} -declare module '*.ico' { - const src: string; - export default src; -} - -// media -declare module '*.mp4' { - const src: string; - export default src; -} -declare module '*.webm' { - const src: string; - export default src; -} -declare module '*.ogg' { - const src: string; - export default src; -} -declare module '*.mp3' { - const src: string; - export default src; -} -declare module '*.wav' { - const src: string; - export default src; -} -declare module '*.flac' { - const src: string; - export default src; -} -declare module '*.aac' { - const src: string; - export default src; -} - -declare module '*.opus' { - const src: string; - export default src; -} - -// fonts -declare module '*.woff' { - const src: string; - export default src; -} -declare module '*.woff2' { - const src: string; - export default src; -} -declare module '*.eot' { - const src: string; - export default src; -} -declare module '*.ttf' { - const src: string; - export default src; -} -declare module '*.otf' { - const src: string; - export default src; -} - -// other -declare module '*.webmanifest' { - const src: string; - export default src; -} -declare module '*.pdf' { - const src: string; - export default src; -} -declare module '*.txt' { - const src: string; - export default src; -} - -// wasm?init -declare module '*.wasm?init' { - const initWasm: (options: WebAssembly.Imports) => Promise; - export default initWasm; -} - -// web worker -declare module '*?worker' { - const workerConstructor: { - new (): Worker; - }; - export default workerConstructor; -} - -declare module '*?worker&inline' { - const workerConstructor: { - new (): Worker; - }; - export default workerConstructor; -} - -declare module '*?worker&url' { - const src: string; - export default src; -} - -declare module '*?sharedworker' { - const sharedWorkerConstructor: { - new (): SharedWorker; - }; - export default sharedWorkerConstructor; -} - -declare module '*?sharedworker&inline' { - const sharedWorkerConstructor: { - new (): SharedWorker; - }; - export default sharedWorkerConstructor; -} - -declare module '*?sharedworker&url' { - const src: string; - export default src; -} - -declare module '*?raw' { - const src: string; - export default src; -} - -declare module '*?url' { - const src: string; - export default src; -} - -declare module '*?inline' { - const src: string; - export default src; -} diff --git a/packages/astro/client-image.d.ts b/packages/astro/client-image.d.ts deleted file mode 100644 index ffcc1c63c95f..000000000000 --- a/packages/astro/client-image.d.ts +++ /dev/null @@ -1,41 +0,0 @@ -/// - -// TODO: Merge this file with `client-base.d.ts` in 3.0, when the `astro:assets` feature isn't under a flag anymore. - -type InputFormat = import('./dist/assets/types.js').ImageInputFormat; - -interface ImageMetadata { - src: string; - width: number; - height: number; - format: InputFormat; -} - -declare module '*.gif' { - 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; -} -declare module '*.svg' { - const metadata: ImageMetadata; - export default metadata; -} diff --git a/packages/astro/client.d.ts b/packages/astro/client.d.ts index 96f59d5868a0..7f701b0c0507 100644 --- a/packages/astro/client.d.ts +++ b/packages/astro/client.d.ts @@ -1,31 +1,465 @@ -/// +/// -// images +// eslint-disable-next-line @typescript-eslint/no-namespace +declare namespace App { + // eslint-disable-next-line @typescript-eslint/no-empty-interface + export interface Locals {} +} + +interface ImportMetaEnv { + /** + * The prefix for Astro-generated asset links if the build.assetsPrefix config option is set. This can be used to create asset links not handled by Astro. + */ + readonly ASSETS_PREFIX: string; + /** + * This is set to the site option specified in your project’s Astro config file. + */ + readonly SITE: string; +} + +interface ImportMeta { + /** + * Astro and Vite expose environment variables through `import.meta.env`. For a complete list of the environment variables available, see the two references below. + * + * - [Astro reference](https://docs.astro.build/en/guides/environment-variables/#default-environment-variables) + * - [Vite reference](https://vitejs.dev/guide/env-and-mode.html#env-variables) + */ + readonly env: ImportMetaEnv; +} + +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's type here is different from the internal function since the Vite module implicitly pass the service config + /** + * Get an optimized image and the necessary attributes to render it. + * + * **Example** + * ```astro + * --- + * import { getImage } from 'astro:assets'; + * import originalImage from '../assets/image.png'; + * + * const optimizedImage = await getImage({src: originalImage, width: 1280 }); + * --- + * + * ``` + * + * This is functionally equivalent to using the `` component, as the component calls this function internally. + */ + getImage: ( + options: + | import('./dist/assets/types.js').ImageTransform + | import('./dist/assets/types.js').UnresolvedImageTransform + ) => Promise; + getConfiguredImageService: typeof import('./dist/assets/index.js').getConfiguredImageService; + Image: typeof import('./components/Image.astro').default; + }; + + type WithRequired = T & { [P in K]-?: T[P] }; + type Simplify = { [KeyType in keyof T]: T[KeyType] }; + type ImgAttributes = WithRequired< + Omit, 'src' | 'width' | 'height'>, + 'alt' + >; + + export type LocalImageProps = Simplify< + import('./dist/assets/types.js').LocalImageProps + >; + export type RemoteImageProps = Simplify< + import('./dist/assets/types.js').RemoteImageProps + >; + export const { getImage, getConfiguredImageService, Image }: AstroAssets; +} + +type InputFormat = import('./dist/assets/types.js').ImageInputFormat; + +interface ImageMetadata { + src: string; + width: number; + height: number; + format: InputFormat; +} + +declare module '*.gif' { + 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; +} +declare module '*.svg' { + const metadata: ImageMetadata; + export default metadata; +} + +declare module 'astro:transitions' { + type TransitionModule = typeof import('./dist/transitions/index.js'); + export const slide: TransitionModule['slide']; + export const fade: TransitionModule['fade']; + + type ViewTransitionsModule = typeof import('./components/ViewTransitions.astro'); + export const ViewTransitions: ViewTransitionsModule['default']; +} + +type MD = import('./dist/@types/astro').MarkdownInstance>; +interface ExportedMarkdownModuleEntities { + frontmatter: MD['frontmatter']; + file: MD['file']; + url: MD['url']; + getHeadings: MD['getHeadings']; + /** @deprecated Renamed to `getHeadings()` */ + getHeaders: () => void; + Content: MD['Content']; + rawContent: MD['rawContent']; + compiledContent: MD['compiledContent']; + load: MD['default']; +} + +declare module '*.md' { + const { load }: ExportedMarkdownModuleEntities; + export const { + frontmatter, + file, + url, + getHeadings, + getHeaders, + Content, + rawContent, + compiledContent, + }: ExportedMarkdownModuleEntities; + export default load; +} + +declare module '*.markdown' { + const { load }: ExportedMarkdownModuleEntities; + export const { + frontmatter, + file, + url, + getHeadings, + getHeaders, + Content, + rawContent, + compiledContent, + }: ExportedMarkdownModuleEntities; + export default load; +} + +declare module '*.mkdn' { + const { load }: ExportedMarkdownModuleEntities; + export const { + frontmatter, + file, + url, + getHeadings, + getHeaders, + Content, + rawContent, + compiledContent, + }: ExportedMarkdownModuleEntities; + export default load; +} + +declare module '*.mkd' { + const { load }: ExportedMarkdownModuleEntities; + export const { + frontmatter, + file, + url, + getHeadings, + getHeaders, + Content, + rawContent, + compiledContent, + }: ExportedMarkdownModuleEntities; + export default load; +} + +declare module '*.mdwn' { + const { load }: ExportedMarkdownModuleEntities; + export const { + frontmatter, + file, + url, + getHeadings, + getHeaders, + Content, + rawContent, + compiledContent, + }: ExportedMarkdownModuleEntities; + export default load; +} + +declare module '*.mdown' { + const { load }: ExportedMarkdownModuleEntities; + export const { + frontmatter, + file, + url, + getHeadings, + getHeaders, + Content, + rawContent, + compiledContent, + }: ExportedMarkdownModuleEntities; + export default load; +} + +declare module '*.mdx' { + type MDX = import('./dist/@types/astro').MDXInstance>; + + export const frontmatter: MDX['frontmatter']; + export const file: MDX['file']; + export const url: MDX['url']; + export const getHeadings: MDX['getHeadings']; + export const Content: MDX['Content']; + + const load: MDX['default']; + export default load; +} + +declare module 'astro:ssr-manifest' { + export const manifest: import('./dist/@types/astro').SSRManifest; +} + +// Everything below are Vite's types (apart from image types, which are in `client.d.ts`) + +// CSS modules +type CSSModuleClasses = { readonly [key: string]: string }; + +declare module '*.module.css' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.scss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sass' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.less' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.styl' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.stylus' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.pcss' { + const classes: CSSModuleClasses; + export default classes; +} +declare module '*.module.sss' { + const classes: CSSModuleClasses; + export default classes; +} + +// CSS +declare module '*.css' { + const css: string; + export default css; +} +declare module '*.scss' { + const css: string; + export default css; +} +declare module '*.sass' { + const css: string; + export default css; +} +declare module '*.less' { + const css: string; + export default css; +} +declare module '*.styl' { + const css: string; + export default css; +} +declare module '*.stylus' { + const css: string; + export default css; +} +declare module '*.pcss' { + const css: string; + export default css; +} +declare module '*.sss' { + const css: string; + export default css; +} + +// Built-in asset types +// see `src/node/constants.ts` + +// images +declare module '*.jfif' { const src: string; export default src; } -declare module '*.jpeg' { +declare module '*.pjpeg' { const src: string; export default src; } -declare module '*.png' { +declare module '*.pjp' { const src: string; export default src; } -declare module '*.gif' { +declare module '*.ico' { const src: string; export default src; } -declare module '*.svg' { + +// media +declare module '*.mp4' { const src: string; export default src; } -declare module '*.webp' { +declare module '*.webm' { + const src: string; + export default src; +} +declare module '*.ogg' { + const src: string; + export default src; +} +declare module '*.mp3' { + const src: string; + export default src; +} +declare module '*.wav' { + const src: string; + export default src; +} +declare module '*.flac' { + const src: string; + export default src; +} +declare module '*.aac' { + const src: string; + export default src; +} + +declare module '*.opus' { + const src: string; + export default src; +} + +// fonts +declare module '*.woff' { + const src: string; + export default src; +} +declare module '*.woff2' { + const src: string; + export default src; +} +declare module '*.eot' { + const src: string; + export default src; +} +declare module '*.ttf' { + const src: string; + export default src; +} +declare module '*.otf' { const src: string; export default src; } -declare module '*.avif' { + +// other +declare module '*.webmanifest' { + const src: string; + export default src; +} +declare module '*.pdf' { + const src: string; + export default src; +} +declare module '*.txt' { + const src: string; + export default src; +} + +// wasm?init +declare module '*.wasm?init' { + const initWasm: (options: WebAssembly.Imports) => Promise; + export default initWasm; +} + +// web worker +declare module '*?worker' { + const workerConstructor: { + new (): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&inline' { + const workerConstructor: { + new (): Worker; + }; + export default workerConstructor; +} + +declare module '*?worker&url' { + const src: string; + export default src; +} + +declare module '*?sharedworker' { + const sharedWorkerConstructor: { + new (): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&inline' { + const sharedWorkerConstructor: { + new (): SharedWorker; + }; + export default sharedWorkerConstructor; +} + +declare module '*?sharedworker&url' { + const src: string; + export default src; +} + +declare module '*?raw' { + const src: string; + export default src; +} + +declare module '*?url' { + const src: string; + export default src; +} + +declare module '*?inline' { const src: string; export default src; } diff --git a/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs b/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs index 8a9f43bcc93c..78c248963065 100644 --- a/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs +++ b/packages/astro/e2e/fixtures/view-transitions/astro.config.mjs @@ -9,7 +9,6 @@ export default defineConfig({ integrations: [react()], experimental: { viewTransitions: true, - assets: true, }, vite: { build: { diff --git a/packages/astro/env.d.ts b/packages/astro/env.d.ts index 079370becebd..876a29c60f49 100644 --- a/packages/astro/env.d.ts +++ b/packages/astro/env.d.ts @@ -1,8 +1,8 @@ -/// +/// // Caution! The types here are only available inside Astro files (injected automatically by our language server) // As such, if the typings you're trying to add should be available inside ex: React components, they should instead -// be inside `client-base.d.ts` +// be inside `client.d.ts` type Astro = import('./dist/@types/astro.js').AstroGlobal; diff --git a/packages/astro/package.json b/packages/astro/package.json index 18918f934805..0c04cc5585a4 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -34,8 +34,6 @@ "./env": "./env.d.ts", "./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/*", @@ -91,8 +89,6 @@ "zod.mjs", "env.d.ts", "client.d.ts", - "client-base.d.ts", - "client-image.d.ts", "content-types.template.d.ts", "content-module.template.mjs", "import-meta.d.ts", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index d070b9825a5e..e2b3e6d63f7f 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -132,7 +132,6 @@ export interface CLIFlags { config?: string; drafts?: boolean; open?: boolean; - experimentalAssets?: boolean; } /** @@ -1231,27 +1230,6 @@ export interface AstroUserConfig { * These flags are not guaranteed to be stable. */ experimental?: { - /** - * @docs - * @name experimental.assets - * @type {boolean} - * @default `false` - * @version 2.1.0 - * @description - * Enable experimental support for optimizing and resizing images. With this enabled, a new `astro:assets` module will be exposed. - * - * To enable this feature, set `experimental.assets` to `true` in your Astro config: - * - * ```js - * { - * experimental: { - * assets: true, - * }, - * } - * ``` - */ - assets?: boolean; - /** * @docs * @name experimental.viewTransitions diff --git a/packages/astro/src/assets/image-endpoint.ts b/packages/astro/src/assets/image-endpoint.ts index 8dc15c36ae5c..d9a101679d35 100644 --- a/packages/astro/src/assets/image-endpoint.ts +++ b/packages/astro/src/assets/image-endpoint.ts @@ -1,11 +1,8 @@ import mime from 'mime/lite.js'; 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'; // @ts-expect-error -import { imageServiceConfig } from 'astro:assets'; +import { getConfiguredImageService, imageServiceConfig } from 'astro:assets'; async function loadRemoteImage(src: URL) { try { @@ -28,7 +25,7 @@ export const GET: APIRoute = async ({ request }) => { try { const imageService = await getConfiguredImageService(); - if (!isLocalService(imageService)) { + if (!('transform' in imageService)) { throw new Error('Configured image service is not a local service'); } @@ -70,3 +67,7 @@ export const GET: APIRoute = async ({ request }) => { return new Response(`Server Error: ${err}`, { status: 500 }); } }; + +function isRemotePath(src: string) { + return /^(http|ftp|https|ws):?\/\//.test(src) || src.startsWith('data:'); +} diff --git a/packages/astro/src/assets/internal.ts b/packages/astro/src/assets/internal.ts index 06e4f8cc0c89..a49828a46f04 100644 --- a/packages/astro/src/assets/internal.ts +++ b/packages/astro/src/assets/internal.ts @@ -9,6 +9,7 @@ import type { } from './types.js'; export function injectImageEndpoint(settings: AstroSettings) { + // TODO: Add a setting to disable the image endpoint settings.injectedRoutes.push({ pattern: '/_image', entryPoint: 'astro/assets/image-endpoint', diff --git a/packages/astro/src/cli/flags.ts b/packages/astro/src/cli/flags.ts index 703422d50d61..3d7360a290e7 100644 --- a/packages/astro/src/cli/flags.ts +++ b/packages/astro/src/cli/flags.ts @@ -23,9 +23,6 @@ export function flagsToAstroInlineConfig(flags: Flags): AstroInlineConfig { typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined, open: typeof flags.open === 'boolean' ? flags.open : undefined, }, - experimental: { - assets: typeof flags.experimentalAssets === 'boolean' ? flags.experimentalAssets : undefined, - }, }; } diff --git a/packages/astro/src/content/types-generator.ts b/packages/astro/src/content/types-generator.ts index 238e32c5f7ce..078197cd04f1 100644 --- a/packages/astro/src/content/types-generator.ts +++ b/packages/astro/src/content/types-generator.ts @@ -155,8 +155,7 @@ export async function createContentTypesGenerator({ fileURLToPath(event.entry), contentPaths, contentEntryExts, - dataEntryExts, - settings.config.experimental.assets + dataEntryExts ); if (fileType === 'ignored') { return { shouldGenerateTypes: false }; diff --git a/packages/astro/src/content/utils.ts b/packages/astro/src/content/utils.ts index d273dc105ec4..369e187a8230 100644 --- a/packages/astro/src/content/utils.ts +++ b/packages/astro/src/content/utils.ts @@ -93,8 +93,7 @@ export async function getEntryData( _internal: EntryInternal; }, collectionConfig: CollectionConfig, - pluginContext: PluginContext, - config: AstroConfig + pluginContext: PluginContext ) { let data; if (collectionConfig.type === 'data') { @@ -106,12 +105,6 @@ export async function getEntryData( let schema = collectionConfig.schema; if (typeof schema === 'function') { - if (!config.experimental.assets) { - throw new Error( - 'The function shape for schema can only be used when `experimental.assets` is enabled.' - ); - } - schema = schema({ image: createImage(pluginContext, entry._internal.filePath), }); @@ -250,9 +243,7 @@ export function getEntryType( entryPath: string, paths: Pick, contentFileExts: string[], - dataFileExts: string[], - // TODO: Unflag this when we're ready to release assets - erika, 2023-04-12 - experimentalAssets = false + dataFileExts: string[] ): 'content' | 'data' | 'config' | 'ignored' | 'unsupported' { const { ext, base } = path.parse(entryPath); const fileUrl = pathToFileURL(entryPath); @@ -260,7 +251,7 @@ export function getEntryType( if ( hasUnderscoreBelowContentDirectoryPath(fileUrl, paths.contentDir) || isOnIgnoreList(base) || - (experimentalAssets && isImageAsset(ext)) + isImageAsset(ext) ) { return 'ignored'; } else if (contentFileExts.includes(ext)) { diff --git a/packages/astro/src/content/vite-plugin-content-imports.ts b/packages/astro/src/content/vite-plugin-content-imports.ts index a659dd4a0181..4643e0922872 100644 --- a/packages/astro/src/content/vite-plugin-content-imports.ts +++ b/packages/astro/src/content/vite-plugin-content-imports.ts @@ -131,13 +131,7 @@ export const _internal = { configureServer(viteServer) { viteServer.watcher.on('all', async (event, entry) => { if (CHOKIDAR_MODIFIED_EVENTS.includes(event)) { - const entryType = getEntryType( - entry, - contentPaths, - contentEntryExts, - dataEntryExts, - settings.config.experimental.assets - ); + const entryType = getEntryType(entry, contentPaths, contentEntryExts, dataEntryExts); if (!COLLECTION_TYPES_TO_INVALIDATE_ON.includes(entryType)) return; // The content config could depend on collection entries via `reference()`. @@ -194,7 +188,7 @@ type GetEntryModuleParams = async function getContentEntryModule( params: GetEntryModuleParams ): Promise { - const { fileId, contentDir, pluginContext, config } = params; + const { fileId, contentDir, pluginContext } = params; const { collectionConfig, entryConfig, entry, rawContents, collection } = await getEntryModuleBaseInfo(params); @@ -221,8 +215,7 @@ async function getContentEntryModule( ? await getEntryData( { id, collection, _internal, unvalidatedData }, collectionConfig, - pluginContext, - config + pluginContext ) : unvalidatedData; @@ -241,7 +234,7 @@ async function getContentEntryModule( async function getDataEntryModule( params: GetEntryModuleParams ): Promise { - const { fileId, contentDir, pluginContext, config } = params; + const { fileId, contentDir, pluginContext } = params; const { collectionConfig, entryConfig, entry, rawContents, collection } = await getEntryModuleBaseInfo(params); @@ -256,8 +249,7 @@ async function getDataEntryModule( ? await getEntryData( { id, collection, _internal, unvalidatedData }, collectionConfig, - pluginContext, - config + pluginContext ) : unvalidatedData; diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts index f8d1bf6b490c..c99a8881bf6e 100644 --- a/packages/astro/src/core/build/generate.ts +++ b/packages/astro/src/core/build/generate.ts @@ -186,15 +186,13 @@ export async function generatePages(opts: StaticBuildOptions, internals: BuildIn } } - if (opts.settings.config.experimental.assets) { - info(opts.logging, null, `\n${bgGreen(black(` generating optimized images `))}`); - for (const imageData of getStaticImageList()) { - await generateImage(opts, imageData[1].options, imageData[1].path); - } - - delete globalThis?.astroAsset?.addStaticImage; + info(opts.logging, null, `\n${bgGreen(black(` generating optimized images `))}`); + for (const imageData of getStaticImageList()) { + await generateImage(opts, imageData[1].options, imageData[1].path); } + delete globalThis?.astroAsset?.addStaticImage; + await runHookBuildGenerated({ config: opts.settings.config, logging: opts.logging, diff --git a/packages/astro/src/core/build/index.ts b/packages/astro/src/core/build/index.ts index 5b1ecf4040f3..07b9b2f7ca17 100644 --- a/packages/astro/src/core/build/index.ts +++ b/packages/astro/src/core/build/index.ts @@ -102,9 +102,7 @@ class AstroBuilder { logging, }); - // HACK: Since we only inject the endpoint if `experimental.assets` is on and it's possible for an integration to - // add that flag, we need to only check and inject the endpoint after running the config setup hook. - if (this.settings.config.experimental.assets && isServerLikeOutput(this.settings.config)) { + if (isServerLikeOutput(this.settings.config)) { this.settings = injectImageEndpoint(this.settings); } diff --git a/packages/astro/src/core/build/plugins/plugin-pages.ts b/packages/astro/src/core/build/plugins/plugin-pages.ts index 966426439734..ff63acd740ef 100644 --- a/packages/astro/src/core/build/plugins/plugin-pages.ts +++ b/packages/astro/src/core/build/plugins/plugin-pages.ts @@ -8,6 +8,7 @@ import type { StaticBuildOptions } from '../types'; import { MIDDLEWARE_MODULE_ID } from './plugin-middleware.js'; import { RENDERERS_MODULE_ID } from './plugin-renderers.js'; import { ASTRO_PAGE_EXTENSION_POST_PATTERN, getPathFromVirtualModulePageName } from './util.js'; +import type { AstroSettings } from '../../../@types/astro'; export const ASTRO_PAGE_MODULE_ID = '@astro-page:'; export const ASTRO_PAGE_RESOLVED_MODULE_ID = '\0' + ASTRO_PAGE_MODULE_ID; @@ -74,11 +75,7 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V exports.push(`export { renderers };`); // The middleware should not be imported by the pages - if ( - // TODO: remover in Astro 4.0 - !opts.settings.config.build.excludeMiddleware || - opts.settings.adapter?.adapterFeatures?.edgeMiddleware === true - ) { + if (shouldBundleMiddleware(opts.settings)) { const middlewareModule = await this.resolve(MIDDLEWARE_MODULE_ID); if (middlewareModule) { imports.push(`import { onRequest } from "${middlewareModule.id}";`); @@ -94,6 +91,17 @@ function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): V }; } +export function shouldBundleMiddleware(settings: AstroSettings) { + // TODO: Remove in Astro 4.0 + if (settings.config.build.excludeMiddleware === true) { + return false; + } + if (settings.adapter?.adapterFeatures?.edgeMiddleware === true) { + return false; + } + return true; +} + export function pluginPages(opts: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin { return { build: 'ssr', diff --git a/packages/astro/src/core/build/plugins/plugin-prerender.ts b/packages/astro/src/core/build/plugins/plugin-prerender.ts index a0d6a9c7bf6d..402264c6edb5 100644 --- a/packages/astro/src/core/build/plugins/plugin-prerender.ts +++ b/packages/astro/src/core/build/plugins/plugin-prerender.ts @@ -14,7 +14,7 @@ function vitePluginPrerender(opts: StaticBuildOptions, internals: BuildInternals extendManualChunks(outputOptions, { after(id, meta) { // Split the Astro runtime into a separate chunk for readability - if (id.includes('astro/dist')) { + if (id.includes('astro/dist/runtime')) { return 'astro'; } const pageInfo = internals.pagesByViteID.get(id); diff --git a/packages/astro/src/core/config/config.ts b/packages/astro/src/core/config/config.ts index 29b0bb23a415..ba089c9a7727 100644 --- a/packages/astro/src/core/config/config.ts +++ b/packages/astro/src/core/config/config.ts @@ -124,8 +124,6 @@ export function resolveFlags(flags: Partial): CLIFlags { host: typeof flags.host === 'string' || typeof flags.host === 'boolean' ? flags.host : undefined, drafts: typeof flags.drafts === 'boolean' ? flags.drafts : undefined, - experimentalAssets: - typeof flags.experimentalAssets === 'boolean' ? flags.experimentalAssets : undefined, }; } diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts index 9b7f42327f3d..48b0f3a59ac9 100644 --- a/packages/astro/src/core/config/schema.ts +++ b/packages/astro/src/core/config/schema.ts @@ -44,7 +44,6 @@ const ASTRO_CONFIG_DEFAULTS = { legacy: {}, redirects: {}, experimental: { - assets: false, viewTransitions: false, optimizeHoistedScript: false, }, @@ -241,7 +240,6 @@ export const AstroConfigSchema = z.object({ .default(ASTRO_CONFIG_DEFAULTS.vite), experimental: z .object({ - assets: z.boolean().optional().default(ASTRO_CONFIG_DEFAULTS.experimental.assets), viewTransitions: z .boolean() .optional() diff --git a/packages/astro/src/core/config/settings.ts b/packages/astro/src/core/config/settings.ts index 1d0938c00f33..30ca7c4c2b3b 100644 --- a/packages/astro/src/core/config/settings.ts +++ b/packages/astro/src/core/config/settings.ts @@ -17,7 +17,6 @@ export function createBaseSettings(config: AstroConfig): AstroSettings { config, tsConfig: undefined, tsConfigPath: undefined, - adapter: undefined, injectedRoutes: [], resolvedInjectedRoutes: [], diff --git a/packages/astro/src/core/create-vite.ts b/packages/astro/src/core/create-vite.ts index 846d6109d3a0..5b2ebfa21bab 100644 --- a/packages/astro/src/core/create-vite.ts +++ b/packages/astro/src/core/create-vite.ts @@ -132,7 +132,7 @@ export async function createVite( astroContentImportPlugin({ fs, settings }), astroContentAssetPropagationPlugin({ mode, settings }), vitePluginSSRManifest(), - settings.config.experimental.assets ? [astroAssetsPlugin({ settings, logging, mode })] : [], + astroAssetsPlugin({ settings, logging, mode }), astroTransitions({ config: settings.config }), ], publicDir: fileURLToPath(settings.config.publicDir), diff --git a/packages/astro/src/core/dev/container.ts b/packages/astro/src/core/dev/container.ts index d4e41e96d46a..4aeb35f3a486 100644 --- a/packages/astro/src/core/dev/container.ts +++ b/packages/astro/src/core/dev/container.ts @@ -50,11 +50,7 @@ export async function createContainer({ isRestart, }); - // HACK: Since we only inject the endpoint if `experimental.assets` is on and it's possible for an integration to - // add that flag, we need to only check and inject the endpoint after running the config setup hook. - if (settings.config.experimental.assets) { - settings = injectImageEndpoint(settings); - } + settings = injectImageEndpoint(settings); const { host, headers, open } = settings.config.server; diff --git a/packages/astro/src/core/routing/manifest/create.ts b/packages/astro/src/core/routing/manifest/create.ts index e669f293b3f4..124d870d9070 100644 --- a/packages/astro/src/core/routing/manifest/create.ts +++ b/packages/astro/src/core/routing/manifest/create.ts @@ -185,7 +185,12 @@ function injectedRouteToItem( { config, cwd }: { config: AstroConfig; cwd?: string }, { pattern, entryPoint }: InjectedRoute ): Item { - const resolved = require.resolve(entryPoint, { paths: [cwd || fileURLToPath(config.root)] }); + let resolved: string; + try { + resolved = require.resolve(entryPoint, { paths: [cwd || fileURLToPath(config.root)] }); + } catch (e) { + resolved = fileURLToPath(new URL(entryPoint, config.root)); + } const ext = path.extname(pattern); diff --git a/packages/astro/src/vite-plugin-inject-env-ts/index.ts b/packages/astro/src/vite-plugin-inject-env-ts/index.ts index 8ac7c5281e90..0f0fbb86d01a 100644 --- a/packages/astro/src/vite-plugin-inject-env-ts/index.ts +++ b/packages/astro/src/vite-plugin-inject-env-ts/index.ts @@ -50,26 +50,6 @@ export async function setUpEnvTs({ if (fs.existsSync(envTsPath)) { let typesEnvContents = await fs.promises.readFile(envTsPath, 'utf-8'); - // TODO: Remove this logic in 3.0, as `astro/client-image` will be merged into `astro/client` - if (settings.config.experimental.assets && typesEnvContents.includes('types="astro/client"')) { - typesEnvContents = typesEnvContents.replace( - 'types="astro/client"', - 'types="astro/client-image"' - ); - await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8'); - info(logging, 'assets', `Added ${bold(envTsPathRelativetoRoot)} types`); - } else if ( - !settings.config.experimental.assets && - typesEnvContents.includes('types="astro/client-image"') - ) { - typesEnvContents = typesEnvContents.replace( - 'types="astro/client-image"', - 'types="astro/client"' - ); - await fs.promises.writeFile(envTsPath, typesEnvContents, 'utf-8'); - info(logging, 'assets', `Removed ${bold(envTsPathRelativetoRoot)} types`); - } - if (!fs.existsSync(dotAstroDir)) // Add `.astro` types reference if none exists return; @@ -83,11 +63,7 @@ export async function setUpEnvTs({ } else { // Otherwise, inject the `env.d.ts` file let referenceDefs: string[] = []; - if (settings.config.experimental.assets) { - referenceDefs.push('/// '); - } else { - referenceDefs.push('/// '); - } + referenceDefs.push('/// '); if (fs.existsSync(dotAstroDir)) { referenceDefs.push(dotAstroTypeReference); diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index be77b3caadab..ae26bfb420f2 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -75,7 +75,6 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu ...settings.config.markdown, fileURL: new URL(`file://${fileId}`), frontmatter: raw.data, - experimentalAssets: settings.config.experimental.assets, }); let html = renderResult.code; @@ -83,7 +82,7 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu // Resolve all the extracted images from the content let imagePaths: { raw: string; resolved: string }[] = []; - if (settings.config.experimental.assets && renderResult.vfile.data.imagePaths) { + if (renderResult.vfile.data.imagePaths) { for (let imagePath of renderResult.vfile.data.imagePaths.values()) { imagePaths.push({ raw: imagePath, @@ -116,7 +115,7 @@ export default function markdown({ settings, logging }: AstroPluginOptions): Plu import { AstroError, AstroErrorData } from ${JSON.stringify(astroErrorModulePath)}; ${layout ? `import Layout from ${JSON.stringify(layout)};` : ''} - ${settings.config.experimental.assets ? 'import { getImage } from "astro:assets";' : ''} + import { getImage } from "astro:assets"; export const images = { ${imagePaths.map( diff --git a/packages/astro/test/core-image.test.js b/packages/astro/test/core-image.test.js index 8c09de245211..ff0adde52324 100644 --- a/packages/astro/test/core-image.test.js +++ b/packages/astro/test/core-image.test.js @@ -20,9 +20,6 @@ describe('astro:image', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/core-image/', - experimental: { - assets: true, - }, image: { service: testImageService({ foo: 'bar' }), }, @@ -434,9 +431,6 @@ describe('astro:image', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/core-image-errors/', - experimental: { - assets: true, - }, image: { service: testImageService(), }, @@ -502,9 +496,6 @@ describe('astro:image', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/core-image-base/', - experimental: { - assets: true, - }, image: { service: testImageService(), }, @@ -558,9 +549,6 @@ describe('astro:image', () => { root: './fixtures/core-image-ssr/', output: 'server', adapter: testAdapter(), - experimental: { - assets: true, - }, image: { service: testImageService(), }, @@ -582,9 +570,6 @@ describe('astro:image', () => { before(async () => { fixture = await loadFixture({ root: './fixtures/core-image-ssg/', - experimental: { - assets: true, - }, image: { service: testImageService(), }, @@ -748,9 +733,6 @@ describe('astro:image', () => { root: './fixtures/core-image-ssr/', output: 'server', adapter: testAdapter(), - experimental: { - assets: true, - }, image: { service: testImageService(), }, @@ -775,9 +757,6 @@ describe('astro:image', () => { root: './fixtures/core-image-ssr/', output: 'server', adapter: testAdapter(), - experimental: { - assets: true, - }, image: { service: testImageService(), }, diff --git a/packages/astro/test/fixtures/astro-assets-prefix/astro.config.mjs b/packages/astro/test/fixtures/astro-assets-prefix/astro.config.mjs index 7393b72b8aff..a5c3c5532628 100644 --- a/packages/astro/test/fixtures/astro-assets-prefix/astro.config.mjs +++ b/packages/astro/test/fixtures/astro-assets-prefix/astro.config.mjs @@ -1,5 +1,5 @@ -import { defineConfig } from 'astro/config'; import react from '@astrojs/react'; +import { defineConfig } from 'astro/config'; import { testImageService } from '../../test-image-service.js'; // https://astro.build/config @@ -10,9 +10,6 @@ export default defineConfig({ build: { assetsPrefix: 'http://localhost:4321', }, - experimental: { - assets: true, - }, image: { service: testImageService(), }, diff --git a/packages/astro/test/fixtures/astro-assets/src/pages/index.astro b/packages/astro/test/fixtures/astro-assets/src/pages/index.astro index 341f2744c592..8da7feb0cc1e 100644 --- a/packages/astro/test/fixtures/astro-assets/src/pages/index.astro +++ b/packages/astro/test/fixtures/astro-assets/src/pages/index.astro @@ -9,7 +9,7 @@ import p2Url from '../images/penguin2.jpg?url';

Icons

- +