merge in main

This commit is contained in:
Dan Jutan 2022-09-02 14:00:53 -04:00
commit 2052aefae0
134 changed files with 2881 additions and 1908 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Move ast-types as dev dependency

View file

@ -0,0 +1,5 @@
---
'@astrojs/image': minor
---
feat: throw if alt text is missing

View file

@ -1,5 +0,0 @@
---
'@astrojs/mdx': minor
---
Introduce new `extendPlugins` configuration option. This defaults to inheriting all remark and rehype plugins from your `markdown` config, with options to use either Astro's defaults or no inheritance at all.

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Add docs link to "missing adapter" error msg

View file

@ -0,0 +1,5 @@
---
'@astrojs/image': patch
---
Fixes a bug that broke support for local images with spaces in the filename

View file

@ -0,0 +1,5 @@
---
'@astrojs/mdx': patch
---
Fix: Add GFM and Smartypants to MDX by default

3
.github/CODEOWNERS vendored
View file

@ -1 +1,2 @@
* @snowpackjs/maintainers
README.md @withastro/maintainers-docs
packages/astro/src/@types/astro.ts @withastro/maintainers-docs

View file

@ -12,6 +12,9 @@
## Docs
<!-- Is this a visible change? You probably need to update docs! -->
<!-- Could this affect a users behavior? We probably need to update docs! -->
<!-- If docs will be needed or youre not sure, uncomment the next line: -->
<!-- /cc @withastro/maintainers-docs for feedback! -->
<!-- DON'T DELETE THIS SECTION! If no docs added, explain why.-->
<!-- https://github.com/withastro/docs -->
<!-- https://github.com/withastro/docs -->

View file

@ -3,7 +3,11 @@ image:
file: .Dockerfile
# Commands to start on workspace startup
tasks:
- init: |
- before: |
# Get latest pnpm version, in case the custom docker image was not updated
# Until this issue gets resolved - https://github.com/gitpod-io/gitpod/issues/12551
curl -fsSL https://get.pnpm.io/install.sh | SHELL=`which bash` bash -
init: |
pnpm install
pnpm run build
command: |

View file

@ -9,7 +9,7 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"astro": "^1.1.2"
"dependencies": {
"astro": "^1.1.3"
}
}

View file

@ -41,15 +41,19 @@ const { title } = Astro.props;
margin: 0;
}
:global(h1) {
</style>
<style is:global>
h1 {
font-size: var(--font-size-xl);
}
:global(h2) {
h2 {
font-size: var(--font-size-lg);
}
:global(code) {
code {
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
Bitstream Vera Sans Mono, Courier New, monospace;
}

View file

@ -9,10 +9,10 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/mdx": "^0.10.3",
"dependencies": {
"astro": "^1.1.3",
"@astrojs/mdx": "^0.11.0",
"@astrojs/rss": "^1.0.0",
"@astrojs/sitemap": "^1.0.0",
"astro": "^1.1.2"
"@astrojs/sitemap": "^1.0.0"
}
}

View file

@ -11,6 +11,6 @@
},
"devDependencies": {
"@example/my-component": "workspace:*",
"astro": "^1.1.2"
"astro": "^1.1.3"
}
}

View file

@ -7,7 +7,7 @@
"build": "astro --root demo build",
"serve": "astro --root demo preview"
},
"devDependencies": {
"astro": "^1.1.2"
"dependencies": {
"astro": "^1.1.3"
}
}

View file

@ -11,19 +11,17 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.3",
"preact": "^10.7.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"@astrojs/react": "^1.1.0",
"@astrojs/preact": "^1.0.2",
"@algolia/client-search": "^4.13.1",
"@docsearch/css": "^3.1.0",
"@docsearch/react": "^3.1.0",
"@types/react": "^17.0.45",
"preact": "^10.7.3",
"react": "^18.1.0",
"react-dom": "^18.1.0"
},
"devDependencies": {
"@astrojs/preact": "^1.0.2",
"@types/node": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@astrojs/react": "^1.1.1",
"astro": "^1.1.2"
"@types/react-dom": "^18.0.0"
}
}

View file

@ -135,13 +135,15 @@ const lang = getLanguageFromURL(currentPage);
max-width: 200px;
}
:global(.search-item > *) {
flex-grow: 1;
}
@media (min-width: 50em) {
.search-item {
max-width: 400px;
}
}
</style>
<style is:global>
.search-item > * {
flex-grow: 1;
}
</style>

View file

@ -101,13 +101,15 @@ const sidebar = SIDEBAR[langCode];
font-weight: 600;
}
:global(:root.theme-dark) .nav-link a[aria-current='page'] {
color: hsla(var(--color-base-white), 100%, 1);
}
@media (min-width: 50em) {
.nav-groups {
padding: 0;
}
}
</style>
<style is:global>
:root.theme-dark .nav-link a[aria-current='page'] {
color: hsla(var(--color-base-white), 100%, 1);
}
</style>

View file

@ -47,11 +47,6 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
overflow-x: hidden;
}
.layout :global(> *) {
width: 100%;
height: 100%;
}
.grid-sidebar {
height: 100vh;
position: sticky;
@ -78,15 +73,6 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
display: none;
}
:global(.mobile-sidebar-toggle) {
overflow: hidden;
}
:global(.mobile-sidebar-toggle) #grid-left {
display: block;
top: 2rem;
}
@media (min-width: 50em) {
.layout {
overflow: initial;
@ -116,6 +102,21 @@ const githubEditUrl = `${CONFIG.GITHUB_EDIT_URL}/${currentFile}`;
}
}
</style>
<style is:global>
.layout > * {
width: 100%;
height: 100%;
}
.mobile-sidebar-toggle {
overflow: hidden;
}
.mobile-sidebar-toggle #grid-left {
display: block;
top: 2rem;
}
</style>
</head>
<body>

View file

@ -9,7 +9,7 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"astro": "^1.1.2"
"dependencies": {
"astro": "^1.1.3"
}
}

View file

@ -9,11 +9,10 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/alpinejs": "^0.1.1",
"@types/alpinejs": "^3.7.0",
"dependencies": {
"astro": "^1.1.3",
"alpinejs": "^3.10.2",
"astro": "^1.1.2"
},
"dependencies": {}
"@astrojs/alpinejs": "^0.1.1",
"@types/alpinejs": "^3.7.0"
}
}

View file

@ -9,12 +9,10 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/lit": "^1.0.1",
"astro": "^1.1.2"
},
"dependencies": {
"@webcomponents/template-shadowroot": "^0.1.0",
"lit": "^2.2.5"
"astro": "^1.1.3",
"lit": "^2.2.5",
"@astrojs/lit": "^1.0.0",
"@webcomponents/template-shadowroot": "^0.1.0"
}
}

View file

@ -9,20 +9,18 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/preact": "^1.0.2",
"@astrojs/react": "^1.1.1",
"@astrojs/solid-js": "^1.1.0",
"@astrojs/svelte": "^1.0.0",
"@astrojs/vue": "^1.0.0",
"astro": "^1.1.2"
},
"dependencies": {
"astro": "^1.1.3",
"preact": "^10.7.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"solid-js": "^1.4.3",
"svelte": "^3.48.0",
"vue": "^3.2.37"
"vue": "^3.2.37",
"@astrojs/preact": "^1.0.2",
"@astrojs/react": "^1.1.0",
"@astrojs/solid-js": "^1.1.0",
"@astrojs/svelte": "^1.0.0",
"@astrojs/vue": "^1.0.0"
}
}

View file

@ -9,11 +9,9 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/preact": "^1.0.2",
"astro": "^1.1.2"
},
"dependencies": {
"preact": "^10.7.3"
"astro": "^1.1.3",
"preact": "^10.7.3",
"@astrojs/preact": "^1.0.2"
}
}

View file

@ -9,14 +9,12 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/react": "^1.1.1",
"@types/react": "^18.0.10",
"@types/react-dom": "^18.0.5",
"astro": "^1.1.2"
},
"dependencies": {
"astro": "^1.1.3",
"react": "^18.1.0",
"react-dom": "^18.1.0"
"react-dom": "^18.1.0",
"@astrojs/react": "^1.1.0",
"@types/react": "^18.0.10",
"@types/react-dom": "^18.0.5"
}
}

View file

@ -9,11 +9,9 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/solid-js": "^1.1.0",
"astro": "^1.1.2"
},
"dependencies": {
"solid-js": "^1.4.3"
"astro": "^1.1.3",
"solid-js": "^1.4.3",
"@astrojs/solid-js": "^1.1.0"
}
}

View file

@ -9,11 +9,9 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/svelte": "^1.0.0",
"astro": "^1.1.2"
},
"dependencies": {
"svelte": "^3.48.0"
"svelte": "^3.48.0",
"@astrojs/svelte": "^1.0.0",
"astro": "^1.1.3"
}
}

View file

@ -9,11 +9,9 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/vue": "^1.0.0",
"astro": "^1.1.2"
},
"dependencies": {
"vue": "^3.2.37"
"astro": "^1.1.3",
"vue": "^3.2.37",
"@astrojs/vue": "^1.0.0"
}
}

View file

@ -9,7 +9,7 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"astro": "^1.1.2"
"dependencies": {
"astro": "^1.1.3"
}
}

View file

@ -9,7 +9,7 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"astro": "^1.1.2"
"dependencies": {
"astro": "^1.1.3"
}
}

View file

@ -10,16 +10,15 @@
"astro": "astro",
"server": "node server/server.mjs"
},
"devDependencies": {
"@astrojs/node": "^1.0.0",
"devDependencies": {},
"dependencies": {
"astro": "^1.1.3",
"svelte": "^3.48.0",
"@astrojs/svelte": "^1.0.0",
"astro": "^1.1.2",
"@astrojs/node": "^1.0.1",
"concurrently": "^7.2.1",
"lightcookie": "^1.0.25",
"unocss": "^0.15.6",
"vite-imagetools": "^4.0.4"
},
"dependencies": {
"svelte": "^3.48.0"
}
}

View file

@ -9,12 +9,10 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/react": "^1.1.1",
"astro": "^1.1.2"
},
"dependencies": {
"astro": "^1.1.3",
"react": "^18.1.0",
"react-dom": "^18.1.0"
"react-dom": "^18.1.0",
"@astrojs/react": "^1.1.0"
}
}

View file

@ -9,9 +9,9 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/markdown-remark": "^1.1.1",
"astro": "^1.1.2",
"dependencies": {
"astro": "^1.1.3",
"@astrojs/markdown-remark": "^1.1.0",
"hast-util-select": "5.0.1",
"rehype-autolink-headings": "^6.1.1",
"rehype-slug": "^5.0.1",

View file

@ -9,8 +9,7 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/markdown-remark": "^1.1.1",
"astro": "^1.1.2"
"dependencies": {
"astro": "^1.1.3"
}
}

View file

@ -9,10 +9,10 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"@astrojs/mdx": "^0.10.3",
"dependencies": {
"astro": "^1.1.3",
"preact": "^10.6.5",
"@astrojs/preact": "^1.0.2",
"astro": "^1.1.2",
"preact": "^10.6.5"
"@astrojs/mdx": "^0.11.0"
}
}

View file

@ -10,12 +10,10 @@
"astro": "astro"
},
"dependencies": {
"@nanostores/preact": "^0.1.3",
"nanostores": "^0.5.12",
"preact": "^10.7.3"
},
"devDependencies": {
"astro": "^1.1.3",
"preact": "^10.7.3",
"@astrojs/preact": "^1.0.2",
"astro": "^1.1.2"
"nanostores": "^0.5.12",
"@nanostores/preact": "^0.1.3"
}
}

View file

@ -9,9 +9,9 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"dependencies": {
"astro": "^1.1.3",
"@astrojs/tailwind": "^1.0.0",
"astro": "^1.1.2",
"autoprefixer": "^10.4.7",
"canvas-confetti": "^1.5.1",
"postcss": "^8.4.14",

View file

@ -9,8 +9,8 @@
"preview": "astro preview",
"astro": "astro"
},
"devDependencies": {
"astro": "^1.1.2",
"dependencies": {
"astro": "^1.1.3",
"vite-plugin-pwa": "0.11.11",
"workbox-window": "^6.5.3"
}

View file

@ -11,8 +11,8 @@
"astro": "astro",
"test": "vitest"
},
"devDependencies": {
"astro": "^1.1.2",
"dependencies": {
"astro": "^1.1.3",
"vitest": "^0.20.3"
}
}

View file

@ -1,5 +1,13 @@
# astro
## 1.1.3
### Patch Changes
- [#4574](https://github.com/withastro/astro/pull/4574) [`b92c24f40`](https://github.com/withastro/astro/commit/b92c24f4097f264a458c6f5044528c33fc897f01) Thanks [@delucis](https://github.com/delucis)! - Update `astro add` to list official integrations & adapters with same organisation we use in docs
* [#4566](https://github.com/withastro/astro/pull/4566) [`9ad307a9f`](https://github.com/withastro/astro/commit/9ad307a9fca064dcd9b2f27c3243d09d9154a5dc) Thanks [@bluwy](https://github.com/bluwy)! - Remove unused CSS for `client:load` components
## 1.1.2
### Patch Changes

View file

@ -1,6 +1,6 @@
{
"name": "astro",
"version": "1.1.2",
"version": "1.1.3",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",
@ -107,7 +107,6 @@
"@babel/types": "^7.18.4",
"@proload/core": "^0.3.2",
"@proload/plugin-tsm": "^0.2.1",
"ast-types": "^0.14.2",
"boxen": "^6.2.1",
"ci-info": "^3.3.1",
"common-ancestor-path": "^1.0.1",
@ -172,6 +171,7 @@
"@types/send": "^0.17.1",
"@types/unist": "^2.0.6",
"@types/yargs-parser": "^21.0.0",
"ast-types": "^0.14.2",
"astro-scripts": "workspace:*",
"chai": "^4.3.6",
"cheerio": "^1.0.0-rc.11",

View file

@ -66,29 +66,29 @@ export default async function add(names: string[], { cwd, flags, logging, teleme
['--yes', 'Accept all prompts.'],
['--help', 'Show this help message.'],
],
'Recommended: UI Frameworks': [
'UI Frameworks': [
['react', 'astro add react'],
['preact', 'astro add preact'],
['vue', 'astro add vue'],
['svelte', 'astro add svelte'],
['solid-js', 'astro add solid-js'],
['lit', 'astro add lit'],
['alpine', 'astro add alpine'],
],
'Recommended: Hosting': [
'SSR Adapters': [
['netlify', 'astro add netlify'],
['vercel', 'astro add vercel'],
['cloudflare', 'astro add cloudflare'],
['deno', 'astro add deno'],
['cloudflare', 'astro add cloudflare'],
['node', 'astro add node'],
],
'Recommended: Integrations': [
Others: [
['tailwind', 'astro add tailwind'],
['image', 'astro add image'],
['mdx', 'astro add mdx'],
['partytown', 'astro add partytown'],
['sitemap', 'astro add sitemap'],
],
'Example: Add an SSR Adapter': [
['netlify', 'astro add netlify'],
['vercel', 'astro add vercel'],
['deno', 'astro add deno'],
['prefetch', 'astro add prefetch'],
],
},
description: `For more integrations, check out: ${cyan('https://astro.build/integrations')}`,

View file

@ -1,12 +1,15 @@
import type { OutputChunk, RenderedChunk } from 'rollup';
import type { PageBuildData, ViteID } from './types';
import { prependForwardSlash } from '../path.js';
import { prependForwardSlash, removeFileExtension } from '../path.js';
import { viteID } from '../util.js';
export interface BuildInternals {
// Pure CSS chunks are chunks that only contain CSS.
pureCSSChunks: Set<RenderedChunk>;
/**
* The module ids of all CSS chunks, used to deduplicate CSS assets between
* SSR build and client build in vite-plugin-css.
*/
cssChunkModuleIds: Set<string>;
// A mapping of hoisted script ids back to the exact hoisted scripts it references
hoistedScriptIdToHoistedMap: Map<string, Set<string>>;
@ -59,10 +62,6 @@ export interface BuildInternals {
* @returns {BuildInternals}
*/
export function createBuildInternals(): BuildInternals {
// Pure CSS chunks are chunks that only contain CSS.
// This is all of them, and chunkToReferenceIdMap maps them to a hash id used to find the final file.
const pureCSSChunks = new Set<RenderedChunk>();
// These are for tracking hoisted script bundling
const hoistedScriptIdToHoistedMap = new Map<string, Set<string>>();
@ -70,7 +69,7 @@ export function createBuildInternals(): BuildInternals {
const hoistedScriptIdToPagesMap = new Map<string, Set<string>>();
return {
pureCSSChunks,
cssChunkModuleIds: new Set(),
hoistedScriptIdToHoistedMap,
hoistedScriptIdToPagesMap,
entrySpecifierToBundleMap: new Map<string, string>(),
@ -137,8 +136,15 @@ export function* getPageDatasByClientOnlyID(
): Generator<PageBuildData, void, unknown> {
const pagesByClientOnly = internals.pagesByClientOnly;
if (pagesByClientOnly.size) {
const pathname = `/@fs${prependForwardSlash(viteid)}`;
const pageBuildDatas = pagesByClientOnly.get(pathname);
let pathname = `/@fs${prependForwardSlash(viteid)}`;
let pageBuildDatas = pagesByClientOnly.get(viteid);
// BUG! The compiler partially resolves .jsx to remove the file extension so we have to check again.
// We should probably get rid of all `@fs` usage and always fully resolve via Vite,
// but this would be a bigger change.
if (!pageBuildDatas) {
pathname = `/@fs${prependForwardSlash(removeFileExtension(viteid))}`;
pageBuildDatas = pagesByClientOnly.get(pathname);
}
if (pageBuildDatas) {
for (const pageData of pageBuildDatas) {
yield pageData;

View file

@ -28,9 +28,9 @@ export async function staticBuild(opts: StaticBuildOptions) {
if (isModeServerWithNoAdapter(opts.astroConfig)) {
throw new Error(`Cannot use \`output: 'server'\` without an adapter.
Install and configure the appropriate server adapter for your final deployment.
Example:
Learn more: https://docs.astro.build/en/guides/server-side-rendering/
// astro.config.js
// Example: astro.config.js
import netlify from '@astrojs/netlify';
export default {
output: 'server',

View file

@ -134,11 +134,30 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
// Chunks that have the viteMetadata.importedCss are CSS chunks
if (meta.importedCss.size) {
// In the SSR build, keep track of all CSS chunks' modules as the client build may
// duplicate them, e.g. for `client:load` components that render in SSR and client
// for hydation.
if (options.target === 'server') {
for (const id of Object.keys(c.modules)) {
internals.cssChunkModuleIds.add(id);
}
}
// In the client build, we bail if the chunk is a duplicated CSS chunk tracked from
// above. We remove all the importedCss to prevent emitting the CSS asset.
if (options.target === 'client') {
if (Object.keys(c.modules).every((id) => internals.cssChunkModuleIds.has(id))) {
for (const importedCssImport of meta.importedCss) {
delete bundle[importedCssImport];
}
return;
}
}
// For the client build, client:only styles need to be mapped
// over to their page. For this chunk, determine if it's a child of a
// client:only component and if so, add its CSS to the page it belongs to.
if (options.target === 'client') {
for (const [id] of Object.entries(c.modules)) {
for (const id of Object.keys(c.modules)) {
for (const pageData of getParentClientOnlys(id, this)) {
for (const importedCssImport of meta.importedCss) {
pageData.css.set(importedCssImport, { depth: -1 });
@ -148,7 +167,7 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
}
// For this CSS chunk, walk parents until you find a page. Add the CSS to that page.
for (const [id] of Object.entries(c.modules)) {
for (const id of Object.keys(c.modules)) {
for (const [pageInfo, depth] of walkParentInfos(id, this)) {
if (moduleIsTopLevelPage(pageInfo)) {
const pageViteID = pageInfo.id;

View file

@ -46,3 +46,8 @@ function isString(path: unknown): path is string {
export function joinPaths(...paths: (string | undefined)[]) {
return paths.filter(isString).map(trimSlashes).join('/');
}
export function removeFileExtension(path: string) {
let idx = path.lastIndexOf('.');
return idx === -1 ? path : path.slice(0, idx);
}

View file

@ -354,5 +354,12 @@ describe('CSS', function () {
expect(allInjectedStyles).to.contain('.vue-scss{');
expect(allInjectedStyles).to.contain('.vue-scoped[data-v-');
});
it('remove unused styles from client:load components', async () => {
const bundledAssets = await fixture.readdir('./assets');
// SvelteDynamic styles is already included in the main page css asset
const unusedCssAsset = bundledAssets.find((asset) => /SvelteDynamic\..*\.css/.test(asset));
expect(unusedCssAsset, 'Found unused style ' + unusedCssAsset).to.be.undefined;
});
});
});

View file

@ -33,6 +33,12 @@ describe('Client only components', () => {
expect(css).to.match(/yellowgreen/, 'Svelte styles are added');
expect(css).to.match(/Courier New/, 'Global styles are added');
});
it('Includes CSS from components that use CSS modules', async () => {
const html = await fixture.readFile('/css-modules/index.html');
const $ = cheerioLoad(html);
expect($('link[rel=stylesheet]')).to.have.a.lengthOf(1);
});
});
describe('Client only components subpath', () => {

View file

@ -0,0 +1,7 @@
<h1 id="svelte-dynamic" class="svelte-dynamic">Svelte Dynamic</h1>
<style>
.svelte-dynamic {
font-family: Courier, monospace;
}
</style>

View file

@ -18,6 +18,7 @@ import VueSass from '../components/VueSass.vue';
import VueScoped from '../components/VueScoped.vue';
import VueScss from '../components/VueScss.vue';
import ReactDynamic from '../components/ReactDynamic.jsx';
import SvelteDynamic from '../components/SvelteDynamic.svelte';
import '../styles/imported-url.css';
import '../styles/imported.sass';
@ -69,6 +70,7 @@ import '../styles/imported.scss';
<VueScoped />
<VueScss />
<ReactDynamic client:load />
<SvelteDynamic client:load />
</div>
</body>
</html>

View file

@ -0,0 +1,11 @@
import Styles from './styles.module.scss';
const ClientApp = () => {
return (
<div>
<h2 className={Styles.red}>This text should be red</h2>
</div>
);
};
export default ClientApp;

View file

@ -0,0 +1,3 @@
.red {
color: red;
}

View file

@ -0,0 +1,11 @@
---
import UsingCSSModules from '../components/UsingCSSModules.jsx';
---
<html>
<head>
<title>Using CSS modules</title>
</head>
<body>
<UsingCSSModules client:only="react" />
</body>
</html>

View file

@ -1,5 +1,11 @@
# @astrojs/cloudflare
## 1.0.2
### Patch Changes
- [#4558](https://github.com/withastro/astro/pull/4558) [`742966456`](https://github.com/withastro/astro/commit/7429664566f05ecebf6d57906f950627e62e690c) Thanks [@tony-sull](https://github.com/tony-sull)! - Adding the `withastro` keyword to include the adapters on the [Integrations Catalog](https://astro.build/integrations)
## 1.0.1
### Patch Changes

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/cloudflare",
"description": "Deploy your site to cloudflare pages functions",
"version": "1.0.1",
"version": "1.0.2",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -12,6 +12,7 @@
"directory": "packages/integrations/cloudflare"
},
"keywords": [
"withastro",
"astro-adapter"
],
"bugs": "https://github.com/withastro/astro/issues",

View file

@ -1,5 +1,13 @@
# @astrojs/node
## 1.0.1
### Patch Changes
- [#4558](https://github.com/withastro/astro/pull/4558) [`742966456`](https://github.com/withastro/astro/commit/7429664566f05ecebf6d57906f950627e62e690c) Thanks [@tony-sull](https://github.com/tony-sull)! - Adding the `withastro` keyword to include the adapters on the [Integrations Catalog](https://astro.build/integrations)
* [#4562](https://github.com/withastro/astro/pull/4562) [`294122b4e`](https://github.com/withastro/astro/commit/294122b4e423107bd9d4854a266f029acbe5e293) Thanks [@zicklag](https://github.com/zicklag)! - Make Deno SSR Backend Render Custom 404 Pages
## 1.0.0
### Major Changes
@ -40,7 +48,7 @@
The new `Astro.clientAddress` property allows you to get the IP address of the requested user.
```astro
<div>Your address {Astro.clientAddress}</div>
```
This property is only available when building for SSR, and only if the adapter you are using supports providing the IP address. If you attempt to access the property in a SSG app it will throw an error.

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/deno",
"description": "Deploy your site to a Deno server",
"version": "1.0.0",
"version": "1.0.1",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -12,6 +12,7 @@
"directory": "packages/integrations/deno"
},
"keywords": [
"withastro",
"astro-adapter"
],
"bugs": "https://github.com/withastro/astro/issues",

View file

@ -29,9 +29,21 @@ export function start(manifest: SSRManifest, options: Options) {
return await app.render(request);
}
// If the request path wasn't found in astro,
// try to fetch a static file instead
const url = new URL(request.url);
const localPath = new URL('.' + url.pathname, clientRoot);
return fetch(localPath.toString());
const fileResp = await fetch(localPath.toString());
// If the static file can't be found
if (fileResp.status == 404) {
// Render the astro custom 404 page
return await app.render(request);
// If the static file is found
} else {
return fileResp;
}
};
const port = options.port ?? 8085;

View file

@ -26,6 +26,21 @@ Deno.test({
},
});
Deno.test({
name: 'Custom 404',
async fn() {
await startApp(async () => {
const resp = await fetch('http://127.0.0.1:8085/this-does-not-exist');
assertEquals(resp.status, 404);
const html = await resp.text();
assert(html);
const doc = new DOMParser().parseFromString(html, `text/html`);
const header = doc.querySelector('#custom-404');
assert(header, 'displays custom 404');
});
},
});
Deno.test({
name: 'Loads style assets',
async fn() {

View file

@ -0,0 +1 @@
<h1 id="custom-404">Custom 404 Page</h1>

View file

@ -1,5 +1,18 @@
# @astrojs/image
## 0.4.0
### Minor Changes
- [#4482](https://github.com/withastro/astro/pull/4482) [`00c605ce3`](https://github.com/withastro/astro/commit/00c605ce350be83a07c5855f7b99ee41eee1ee38) Thanks [@tony-sull](https://github.com/tony-sull)! - `<Image />` and `<Picture />` now support using images in the `/public` directory :tada:
- Moving handling of local image files into the Vite plugin
- Optimized image files are now built to `/dist` with hashes provided by Vite, removing the need for a `/dist/_image` directory
- Removes three npm dependencies: `etag`, `slash`, and `tiny-glob`
- Replaces `mrmime` with the `mime` package already used by Astro's SSR server
- Simplifies the injected `_image` route to work for both `dev` and `build`
- Adds a new test suite for using images with `@astrojs/mdx` - including optimizing images straight from `/public`
## 0.3.7
### Patch Changes

View file

@ -18,7 +18,7 @@ This **[Astro integration][astro-integration]** makes it easy to optimize images
Images play a big role in overall site performance and usability. Serving properly sized images makes all the difference but is often tricky to automate.
This integration provides `<Image />` and `<Picture>` components as well as a basic image transformer powered by [sharp](https://sharp.pixelplumbing.com/), with full support for static sites and server-side rendering. The built-in `sharp` transformer is also replacable, opening the door for future integrations that work with your favorite hosted image service.
This integration provides `<Image />` and `<Picture>` components as well as a basic image transformer powered by [sharp](https://sharp.pixelplumbing.com/), with full support for static sites and server-side rendering. The built-in `sharp` transformer is also replaceable, opening the door for future integrations that work with your favorite hosted image service.
## Installation
@ -90,6 +90,10 @@ import { Image, Picture } from '@astrojs/image/components';
The included `sharp` transformer supports resizing images and encoding them to different image formats. Third-party image services will be able to add support for custom transformations as well (ex: `blur`, `filter`, `rotate`, etc).
Astros `<Image />` and `<Picture />` components require the `alt` attribute, which provides descriptive text for images. A warning will be logged if alt text is missing, and a future release of the integration will throw an error if no alt text is provided.
If the image is merely decorative (i.e. doesnt contribute to the understanding of the page), set `alt=""` so that the image is properly understood and ignored by screen readers.
### `<Image />`
The built-in `<Image />` component is used to create an optimized `<img />` for both remote images hosted on other domains as well as local images imported from your project's `src` directory.
@ -106,7 +110,23 @@ In addition to the component-specific properties, any valid HTML attribute for t
Source for the original image file.
For images in your project's repository, use the `src` relative to the `public` directory. For remote images, provide the full URL.
For images located in your project's `src`: use the file path relative to the `src` directory. (e.g. `src="../assets/source-pic.png"`)
For images located in your `public` directory: use the URL path relative to the `public` directory. (e.g. `src="/images/public-image.jpg"`)
For remote images, provide the full URL. (e.g. `src="https://astro.build/assets/blog/astro-1-release-update.avif"`)
#### alt
<p>
**Type:** `string`<br>
**Required:** `true`
</p>
Defines an alternative text description of the image.
Set to an empty string (`alt=""`) if the image is not a key part of the content (e.g. it's decoration or a tracking pixel).
#### format
@ -182,17 +202,23 @@ A `number` can also be provided, useful when the aspect ratio is calculated at b
Source for the original image file.
For images in your project's repository, use the `src` relative to the `public` directory. For remote images, provide the full URL.
For images located in your project's `src`: use the file path relative to the `src` directory. (e.g. `src="../assets/source-pic.png"`)
For images located in your `public` directory: use the URL path relative to the `public` directory. (e.g. `src="/images/public-image.jpg"`)
For remote images, provide the full URL. (e.g. `src="https://astro.build/assets/blog/astro-1-release-update.avif"`)
#### alt
<p>
**Type:** `string`<br>
**Default:** `undefined`
**Required:** `true`
</p>
If provided, the `alt` string will be included on the built `<img />` element.
Defines an alternative text description of the image.
Set to an empty string (`alt=""`) if the image is not a key part of the content (e.g. it's decoration or a tracking pixel).
#### sizes
@ -262,7 +288,7 @@ const { src } = await getImage('../assets/hero.png');
<html>
<head>
<link rel="preload" as="image" href={src}>
<link rel="preload" as="image" href={src} alt="alt text">
</head>
</html>
```
@ -326,19 +352,37 @@ import heroImage from '../assets/hero.png';
---
// optimized image, keeping the original width, height, and image format
<Image src={heroImage} />
<Image src={heroImage} alt="descriptive text" />
// height will be recalculated to match the original aspect ratio
<Image src={heroImage} width={300} />
<Image src={heroImage} width={300} alt="descriptive text" />
// cropping to a specific width and height
<Image src={heroImage} width={300} height={600} />
<Image src={heroImage} width={300} height={600} alt="descriptive text" />
// cropping to a specific aspect ratio and converting to an avif format
<Image src={heroImage} aspectRatio="16:9" format="avif" />
<Image src={heroImage} aspectRatio="16:9" format="avif" alt="descriptive text" />
// image imports can also be inlined directly
<Image src={import('../assets/hero.png')} />
<Image src={import('../assets/hero.png')} alt="descriptive text" />
```
#### Images in `/public`
Files in the `/public` directory are always served or copied as-is, with no processing. We recommend that local images are always kept in `src/` so that Astro can transform, optimize and bundle them. But if you absolutely must keep an image in `public/`, use its relative URL path as the image's `src=` attribute. It will be treated as a remote image, which requires an `aspectRatio` attribute.
Alternatively, you can import an image from your `public/` directory in your frontmatter and use a variable in your `src=` attribute. You cannot, however, import this directly inside the component as its `src` value.
For example, use an image located at `public/social.png` in either static or SSR builds like so:
```astro title="src/pages/page.astro"
---
import { Image } from '@astrojs/image/components';
import socialImage from '/social.png';
---
// In static builds: the image will be built and optimized to `/dist`.
// In SSR builds: the image will be optimized by the server when requested by a browser.
<Image src={socialImage} width={1280} aspectRatio="16:9" alt="descriptive text" />
```
### Remote images
@ -353,13 +397,13 @@ const imageUrl = 'https://www.google.com/images/branding/googlelogo/2x/googlelog
---
// cropping to a specific width and height
<Image src={imageUrl} width={544} height={184} />
<Image src={imageUrl} width={544} height={184} alt="descriptive text" />
// height will be recalculated to match the aspect ratio
<Image src={imageUrl} width={300} aspectRatio={16/9} />
<Image src={imageUrl} width={300} aspectRatio={16/9} alt="descriptive text" />
// cropping to a specific height and aspect ratio and converting to an avif format
<Image src={imageUrl} height={200} aspectRatio="16:9" format="avif" />
<Image src={imageUrl} height={200} aspectRatio="16:9" format="avif" alt="descriptive text" />
```
### Responsive pictures
@ -379,13 +423,13 @@ const imageUrl = 'https://www.google.com/images/branding/googlelogo/2x/googlelog
---
// Local image with multiple sizes
<Picture src={hero} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="My hero image" />
<Picture src={hero} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="descriptive text" />
// Remote image (aspect ratio is required)
<Picture src={imageUrl} widths={[200, 400, 800]} aspectRatio="4:3" sizes="(max-width: 800px) 100vw, 800px" alt="My hero image" />
<Picture src={imageUrl} widths={[200, 400, 800]} aspectRatio="4:3" sizes="(max-width: 800px) 100vw, 800px" alt="descriptive text" />
// Inlined imports are supported
<Picture src={import("../assets/hero.png")} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="My hero image" />
<Picture src={import("../assets/hero.png")} widths={[200, 400, 800]} sizes="(max-width: 800px) 100vw, 800px" alt="descriptive text" />
```
## Troubleshooting

View file

@ -1,6 +1,7 @@
---
// @ts-ignore
import { getImage } from '../dist/index.js';
import { warnForMissingAlt } from './index.js';
import type { ImgHTMLAttributes } from './index.js';
import type { ImageMetadata, TransformOptions, OutputFormat } from '../dist/index.js';
@ -8,10 +9,14 @@ interface LocalImageProps
extends Omit<TransformOptions, 'src'>,
Omit<ImgHTMLAttributes, 'src' | 'width' | 'height'> {
src: ImageMetadata | Promise<{ default: ImageMetadata }>;
/** Defines an alternative text description of the image. Set to an empty string (alt="") if the image is not a key part of the content (it's decoration or a tracking pixel). */
alt: string;
}
interface RemoteImageProps extends TransformOptions, astroHTML.JSX.ImgHTMLAttributes {
src: string;
/** Defines an alternative text description of the image. Set to an empty string (alt="") if the image is not a key part of the content (it's decoration or a tracking pixel). */
alt: string;
format: OutputFormat;
width: number;
height: number;
@ -21,6 +26,10 @@ export type Props = LocalImageProps | RemoteImageProps;
const { loading = 'lazy', decoding = 'async', ...props } = Astro.props as Props;
if (props.alt === undefined || props.alt === null) {
warnForMissingAlt();
}
const attrs = await getImage(props);
---

View file

@ -1,5 +1,6 @@
---
import { getPicture } from '../dist/index.js';
import { warnForMissingAlt } from './index.js';
import type { ImgHTMLAttributes, HTMLAttributes } from './index.js';
import type { ImageMetadata, OutputFormat, TransformOptions } from '../dist/index.js';
@ -8,7 +9,8 @@ interface LocalImageProps
Omit<TransformOptions, 'src'>,
Pick<astroHTML.JSX.ImgHTMLAttributes, 'loading' | 'decoding'> {
src: ImageMetadata | Promise<{ default: ImageMetadata }>;
alt?: string;
/** Defines an alternative text description of the image. Set to an empty string (alt="") if the image is not a key part of the content (it's decoration or a tracking pixel). */
alt: string;
sizes: HTMLImageElement['sizes'];
widths: number[];
formats?: OutputFormat[];
@ -19,7 +21,8 @@ interface RemoteImageProps
TransformOptions,
Pick<ImgHTMLAttributes, 'loading' | 'decoding'> {
src: string;
alt?: string;
/** Defines an alternative text description of the image. Set to an empty string (alt="") if the image is not a key part of the content (it's decoration or a tracking pixel). */
alt: string;
sizes: HTMLImageElement['sizes'];
widths: number[];
aspectRatio: TransformOptions['aspectRatio'];
@ -40,6 +43,10 @@ const {
...attrs
} = Astro.props as Props;
if (alt === undefined || alt === null) {
warnForMissingAlt();
}
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio });
---

View file

@ -11,3 +11,19 @@ export type HTMLAttributes = Omit<
astroHTML.JSX.HTMLAttributes,
'client:list' | 'set:text' | 'set:html' | 'is:raw'
>;
let altWarningShown = false;
export function warnForMissingAlt() {
if (altWarningShown === true) {
return;
}
altWarningShown = true;
console.warn(`\n[@astrojs/image] "alt" text was not provided for an <Image> or <Picture> component.
A future release of @astrojs/image may throw a build error when "alt" text is missing.
The "alt" attribute holds a text description of the image, which isn't mandatory but is incredibly useful for accessibility. Set to an empty string (alt="") if the image is not a key part of the content (it's decoration or a tracking pixel).\n`);
}

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/image",
"description": "Load and transform images in your Astro site.",
"version": "0.3.7",
"version": "0.4.0",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -21,9 +21,8 @@
"homepage": "https://docs.astro.build/en/guides/integrations-guide/image/",
"exports": {
".": "./dist/index.js",
"./endpoint": "./dist/endpoint.js",
"./sharp": "./dist/loaders/sharp.js",
"./endpoints/dev": "./dist/endpoints/dev.js",
"./endpoints/prod": "./dist/endpoints/prod.js",
"./components": "./components/index.js",
"./package.json": "./package.json",
"./client": "./client.d.ts",
@ -41,19 +40,15 @@
"test": "mocha --exit --timeout 20000 test"
},
"dependencies": {
"etag": "^1.8.1",
"image-size": "^1.0.1",
"mrmime": "^1.0.0",
"sharp": "^0.30.6",
"slash": "^4.0.0",
"tiny-glob": "^0.2.9"
"image-size": "^1.0.2",
"magic-string": "^0.25.9",
"mime": "^3.0.0",
"sharp": "^0.30.6"
},
"devDependencies": {
"@types/etag": "^1.8.1",
"@types/sharp": "^0.30.4",
"@types/sharp": "^0.30.5",
"astro": "workspace:*",
"astro-scripts": "workspace:*",
"kleur": "^4.1.4",
"tiny-glob": "^0.2.9"
"kleur": "^4.1.4"
}
}

View file

@ -1,12 +1,12 @@
import type { AstroConfig } from 'astro';
import { bgGreen, black, cyan, dim, green } from 'kleur/colors';
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { OUTPUT_DIR } from '../constants.js';
import type { SSRImageService, TransformOptions } from '../loaders/index.js';
import { isRemoteImage, loadLocalImage, loadRemoteImage } from '../utils/images.js';
import { loadLocalImage, loadRemoteImage } from '../utils/images.js';
import { debug, info, LoggerLevel, warn } from '../utils/logger.js';
import { ensureDir } from '../utils/paths.js';
import { isRemoteImage } from '../utils/paths.js';
function getTimeStat(timeStart: number, timeEnd: number) {
const buildTime = timeEnd - timeStart;
@ -16,12 +16,12 @@ function getTimeStat(timeStart: number, timeEnd: number) {
export interface SSGBuildParams {
loader: SSRImageService;
staticImages: Map<string, Map<string, TransformOptions>>;
srcDir: URL;
config: AstroConfig;
outDir: URL;
logLevel: LoggerLevel;
}
export async function ssgBuild({ loader, staticImages, srcDir, outDir, logLevel }: SSGBuildParams) {
export async function ssgBuild({ loader, staticImages, config, outDir, logLevel }: SSGBuildParams) {
const timer = performance.now();
info({
@ -35,15 +35,21 @@ export async function ssgBuild({ loader, staticImages, srcDir, outDir, logLevel
const inputFiles = new Set<string>();
// process transforms one original image file at a time
for (const [src, transformsMap] of staticImages) {
for (let [src, transformsMap] of staticImages) {
let inputFile: string | undefined = undefined;
let inputBuffer: Buffer | undefined = undefined;
// Vite will prefix a hashed image with the base path, we need to strip this
// off to find the actual file relative to /dist
if (config.base && src.startsWith(config.base)) {
src = src.substring(config.base.length - 1);
}
if (isRemoteImage(src)) {
// try to load the remote image
inputBuffer = await loadRemoteImage(src);
} else {
const inputFileURL = new URL(`.${src}`, srcDir);
const inputFileURL = new URL(`.${src}`, outDir);
inputFile = fileURLToPath(inputFileURL);
inputBuffer = await loadLocalImage(inputFile);
@ -62,39 +68,21 @@ export async function ssgBuild({ loader, staticImages, srcDir, outDir, logLevel
debug({ level: logLevel, prefix: false, message: `${green('▶')} ${src}` });
let timeStart = performance.now();
if (inputFile) {
const to = inputFile.replace(fileURLToPath(srcDir), fileURLToPath(outDir));
await ensureDir(path.dirname(to));
await fs.copyFile(inputFile, to);
const timeEnd = performance.now();
const timeChange = getTimeStat(timeStart, timeEnd);
const timeIncrease = `(+${timeChange})`;
const pathRelative = inputFile.replace(fileURLToPath(srcDir), '');
debug({
level: logLevel,
prefix: false,
message: ` ${cyan('└─')} ${dim(`(original) ${pathRelative}`)} ${dim(timeIncrease)}`,
});
}
// process each transformed versiono of the
for (const [filename, transform] of transforms) {
timeStart = performance.now();
let outputFile: string;
if (isRemoteImage(src)) {
const outputFileURL = new URL(path.join('./', OUTPUT_DIR, path.basename(filename)), outDir);
const outputFileURL = new URL(path.join('./', path.basename(filename)), outDir);
outputFile = fileURLToPath(outputFileURL);
} else {
const outputFileURL = new URL(path.join('./', OUTPUT_DIR, filename), outDir);
const outputFileURL = new URL(path.join('./', filename), outDir);
outputFile = fileURLToPath(outputFileURL);
}
const { data } = await loader.transform(inputBuffer, transform);
ensureDir(path.dirname(outputFile));
await fs.writeFile(outputFile, data);
const timeEnd = performance.now();

View file

@ -1,29 +0,0 @@
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import glob from 'tiny-glob';
import { ensureDir } from '../utils/paths.js';
async function globImages(dir: URL) {
const srcPath = fileURLToPath(dir);
return await glob('./**/*.{heic,heif,avif,jpeg,jpg,png,tiff,webp,gif}', {
cwd: fileURLToPath(dir),
});
}
export interface SSRBuildParams {
srcDir: URL;
outDir: URL;
}
export async function ssrBuild({ srcDir, outDir }: SSRBuildParams) {
const images = await globImages(srcDir);
for (const image of images) {
const from = path.join(fileURLToPath(srcDir), image);
const to = path.join(fileURLToPath(outDir), image);
await ensureDir(path.dirname(to));
await fs.copyFile(from, to);
}
}

View file

@ -1,3 +0,0 @@
export const PKG_NAME = '@astrojs/image';
export const ROUTE_PATTERN = '/_image';
export const OUTPUT_DIR = '/_image';

View file

@ -1,31 +1,39 @@
import type { APIRoute } from 'astro';
import etag from 'etag';
import { lookup } from 'mrmime';
import mime from 'mime';
// @ts-ignore
import loader from 'virtual:image-loader';
import { isRemoteImage, loadLocalImage, loadRemoteImage } from '../utils/images.js';
import { etag } from './utils/etag.js';
import { isRemoteImage } from './utils/paths.js';
async function loadRemoteImage(src: URL) {
try {
const res = await fetch(src);
if (!res.ok) {
return undefined;
}
return Buffer.from(await res.arrayBuffer());
} catch {
return undefined;
}
}
export const get: APIRoute = async ({ request }) => {
try {
const url = new URL(request.url);
const transform = loader.parseTransform(url.searchParams);
if (!transform) {
return new Response('Bad Request', { status: 400 });
}
let inputBuffer: Buffer | undefined = undefined;
if (isRemoteImage(transform.src)) {
inputBuffer = await loadRemoteImage(transform.src);
} else {
const clientRoot = new URL('../client/', import.meta.url);
const localPath = new URL('.' + transform.src, clientRoot);
inputBuffer = await loadLocalImage(localPath);
}
// TODO: handle config subpaths?
const sourceUrl = isRemoteImage(transform.src)
? new URL(transform.src)
: new URL(transform.src, url.origin);
inputBuffer = await loadRemoteImage(sourceUrl);
if (!inputBuffer) {
return new Response(`"${transform.src} not found`, { status: 404 });
return new Response('Not Found', { status: 404 });
}
const { data, format } = await loader.transform(inputBuffer, transform);
@ -33,9 +41,9 @@ export const get: APIRoute = async ({ request }) => {
return new Response(data, {
status: 200,
headers: {
'Content-Type': lookup(format) || '',
'Content-Type': mime.getType(format) || '',
'Cache-Control': 'public, max-age=31536000',
ETag: etag(inputBuffer),
ETag: etag(data.toString()),
Date: new Date().toUTCString(),
},
});

View file

@ -1,32 +0,0 @@
import type { APIRoute } from 'astro';
import { lookup } from 'mrmime';
import loader from '../loaders/sharp.js';
import { loadImage } from '../utils/images.js';
export const get: APIRoute = async ({ request }) => {
try {
const url = new URL(request.url);
const transform = loader.parseTransform(url.searchParams);
if (!transform) {
return new Response('Bad Request', { status: 400 });
}
const inputBuffer = await loadImage(transform.src);
if (!inputBuffer) {
return new Response(`"${transform.src} not found`, { status: 404 });
}
const { data, format } = await loader.transform(inputBuffer, transform);
return new Response(data, {
status: 200,
headers: {
'Content-Type': lookup(format) || '',
},
});
} catch (err: unknown) {
return new Response(`Server Error: ${err}`, { status: 500 });
}
};

View file

@ -1,21 +1,19 @@
import type { AstroConfig, AstroIntegration } from 'astro';
import { ssgBuild } from './build/ssg.js';
import { ssrBuild } from './build/ssr.js';
import { PKG_NAME, ROUTE_PATTERN } from './constants.js';
import { ImageService, TransformOptions } from './loaders/index.js';
import type { ImageService, TransformOptions } from './loaders/index.js';
import type { LoggerLevel } from './utils/logger.js';
import { filenameFormat, propsToFilename } from './utils/paths.js';
import { joinPaths, prependForwardSlash, propsToFilename } from './utils/paths.js';
import { createPlugin } from './vite-plugin-astro-image.js';
export { getImage } from './lib/get-image.js';
export { getPicture } from './lib/get-picture.js';
export * from './loaders/index.js';
export type { ImageMetadata } from './vite-plugin-astro-image.js';
const PKG_NAME = '@astrojs/image';
const ROUTE_PATTERN = '/_image';
interface ImageIntegration {
loader?: ImageService;
addStaticImage?: (transform: TransformOptions) => void;
filenameFormat?: (transform: TransformOptions, searchParams: URLSearchParams) => string;
addStaticImage?: (transform: TransformOptions) => string;
}
declare global {
@ -38,12 +36,11 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
...options,
};
let _config: AstroConfig;
// During SSG builds, this is used to track all transformed images required.
const staticImages = new Map<string, Map<string, TransformOptions>>();
let _config: AstroConfig;
let output: 'server' | 'static';
function getViteConfiguration() {
return {
plugins: [createPlugin(_config, resolvedOptions)],
@ -59,25 +56,18 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
return {
name: PKG_NAME,
hooks: {
'astro:config:setup': ({ command, config, injectRoute, updateConfig }) => {
'astro:config:setup': ({ command, config, updateConfig, injectRoute }) => {
_config = config;
// Always treat `astro dev` as SSR mode, even without an adapter
output = command === 'dev' ? 'server' : config.output;
updateConfig({ vite: getViteConfiguration() });
if (output === 'server') {
if (command === 'dev' || config.output === 'server') {
injectRoute({
pattern: ROUTE_PATTERN,
entryPoint:
command === 'dev' ? '@astrojs/image/endpoints/dev' : '@astrojs/image/endpoints/prod',
entryPoint: '@astrojs/image/endpoint',
});
}
},
'astro:server:setup': async ({ server }) => {
globalThis.astroImage = {};
},
'astro:build:setup': () => {
// Used to cache all images rendered to HTML
// Added to globalThis to share the same map in Node and Vite
@ -86,26 +76,28 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
? staticImages.get(transform.src)!
: new Map<string, TransformOptions>();
srcTranforms.set(propsToFilename(transform), transform);
const filename = propsToFilename(transform);
srcTranforms.set(filename, transform);
staticImages.set(transform.src, srcTranforms);
// Prepend the Astro config's base path, if it was used.
// Doing this here makes sure that base is ignored when building
// staticImages to /dist, but the rendered HTML will include the
// base prefix for `src`.
return prependForwardSlash(joinPaths(_config.base, filename));
}
// Helpers for building static images should only be available for SSG
globalThis.astroImage =
output === 'static'
_config.output === 'static'
? {
addStaticImage,
filenameFormat,
}
: {};
},
'astro:build:done': async ({ dir }) => {
if (output === 'server') {
// for SSR builds, copy all image files from src to dist
// to make sure they are available for use in production
await ssrBuild({ srcDir: _config.srcDir, outDir: dir });
} else {
if (_config.output === 'static') {
// for SSG builds, build all requested image transforms to dist
const loader = globalThis?.astroImage?.loader;
@ -113,7 +105,7 @@ export default function integration(options: IntegrationOptions = {}): AstroInte
await ssgBuild({
loader,
staticImages,
srcDir: _config.srcDir,
config: _config,
outDir: dir,
logLevel: resolvedOptions.logLevel,
});

View file

@ -1,10 +1,9 @@
/// <reference types="astro/astro-jsx" />
import slash from 'slash';
import { ROUTE_PATTERN } from '../constants.js';
import { ImageService, isSSRService, OutputFormat, TransformOptions } from '../loaders/index.js';
import type { ImageService, OutputFormat, TransformOptions } from '../loaders/index.js';
import { isSSRService, parseAspectRatio } from '../loaders/index.js';
import sharp from '../loaders/sharp.js';
import { isRemoteImage, parseAspectRatio } from '../utils/images.js';
import { ImageMetadata } from '../vite-plugin-astro-image.js';
import { isRemoteImage } from '../utils/paths.js';
import type { ImageMetadata } from '../vite-plugin-astro-image.js';
export interface GetImageTransform extends Omit<TransformOptions, 'src'> {
src: string | ImageMetadata | Promise<{ default: ImageMetadata }>;
@ -132,25 +131,26 @@ export async function getImage(
throw new Error('@astrojs/image: loader not found!');
}
// For SSR services, build URLs for the injected route
if (isSSRService(_loader)) {
const { searchParams } = _loader.serializeTransform(resolved);
const { searchParams } = isSSRService(_loader)
? _loader.serializeTransform(resolved)
: sharp.serializeTransform(resolved);
// cache all images rendered to HTML
if (globalThis.astroImage?.addStaticImage) {
globalThis.astroImage.addStaticImage(resolved);
}
let src: string;
const src = globalThis.astroImage?.filenameFormat
? globalThis.astroImage.filenameFormat(resolved, searchParams)
: `${ROUTE_PATTERN}?${searchParams.toString()}`;
return {
...attributes,
src: slash(src), // Windows compat
};
if (/^[\/\\]?@astroimage/.test(resolved.src)) {
src = `${resolved.src}?${searchParams.toString()}`;
} else {
searchParams.set('href', resolved.src);
src = `/_image?${searchParams.toString()}`;
}
// For hosted services, return the `<img />` attributes as-is
return attributes;
// cache all images rendered to HTML
if (globalThis.astroImage?.addStaticImage) {
src = globalThis.astroImage.addStaticImage(resolved);
}
return {
...attributes,
src,
};
}

View file

@ -1,8 +1,7 @@
/// <reference types="astro/astro-jsx" />
import { lookup } from 'mrmime';
import mime from 'mime';
import { extname } from 'node:path';
import { OutputFormat, TransformOptions } from '../loaders/index.js';
import { parseAspectRatio } from '../utils/images.js';
import { OutputFormat, parseAspectRatio, TransformOptions } from '../loaders/index.js';
import { ImageMetadata } from '../vite-plugin-astro-image.js';
import { getImage } from './get-image.js';
@ -71,7 +70,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
);
return {
type: lookup(format) || format,
type: mime.getType(format) || format,
srcset: imgs.join(','),
};
}

View file

@ -12,6 +12,28 @@ export type InputFormat =
export type OutputFormat = 'avif' | 'jpeg' | 'png' | 'webp';
export function isOutputFormat(value: string): value is OutputFormat {
return ['avif', 'jpeg', 'png', 'webp'].includes(value);
}
export function isAspectRatioString(value: string): value is `${number}:${number}` {
return /^\d*:\d*$/.test(value);
}
export function parseAspectRatio(aspectRatio: TransformOptions['aspectRatio']) {
if (!aspectRatio) {
return undefined;
}
// parse aspect ratio strings, if required (ex: "16:9")
if (typeof aspectRatio === 'number') {
return aspectRatio;
} else {
const [width, height] = aspectRatio.split(':');
return parseInt(width) / parseInt(height);
}
}
/**
* Defines the original image and transforms that need to be applied to it.
*/

View file

@ -1,5 +1,5 @@
import sharp from 'sharp';
import { isAspectRatioString, isOutputFormat } from '../utils/images.js';
import { isAspectRatioString, isOutputFormat } from '../loaders/index.js';
import type { OutputFormat, SSRImageService, TransformOptions } from './index.js';
class SharpService implements SSRImageService {
@ -37,16 +37,10 @@ class SharpService implements SSRImageService {
searchParams.append('ar', transform.aspectRatio.toString());
}
searchParams.append('href', transform.src);
return { searchParams };
}
parseTransform(searchParams: URLSearchParams) {
if (!searchParams.has('href')) {
return undefined;
}
let transform: TransformOptions = { src: searchParams.get('href')! };
if (searchParams.has('q')) {

View file

@ -0,0 +1,44 @@
/**
* FNV-1a Hash implementation
* @author Travis Webb (tjwebb) <me@traviswebb.com>
*
* Ported from https://github.com/tjwebb/fnv-plus/blob/master/index.js
*
* Simplified, optimized and add modified for 52 bit, which provides a larger hash space
* and still making use of Javascript's 53-bit integer space.
*/
export const fnv1a52 = (str: string) => {
const len = str.length;
let i = 0,
t0 = 0,
v0 = 0x2325,
t1 = 0,
v1 = 0x8422,
t2 = 0,
v2 = 0x9ce4,
t3 = 0,
v3 = 0xcbf2;
while (i < len) {
v0 ^= str.charCodeAt(i++);
t0 = v0 * 435;
t1 = v1 * 435;
t2 = v2 * 435;
t3 = v3 * 435;
t2 += v0 << 8;
t3 += v1 << 8;
t1 += t0 >>> 16;
v0 = t0 & 65535;
t2 += t1 >>> 16;
v1 = t1 & 65535;
v3 = (t3 + (t2 >>> 16)) & 65535;
v2 = t2 & 65535;
}
return (v3 & 15) * 281474976710656 + v2 * 4294967296 + v1 * 65536 + (v0 ^ (v3 >> 4));
};
export const etag = (payload: string, weak = false) => {
const prefix = weak ? 'W/"' : '"';
return prefix + fnv1a52(payload).toString(36) + payload.length.toString(36) + '"';
};

View file

@ -1,17 +1,4 @@
import fs from 'node:fs/promises';
import type { OutputFormat, TransformOptions } from '../loaders/index.js';
export function isOutputFormat(value: string): value is OutputFormat {
return ['avif', 'jpeg', 'png', 'webp'].includes(value);
}
export function isAspectRatioString(value: string): value is `${number}:${number}` {
return /^\d*:\d*$/.test(value);
}
export function isRemoteImage(src: string) {
return /^http(s?):\/\//.test(src);
}
export async function loadLocalImage(src: string | URL) {
try {
@ -34,21 +21,3 @@ export async function loadRemoteImage(src: string) {
return undefined;
}
}
export async function loadImage(src: string) {
return isRemoteImage(src) ? await loadRemoteImage(src) : await loadLocalImage(src);
}
export function parseAspectRatio(aspectRatio: TransformOptions['aspectRatio']) {
if (!aspectRatio) {
return undefined;
}
// parse aspect ratio strings, if required (ex: "16:9")
if (typeof aspectRatio === 'number') {
return aspectRatio;
} else {
const [width, height] = aspectRatio.split(':');
return parseInt(width) / parseInt(height);
}
}

View file

@ -1,9 +1,10 @@
import sizeOf from 'image-size';
import fs from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { InputFormat } from '../loaders/index.js';
import { ImageMetadata } from '../vite-plugin-astro-image.js';
export async function metadata(src: string): Promise<ImageMetadata | undefined> {
export async function metadata(src: URL): Promise<ImageMetadata | undefined> {
const file = await fs.readFile(src);
const { width, height, type, orientation } = await sizeOf(file);
@ -14,7 +15,7 @@ export async function metadata(src: string): Promise<ImageMetadata | undefined>
}
return {
src,
src: fileURLToPath(src),
width: isPortrait ? height : width,
height: isPortrait ? width : height,
format: type as InputFormat,

View file

@ -1,54 +1,74 @@
import fs from 'node:fs';
import path from 'node:path';
import { OUTPUT_DIR } from '../constants.js';
import type { TransformOptions } from '../loaders/index.js';
import { isRemoteImage } from './images.js';
import { OutputFormat, TransformOptions } from '../loaders/index.js';
import { shorthash } from './shorthash.js';
export function isRemoteImage(src: string) {
return /^http(s?):\/\//.test(src);
}
function removeQueryString(src: string) {
const index = src.lastIndexOf('?');
return index > 0 ? src.substring(0, index) : src;
}
function removeExtname(src: string) {
const ext = path.extname(src);
function extname(src: string, format?: OutputFormat) {
const index = src.lastIndexOf('.');
if (!ext) {
if (index <= 0) {
return undefined;
}
return src.substring(index);
}
function removeExtname(src: string) {
const index = src.lastIndexOf('.');
if (index <= 0) {
return src;
}
const index = src.lastIndexOf(ext);
return src.substring(0, index);
}
export function ensureDir(dir: string) {
fs.mkdirSync(dir, { recursive: true });
function basename(src: string) {
return src.replace(/^.*[\\\/]/, '');
}
export function propsToFilename({ src, width, height, format }: TransformOptions) {
export function propsToFilename(transform: TransformOptions) {
// strip off the querystring first, then remove the file extension
let filename = removeQueryString(src);
const ext = path.extname(filename);
let filename = removeQueryString(transform.src);
filename = basename(filename);
filename = removeExtname(filename);
// for remote images, add a hash of the full URL to dedupe images with the same filename
if (isRemoteImage(src)) {
filename += `-${shorthash(src)}`;
}
const ext = transform.format || extname(transform.src)?.substring(1);
if (width && height) {
return `${filename}_${width}x${height}.${format}`;
} else if (width) {
return `${filename}_${width}w.${format}`;
} else if (height) {
return `${filename}_${height}h.${format}`;
}
return format ? src.replace(ext, format) : src;
return `/${filename}_${shorthash(JSON.stringify(transform))}.${ext}`;
}
export function filenameFormat(transform: TransformOptions) {
return isRemoteImage(transform.src)
? path.join(OUTPUT_DIR, path.basename(propsToFilename(transform)))
: path.join(OUTPUT_DIR, path.dirname(transform.src), path.basename(propsToFilename(transform)));
export function appendForwardSlash(path: string) {
return path.endsWith('/') ? path : path + '/';
}
export function prependForwardSlash(path: string) {
return path[0] === '/' ? path : '/' + path;
}
export function removeTrailingForwardSlash(path: string) {
return path.endsWith('/') ? path.slice(0, path.length - 1) : path;
}
export function removeLeadingForwardSlash(path: string) {
return path.startsWith('/') ? path.substring(1) : path;
}
export function trimSlashes(path: string) {
return path.replace(/^\/|\/$/g, '');
}
function isString(path: unknown): path is string {
return typeof path === 'string' || path instanceof String;
}
export function joinPaths(...paths: (string | undefined)[]) {
return paths.filter(isString).map(trimSlashes).join('/');
}

View file

@ -1,10 +1,14 @@
import type { AstroConfig } from 'astro';
import { pathToFileURL } from 'node:url';
import type { PluginContext } from 'rollup';
import MagicString from 'magic-string';
import fs from 'node:fs/promises';
import path, { basename, extname, join } from 'node:path';
import { Readable } from 'node:stream';
import { fileURLToPath, pathToFileURL } from 'node:url';
import slash from 'slash';
import type { Plugin, ResolvedConfig } from 'vite';
import type { IntegrationOptions } from './index.js';
import type { InputFormat } from './loaders/index.js';
import sharp from './loaders/sharp.js';
import { metadata } from './utils/metadata.js';
export interface ImageMetadata {
@ -21,19 +25,6 @@ export function createPlugin(config: AstroConfig, options: Required<IntegrationO
const virtualModuleId = 'virtual:image-loader';
let resolvedConfig: ResolvedConfig;
let loaderModuleId: string;
async function resolveLoader(context: PluginContext) {
if (!loaderModuleId) {
const module = await context.resolve(options.serviceEntryPoint);
if (!module) {
throw new Error(`"${options.serviceEntryPoint}" could not be found`);
}
loaderModuleId = module.id;
}
return loaderModuleId;
}
return {
name: '@astrojs/image',
@ -46,7 +37,7 @@ export function createPlugin(config: AstroConfig, options: Required<IntegrationO
// This ensures the module is available in `astro dev` and is included
// in the SSR server bundle.
if (id === virtualModuleId) {
return await resolveLoader(this);
return await this.resolve(options.serviceEntryPoint);
}
},
async load(id) {
@ -55,19 +46,91 @@ export function createPlugin(config: AstroConfig, options: Required<IntegrationO
return null;
}
const meta = await metadata(id);
const url = pathToFileURL(id);
const fileUrl = pathToFileURL(id);
const src = resolvedConfig.isProduction
? fileUrl.pathname.replace(config.srcDir.pathname, '/')
: id;
const meta = await metadata(url);
const output = {
...meta,
src: slash(src), // Windows compat
};
if (!meta) {
return;
}
return `export default ${JSON.stringify(output)}`;
if (!this.meta.watchMode) {
const pathname = decodeURI(url.pathname);
const filename = basename(pathname, extname(pathname) + `.${meta.format}`);
const handle = this.emitFile({
name: filename,
source: await fs.readFile(url),
type: 'asset',
});
meta.src = `__ASTRO_IMAGE_ASSET__${handle}__`;
} else {
const relId = path.relative(fileURLToPath(config.srcDir), id);
meta.src = join('/@astroimage', relId);
// Windows compat
meta.src = slash(meta.src);
}
return `export default ${JSON.stringify(meta)}`;
},
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
if (req.url?.startsWith('/@astroimage/')) {
const [, id] = req.url.split('/@astroimage/');
const url = new URL(id, config.srcDir);
const file = await fs.readFile(url);
const meta = await metadata(url);
if (!meta) {
return next();
}
const transform = await sharp.parseTransform(url.searchParams);
if (!transform) {
return next();
}
const result = await sharp.transform(file, transform);
res.setHeader('Content-Type', `image/${result.format}`);
res.setHeader('Cache-Control', 'max-age=360000');
const stream = Readable.from(result.data);
return stream.pipe(res);
}
return next();
});
},
async renderChunk(code) {
const assetUrlRE = /__ASTRO_IMAGE_ASSET__([a-z\d]{8})__(?:_(.*?)__)?/g;
let match;
let s;
while ((match = assetUrlRE.exec(code))) {
s = s || (s = new MagicString(code));
const [full, hash, postfix = ''] = match;
const file = this.getFileName(hash);
const outputFilepath = resolvedConfig.base + file + postfix;
s.overwrite(match.index, match.index + full.length, outputFilepath);
}
if (s) {
return {
code: s.toString(),
map: resolvedConfig.build.sourcemap ? s.generateMap({ hires: true }) : null,
};
} else {
return null;
}
},
};
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

View file

@ -1,5 +1,6 @@
---
import socialJpg from '../assets/social.jpg';
import introJpg from '../assets/blog/introducing astro.jpg';
import { Image } from '@astrojs/image/components';
---
@ -8,12 +9,16 @@ import { Image } from '@astrojs/image/components';
<!-- Head Stuff -->
</head>
<body>
<Image id="social-jpg" src={socialJpg} width={506} height={253} />
<Image id="hero" src="/hero.jpg" width={768} height={414} format="webp" alt="hero" />
<br />
<Image id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="webp" />
<Image id="spaces" src={introJpg} width={768} height={414} format="webp" alt="spaces" />
<br />
<Image id="inline" src={import('../assets/social.jpg')} width={506} />
<Image id="social-jpg" src={socialJpg} width={506} height={253} alt="social-jpg" />
<br />
<Image id="query" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png?token=abc" width={544} height={184} format="webp" />
<Image id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="webp" alt="Google" />
<br />
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
<br />
<Image id="query" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png?token=abc" width={544} height={184} format="webp" alt="query" />
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

View file

@ -8,6 +8,8 @@ import { Picture } from '@astrojs/image/components';
<!-- Head Stuff -->
</head>
<body>
<Picture id="hero" src="/hero.jpg" sizes="100vw" widths={[384, 768]} aspectRatio={768/414} alt="Hero image" />
<br />
<Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Social image" />
<br />
<Picture id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} alt="Google logo" />

View file

@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import image from '@astrojs/image';
// https://astro.build/config
export default defineConfig({
site: 'http://localhost:3000',
integrations: [image({ logLevel: 'silent' })]
});

View file

@ -0,0 +1,10 @@
{
"name": "@test/no-alt-text-image",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/image": "workspace:*",
"@astrojs/node": "workspace:*",
"astro": "workspace:*"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,44 @@
import { createServer } from 'http';
import fs from 'fs';
import mime from 'mime';
import { handler as ssrHandler } from '../dist/server/entry.mjs';
const clientRoot = new URL('../dist/client/', import.meta.url);
async function handle(req, res) {
ssrHandler(req, res, async (err) => {
if (err) {
res.writeHead(500);
res.end(err.stack);
return;
}
let local = new URL('.' + req.url, clientRoot);
try {
const data = await fs.promises.readFile(local);
res.writeHead(200, {
'Content-Type': mime.getType(req.url),
});
res.end(data);
} catch {
res.writeHead(404);
res.end();
}
});
}
const server = createServer((req, res) => {
handle(req, res).catch((err) => {
console.error(err);
res.writeHead(500, {
'Content-Type': 'text/plain',
});
res.end(err.toString());
});
});
server.listen(8085);
console.log('Serving at http://localhost:8085');
// Silence weird <time> warning
console.error = () => {};

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,13 @@
---
import socialJpg from '../assets/social.jpg';
import { Image } from '@astrojs/image/components';
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Image id="social-jpg" src={socialJpg} width={506} height={253} />
</body>
</html>

View file

@ -0,0 +1,8 @@
import { defineConfig } from 'astro/config';
import image from '@astrojs/image';
// https://astro.build/config
export default defineConfig({
site: 'http://localhost:3000',
integrations: [image({ logLevel: 'silent' })]
});

View file

@ -0,0 +1,10 @@
{
"name": "@test/no-alt-text-picture",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/image": "workspace:*",
"@astrojs/node": "workspace:*",
"astro": "workspace:*"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,44 @@
import { createServer } from 'http';
import fs from 'fs';
import mime from 'mime';
import { handler as ssrHandler } from '../dist/server/entry.mjs';
const clientRoot = new URL('../dist/client/', import.meta.url);
async function handle(req, res) {
ssrHandler(req, res, async (err) => {
if (err) {
res.writeHead(500);
res.end(err.stack);
return;
}
let local = new URL('.' + req.url, clientRoot);
try {
const data = await fs.promises.readFile(local);
res.writeHead(200, {
'Content-Type': mime.getType(req.url),
});
res.end(data);
} catch {
res.writeHead(404);
res.end();
}
});
}
const server = createServer((req, res) => {
handle(req, res).catch((err) => {
console.error(err);
res.writeHead(500, {
'Content-Type': 'text/plain',
});
res.end(err.toString());
});
});
server.listen(8085);
console.log('Serving at http://localhost:8085');
// Silence weird <time> warning
console.error = () => {};

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,13 @@
---
import socialJpg from '../assets/social.jpg';
import { Picture } from '@astrojs/image/components';
---
<html>
<head>
<!-- Head Stuff -->
</head>
<body>
<Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" />
</body>
</html>

View file

@ -7,42 +7,42 @@ import { Image } from '@astrojs/image/components';
<!-- Head Stuff -->
</head>
<body>
<Image id='landscape-0' src={import('../assets/Landscape_0.jpg')} />
<Image id='landscape-0' src={import('../assets/Landscape_0.jpg')} alt="landscape-0" />
<br />
<Image id='landscape-1' src={import('../assets/Landscape_1.jpg')} />
<Image id='landscape-1' src={import('../assets/Landscape_1.jpg')} alt="landscape-1" />
<br />
<Image id='landscape-2' src={import('../assets/Landscape_2.jpg')} />
<Image id='landscape-2' src={import('../assets/Landscape_2.jpg')} alt="landscape-2" />
<br />
<Image id='landscape-3' src={import('../assets/Landscape_3.jpg')} />
<Image id='landscape-3' src={import('../assets/Landscape_3.jpg')} alt="landscape-3" />
<br />
<Image id='landscape-4' src={import('../assets/Landscape_4.jpg')} />
<Image id='landscape-4' src={import('../assets/Landscape_4.jpg')} alt="landscape-4" />
<br />
<Image id='landscape-5' src={import('../assets/Landscape_5.jpg')} />
<Image id='landscape-5' src={import('../assets/Landscape_5.jpg')} alt="landscape-5" />
<br />
<Image id='landscape-6' src={import('../assets/Landscape_6.jpg')} />
<Image id='landscape-6' src={import('../assets/Landscape_6.jpg')} alt="landscape-6" />
<br />
<Image id='landscape-7' src={import('../assets/Landscape_7.jpg')} />
<Image id='landscape-7' src={import('../assets/Landscape_7.jpg')} alt="landscape-7" />
<br />
<Image id='landscape-8' src={import('../assets/Landscape_8.jpg')} />
<Image id='landscape-8' src={import('../assets/Landscape_8.jpg')} alt="landscape-8" />
<br />
<Image id='portrait-0' src={import('../assets/Portrait_0.jpg')} />
<Image id='portrait-0' src={import('../assets/Portrait_0.jpg')} alt="portrait-0" />
<br />
<Image id='portrait-1' src={import('../assets/Portrait_1.jpg')} />
<Image id='portrait-1' src={import('../assets/Portrait_1.jpg')} alt="portrait-1" />
<br />
<Image id='portrait-2' src={import('../assets/Portrait_2.jpg')} />
<Image id='portrait-2' src={import('../assets/Portrait_2.jpg')} alt="portrait-2" />
<br />
<Image id='portrait-3' src={import('../assets/Portrait_3.jpg')} />
<Image id='portrait-3' src={import('../assets/Portrait_3.jpg')} alt="portrait-3" />
<br />
<Image id='portrait-4' src={import('../assets/Portrait_4.jpg')} />
<Image id='portrait-4' src={import('../assets/Portrait_4.jpg')} alt="portrait-4" />
<br />
<Image id='portrait-5' src={import('../assets/Portrait_5.jpg')} />
<Image id='portrait-5' src={import('../assets/Portrait_5.jpg')} alt="portrait-5" />
<br />
<Image id='portrait-6' src={import('../assets/Portrait_6.jpg')} />
<Image id='portrait-6' src={import('../assets/Portrait_6.jpg')} alt="portrait-6" />
<br />
<Image id='portrait-7' src={import('../assets/Portrait_7.jpg')} />
<Image id='portrait-7' src={import('../assets/Portrait_7.jpg')} alt="portrait-7" />
<br />
<Image id='portrait-8' src={import('../assets/Portrait_8.jpg')} />
<Image id='portrait-8' src={import('../assets/Portrait_8.jpg')} alt="portrait-8" />
<br />
</body>
</html>

View file

@ -0,0 +1,9 @@
import { defineConfig } from 'astro/config';
import image from '@astrojs/image';
import mdx from '@astrojs/mdx';
// https://astro.build/config
export default defineConfig({
site: 'http://localhost:3000',
integrations: [image({ logLevel: 'silent' }), mdx()]
});

View file

@ -0,0 +1,11 @@
{
"name": "@test/with-mdx",
"version": "0.0.0",
"private": true,
"dependencies": {
"@astrojs/image": "workspace:*",
"@astrojs/mdx": "workspace:*",
"@astrojs/node": "workspace:*",
"astro": "workspace:*"
}
}

Some files were not shown because too many files have changed in this diff Show more