Merge branch 'main' into feat/squoosh-lib
This commit is contained in:
commit
27a7c4b2ea
121 changed files with 2239 additions and 1455 deletions
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
"astro": patch
|
||||
---
|
||||
|
||||
Improve third-party Astro package support
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@astrojs/vue': patch
|
||||
---
|
||||
|
||||
Mark vueperslides as a default noExternal
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@astrojs/image': patch
|
||||
---
|
||||
|
||||
Parallelize image transforms
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Remove regression when there is duplicate client/server CSS
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Correctly escape paths in file names
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Ensure SSR module is loaded before testing if it's CSS in dev
|
6
.changeset/tasty-owls-watch.md
Normal file
6
.changeset/tasty-owls-watch.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
'astro': patch
|
||||
'@astrojs/react': patch
|
||||
---
|
||||
|
||||
Fix framework components on Vercel Edge
|
|
@ -1,5 +0,0 @@
|
|||
---
|
||||
'@astrojs/alpinejs': patch
|
||||
---
|
||||
|
||||
Update homepage link
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5"
|
||||
"astro": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"@astrojs/mdx": "^0.11.1",
|
||||
"@astrojs/rss": "^1.0.0",
|
||||
"@astrojs/sitemap": "^1.0.0"
|
||||
|
|
19
examples/component/.gitignore
vendored
19
examples/component/.gitignore
vendored
|
@ -1,19 +0,0 @@
|
|||
# build output
|
||||
dist/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
|
@ -1,2 +0,0 @@
|
|||
# Expose Astro dependencies for `pnpm` users
|
||||
shamefully-hoist=true
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"startCommand": "npm start",
|
||||
"env": {
|
||||
"ENABLE_CJS_IMPORTS": true
|
||||
}
|
||||
}
|
|
@ -1,12 +1,13 @@
|
|||
# Astro Starter Kit: Component
|
||||
# Astro Starter Kit: Component Package
|
||||
|
||||
This is a template for an Astro component library. Use this template for writing components to use in multiple projects or publish to NPM.
|
||||
|
||||
```
|
||||
npm init astro -- --template component
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/component)
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/non-html-pages)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
|
@ -14,34 +15,19 @@ Inside of your Astro project, you'll see the following folders and files:
|
|||
|
||||
```
|
||||
/
|
||||
├── demo/
|
||||
│ ├── public/
|
||||
│ └── src/
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── packages/
|
||||
└── my-component/
|
||||
├── index.js
|
||||
└── package.json
|
||||
├── index.ts
|
||||
├── src
|
||||
│ └── MyComponent.astro
|
||||
├── tsconfig.json
|
||||
├── package.json
|
||||
```
|
||||
|
||||
This project uses **workspaces** to develop a single package, `@example/my-component`, from `packages/my-component`. It also includes a `demo` Astro site for testing and demonstrating the component.
|
||||
|
||||
|
||||
The `index.ts` file is the "entry point" for your package. Export your components in `index.ts` to make them importable from your package.
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :--------------------- | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
| `npm link` | Registers this package locally. Run `npm link my-component-library` in an Astro project to install your components
|
||||
| `npm publish` | [Publishes](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages#publishing-unscoped-public-packages) this package to NPM. Requires you to be [logged in](https://docs.npmjs.com/cli/v8/commands/npm-adduser)
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"name": "@example/my-component-demo",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@example/my-component": "workspace:*",
|
||||
"astro": "^1.1.5"
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 36 36">
|
||||
<path fill="#000" d="M22.25 4h-8.5a1 1 0 0 0-.96.73l-5.54 19.4a.5.5 0 0 0 .62.62l5.05-1.44a2 2 0 0 0 1.38-1.4l3.22-11.66a.5.5 0 0 1 .96 0l3.22 11.67a2 2 0 0 0 1.38 1.39l5.05 1.44a.5.5 0 0 0 .62-.62l-5.54-19.4a1 1 0 0 0-.96-.73Z"/>
|
||||
<path fill="url(#gradient)" d="M18 28a7.63 7.63 0 0 1-5-2c-1.4 2.1-.35 4.35.6 5.55.14.17.41.07.47-.15.44-1.8 2.93-1.22 2.93.6 0 2.28.87 3.4 1.72 3.81.34.16.59-.2.49-.56-.31-1.05-.29-2.46 1.29-3.25 3-1.5 3.17-4.83 2.5-6-.67.67-2.6 2-5 2Z"/>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x1="16" x2="16" y1="32" y2="24" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#000"/>
|
||||
<stop offset="1" stop-color="#000" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<style>
|
||||
@media (prefers-color-scheme:dark){:root{filter:invert(100%)}}
|
||||
</style>
|
||||
</svg>
|
Before Width: | Height: | Size: 873 B |
|
@ -1,25 +0,0 @@
|
|||
---
|
||||
import * as Component from '@example/my-component';
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<meta name="generator" content={Astro.generator} />
|
||||
<title>Welcome to Astro</title>
|
||||
<style is:global>
|
||||
h {
|
||||
display: block;
|
||||
font-size: 2em;
|
||||
font-weight: bold;
|
||||
margin-block: 0.67em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<Component.Heading>Welcome to Astro</Component.Heading>
|
||||
<Component.Button>Plain Button</Component.Button>
|
||||
</body>
|
||||
</html>
|
6
examples/component/index.ts
Normal file
6
examples/component/index.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
// Do not write code directly here, instead use the `src` folder!
|
||||
// Then, use this file to export everything you want your user to access.
|
||||
|
||||
import MyComponent from './src/MyComponent.astro';
|
||||
|
||||
export default MyComponent;
|
|
@ -1,13 +1,23 @@
|
|||
{
|
||||
"name": "@example/component",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "astro --root demo dev",
|
||||
"build": "astro --root demo build",
|
||||
"serve": "astro --root demo preview"
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5"
|
||||
"files": [
|
||||
"src",
|
||||
"index.ts"
|
||||
],
|
||||
"keywords": [
|
||||
"astro-component"
|
||||
],
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"astro": "^1.1.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"astro": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
---
|
||||
export interface Props extends Record<any, any> {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const { type, ...props } = {
|
||||
...Astro.props,
|
||||
};
|
||||
|
||||
props.type = type || 'button';
|
||||
---
|
||||
|
||||
<button {...props}><slot /></button>
|
|
@ -1,15 +0,0 @@
|
|||
---
|
||||
export interface Props extends Record<any, any> {
|
||||
level?: number | string;
|
||||
role?: string;
|
||||
}
|
||||
|
||||
const { level, role, ...props } = {
|
||||
...Astro.props,
|
||||
};
|
||||
|
||||
props.role = role || 'heading';
|
||||
props['aria-level'] = level || '1';
|
||||
---
|
||||
|
||||
<h {...props}><slot /></h>
|
|
@ -1,37 +0,0 @@
|
|||
# Example `@example/my-component`
|
||||
|
||||
This is an example package, exported as `@example/my-component`. It consists of two Astro components, **Button** and **Heading**.
|
||||
|
||||
### Button
|
||||
|
||||
The **Button** component generates a `<button>` with a default **type** of **button**.
|
||||
|
||||
```astro
|
||||
---
|
||||
import * as Component from '@example/my-component'
|
||||
---
|
||||
<Component.Button>Plain Button</Component.Button>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- generated html -->
|
||||
<button type="button">Plain Button</button>
|
||||
```
|
||||
|
||||
### Heading
|
||||
|
||||
The **Heading** component generates an `<h>` tag with a default **role** of **heading** and a **level** attribute that gets written to **aria-level**.
|
||||
|
||||
```astro
|
||||
---
|
||||
import * as Component from '@example/my-component'
|
||||
---
|
||||
<Component.Heading>Heading</Component.Heading>
|
||||
<Component.Heading level="2">Subheading</Component.Heading>
|
||||
```
|
||||
|
||||
```html
|
||||
<!-- generated html -->
|
||||
<h role="heading" aria-level="1">Plain Button</h>
|
||||
<h role="heading" aria-level="2">Subheading</h>
|
||||
```
|
|
@ -1,2 +0,0 @@
|
|||
export { default as Button } from './Button.astro';
|
||||
export { default as Heading } from './Heading.astro';
|
|
@ -1,22 +0,0 @@
|
|||
{
|
||||
"name": "@example/my-component",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./index.js",
|
||||
"./Button": "./Button.astro",
|
||||
"./Heading": "./Heading.astro"
|
||||
},
|
||||
"files": [
|
||||
"index.js",
|
||||
"Button.astro",
|
||||
"Heading.jsx"
|
||||
],
|
||||
"keywords": [
|
||||
"astro-component",
|
||||
"button",
|
||||
"heading",
|
||||
"example"
|
||||
]
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
packages:
|
||||
- 'packages/**/*'
|
||||
- 'demo'
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"infiniteLoopProtection": true,
|
||||
"hardReloadOnChange": false,
|
||||
"view": "browser",
|
||||
"template": "node",
|
||||
"container": {
|
||||
"port": 3000,
|
||||
"startScript": "start",
|
||||
"node": "14"
|
||||
}
|
||||
}
|
8
examples/component/src/MyComponent.astro
Normal file
8
examples/component/src/MyComponent.astro
Normal file
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
// Write your component code in this file!
|
||||
export interface Props {
|
||||
prefix?: string
|
||||
}
|
||||
---
|
||||
|
||||
<div>{Astro.props.prefix} My special component</div>
|
3
examples/component/tsconfig.json
Normal file
3
examples/component/tsconfig.json
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"extends": "astro/tsconfigs/base"
|
||||
}
|
|
@ -11,12 +11,12 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"preact": "^10.7.3",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"@astrojs/react": "^1.1.0",
|
||||
"@astrojs/preact": "^1.0.2",
|
||||
"@astrojs/preact": "^1.1.0",
|
||||
"@algolia/client-search": "^4.13.1",
|
||||
"@docsearch/css": "^3.1.0",
|
||||
"@docsearch/react": "^3.1.0",
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"alpinejs": "^3.10.2",
|
||||
"@astrojs/alpinejs": "^0.1.1",
|
||||
"@astrojs/alpinejs": "^0.1.2",
|
||||
"@types/alpinejs": "^3.7.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"lit": "^2.2.5",
|
||||
"@astrojs/lit": "^1.0.0",
|
||||
"@webcomponents/template-shadowroot": "^0.1.0"
|
||||
|
|
|
@ -10,17 +10,17 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"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",
|
||||
"@astrojs/preact": "^1.0.2",
|
||||
"@astrojs/preact": "^1.1.0",
|
||||
"@astrojs/react": "^1.1.0",
|
||||
"@astrojs/solid-js": "^1.1.0",
|
||||
"@astrojs/svelte": "^1.0.0",
|
||||
"@astrojs/vue": "^1.0.0"
|
||||
"@astrojs/vue": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"preact": "^10.7.3",
|
||||
"@astrojs/preact": "^1.0.2"
|
||||
"@astrojs/preact": "^1.1.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"react": "^18.1.0",
|
||||
"react-dom": "^18.1.0",
|
||||
"@astrojs/react": "^1.1.0",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"solid-js": "^1.4.3",
|
||||
"@astrojs/solid-js": "^1.1.0"
|
||||
}
|
||||
|
|
|
@ -12,6 +12,6 @@
|
|||
"dependencies": {
|
||||
"svelte": "^3.48.0",
|
||||
"@astrojs/svelte": "^1.0.0",
|
||||
"astro": "^1.1.5"
|
||||
"astro": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"vue": "^3.2.37",
|
||||
"@astrojs/vue": "^1.0.0"
|
||||
"@astrojs/vue": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5"
|
||||
"astro": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5"
|
||||
"astro": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5"
|
||||
"astro": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
"devDependencies": {},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"svelte": "^3.48.0",
|
||||
"@astrojs/svelte": "^1.0.0",
|
||||
"@astrojs/node": "^1.0.1",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"@astrojs/markdown-remark": "^1.1.0",
|
||||
"hast-util-select": "5.0.1",
|
||||
"rehype-autolink-headings": "^6.1.1",
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5"
|
||||
"astro": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"preact": "^10.6.5",
|
||||
"@astrojs/preact": "^1.0.2",
|
||||
"@astrojs/preact": "^1.1.0",
|
||||
"@astrojs/mdx": "^0.11.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"preact": "^10.7.3",
|
||||
"@astrojs/preact": "^1.0.2",
|
||||
"@astrojs/preact": "^1.1.0",
|
||||
"nanostores": "^0.5.12",
|
||||
"@nanostores/preact": "^0.1.3"
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"@astrojs/tailwind": "^1.0.0",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"canvas-confetti": "^1.5.1",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"astro": "astro"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"vite-plugin-pwa": "0.11.11",
|
||||
"workbox-window": "^6.5.3"
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^1.1.5",
|
||||
"astro": "^1.1.7",
|
||||
"vitest": "^0.20.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,27 @@
|
|||
# astro
|
||||
|
||||
## 1.1.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4646](https://github.com/withastro/astro/pull/4646) [`98f242cdc`](https://github.com/withastro/astro/commit/98f242cdcd860679ad787ffb387558cb1dc93b87) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Add cyclic ref detection when serializing props
|
||||
|
||||
- [#4656](https://github.com/withastro/astro/pull/4656) [`6d845c353`](https://github.com/withastro/astro/commit/6d845c353d5688f30787c4361f86c605fb638dd9) Thanks [@matthewp](https://github.com/matthewp)! - Fix bug with using `assert` as import identifier
|
||||
|
||||
- [#4403](https://github.com/withastro/astro/pull/4403) [`d31e72c3b`](https://github.com/withastro/astro/commit/d31e72c3ba8270d1e8d33c533502b3c4c6390a15) Thanks [@JohnDaly](https://github.com/JohnDaly)! - Fix for components, declared with JSXMemberExpression nodes, that failed to hydrate due to incomplete 'component-export' metadata
|
||||
|
||||
## 1.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4623](https://github.com/withastro/astro/pull/4623) [`eb1862b4e`](https://github.com/withastro/astro/commit/eb1862b4e68b399eecc7267ea9e0bee36983b0cb) Thanks [@delucis](https://github.com/delucis)! - Improve third-party Astro package support
|
||||
|
||||
- [#4643](https://github.com/withastro/astro/pull/4643) [`307b7b97c`](https://github.com/withastro/astro/commit/307b7b97ce79d076ceb4fdc25fd28a27077deb34) Thanks [@matthewp](https://github.com/matthewp)! - Remove regression when there is duplicate client/server CSS
|
||||
|
||||
- [#4584](https://github.com/withastro/astro/pull/4584) [`29a5fdc15`](https://github.com/withastro/astro/commit/29a5fdc1535fc389035d8107025f7490bfa976ed) Thanks [@bluwy](https://github.com/bluwy)! - Correctly escape paths in file names
|
||||
|
||||
- [#4621](https://github.com/withastro/astro/pull/4621) [`0068afb87`](https://github.com/withastro/astro/commit/0068afb876342ae76154e552dfc5bb6832b665ed) Thanks [@AllanChain](https://github.com/AllanChain)! - Ensure SSR module is loaded before testing if it's CSS in dev
|
||||
|
||||
## 1.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
|
24
packages/astro/e2e/error-cyclic.test.js
Normal file
24
packages/astro/e2e/error-cyclic.test.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { expect } from '@playwright/test';
|
||||
import { testFactory, getErrorOverlayMessage } from './test-utils.js';
|
||||
|
||||
const test = testFactory({ root: './fixtures/error-cyclic/' });
|
||||
|
||||
let devServer;
|
||||
|
||||
test.beforeEach(async ({ astro }) => {
|
||||
devServer = await astro.startDevServer();
|
||||
});
|
||||
|
||||
test.afterEach(async ({ astro }) => {
|
||||
await devServer.stop();
|
||||
astro.resetAllFiles();
|
||||
});
|
||||
|
||||
test.describe('Error: Cyclic Reference', () => {
|
||||
test('overlay', async ({ page, astro }) => {
|
||||
await page.goto(astro.resolveUrl('/'));
|
||||
|
||||
const message = await getErrorOverlayMessage(page);
|
||||
expect(message).toMatch('Cyclic reference');
|
||||
});
|
||||
});
|
|
@ -1,10 +1,7 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
vite: {
|
||||
ssr: {
|
||||
noExternal: ['@example/my-component'],
|
||||
},
|
||||
},
|
||||
integrations: [preact()],
|
||||
});
|
9
packages/astro/e2e/fixtures/error-cyclic/package.json
Normal file
9
packages/astro/e2e/fixtures/error-cyclic/package.json
Normal file
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "@e2e/error-cyclic",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*",
|
||||
"@astrojs/preact": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
import { useState } from 'preact/hooks';
|
||||
|
||||
/** a counter written in Preact */
|
||||
export function PreactCounter({ children, id }) {
|
||||
const [count, setCount] = useState(0);
|
||||
const add = () => setCount((i) => i + 1);
|
||||
const subtract = () => setCount((i) => i - 1);
|
||||
|
||||
return (
|
||||
<div id={id} class="counter">
|
||||
<button class="decrement" onClick={subtract}>-</button>
|
||||
<pre>{count}</pre>
|
||||
<button class="increment" onClick={add}>+</button>
|
||||
<div class="children">{children}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
import { PreactCounter } from '../components/PreactCounter'
|
||||
|
||||
const cycle: any = { foo: ['bar'] }
|
||||
cycle.foo.push(cycle)
|
||||
---
|
||||
|
||||
<PreactCounter client:load cycle={cycle} />
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
import mdx from "@astrojs/mdx";
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
integrations: [preact()],
|
||||
integrations: [preact(), mdx()]
|
||||
});
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@astrojs/mdx": "workspace:*",
|
||||
"@astrojs/preact": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
---
|
||||
import * as ns from '../components/PreactCounter.tsx';
|
||||
import { components } from '../components/PreactCounter.tsx';
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
|
@ -10,9 +11,13 @@ import * as ns from '../components/PreactCounter.tsx';
|
|||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<ns.components.PreactCounter id="preact-counter" client:load>
|
||||
<h1>preact</h1>
|
||||
<ns.components.PreactCounter id="preact-counter-namespace" client:load>
|
||||
<h1>preact (namespace import)</h1>
|
||||
</ns.components.PreactCounter>
|
||||
|
||||
<components.PreactCounter id="preact-counter-named" client:load>
|
||||
<h1>preact (named import)</h1>
|
||||
</components.PreactCounter>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import * as ns from '../components/PreactCounter.tsx';
|
||||
import { components } from '../components/PreactCounter.tsx';
|
||||
|
||||
<ns.components.PreactCounter id="preact-counter-namespace" client:load>
|
||||
preact (namespace import)
|
||||
</ns.components.PreactCounter>
|
||||
|
||||
<components.PreactCounter id="preact-counter-named" client:load>
|
||||
preact (named import)
|
||||
</components.PreactCounter>
|
|
@ -19,18 +19,68 @@ test.describe('Hydrating namespaced components', () => {
|
|||
test('Preact Component', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
const counter = await page.locator('#preact-counter');
|
||||
await expect(counter, 'component is visible').toBeVisible();
|
||||
// Counter declared with: <ns.components.PreactCounter id="preact-counter-namespace" client:load>
|
||||
const namespacedCounter = await page.locator('#preact-counter-namespace');
|
||||
await expect(namespacedCounter, 'component is visible').toBeVisible();
|
||||
|
||||
const count = await counter.locator('pre');
|
||||
await expect(count, 'initial count is 0').toHaveText('0');
|
||||
const namespacedCount = await namespacedCounter.locator('pre');
|
||||
await expect(namespacedCount, 'initial count is 0').toHaveText('0');
|
||||
|
||||
const children = await counter.locator('.children');
|
||||
await expect(children, 'children exist').toHaveText('preact');
|
||||
const namespacedChildren = await namespacedCounter.locator('.children');
|
||||
await expect(namespacedChildren, 'children exist').toHaveText('preact (namespace import)');
|
||||
|
||||
const increment = await counter.locator('.increment');
|
||||
await increment.click();
|
||||
const namespacedIncrement = await namespacedCounter.locator('.increment');
|
||||
await namespacedIncrement.click();
|
||||
|
||||
await expect(count, 'count incremented by 1').toHaveText('1');
|
||||
await expect(namespacedCount, 'count incremented by 1').toHaveText('1');
|
||||
|
||||
// Counter declared with: <components.PreactCounterTwo id="preact-counter-named" client:load>
|
||||
const namedCounter = await page.locator('#preact-counter-named');
|
||||
await expect(namedCounter, 'component is visible').toBeVisible();
|
||||
|
||||
const namedCount = await namedCounter.locator('pre');
|
||||
await expect(namedCount, 'initial count is 0').toHaveText('0');
|
||||
|
||||
const namedChildren = await namedCounter.locator('.children');
|
||||
await expect(namedChildren, 'children exist').toHaveText('preact (named import)');
|
||||
|
||||
const namedIncrement = await namedCounter.locator('.increment');
|
||||
await namedIncrement.click();
|
||||
|
||||
await expect(namedCount, 'count incremented by 1').toHaveText('1');
|
||||
});
|
||||
|
||||
test('MDX', async ({ page }) => {
|
||||
await page.goto('/mdx');
|
||||
|
||||
// Counter declared with: <ns.components.PreactCounter id="preact-counter-namespace" client:load>
|
||||
const namespacedCounter = await page.locator('#preact-counter-namespace');
|
||||
await expect(namespacedCounter, 'component is visible').toBeVisible();
|
||||
|
||||
const namespacedCount = await namespacedCounter.locator('pre');
|
||||
await expect(namespacedCount, 'initial count is 0').toHaveText('0');
|
||||
|
||||
const namespacedChildren = await namespacedCounter.locator('.children');
|
||||
await expect(namespacedChildren, 'children exist').toHaveText('preact (namespace import)');
|
||||
|
||||
const namespacedIncrement = await namespacedCounter.locator('.increment');
|
||||
await namespacedIncrement.click();
|
||||
|
||||
await expect(namespacedCount, 'count incremented by 1').toHaveText('1');
|
||||
|
||||
// Counter declared with: <components.PreactCounterTwo id="preact-counter-named" client:load>
|
||||
const namedCounter = await page.locator('#preact-counter-named');
|
||||
await expect(namedCounter, 'component is visible').toBeVisible();
|
||||
|
||||
const namedCount = await namedCounter.locator('pre');
|
||||
await expect(namedCount, 'initial count is 0').toHaveText('0');
|
||||
|
||||
const namedChildren = await namedCounter.locator('.children');
|
||||
await expect(namedChildren, 'children exist').toHaveText('preact (named import)');
|
||||
|
||||
const namedIncrement = await namedCounter.locator('.increment');
|
||||
await namedIncrement.click();
|
||||
|
||||
await expect(namedCount, 'count incremented by 1').toHaveText('1');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "astro",
|
||||
"version": "1.1.5",
|
||||
"version": "1.1.7",
|
||||
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
|
||||
"type": "module",
|
||||
"author": "withastro",
|
||||
|
@ -94,7 +94,7 @@
|
|||
"test:e2e:match": "playwright test -g"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/compiler": "^0.23.4",
|
||||
"@astrojs/compiler": "^0.23.5",
|
||||
"@astrojs/language-server": "^0.23.0",
|
||||
"@astrojs/markdown-remark": "^1.1.1",
|
||||
"@astrojs/telemetry": "^1.0.0",
|
||||
|
|
|
@ -243,7 +243,7 @@ class DependencyWalker {
|
|||
|
||||
dir = parentDir;
|
||||
}
|
||||
} catch (e) {
|
||||
} catch {
|
||||
// Give up! Who knows where the `package.json` is…
|
||||
}
|
||||
}
|
||||
|
|
|
@ -207,7 +207,8 @@ export default function astroJSX(): PluginObj {
|
|||
break;
|
||||
}
|
||||
if (namespace.at(0) === local) {
|
||||
path.setData('import', { name: imported, path: source });
|
||||
const name = imported === '*' ? imported : tagName;
|
||||
path.setData('import', { name, path: source });
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,7 +146,7 @@ export async function generateHydrateScript(
|
|||
if (renderer.clientEntrypoint) {
|
||||
island.props['component-export'] = componentExport.value;
|
||||
island.props['renderer-url'] = await result.resolve(decodeURI(renderer.clientEntrypoint));
|
||||
island.props['props'] = escapeHTML(serializeProps(props));
|
||||
island.props['props'] = escapeHTML(serializeProps(props, metadata));
|
||||
}
|
||||
|
||||
island.props['ssr'] = '';
|
||||
|
|
|
@ -303,7 +303,8 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
|||
// Include componentExport name, componentUrl, and props in hash to dedupe identical islands
|
||||
const astroId = shorthash(
|
||||
`<!--${metadata.componentExport!.value}:${metadata.componentUrl}-->\n${html}\n${serializeProps(
|
||||
props
|
||||
props,
|
||||
metadata
|
||||
)}`
|
||||
);
|
||||
|
||||
|
|
|
@ -79,7 +79,10 @@ export async function renderPage(
|
|||
controller.enqueue(encoder.encode('<!DOCTYPE html>\n'));
|
||||
}
|
||||
}
|
||||
controller.enqueue(encoder.encode(html));
|
||||
// Convert HTML object to string
|
||||
// for environments that won't "toString" automatically
|
||||
// (ex. Cloudflare and Vercel Edge)
|
||||
controller.enqueue(encoder.encode(String(html)));
|
||||
i++;
|
||||
}
|
||||
controller.close();
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { AstroComponentMetadata } from '../../@types/astro';
|
||||
|
||||
type ValueOf<T> = T[keyof T];
|
||||
|
||||
const PROP_TYPE = {
|
||||
|
@ -11,19 +13,31 @@ const PROP_TYPE = {
|
|||
URL: 7,
|
||||
};
|
||||
|
||||
function serializeArray(value: any[]): any[] {
|
||||
return value.map((v) => convertToSerializedForm(v));
|
||||
function serializeArray(value: any[], metadata: AstroComponentMetadata): any[] {
|
||||
return value.map((v) => convertToSerializedForm(v, metadata));
|
||||
}
|
||||
|
||||
function serializeObject(value: Record<any, any>): Record<any, any> {
|
||||
function serializeObject(
|
||||
value: Record<any, any>,
|
||||
metadata: AstroComponentMetadata
|
||||
): Record<any, any> {
|
||||
if (cyclicRefs.has(value)) {
|
||||
throw new Error(`Cyclic reference detected while serializing props for <${metadata.displayName} client:${metadata.hydrate}>!
|
||||
|
||||
Cyclic references cannot be safely serialized for client-side usage. Please remove the cyclic reference.`);
|
||||
}
|
||||
cyclicRefs.add(value);
|
||||
return Object.fromEntries(
|
||||
Object.entries(value).map(([k, v]) => {
|
||||
return [k, convertToSerializedForm(v)];
|
||||
return [k, convertToSerializedForm(v, metadata)];
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
|
||||
function convertToSerializedForm(
|
||||
value: any,
|
||||
metadata: AstroComponentMetadata
|
||||
): [ValueOf<typeof PROP_TYPE>, any] {
|
||||
const tag = Object.prototype.toString.call(value);
|
||||
switch (tag) {
|
||||
case '[object Date]': {
|
||||
|
@ -33,10 +47,16 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
|
|||
return [PROP_TYPE.RegExp, (value as RegExp).source];
|
||||
}
|
||||
case '[object Map]': {
|
||||
return [PROP_TYPE.Map, JSON.stringify(serializeArray(Array.from(value as Map<any, any>)))];
|
||||
return [
|
||||
PROP_TYPE.Map,
|
||||
JSON.stringify(serializeArray(Array.from(value as Map<any, any>), metadata)),
|
||||
];
|
||||
}
|
||||
case '[object Set]': {
|
||||
return [PROP_TYPE.Set, JSON.stringify(serializeArray(Array.from(value as Set<any>)))];
|
||||
return [
|
||||
PROP_TYPE.Set,
|
||||
JSON.stringify(serializeArray(Array.from(value as Set<any>), metadata)),
|
||||
];
|
||||
}
|
||||
case '[object BigInt]': {
|
||||
return [PROP_TYPE.BigInt, (value as bigint).toString()];
|
||||
|
@ -45,11 +65,11 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
|
|||
return [PROP_TYPE.URL, (value as URL).toString()];
|
||||
}
|
||||
case '[object Array]': {
|
||||
return [PROP_TYPE.JSON, JSON.stringify(serializeArray(value))];
|
||||
return [PROP_TYPE.JSON, JSON.stringify(serializeArray(value, metadata))];
|
||||
}
|
||||
default: {
|
||||
if (value !== null && typeof value === 'object') {
|
||||
return [PROP_TYPE.Value, serializeObject(value)];
|
||||
return [PROP_TYPE.Value, serializeObject(value, metadata)];
|
||||
} else {
|
||||
return [PROP_TYPE.Value, value];
|
||||
}
|
||||
|
@ -57,6 +77,9 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
|
|||
}
|
||||
}
|
||||
|
||||
export function serializeProps(props: any) {
|
||||
return JSON.stringify(serializeObject(props));
|
||||
let cyclicRefs = new WeakSet<any>();
|
||||
export function serializeProps(props: any, metadata: AstroComponentMetadata) {
|
||||
const serialized = JSON.stringify(serializeObject(props, metadata));
|
||||
cyclicRefs = new WeakSet<any>();
|
||||
return serialized;
|
||||
}
|
||||
|
|
|
@ -120,6 +120,7 @@ async function handle500Response(
|
|||
) {
|
||||
res.on('close', () => setTimeout(() => viteServer.ws.send(getViteErrorPayload(err)), 200));
|
||||
if (res.headersSent) {
|
||||
res.write(`<script type="module" src="/@vite/client"></script>`);
|
||||
res.end();
|
||||
} else {
|
||||
writeHtmlResponse(
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
# @astrojs/alpinejs
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4622](https://github.com/withastro/astro/pull/4622) [`63cd9d89e`](https://github.com/withastro/astro/commit/63cd9d89e8b83ce5e39cdae84a8342e28d1940cc) Thanks [@mohammed-elhaouari](https://github.com/mohammed-elhaouari)! - Update homepage link
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
|
|
@ -18,14 +18,16 @@ The `astro add` command-line tool automates the installation for you. Run one of
|
|||
|
||||
```sh
|
||||
# Using NPM
|
||||
npm run astro add alpinejs
|
||||
npx astro add alpinejs
|
||||
# Using Yarn
|
||||
yarn astro add alpinejs
|
||||
# Using PNPM
|
||||
pnpm astro add alpinejs
|
||||
```
|
||||
|
||||
Finally, in the terminal window running Astro, press `CTRL+C` and then type `npm run astro dev` to restart the dev server.
|
||||
Finally, in the terminal window running Astro, press `CTRL+C` and then restart the dev server.
|
||||
|
||||
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
|
||||
|
||||
### Manual Install
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@astrojs/alpinejs",
|
||||
"description": "The official Alpine.js integration for Astro.",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"author": "withastro",
|
||||
|
|
|
@ -1,5 +1,21 @@
|
|||
# @astrojs/image
|
||||
|
||||
## 0.6.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#4642](https://github.com/withastro/astro/pull/4642) [`e4348a4eb`](https://github.com/withastro/astro/commit/e4348a4eb49466579204eb5f7fb8823736f467c0) Thanks [@beeb](https://github.com/beeb)! - Added a `background` option to specify a background color to replace transparent pixels (alpha layer).
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4649](https://github.com/withastro/astro/pull/4649) [`db70afdcd`](https://github.com/withastro/astro/commit/db70afdcd5b7d6b39c9953e88dbdadc5e3a93175) Thanks [@tony-sull](https://github.com/tony-sull)! - Fixes a bug related to filenames for remote images in SSG builds
|
||||
|
||||
## 0.5.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#4626](https://github.com/withastro/astro/pull/4626) [`494c2b835`](https://github.com/withastro/astro/commit/494c2b8353d1975d840c5acaf70cb513b99c58e5) Thanks [@altano](https://github.com/altano)! - Parallelize image transforms
|
||||
|
||||
## 0.5.0
|
||||
|
||||
### Minor Changes
|
||||
|
|
|
@ -29,16 +29,16 @@ The `astro add` command-line tool automates the installation for you. Run one of
|
|||
|
||||
```sh
|
||||
# Using NPM
|
||||
npm run astro add image
|
||||
npx astro add image
|
||||
# Using Yarn
|
||||
yarn astro add image
|
||||
# Using PNPM
|
||||
pnpm astro add image
|
||||
```
|
||||
|
||||
Then, restart the dev server by typing `CTRL-C` and then `npm run astro dev` in the terminal window that was running Astro.
|
||||
Finally, in the terminal window running Astro, press `CTRL+C` and then restart the dev server.
|
||||
|
||||
Because this command is new, it might not properly set things up. If that happens, [feel free to log an issue on our GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
|
||||
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
|
||||
|
||||
### Manual Install
|
||||
|
||||
|
@ -190,6 +190,24 @@ A `string` can be provided in the form of `{width}:{height}`, ex: `16:9` or `3:4
|
|||
|
||||
A `number` can also be provided, useful when the aspect ratio is calculated at build time. This can be an inline number such as `1.777` or inlined as a JSX expression like `aspectRatio={16/9}`.
|
||||
|
||||
#### background
|
||||
|
||||
<p>
|
||||
|
||||
**Type:** `ColorDefinition`<br>
|
||||
**Default:** `undefined`
|
||||
</p>
|
||||
|
||||
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
|
||||
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
|
||||
as default replacement for transparent pixels.
|
||||
|
||||
The parameter accepts a `string` as value.
|
||||
|
||||
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
|
||||
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
|
||||
`rgb(100,100,100)`.
|
||||
|
||||
### `<Picture /`>
|
||||
|
||||
#### src
|
||||
|
@ -271,6 +289,24 @@ A `number` can also be provided, useful when the aspect ratio is calculated at b
|
|||
|
||||
The output formats to be used in the optimized image. If not provided, `webp` and `avif` will be used in addition to the original image format.
|
||||
|
||||
#### background
|
||||
|
||||
<p>
|
||||
|
||||
**Type:** `ColorDefinition`<br>
|
||||
**Default:** `undefined`
|
||||
</p>
|
||||
|
||||
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
|
||||
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
|
||||
as default replacement for transparent pixels.
|
||||
|
||||
The parameter accepts a `string` as value.
|
||||
|
||||
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
|
||||
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
|
||||
`rgb(100,100,100)`.
|
||||
|
||||
### `getImage`
|
||||
|
||||
This is the helper function used by the `<Image />` component to build `<img />` attributes for the transformed image. This helper can be used directly for more complex use cases that aren't currently supported by the `<Image />` component.
|
||||
|
|
|
@ -17,7 +17,7 @@ interface RemoteImageProps extends TransformOptions, astroHTML.JSX.ImgHTMLAttrib
|
|||
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;
|
||||
format?: OutputFormat;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ interface RemoteImageProps
|
|||
widths: number[];
|
||||
aspectRatio: TransformOptions['aspectRatio'];
|
||||
formats?: OutputFormat[];
|
||||
background: TransformOptions['background'];
|
||||
}
|
||||
|
||||
export type Props = LocalImageProps | RemoteImageProps;
|
||||
|
@ -37,6 +38,7 @@ const {
|
|||
sizes,
|
||||
widths,
|
||||
aspectRatio,
|
||||
background,
|
||||
formats = ['avif', 'webp'],
|
||||
loading = 'lazy',
|
||||
decoding = 'async',
|
||||
|
@ -47,7 +49,7 @@ if (alt === undefined || alt === null) {
|
|||
warnForMissingAlt();
|
||||
}
|
||||
|
||||
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio });
|
||||
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio, background });
|
||||
---
|
||||
|
||||
<picture {...attrs}>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "@astrojs/image",
|
||||
"description": "Load and transform images in your Astro site.",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"type": "module",
|
||||
"types": "./dist/index.d.ts",
|
||||
"author": "withastro",
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
/// <reference types="astro/astro-jsx" />
|
||||
import type { ImageService, OutputFormat, TransformOptions } from '../loaders/index.js';
|
||||
import type {
|
||||
ColorDefinition,
|
||||
ImageService,
|
||||
OutputFormat,
|
||||
TransformOptions,
|
||||
} from '../loaders/index.js';
|
||||
import { isSSRService, parseAspectRatio } from '../loaders/index.js';
|
||||
import sharp from '../loaders/squoosh.js';
|
||||
import { isRemoteImage } from '../utils/paths.js';
|
||||
|
@ -63,7 +68,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
|
|||
// resolve the metadata promise, usually when the ESM import is inlined
|
||||
const metadata = 'then' in input.src ? (await input.src).default : input.src;
|
||||
|
||||
let { width, height, aspectRatio, format = metadata.format, ...rest } = input;
|
||||
let { width, height, aspectRatio, background, format = metadata.format, ...rest } = input;
|
||||
|
||||
if (!width && !height) {
|
||||
// neither dimension was provided, use the file metadata
|
||||
|
@ -86,6 +91,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
|
|||
height,
|
||||
aspectRatio,
|
||||
format: format as OutputFormat,
|
||||
background: background as ColorDefinition | undefined,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface GetPictureParams {
|
|||
widths: number[];
|
||||
formats: OutputFormat[];
|
||||
aspectRatio?: TransformOptions['aspectRatio'];
|
||||
background?: TransformOptions['background'];
|
||||
}
|
||||
|
||||
export interface GetPictureResult {
|
||||
|
@ -36,7 +37,7 @@ async function resolveFormats({ src, formats }: GetPictureParams) {
|
|||
unique.add(extname(metadata.src).replace('.', '') as OutputFormat);
|
||||
}
|
||||
|
||||
return [...unique];
|
||||
return Array.from(unique).filter(Boolean);
|
||||
}
|
||||
|
||||
export async function getPicture(params: GetPictureParams): Promise<GetPictureResult> {
|
||||
|
@ -64,6 +65,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
|
|||
format,
|
||||
width,
|
||||
height: Math.round(width / aspectRatio!),
|
||||
background: params.background,
|
||||
});
|
||||
return `${img.src} ${width}w`;
|
||||
})
|
||||
|
@ -83,6 +85,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
|
|||
width: Math.max(...widths),
|
||||
aspectRatio,
|
||||
format: allFormats[allFormats.length - 1],
|
||||
background: params.background,
|
||||
});
|
||||
|
||||
const sources = await Promise.all(allFormats.map((format) => getSource(format)));
|
||||
|
|
290
packages/integrations/image/src/loaders/colornames.ts
Normal file
290
packages/integrations/image/src/loaders/colornames.ts
Normal file
|
@ -0,0 +1,290 @@
|
|||
export type NamedColor =
|
||||
| 'aliceblue'
|
||||
| 'antiquewhite'
|
||||
| 'aqua'
|
||||
| 'aquamarine'
|
||||
| 'azure'
|
||||
| 'beige'
|
||||
| 'bisque'
|
||||
| 'black'
|
||||
| 'blanchedalmond'
|
||||
| 'blue'
|
||||
| 'blueviolet'
|
||||
| 'brown'
|
||||
| 'burlywood'
|
||||
| 'cadetblue'
|
||||
| 'chartreuse'
|
||||
| 'chocolate'
|
||||
| 'coral'
|
||||
| 'cornflowerblue'
|
||||
| 'cornsilk'
|
||||
| 'crimson'
|
||||
| 'cyan'
|
||||
| 'darkblue'
|
||||
| 'darkcyan'
|
||||
| 'darkgoldenrod'
|
||||
| 'darkgray'
|
||||
| 'darkgreen'
|
||||
| 'darkkhaki'
|
||||
| 'darkmagenta'
|
||||
| 'darkolivegreen'
|
||||
| 'darkorange'
|
||||
| 'darkorchid'
|
||||
| 'darkred'
|
||||
| 'darksalmon'
|
||||
| 'darkseagreen'
|
||||
| 'darkslateblue'
|
||||
| 'darkslategray'
|
||||
| 'darkturquoise'
|
||||
| 'darkviolet'
|
||||
| 'deeppink'
|
||||
| 'deepskyblue'
|
||||
| 'dimgray'
|
||||
| 'dodgerblue'
|
||||
| 'firebrick'
|
||||
| 'floralwhite'
|
||||
| 'forestgreen'
|
||||
| 'fuchsia'
|
||||
| 'gainsboro'
|
||||
| 'ghostwhite'
|
||||
| 'gold'
|
||||
| 'goldenrod'
|
||||
| 'gray'
|
||||
| 'green'
|
||||
| 'greenyellow'
|
||||
| 'honeydew'
|
||||
| 'hotpink'
|
||||
| 'indianred'
|
||||
| 'indigo'
|
||||
| 'ivory'
|
||||
| 'khaki'
|
||||
| 'lavender'
|
||||
| 'lavenderblush'
|
||||
| 'lawngreen'
|
||||
| 'lemonchiffon'
|
||||
| 'lightblue'
|
||||
| 'lightcoral'
|
||||
| 'lightcyan'
|
||||
| 'lightgoldenrodyellow'
|
||||
| 'lightgray'
|
||||
| 'lightgreen'
|
||||
| 'lightpink'
|
||||
| 'lightsalmon'
|
||||
| 'lightsalmon'
|
||||
| 'lightseagreen'
|
||||
| 'lightskyblue'
|
||||
| 'lightslategray'
|
||||
| 'lightsteelblue'
|
||||
| 'lightyellow'
|
||||
| 'lime'
|
||||
| 'limegreen'
|
||||
| 'linen'
|
||||
| 'magenta'
|
||||
| 'maroon'
|
||||
| 'mediumaquamarine'
|
||||
| 'mediumblue'
|
||||
| 'mediumorchid'
|
||||
| 'mediumpurple'
|
||||
| 'mediumseagreen'
|
||||
| 'mediumslateblue'
|
||||
| 'mediumslateblue'
|
||||
| 'mediumspringgreen'
|
||||
| 'mediumturquoise'
|
||||
| 'mediumvioletred'
|
||||
| 'midnightblue'
|
||||
| 'mintcream'
|
||||
| 'mistyrose'
|
||||
| 'moccasin'
|
||||
| 'navajowhite'
|
||||
| 'navy'
|
||||
| 'oldlace'
|
||||
| 'olive'
|
||||
| 'olivedrab'
|
||||
| 'orange'
|
||||
| 'orangered'
|
||||
| 'orchid'
|
||||
| 'palegoldenrod'
|
||||
| 'palegreen'
|
||||
| 'paleturquoise'
|
||||
| 'palevioletred'
|
||||
| 'papayawhip'
|
||||
| 'peachpuff'
|
||||
| 'peru'
|
||||
| 'pink'
|
||||
| 'plum'
|
||||
| 'powderblue'
|
||||
| 'purple'
|
||||
| 'rebeccapurple'
|
||||
| 'red'
|
||||
| 'rosybrown'
|
||||
| 'royalblue'
|
||||
| 'saddlebrown'
|
||||
| 'salmon'
|
||||
| 'sandybrown'
|
||||
| 'seagreen'
|
||||
| 'seashell'
|
||||
| 'sienna'
|
||||
| 'silver'
|
||||
| 'skyblue'
|
||||
| 'slateblue'
|
||||
| 'slategray'
|
||||
| 'snow'
|
||||
| 'springgreen'
|
||||
| 'steelblue'
|
||||
| 'tan'
|
||||
| 'teal'
|
||||
| 'thistle'
|
||||
| 'tomato'
|
||||
| 'turquoise'
|
||||
| 'violet'
|
||||
| 'wheat'
|
||||
| 'white'
|
||||
| 'whitesmoke'
|
||||
| 'yellow'
|
||||
| 'yellowgreen';
|
||||
|
||||
export const htmlColorNames: NamedColor[] = [
|
||||
'aliceblue',
|
||||
'antiquewhite',
|
||||
'aqua',
|
||||
'aquamarine',
|
||||
'azure',
|
||||
'beige',
|
||||
'bisque',
|
||||
'black',
|
||||
'blanchedalmond',
|
||||
'blue',
|
||||
'blueviolet',
|
||||
'brown',
|
||||
'burlywood',
|
||||
'cadetblue',
|
||||
'chartreuse',
|
||||
'chocolate',
|
||||
'coral',
|
||||
'cornflowerblue',
|
||||
'cornsilk',
|
||||
'crimson',
|
||||
'cyan',
|
||||
'darkblue',
|
||||
'darkcyan',
|
||||
'darkgoldenrod',
|
||||
'darkgray',
|
||||
'darkgreen',
|
||||
'darkkhaki',
|
||||
'darkmagenta',
|
||||
'darkolivegreen',
|
||||
'darkorange',
|
||||
'darkorchid',
|
||||
'darkred',
|
||||
'darksalmon',
|
||||
'darkseagreen',
|
||||
'darkslateblue',
|
||||
'darkslategray',
|
||||
'darkturquoise',
|
||||
'darkviolet',
|
||||
'deeppink',
|
||||
'deepskyblue',
|
||||
'dimgray',
|
||||
'dodgerblue',
|
||||
'firebrick',
|
||||
'floralwhite',
|
||||
'forestgreen',
|
||||
'fuchsia',
|
||||
'gainsboro',
|
||||
'ghostwhite',
|
||||
'gold',
|
||||
'goldenrod',
|
||||
'gray',
|
||||
'green',
|
||||
'greenyellow',
|
||||
'honeydew',
|
||||
'hotpink',
|
||||
'indianred',
|
||||
'indigo',
|
||||
'ivory',
|
||||
'khaki',
|
||||
'lavender',
|
||||
'lavenderblush',
|
||||
'lawngreen',
|
||||
'lemonchiffon',
|
||||
'lightblue',
|
||||
'lightcoral',
|
||||
'lightcyan',
|
||||
'lightgoldenrodyellow',
|
||||
'lightgray',
|
||||
'lightgreen',
|
||||
'lightpink',
|
||||
'lightsalmon',
|
||||
'lightsalmon',
|
||||
'lightseagreen',
|
||||
'lightskyblue',
|
||||
'lightslategray',
|
||||
'lightsteelblue',
|
||||
'lightyellow',
|
||||
'lime',
|
||||
'limegreen',
|
||||
'linen',
|
||||
'magenta',
|
||||
'maroon',
|
||||
'mediumaquamarine',
|
||||
'mediumblue',
|
||||
'mediumorchid',
|
||||
'mediumpurple',
|
||||
'mediumseagreen',
|
||||
'mediumslateblue',
|
||||
'mediumslateblue',
|
||||
'mediumspringgreen',
|
||||
'mediumturquoise',
|
||||
'mediumvioletred',
|
||||
'midnightblue',
|
||||
'mintcream',
|
||||
'mistyrose',
|
||||
'moccasin',
|
||||
'navajowhite',
|
||||
'navy',
|
||||
'oldlace',
|
||||
'olive',
|
||||
'olivedrab',
|
||||
'orange',
|
||||
'orangered',
|
||||
'orchid',
|
||||
'palegoldenrod',
|
||||
'palegreen',
|
||||
'paleturquoise',
|
||||
'palevioletred',
|
||||
'papayawhip',
|
||||
'peachpuff',
|
||||
'peru',
|
||||
'pink',
|
||||
'plum',
|
||||
'powderblue',
|
||||
'purple',
|
||||
'rebeccapurple',
|
||||
'red',
|
||||
'rosybrown',
|
||||
'royalblue',
|
||||
'saddlebrown',
|
||||
'salmon',
|
||||
'sandybrown',
|
||||
'seagreen',
|
||||
'seashell',
|
||||
'sienna',
|
||||
'silver',
|
||||
'skyblue',
|
||||
'slateblue',
|
||||
'slategray',
|
||||
'snow',
|
||||
'springgreen',
|
||||
'steelblue',
|
||||
'tan',
|
||||
'teal',
|
||||
'thistle',
|
||||
'tomato',
|
||||
'turquoise',
|
||||
'violet',
|
||||
'wheat',
|
||||
'white',
|
||||
'whitesmoke',
|
||||
'yellow',
|
||||
'yellowgreen',
|
||||
];
|
|
@ -1,3 +1,5 @@
|
|||
import { htmlColorNames, type NamedColor } from './colornames.js';
|
||||
|
||||
/// <reference types="astro/astro-jsx" />
|
||||
export type InputFormat =
|
||||
| 'heic'
|
||||
|
@ -10,16 +12,35 @@ export type InputFormat =
|
|||
| 'webp'
|
||||
| 'gif';
|
||||
|
||||
export type OutputFormat = 'avif' | 'jpeg' | 'jpg' | 'png' | 'webp';
|
||||
export type OutputFormatSupportsAlpha = 'avif' | 'png' | 'webp';
|
||||
export type OutputFormat = OutputFormatSupportsAlpha | 'jpeg' | 'jpg';
|
||||
|
||||
export type ColorDefinition =
|
||||
| NamedColor
|
||||
| `#${string}`
|
||||
| `rgb(${number}, ${number}, ${number})`
|
||||
| `rgb(${number},${number},${number})`;
|
||||
|
||||
export function isOutputFormat(value: string): value is OutputFormat {
|
||||
return ['avif', 'jpeg', 'jpg', 'png', 'webp'].includes(value);
|
||||
}
|
||||
|
||||
export function isOutputFormatSupportsAlpha(value: string): value is OutputFormatSupportsAlpha {
|
||||
return ['avif', 'png', 'webp'].includes(value);
|
||||
}
|
||||
|
||||
export function isAspectRatioString(value: string): value is `${number}:${number}` {
|
||||
return /^\d*:\d*$/.test(value);
|
||||
}
|
||||
|
||||
export function isColor(value: string): value is ColorDefinition {
|
||||
return (
|
||||
(htmlColorNames as string[]).includes(value.toLowerCase()) ||
|
||||
/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(value) ||
|
||||
/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.test(value)
|
||||
);
|
||||
}
|
||||
|
||||
export function parseAspectRatio(aspectRatio: TransformOptions['aspectRatio']) {
|
||||
if (!aspectRatio) {
|
||||
return undefined;
|
||||
|
@ -75,6 +96,15 @@ export interface TransformOptions {
|
|||
* @example "16:9" - strings can be used in the format of `{ratioWidth}:{ratioHeight}`.
|
||||
*/
|
||||
aspectRatio?: number | `${number}:${number}`;
|
||||
/**
|
||||
* The background color to use when converting from a transparent image format to a
|
||||
* non-transparent format. This is useful for converting PNGs to JPEGs.
|
||||
*
|
||||
* @example "white" - a named color
|
||||
* @example "#ffffff" - a hex color
|
||||
* @example "rgb(255, 255, 255)" - an rgb color
|
||||
*/
|
||||
background?: ColorDefinition;
|
||||
}
|
||||
|
||||
export interface HostedImageService<T extends TransformOptions = TransformOptions> {
|
||||
|
@ -161,6 +191,10 @@ export abstract class BaseSSRService implements SSRImageService {
|
|||
searchParams.append('ar', transform.aspectRatio.toString());
|
||||
}
|
||||
|
||||
if (transform.background) {
|
||||
searchParams.append('bg', transform.background);
|
||||
}
|
||||
|
||||
searchParams.append('href', transform.src);
|
||||
|
||||
return { searchParams };
|
||||
|
@ -202,6 +236,10 @@ export abstract class BaseSSRService implements SSRImageService {
|
|||
}
|
||||
}
|
||||
|
||||
if (searchParams.has('bg')) {
|
||||
transform.background = searchParams.get('bg') as ColorDefinition;
|
||||
}
|
||||
|
||||
return transform;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,11 @@ class SharpService extends BaseSSRService {
|
|||
sharpImage.resize(width, height);
|
||||
}
|
||||
|
||||
// remove alpha channel and replace with background color if requested
|
||||
if (transform.background) {
|
||||
sharpImage.flatten({ background: transform.background });
|
||||
}
|
||||
|
||||
if (transform.format) {
|
||||
sharpImage.toFormat(transform.format, { quality: transform.quality });
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ function extname(src: string, format?: OutputFormat) {
|
|||
const index = src.lastIndexOf('.');
|
||||
|
||||
if (index <= 0) {
|
||||
return undefined;
|
||||
return '';
|
||||
}
|
||||
|
||||
return src.substring(index);
|
||||
|
@ -38,11 +38,12 @@ export function propsToFilename(transform: TransformOptions) {
|
|||
// strip off the querystring first, then remove the file extension
|
||||
let filename = removeQueryString(transform.src);
|
||||
filename = basename(filename);
|
||||
const ext = extname(filename);
|
||||
filename = removeExtname(filename);
|
||||
|
||||
const ext = transform.format || extname(transform.src)?.substring(1);
|
||||
const outputExt = transform.format ? `.${transform.format}` : ext;
|
||||
|
||||
return `/${filename}_${shorthash(JSON.stringify(transform))}.${ext}`;
|
||||
return `/${filename}_${shorthash(JSON.stringify(transform))}${outputExt}`;
|
||||
}
|
||||
|
||||
export function appendForwardSlash(path: string) {
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import sharp from 'sharp';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('SSG image with background - dev', function () {
|
||||
let fixture;
|
||||
let devServer;
|
||||
let $;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ root: './fixtures/background-color-image/' });
|
||||
devServer = await fixture.startDevServer();
|
||||
const html = await fixture.fetch('/').then((res) => res.text());
|
||||
$ = cheerio.load(html);
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
title: 'Named color',
|
||||
id: '#named',
|
||||
bg: 'dimgray',
|
||||
},
|
||||
{
|
||||
title: 'Hex color',
|
||||
id: '#hex',
|
||||
bg: '#696969',
|
||||
},
|
||||
{
|
||||
title: 'Hex color short',
|
||||
id: '#hex-short',
|
||||
bg: '#666',
|
||||
},
|
||||
{
|
||||
title: 'RGB color',
|
||||
id: '#rgb',
|
||||
bg: 'rgb(105,105,105)',
|
||||
},
|
||||
{
|
||||
title: 'RGB color with spaces',
|
||||
id: '#rgb-spaced',
|
||||
bg: 'rgb(105, 105, 105)',
|
||||
},
|
||||
].forEach(({ title, id, bg }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
const src = image.attr('src');
|
||||
const [_, params] = src.split('?');
|
||||
const searchParams = new URLSearchParams(params);
|
||||
expect(searchParams.get('bg')).to.equal(bg);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('SSG image with background - build', function () {
|
||||
let fixture;
|
||||
let $;
|
||||
let html;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({ root: './fixtures/background-color-image/' });
|
||||
await fixture.build();
|
||||
|
||||
html = await fixture.readFile('/index.html');
|
||||
$ = cheerio.load(html);
|
||||
});
|
||||
|
||||
async function verifyImage(pathname, expectedBg) {
|
||||
const url = new URL('./fixtures/background-color-image/dist/' + pathname, import.meta.url);
|
||||
const dist = fileURLToPath(url);
|
||||
const data = await sharp(dist).raw().toBuffer();
|
||||
// check that the first RGB pixel indeed has the requested background color
|
||||
expect(data[0]).to.equal(expectedBg[0]);
|
||||
expect(data[1]).to.equal(expectedBg[1]);
|
||||
expect(data[2]).to.equal(expectedBg[2]);
|
||||
}
|
||||
|
||||
[
|
||||
{
|
||||
title: 'Named color',
|
||||
id: '#named',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
{
|
||||
title: 'Hex color',
|
||||
id: '#hex',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
{
|
||||
title: 'Hex color short',
|
||||
id: '#hex-short',
|
||||
bg: [102, 102, 102],
|
||||
},
|
||||
{
|
||||
title: 'RGB color',
|
||||
id: '#rgb',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
{
|
||||
title: 'RGB color with spaces',
|
||||
id: '#rgb-spaced',
|
||||
bg: [105, 105, 105],
|
||||
},
|
||||
].forEach(({ title, id, bg }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
const src = image.attr('src');
|
||||
await verifyImage(src, bg);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
import { expect } from 'chai';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
import testAdapter from '../../../astro/test/test-adapter.js';
|
||||
|
||||
let fixture;
|
||||
|
||||
describe('SSR image with background', function () {
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/background-color-image/',
|
||||
adapter: testAdapter({ streaming: false }),
|
||||
output: 'server',
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
[
|
||||
{
|
||||
title: 'Named color',
|
||||
id: '#named',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: 'dimgray',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Hex color',
|
||||
id: '#hex',
|
||||
query: {
|
||||
f: 'avif',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: '#696969',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Hex color short',
|
||||
id: '#hex-short',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: '#666',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'RGB color',
|
||||
id: '#rgb',
|
||||
query: {
|
||||
f: 'webp',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: 'rgb(105,105,105)',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'RGB color with spaces',
|
||||
id: '#rgb-spaced',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '256',
|
||||
h: '256',
|
||||
href: /^\/assets\/file-icon.\w{8}.png/,
|
||||
bg: 'rgb(105, 105, 105)',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
||||
const request = new Request('http://example.com/');
|
||||
const response = await app.render(request);
|
||||
const html = await response.text();
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const image = $(id);
|
||||
const src = image.attr('src');
|
||||
const [_, params] = src.split('?');
|
||||
|
||||
const searchParams = new URLSearchParams(params);
|
||||
|
||||
for (const [key, value] of Object.entries(query)) {
|
||||
if (typeof value === 'string') {
|
||||
expect(searchParams.get(key)).to.equal(value);
|
||||
} else {
|
||||
expect(searchParams.get(key)).to.match(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
8
packages/integrations/image/test/fixtures/background-color-image/astro.config.mjs
vendored
Normal file
8
packages/integrations/image/test/fixtures/background-color-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/background-color-image/package.json
vendored
Normal file
10
packages/integrations/image/test/fixtures/background-color-image/package.json
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "@test/background-color-image",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@astrojs/image": "workspace:*",
|
||||
"@astrojs/node": "workspace:*",
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
BIN
packages/integrations/image/test/fixtures/background-color-image/public/favicon.ico
vendored
Normal file
BIN
packages/integrations/image/test/fixtures/background-color-image/public/favicon.ico
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
44
packages/integrations/image/test/fixtures/background-color-image/server/server.mjs
vendored
Normal file
44
packages/integrations/image/test/fixtures/background-color-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/background-color-image/src/assets/file-icon.png
vendored
Normal file
BIN
packages/integrations/image/test/fixtures/background-color-image/src/assets/file-icon.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.6 KiB |
21
packages/integrations/image/test/fixtures/background-color-image/src/pages/index.astro
vendored
Normal file
21
packages/integrations/image/test/fixtures/background-color-image/src/pages/index.astro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
import { Image } from '@astrojs/image/components';
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<!-- Head Stuff -->
|
||||
</head>
|
||||
<body>
|
||||
<Image id="named" src={import('../assets/file-icon.png')} width={256} format="jpeg" background="dimgray" alt="named" />
|
||||
<br />
|
||||
<Image id="hex" src={import('../assets/file-icon.png')} width={256} format="avif" background="#696969" alt="hex" />
|
||||
<br />
|
||||
<Image id="hex-short" src={import('../assets/file-icon.png')} width={256} background="#666" alt="hex-short" />
|
||||
<br />
|
||||
<Image id="rgb" src={import('../assets/file-icon.png')} width={256} format="webp" background="rgb(105,105,105)" alt="rgb" />
|
||||
<br />
|
||||
<Image id="rgb-spaced" src={import('../assets/file-icon.png')} width={256} format="jpeg" background="rgb(105, 105, 105)" alt="rgb-spaced" />
|
||||
<br />
|
||||
</body>
|
||||
</html>
|
|
@ -20,5 +20,9 @@ import { Image } from '@astrojs/image/components';
|
|||
<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" />
|
||||
<br />
|
||||
<Image id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="jpeg" alt="Google" background="#333333" />
|
||||
<br />
|
||||
<Image id="ipsum" src="https://picsum.photos/200/300" width={200} height={300} alt="ipsum" format="jpeg" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -12,8 +12,12 @@ import { Picture } from '@astrojs/image/components';
|
|||
<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" />
|
||||
<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" formats={["avif", "webp", "png"]} />
|
||||
<br />
|
||||
<Picture id='inline' src={import('../assets/social.jpg')} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Inline social image" />
|
||||
<br />
|
||||
<Picture id="bg-color" 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" background="rgb(51, 51, 51)" formats={['avif', 'jpeg']} />
|
||||
<br />
|
||||
<Picture id="ipsum" src="https://picsum.photos/200/300" sizes="100vw" widths={[100, 200]} aspectRatio={2/3} formats={["avif", "webp", "jpg"]} alt="ipsum" />
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -15,3 +15,5 @@ import { Image } from '@astrojs/image/components';
|
|||
<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" />
|
||||
<br />
|
||||
<Image id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="jpeg" alt="Google" background="#333333" />
|
||||
|
|
|
@ -50,12 +50,34 @@ describe('SSG images - dev', function () {
|
|||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
@ -120,12 +142,35 @@ describe('SSG images with subpath - dev', function () {
|
|||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
@ -189,12 +234,24 @@ describe('SSG images - build', function () {
|
|||
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.webp/,
|
||||
size: { width: 544, height: 184, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
regex: /^\/300_\w{4,10}/,
|
||||
size: { width: 200, height: 300, type: 'jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
regex: /^\/hero_\w{4,10}.webp/,
|
||||
size: { width: 768, height: 414, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Remote images',
|
||||
id: '#bg-color',
|
||||
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
|
||||
size: { width: 544, height: 184, type: 'jpg' },
|
||||
},
|
||||
].forEach(({ title, id, regex, size }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
@ -253,12 +310,24 @@ describe('SSG images with subpath - build', function () {
|
|||
regex: /^\/docs\/googlelogo_color_272x92dp_\w{4,10}.webp/,
|
||||
size: { width: 544, height: 184, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
regex: /^\/docs\/300_\w{4,10}/,
|
||||
size: { width: 200, height: 300, type: 'jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
regex: /^\/docs\/hero_\w{4,10}.webp/,
|
||||
size: { width: 768, height: 414, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Remote images',
|
||||
id: '#bg-color',
|
||||
regex: /^\/docs\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
|
||||
size: { width: 544, height: 184, type: 'jpg' },
|
||||
},
|
||||
].forEach(({ title, id, regex, size }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
|
|
@ -45,6 +45,16 @@ describe('SSR images - build', async function () {
|
|||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Remote images with search',
|
||||
id: '#query',
|
||||
|
@ -62,6 +72,18 @@ describe('SSR images - build', async function () {
|
|||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
@ -139,6 +161,16 @@ describe('SSR images with subpath - build', function () {
|
|||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Remote images with search',
|
||||
id: '#query',
|
||||
|
@ -156,6 +188,18 @@ describe('SSR images with subpath - build', function () {
|
|||
url: '/_image',
|
||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
|
|
@ -58,6 +58,17 @@ describe('SSR images - dev', function () {
|
|||
},
|
||||
contentType: 'image/webp',
|
||||
},
|
||||
{
|
||||
title: 'Remote wihtout file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
@ -70,6 +81,19 @@ describe('SSR images - dev', function () {
|
|||
},
|
||||
contentType: 'image/webp',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
].forEach(({ title, id, url, query, contentType }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
|
@ -149,6 +173,17 @@ describe('SSR images with subpath - dev', function () {
|
|||
},
|
||||
contentType: 'image/webp',
|
||||
},
|
||||
{
|
||||
title: 'Remote wihtout file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
@ -161,6 +196,19 @@ describe('SSR images with subpath - dev', function () {
|
|||
},
|
||||
contentType: 'image/webp',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpeg',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: '#333333',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
},
|
||||
].forEach(({ title, id, url, query, contentType }) => {
|
||||
it(title, async () => {
|
||||
const image = $(id);
|
||||
|
|
|
@ -56,6 +56,19 @@ describe('SSG pictures - dev', function () {
|
|||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query, alt }) => {
|
||||
it(title, () => {
|
||||
const sources = $(`${id} source`);
|
||||
|
@ -200,6 +213,13 @@ describe('SSG pictures - build', function () {
|
|||
size: { width: 544, height: 184, type: 'png' },
|
||||
alt: 'Google logo',
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
regex: /^\/300_\w{4,10}/,
|
||||
size: { width: 200, height: 300, type: 'jpg' },
|
||||
alt: 'ipsum',
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
@ -288,6 +308,13 @@ describe('SSG pictures with subpath - build', function () {
|
|||
size: { width: 544, height: 184, type: 'png' },
|
||||
alt: 'Google logo',
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
regex: /^\/docs\/300_\w{4,10}/,
|
||||
size: { width: 200, height: 300, type: 'jpg' },
|
||||
alt: 'ipsum',
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
|
|
@ -42,6 +42,17 @@ describe('SSR pictures - build', function () {
|
|||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
alt: 'ipsum',
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
@ -49,6 +60,19 @@ describe('SSR pictures - build', function () {
|
|||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
@ -122,6 +146,17 @@ describe('SSR pictures with subpath - build', function () {
|
|||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
alt: 'ipsum',
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
@ -129,6 +164,19 @@ describe('SSR pictures with subpath - build', function () {
|
|||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query }) => {
|
||||
it(title, async () => {
|
||||
const app = await fixture.loadTestAdapterApp();
|
||||
|
|
|
@ -54,6 +54,19 @@ describe('SSR pictures - dev', function () {
|
|||
contentType: 'image/png',
|
||||
alt: 'Google logo',
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpg',
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
alt: 'ipsum',
|
||||
},
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
@ -67,6 +80,20 @@ describe('SSR pictures - dev', function () {
|
|||
contentType: 'image/jpeg',
|
||||
alt: 'Hero image',
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'png',
|
||||
w: '544',
|
||||
h: '184',
|
||||
bg: 'rgb(51, 51, 51)',
|
||||
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
|
||||
},
|
||||
contentType: 'image/png',
|
||||
alt: 'Google logo',
|
||||
},
|
||||
].forEach(({ title, id, url, query, alt, contentType }) => {
|
||||
it(title, async () => {
|
||||
const sources = $(`${id} source`);
|
||||
|
@ -148,6 +175,20 @@ describe('SSR pictures with subpath - dev', function () {
|
|||
contentType: 'image/png',
|
||||
alt: 'Google logo',
|
||||
},
|
||||
{
|
||||
title: 'Remote without file extension',
|
||||
id: '#ipsum',
|
||||
url: '/_image',
|
||||
query: {
|
||||
f: 'jpg',
|
||||
w: '200',
|
||||
h: '300',
|
||||
href: 'https://picsum.photos/200/300',
|
||||
},
|
||||
contentType: 'image/jpeg',
|
||||
alt: 'ipsum',
|
||||
},
|
||||
,
|
||||
{
|
||||
title: 'Public images',
|
||||
id: '#hero',
|
||||
|
|
|
@ -14,6 +14,7 @@ describe('Sharp service', () => {
|
|||
['width & height', { src, height: 400, width: 200 }],
|
||||
['aspect ratio string', { src, aspectRatio: '16:9' }],
|
||||
['aspect ratio float', { src, aspectRatio: 1.7 }],
|
||||
['background color', { src, format: 'jpeg', background: '#333333' }],
|
||||
].forEach(([description, props]) => {
|
||||
it(description, async () => {
|
||||
const { searchParams } = await sharp.serializeTransform(props);
|
||||
|
@ -31,6 +32,7 @@ describe('Sharp service', () => {
|
|||
verifyProp(props.width, 'w');
|
||||
verifyProp(props.height, 'h');
|
||||
verifyProp(props.aspectRatio, 'ar');
|
||||
verifyProp(props.background, 'bg');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -48,6 +50,11 @@ describe('Sharp service', () => {
|
|||
['width & height', `w=200&h=400&href=${href}`, { src, height: 400, width: 200 }],
|
||||
['aspect ratio string', `ar=16:9&href=${href}`, { src, aspectRatio: '16:9' }],
|
||||
['aspect ratio float', `ar=1.7&href=${href}`, { src, aspectRatio: 1.7 }],
|
||||
[
|
||||
'background color',
|
||||
`f=jpeg&bg=%23333333&href=${href}`,
|
||||
{ src, format: 'jpeg', background: '#333333' },
|
||||
],
|
||||
].forEach(([description, params, expected]) => {
|
||||
it(description, async () => {
|
||||
const searchParams = new URLSearchParams(params);
|
||||
|
|
|
@ -49,6 +49,12 @@ describe('Images in MDX - build', function () {
|
|||
regex: /^\/hero_\w{4,10}.webp/,
|
||||
size: { width: 768, height: 414, type: 'webp' },
|
||||
},
|
||||
{
|
||||
title: 'Background color',
|
||||
id: '#bg-color',
|
||||
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
|
||||
size: { width: 544, height: 184, type: 'jpg' },
|
||||
},
|
||||
].forEach(({ title, id, regex, size }) => {
|
||||
it(title, () => {
|
||||
const image = $(id);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue