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
|
||||
```
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/component)
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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
|
||||
|
|
|
@ -24,24 +24,24 @@ This integration provides `<Image />` and `<Picture>` components as well as a ba
|
|||
|
||||
|
||||
### Quick Install
|
||||
|
||||
|
||||
The `astro add` command-line tool automates the installation for you. Run one of the following commands in a new terminal window. (If you aren't sure which package manager you're using, run the first command.) Then, follow the prompts, and type "y" in the terminal (meaning "yes") for each one.
|
||||
|
||||
|
||||
```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.
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
First, install the `@astrojs/image` package using your package manager. If you're using npm or aren't sure, run this in the terminal:
|
||||
```sh
|
||||
npm install @astrojs/image
|
||||
|
@ -57,7 +57,7 @@ export default {
|
|||
// ...
|
||||
integrations: [image()],
|
||||
}
|
||||
```
|
||||
```
|
||||
Then, restart the dev server.
|
||||
|
||||
### Update `env.d.ts`
|
||||
|
@ -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.
|
||||
|
@ -307,7 +343,7 @@ The integration can be configured to run with a different image service, either
|
|||
|
||||
|
||||
### config.serviceEntryPoint
|
||||
|
||||
|
||||
The `serviceEntryPoint` should resolve to the image service installed from NPM. The default entry point is `@astrojs/image/sharp`, which resolves to the entry point exported from this integration's `package.json`.
|
||||
|
||||
```js
|
||||
|
@ -342,7 +378,7 @@ export default {
|
|||
## Examples
|
||||
|
||||
### Local images
|
||||
|
||||
|
||||
Image files in your project's `src` directory can be imported in frontmatter and passed directly to the `<Image />` component. All other properties are optional and will default to the original image file's properties if not provided.
|
||||
|
||||
```astro
|
||||
|
@ -371,7 +407,7 @@ import heroImage from '../assets/hero.png';
|
|||
|
||||
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.
|
||||
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:
|
||||
|
||||
|
@ -386,7 +422,7 @@ import socialImage from '/social.png';
|
|||
```
|
||||
|
||||
### Remote images
|
||||
|
||||
|
||||
Remote images can be transformed with the `<Image />` component. The `<Image />` component needs to know the final dimensions for the `<img />` element to avoid content layout shifts. For remote images, this means you must either provide `width` and `height`, or one of the dimensions plus the required `aspectRatio`.
|
||||
|
||||
```astro
|
||||
|
|
|
@ -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…
Reference in a new issue