merge in main
5
.changeset/loud-berries-sit.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Move ast-types as dev dependency
|
5
.changeset/polite-pears-hope.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/image': minor
|
||||
---
|
||||
|
||||
feat: throw if alt text is missing
|
|
@ -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.
|
5
.changeset/thick-guests-sell.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Add docs link to "missing adapter" error msg
|
5
.changeset/twelve-singers-accept.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/image': patch
|
||||
---
|
||||
|
||||
Fixes a bug that broke support for local images with spaces in the filename
|
5
.changeset/weak-emus-confess.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'@astrojs/mdx': patch
|
||||
---
|
||||
|
||||
Fix: Add GFM and Smartypants to MDX by default
|
3
.github/CODEOWNERS
vendored
|
@ -1 +1,2 @@
|
|||
* @snowpackjs/maintainers
|
||||
README.md @withastro/maintainers-docs
|
||||
packages/astro/src/@types/astro.ts @withastro/maintainers-docs
|
||||
|
|
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
|
@ -12,6 +12,9 @@
|
|||
|
||||
## Docs
|
||||
|
||||
<!-- Is this a visible change? You probably need to update docs! -->
|
||||
<!-- Could this affect a user’s behavior? We probably need to update docs! -->
|
||||
<!-- If docs will be needed or you’re 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 -->
|
||||
|
|
|
@ -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: |
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.1.2"
|
||||
"dependencies": {
|
||||
"astro": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@example/my-component": "workspace:*",
|
||||
"astro": "^1.1.2"
|
||||
"astro": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"build": "astro --root demo build",
|
||||
"serve": "astro --root demo preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.1.2"
|
||||
"dependencies": {
|
||||
"astro": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.1.2"
|
||||
"dependencies": {
|
||||
"astro": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.1.2"
|
||||
"dependencies": {
|
||||
"astro": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.1.2"
|
||||
"dependencies": {
|
||||
"astro": "^1.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
"astro": "astro",
|
||||
"test": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"astro": "^1.1.2",
|
||||
"dependencies": {
|
||||
"astro": "^1.1.3",
|
||||
"vitest": "^0.20.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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')}`,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
7
packages/astro/test/fixtures/0-css/src/components/SvelteDynamic.svelte
vendored
Normal file
|
@ -0,0 +1,7 @@
|
|||
<h1 id="svelte-dynamic" class="svelte-dynamic">Svelte Dynamic</h1>
|
||||
|
||||
<style>
|
||||
.svelte-dynamic {
|
||||
font-family: Courier, monospace;
|
||||
}
|
||||
</style>
|
|
@ -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>
|
||||
|
|
11
packages/astro/test/fixtures/astro-client-only/src/components/UsingCSSModules.jsx
vendored
Normal 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;
|
3
packages/astro/test/fixtures/astro-client-only/src/components/styles.module.scss
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
.red {
|
||||
color: red;
|
||||
}
|
11
packages/astro/test/fixtures/astro-client-only/src/pages/css-modules.astro
vendored
Normal 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>
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
1
packages/integrations/deno/test/fixtures/basics/src/pages/404.astro
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
<h1 id="custom-404">Custom 404 Page</h1>
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
||||
Astro’s `<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. doesn’t 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
|
||||
|
|
|
@ -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);
|
||||
---
|
||||
|
||||
|
|
|
@ -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 });
|
||||
---
|
||||
|
||||
|
|
|
@ -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`);
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
export const PKG_NAME = '@astrojs/image';
|
||||
export const ROUTE_PATTERN = '/_image';
|
||||
export const OUTPUT_DIR = '/_image';
|
|
@ -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(),
|
||||
},
|
||||
});
|
|
@ -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 });
|
||||
}
|
||||
};
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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(','),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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')) {
|
||||
|
|
44
packages/integrations/image/src/utils/etag.ts
Normal 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) + '"';
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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('/');
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 270 KiB |
BIN
packages/integrations/image/test/fixtures/basic-image/src/assets/blog/introducing astro.jpg
vendored
Normal file
After Width: | Height: | Size: 270 KiB |
|
@ -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>
|
||||
|
|
BIN
packages/integrations/image/test/fixtures/basic-picture/public/hero.jpg
vendored
Normal file
After Width: | Height: | Size: 270 KiB |
|
@ -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" />
|
||||
|
|
8
packages/integrations/image/test/fixtures/no-alt-text-image/astro.config.mjs
vendored
Normal 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' })]
|
||||
});
|
10
packages/integrations/image/test/fixtures/no-alt-text-image/package.json
vendored
Normal 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:*"
|
||||
}
|
||||
}
|
BIN
packages/integrations/image/test/fixtures/no-alt-text-image/public/favicon.ico
vendored
Normal file
After Width: | Height: | Size: 4.2 KiB |
44
packages/integrations/image/test/fixtures/no-alt-text-image/server/server.mjs
vendored
Normal 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 = () => {};
|
BIN
packages/integrations/image/test/fixtures/no-alt-text-image/src/assets/social.jpg
vendored
Normal file
After Width: | Height: | Size: 25 KiB |
13
packages/integrations/image/test/fixtures/no-alt-text-image/src/pages/index.astro
vendored
Normal 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>
|
8
packages/integrations/image/test/fixtures/no-alt-text-picture/astro.config.mjs
vendored
Normal 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' })]
|
||||
});
|
10
packages/integrations/image/test/fixtures/no-alt-text-picture/package.json
vendored
Normal 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:*"
|
||||
}
|
||||
}
|
BIN
packages/integrations/image/test/fixtures/no-alt-text-picture/public/favicon.ico
vendored
Normal file
After Width: | Height: | Size: 4.2 KiB |
44
packages/integrations/image/test/fixtures/no-alt-text-picture/server/server.mjs
vendored
Normal 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 = () => {};
|
BIN
packages/integrations/image/test/fixtures/no-alt-text-picture/src/assets/social.jpg
vendored
Normal file
After Width: | Height: | Size: 25 KiB |
13
packages/integrations/image/test/fixtures/no-alt-text-picture/src/pages/index.astro
vendored
Normal 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>
|
|
@ -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>
|
||||
|
|
9
packages/integrations/image/test/fixtures/with-mdx/astro.config.mjs
vendored
Normal 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()]
|
||||
});
|
11
packages/integrations/image/test/fixtures/with-mdx/package.json
vendored
Normal 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:*"
|
||||
}
|
||||
}
|