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"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5"
|
"astro": "^1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"@astrojs/mdx": "^0.11.1",
|
"@astrojs/mdx": "^0.11.1",
|
||||||
"@astrojs/rss": "^1.0.0",
|
"@astrojs/rss": "^1.0.0",
|
||||||
"@astrojs/sitemap": "^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
|
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
|
## 🚀 Project Structure
|
||||||
|
|
||||||
|
@ -14,34 +15,19 @@ Inside of your Astro project, you'll see the following folders and files:
|
||||||
|
|
||||||
```
|
```
|
||||||
/
|
/
|
||||||
├── demo/
|
├── index.ts
|
||||||
│ ├── public/
|
├── src
|
||||||
│ └── src/
|
│ └── MyComponent.astro
|
||||||
│ └── pages/
|
├── tsconfig.json
|
||||||
│ └── index.astro
|
├── package.json
|
||||||
└── packages/
|
|
||||||
└── my-component/
|
|
||||||
├── index.js
|
|
||||||
└── 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
|
## 🧞 Commands
|
||||||
|
|
||||||
All commands are run from the root of the project, from a terminal:
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
| Command | Action |
|
| Command | Action |
|
||||||
| :--------------------- | :----------------------------------------------- |
|
| :--------------------- | :----------------------------------------------- |
|
||||||
| `npm install` | Installs dependencies |
|
| `npm link` | Registers this package locally. Run `npm link my-component-library` in an Astro project to install your components
|
||||||
| `npm run dev` | Starts local dev server at `localhost:3000` |
|
| `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)
|
||||||
| `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).
|
|
||||||
|
|
|
@ -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",
|
"name": "@example/component",
|
||||||
"version": "0.0.1",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"version": "0.0.1",
|
||||||
"start": "astro --root demo dev",
|
"type": "module",
|
||||||
"build": "astro --root demo build",
|
"exports": {
|
||||||
"serve": "astro --root demo preview"
|
".": "./index.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"files": [
|
||||||
"astro": "^1.1.5"
|
"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"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"preact": "^10.7.3",
|
"preact": "^10.7.3",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"@astrojs/react": "^1.1.0",
|
"@astrojs/react": "^1.1.0",
|
||||||
"@astrojs/preact": "^1.0.2",
|
"@astrojs/preact": "^1.1.0",
|
||||||
"@algolia/client-search": "^4.13.1",
|
"@algolia/client-search": "^4.13.1",
|
||||||
"@docsearch/css": "^3.1.0",
|
"@docsearch/css": "^3.1.0",
|
||||||
"@docsearch/react": "^3.1.0",
|
"@docsearch/react": "^3.1.0",
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"alpinejs": "^3.10.2",
|
"alpinejs": "^3.10.2",
|
||||||
"@astrojs/alpinejs": "^0.1.1",
|
"@astrojs/alpinejs": "^0.1.2",
|
||||||
"@types/alpinejs": "^3.7.0"
|
"@types/alpinejs": "^3.7.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"lit": "^2.2.5",
|
"lit": "^2.2.5",
|
||||||
"@astrojs/lit": "^1.0.0",
|
"@astrojs/lit": "^1.0.0",
|
||||||
"@webcomponents/template-shadowroot": "^0.1.0"
|
"@webcomponents/template-shadowroot": "^0.1.0"
|
||||||
|
|
|
@ -10,17 +10,17 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"preact": "^10.7.3",
|
"preact": "^10.7.3",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"solid-js": "^1.4.3",
|
"solid-js": "^1.4.3",
|
||||||
"svelte": "^3.48.0",
|
"svelte": "^3.48.0",
|
||||||
"vue": "^3.2.37",
|
"vue": "^3.2.37",
|
||||||
"@astrojs/preact": "^1.0.2",
|
"@astrojs/preact": "^1.1.0",
|
||||||
"@astrojs/react": "^1.1.0",
|
"@astrojs/react": "^1.1.0",
|
||||||
"@astrojs/solid-js": "^1.1.0",
|
"@astrojs/solid-js": "^1.1.0",
|
||||||
"@astrojs/svelte": "^1.0.0",
|
"@astrojs/svelte": "^1.0.0",
|
||||||
"@astrojs/vue": "^1.0.0"
|
"@astrojs/vue": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"preact": "^10.7.3",
|
"preact": "^10.7.3",
|
||||||
"@astrojs/preact": "^1.0.2"
|
"@astrojs/preact": "^1.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"react": "^18.1.0",
|
"react": "^18.1.0",
|
||||||
"react-dom": "^18.1.0",
|
"react-dom": "^18.1.0",
|
||||||
"@astrojs/react": "^1.1.0",
|
"@astrojs/react": "^1.1.0",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"solid-js": "^1.4.3",
|
"solid-js": "^1.4.3",
|
||||||
"@astrojs/solid-js": "^1.1.0"
|
"@astrojs/solid-js": "^1.1.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"svelte": "^3.48.0",
|
"svelte": "^3.48.0",
|
||||||
"@astrojs/svelte": "^1.0.0",
|
"@astrojs/svelte": "^1.0.0",
|
||||||
"astro": "^1.1.5"
|
"astro": "^1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"vue": "^3.2.37",
|
"vue": "^3.2.37",
|
||||||
"@astrojs/vue": "^1.0.0"
|
"@astrojs/vue": "^1.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5"
|
"astro": "^1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5"
|
"astro": "^1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5"
|
"astro": "^1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"svelte": "^3.48.0",
|
"svelte": "^3.48.0",
|
||||||
"@astrojs/svelte": "^1.0.0",
|
"@astrojs/svelte": "^1.0.0",
|
||||||
"@astrojs/node": "^1.0.1",
|
"@astrojs/node": "^1.0.1",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"@astrojs/markdown-remark": "^1.1.0",
|
"@astrojs/markdown-remark": "^1.1.0",
|
||||||
"hast-util-select": "5.0.1",
|
"hast-util-select": "5.0.1",
|
||||||
"rehype-autolink-headings": "^6.1.1",
|
"rehype-autolink-headings": "^6.1.1",
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5"
|
"astro": "^1.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"preact": "^10.6.5",
|
"preact": "^10.6.5",
|
||||||
"@astrojs/preact": "^1.0.2",
|
"@astrojs/preact": "^1.1.0",
|
||||||
"@astrojs/mdx": "^0.11.1"
|
"@astrojs/mdx": "^0.11.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,9 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"preact": "^10.7.3",
|
"preact": "^10.7.3",
|
||||||
"@astrojs/preact": "^1.0.2",
|
"@astrojs/preact": "^1.1.0",
|
||||||
"nanostores": "^0.5.12",
|
"nanostores": "^0.5.12",
|
||||||
"@nanostores/preact": "^0.1.3"
|
"@nanostores/preact": "^0.1.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"@astrojs/tailwind": "^1.0.0",
|
"@astrojs/tailwind": "^1.0.0",
|
||||||
"autoprefixer": "^10.4.7",
|
"autoprefixer": "^10.4.7",
|
||||||
"canvas-confetti": "^1.5.1",
|
"canvas-confetti": "^1.5.1",
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"astro": "astro"
|
"astro": "astro"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"vite-plugin-pwa": "0.11.11",
|
"vite-plugin-pwa": "0.11.11",
|
||||||
"workbox-window": "^6.5.3"
|
"workbox-window": "^6.5.3"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"test": "vitest"
|
"test": "vitest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"astro": "^1.1.5",
|
"astro": "^1.1.7",
|
||||||
"vitest": "^0.20.3"
|
"vitest": "^0.20.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,27 @@
|
||||||
# astro
|
# 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
|
## 1.1.5
|
||||||
|
|
||||||
### Patch Changes
|
### 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 { defineConfig } from 'astro/config';
|
||||||
|
import preact from '@astrojs/preact';
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
vite: {
|
integrations: [preact()],
|
||||||
ssr: {
|
|
||||||
noExternal: ['@example/my-component'],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
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 { defineConfig } from 'astro/config';
|
||||||
import preact from '@astrojs/preact';
|
import preact from '@astrojs/preact';
|
||||||
|
import mdx from "@astrojs/mdx";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [preact()],
|
integrations: [preact(), mdx()]
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@astrojs/mdx": "workspace:*",
|
||||||
"@astrojs/preact": "workspace:*",
|
"@astrojs/preact": "workspace:*",
|
||||||
"astro": "workspace:*"
|
"astro": "workspace:*"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
---
|
---
|
||||||
import * as ns from '../components/PreactCounter.tsx';
|
import * as ns from '../components/PreactCounter.tsx';
|
||||||
|
import { components } from '../components/PreactCounter.tsx';
|
||||||
---
|
---
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -10,9 +11,13 @@ import * as ns from '../components/PreactCounter.tsx';
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<ns.components.PreactCounter id="preact-counter" client:load>
|
<ns.components.PreactCounter id="preact-counter-namespace" client:load>
|
||||||
<h1>preact</h1>
|
<h1>preact (namespace import)</h1>
|
||||||
</ns.components.PreactCounter>
|
</ns.components.PreactCounter>
|
||||||
|
|
||||||
|
<components.PreactCounter id="preact-counter-named" client:load>
|
||||||
|
<h1>preact (named import)</h1>
|
||||||
|
</components.PreactCounter>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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 }) => {
|
test('Preact Component', async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
|
|
||||||
const counter = await page.locator('#preact-counter');
|
// Counter declared with: <ns.components.PreactCounter id="preact-counter-namespace" client:load>
|
||||||
await expect(counter, 'component is visible').toBeVisible();
|
const namespacedCounter = await page.locator('#preact-counter-namespace');
|
||||||
|
await expect(namespacedCounter, 'component is visible').toBeVisible();
|
||||||
|
|
||||||
const count = await counter.locator('pre');
|
const namespacedCount = await namespacedCounter.locator('pre');
|
||||||
await expect(count, 'initial count is 0').toHaveText('0');
|
await expect(namespacedCount, 'initial count is 0').toHaveText('0');
|
||||||
|
|
||||||
const children = await counter.locator('.children');
|
const namespacedChildren = await namespacedCounter.locator('.children');
|
||||||
await expect(children, 'children exist').toHaveText('preact');
|
await expect(namespacedChildren, 'children exist').toHaveText('preact (namespace import)');
|
||||||
|
|
||||||
const increment = await counter.locator('.increment');
|
const namespacedIncrement = await namespacedCounter.locator('.increment');
|
||||||
await increment.click();
|
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",
|
"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.",
|
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"author": "withastro",
|
"author": "withastro",
|
||||||
|
@ -94,7 +94,7 @@
|
||||||
"test:e2e:match": "playwright test -g"
|
"test:e2e:match": "playwright test -g"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/compiler": "^0.23.4",
|
"@astrojs/compiler": "^0.23.5",
|
||||||
"@astrojs/language-server": "^0.23.0",
|
"@astrojs/language-server": "^0.23.0",
|
||||||
"@astrojs/markdown-remark": "^1.1.1",
|
"@astrojs/markdown-remark": "^1.1.1",
|
||||||
"@astrojs/telemetry": "^1.0.0",
|
"@astrojs/telemetry": "^1.0.0",
|
||||||
|
|
|
@ -243,7 +243,7 @@ class DependencyWalker {
|
||||||
|
|
||||||
dir = parentDir;
|
dir = parentDir;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch {
|
||||||
// Give up! Who knows where the `package.json` is…
|
// Give up! Who knows where the `package.json` is…
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,7 +207,8 @@ export default function astroJSX(): PluginObj {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (namespace.at(0) === local) {
|
if (namespace.at(0) === local) {
|
||||||
path.setData('import', { name: imported, path: source });
|
const name = imported === '*' ? imported : tagName;
|
||||||
|
path.setData('import', { name, path: source });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -146,7 +146,7 @@ export async function generateHydrateScript(
|
||||||
if (renderer.clientEntrypoint) {
|
if (renderer.clientEntrypoint) {
|
||||||
island.props['component-export'] = componentExport.value;
|
island.props['component-export'] = componentExport.value;
|
||||||
island.props['renderer-url'] = await result.resolve(decodeURI(renderer.clientEntrypoint));
|
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'] = '';
|
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
|
// Include componentExport name, componentUrl, and props in hash to dedupe identical islands
|
||||||
const astroId = shorthash(
|
const astroId = shorthash(
|
||||||
`<!--${metadata.componentExport!.value}:${metadata.componentUrl}-->\n${html}\n${serializeProps(
|
`<!--${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('<!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++;
|
i++;
|
||||||
}
|
}
|
||||||
controller.close();
|
controller.close();
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { AstroComponentMetadata } from '../../@types/astro';
|
||||||
|
|
||||||
type ValueOf<T> = T[keyof T];
|
type ValueOf<T> = T[keyof T];
|
||||||
|
|
||||||
const PROP_TYPE = {
|
const PROP_TYPE = {
|
||||||
|
@ -11,19 +13,31 @@ const PROP_TYPE = {
|
||||||
URL: 7,
|
URL: 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
function serializeArray(value: any[]): any[] {
|
function serializeArray(value: any[], metadata: AstroComponentMetadata): any[] {
|
||||||
return value.map((v) => convertToSerializedForm(v));
|
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(
|
return Object.fromEntries(
|
||||||
Object.entries(value).map(([k, v]) => {
|
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);
|
const tag = Object.prototype.toString.call(value);
|
||||||
switch (tag) {
|
switch (tag) {
|
||||||
case '[object Date]': {
|
case '[object Date]': {
|
||||||
|
@ -33,10 +47,16 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
|
||||||
return [PROP_TYPE.RegExp, (value as RegExp).source];
|
return [PROP_TYPE.RegExp, (value as RegExp).source];
|
||||||
}
|
}
|
||||||
case '[object Map]': {
|
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]': {
|
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]': {
|
case '[object BigInt]': {
|
||||||
return [PROP_TYPE.BigInt, (value as bigint).toString()];
|
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()];
|
return [PROP_TYPE.URL, (value as URL).toString()];
|
||||||
}
|
}
|
||||||
case '[object Array]': {
|
case '[object Array]': {
|
||||||
return [PROP_TYPE.JSON, JSON.stringify(serializeArray(value))];
|
return [PROP_TYPE.JSON, JSON.stringify(serializeArray(value, metadata))];
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
if (value !== null && typeof value === 'object') {
|
if (value !== null && typeof value === 'object') {
|
||||||
return [PROP_TYPE.Value, serializeObject(value)];
|
return [PROP_TYPE.Value, serializeObject(value, metadata)];
|
||||||
} else {
|
} else {
|
||||||
return [PROP_TYPE.Value, value];
|
return [PROP_TYPE.Value, value];
|
||||||
}
|
}
|
||||||
|
@ -57,6 +77,9 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function serializeProps(props: any) {
|
let cyclicRefs = new WeakSet<any>();
|
||||||
return JSON.stringify(serializeObject(props));
|
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));
|
res.on('close', () => setTimeout(() => viteServer.ws.send(getViteErrorPayload(err)), 200));
|
||||||
if (res.headersSent) {
|
if (res.headersSent) {
|
||||||
|
res.write(`<script type="module" src="/@vite/client"></script>`);
|
||||||
res.end();
|
res.end();
|
||||||
} else {
|
} else {
|
||||||
writeHtmlResponse(
|
writeHtmlResponse(
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
# @astrojs/alpinejs
|
# @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
|
## 0.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|
|
@ -18,14 +18,16 @@ The `astro add` command-line tool automates the installation for you. Run one of
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Using NPM
|
# Using NPM
|
||||||
npm run astro add alpinejs
|
npx astro add alpinejs
|
||||||
# Using Yarn
|
# Using Yarn
|
||||||
yarn astro add alpinejs
|
yarn astro add alpinejs
|
||||||
# Using PNPM
|
# Using PNPM
|
||||||
pnpm astro add alpinejs
|
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
|
### Manual Install
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@astrojs/alpinejs",
|
"name": "@astrojs/alpinejs",
|
||||||
"description": "The official Alpine.js integration for Astro.",
|
"description": "The official Alpine.js integration for Astro.",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"author": "withastro",
|
"author": "withastro",
|
||||||
|
|
|
@ -1,5 +1,21 @@
|
||||||
# @astrojs/image
|
# @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
|
## 0.5.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|
|
@ -24,24 +24,24 @@ This integration provides `<Image />` and `<Picture>` components as well as a ba
|
||||||
|
|
||||||
|
|
||||||
### Quick Install
|
### 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.
|
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
|
```sh
|
||||||
# Using NPM
|
# Using NPM
|
||||||
npm run astro add image
|
npx astro add image
|
||||||
# Using Yarn
|
# Using Yarn
|
||||||
yarn astro add image
|
yarn astro add image
|
||||||
# Using PNPM
|
# Using PNPM
|
||||||
pnpm astro add image
|
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
|
### 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:
|
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
|
```sh
|
||||||
npm install @astrojs/image
|
npm install @astrojs/image
|
||||||
|
@ -57,7 +57,7 @@ export default {
|
||||||
// ...
|
// ...
|
||||||
integrations: [image()],
|
integrations: [image()],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Then, restart the dev server.
|
Then, restart the dev server.
|
||||||
|
|
||||||
### Update `env.d.ts`
|
### 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}`.
|
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 /`>
|
### `<Picture /`>
|
||||||
|
|
||||||
#### src
|
#### 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.
|
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`
|
### `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.
|
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
|
### 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`.
|
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
|
```js
|
||||||
|
@ -342,7 +378,7 @@ export default {
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Local images
|
### 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.
|
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
|
```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.
|
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:
|
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
|
||||||
|
|
||||||
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`.
|
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
|
```astro
|
||||||
|
|
|
@ -17,7 +17,7 @@ interface RemoteImageProps extends TransformOptions, astroHTML.JSX.ImgHTMLAttrib
|
||||||
src: string;
|
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). */
|
/** 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;
|
alt: string;
|
||||||
format: OutputFormat;
|
format?: OutputFormat;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ interface RemoteImageProps
|
||||||
widths: number[];
|
widths: number[];
|
||||||
aspectRatio: TransformOptions['aspectRatio'];
|
aspectRatio: TransformOptions['aspectRatio'];
|
||||||
formats?: OutputFormat[];
|
formats?: OutputFormat[];
|
||||||
|
background: TransformOptions['background'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Props = LocalImageProps | RemoteImageProps;
|
export type Props = LocalImageProps | RemoteImageProps;
|
||||||
|
@ -37,6 +38,7 @@ const {
|
||||||
sizes,
|
sizes,
|
||||||
widths,
|
widths,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
|
background,
|
||||||
formats = ['avif', 'webp'],
|
formats = ['avif', 'webp'],
|
||||||
loading = 'lazy',
|
loading = 'lazy',
|
||||||
decoding = 'async',
|
decoding = 'async',
|
||||||
|
@ -47,7 +49,7 @@ if (alt === undefined || alt === null) {
|
||||||
warnForMissingAlt();
|
warnForMissingAlt();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio });
|
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio, background });
|
||||||
---
|
---
|
||||||
|
|
||||||
<picture {...attrs}>
|
<picture {...attrs}>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@astrojs/image",
|
"name": "@astrojs/image",
|
||||||
"description": "Load and transform images in your Astro site.",
|
"description": "Load and transform images in your Astro site.",
|
||||||
"version": "0.5.0",
|
"version": "0.6.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"author": "withastro",
|
"author": "withastro",
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
/// <reference types="astro/astro-jsx" />
|
/// <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 { isSSRService, parseAspectRatio } from '../loaders/index.js';
|
||||||
import sharp from '../loaders/squoosh.js';
|
import sharp from '../loaders/squoosh.js';
|
||||||
import { isRemoteImage } from '../utils/paths.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
|
// resolve the metadata promise, usually when the ESM import is inlined
|
||||||
const metadata = 'then' in input.src ? (await input.src).default : input.src;
|
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) {
|
if (!width && !height) {
|
||||||
// neither dimension was provided, use the file metadata
|
// neither dimension was provided, use the file metadata
|
||||||
|
@ -86,6 +91,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
|
||||||
height,
|
height,
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
format: format as OutputFormat,
|
format: format as OutputFormat,
|
||||||
|
background: background as ColorDefinition | undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface GetPictureParams {
|
||||||
widths: number[];
|
widths: number[];
|
||||||
formats: OutputFormat[];
|
formats: OutputFormat[];
|
||||||
aspectRatio?: TransformOptions['aspectRatio'];
|
aspectRatio?: TransformOptions['aspectRatio'];
|
||||||
|
background?: TransformOptions['background'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GetPictureResult {
|
export interface GetPictureResult {
|
||||||
|
@ -36,7 +37,7 @@ async function resolveFormats({ src, formats }: GetPictureParams) {
|
||||||
unique.add(extname(metadata.src).replace('.', '') as OutputFormat);
|
unique.add(extname(metadata.src).replace('.', '') as OutputFormat);
|
||||||
}
|
}
|
||||||
|
|
||||||
return [...unique];
|
return Array.from(unique).filter(Boolean);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getPicture(params: GetPictureParams): Promise<GetPictureResult> {
|
export async function getPicture(params: GetPictureParams): Promise<GetPictureResult> {
|
||||||
|
@ -64,6 +65,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
|
||||||
format,
|
format,
|
||||||
width,
|
width,
|
||||||
height: Math.round(width / aspectRatio!),
|
height: Math.round(width / aspectRatio!),
|
||||||
|
background: params.background,
|
||||||
});
|
});
|
||||||
return `${img.src} ${width}w`;
|
return `${img.src} ${width}w`;
|
||||||
})
|
})
|
||||||
|
@ -83,6 +85,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
|
||||||
width: Math.max(...widths),
|
width: Math.max(...widths),
|
||||||
aspectRatio,
|
aspectRatio,
|
||||||
format: allFormats[allFormats.length - 1],
|
format: allFormats[allFormats.length - 1],
|
||||||
|
background: params.background,
|
||||||
});
|
});
|
||||||
|
|
||||||
const sources = await Promise.all(allFormats.map((format) => getSource(format)));
|
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" />
|
/// <reference types="astro/astro-jsx" />
|
||||||
export type InputFormat =
|
export type InputFormat =
|
||||||
| 'heic'
|
| 'heic'
|
||||||
|
@ -10,16 +12,35 @@ export type InputFormat =
|
||||||
| 'webp'
|
| 'webp'
|
||||||
| 'gif';
|
| '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 {
|
export function isOutputFormat(value: string): value is OutputFormat {
|
||||||
return ['avif', 'jpeg', 'jpg', 'png', 'webp'].includes(value);
|
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}` {
|
export function isAspectRatioString(value: string): value is `${number}:${number}` {
|
||||||
return /^\d*:\d*$/.test(value);
|
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']) {
|
export function parseAspectRatio(aspectRatio: TransformOptions['aspectRatio']) {
|
||||||
if (!aspectRatio) {
|
if (!aspectRatio) {
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -75,6 +96,15 @@ export interface TransformOptions {
|
||||||
* @example "16:9" - strings can be used in the format of `{ratioWidth}:{ratioHeight}`.
|
* @example "16:9" - strings can be used in the format of `{ratioWidth}:{ratioHeight}`.
|
||||||
*/
|
*/
|
||||||
aspectRatio?: number | `${number}:${number}`;
|
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> {
|
export interface HostedImageService<T extends TransformOptions = TransformOptions> {
|
||||||
|
@ -161,6 +191,10 @@ export abstract class BaseSSRService implements SSRImageService {
|
||||||
searchParams.append('ar', transform.aspectRatio.toString());
|
searchParams.append('ar', transform.aspectRatio.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transform.background) {
|
||||||
|
searchParams.append('bg', transform.background);
|
||||||
|
}
|
||||||
|
|
||||||
searchParams.append('href', transform.src);
|
searchParams.append('href', transform.src);
|
||||||
|
|
||||||
return { searchParams };
|
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;
|
return transform;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,11 @@ class SharpService extends BaseSSRService {
|
||||||
sharpImage.resize(width, height);
|
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) {
|
if (transform.format) {
|
||||||
sharpImage.toFormat(transform.format, { quality: transform.quality });
|
sharpImage.toFormat(transform.format, { quality: transform.quality });
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ function extname(src: string, format?: OutputFormat) {
|
||||||
const index = src.lastIndexOf('.');
|
const index = src.lastIndexOf('.');
|
||||||
|
|
||||||
if (index <= 0) {
|
if (index <= 0) {
|
||||||
return undefined;
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
return src.substring(index);
|
return src.substring(index);
|
||||||
|
@ -38,11 +38,12 @@ export function propsToFilename(transform: TransformOptions) {
|
||||||
// strip off the querystring first, then remove the file extension
|
// strip off the querystring first, then remove the file extension
|
||||||
let filename = removeQueryString(transform.src);
|
let filename = removeQueryString(transform.src);
|
||||||
filename = basename(filename);
|
filename = basename(filename);
|
||||||
|
const ext = extname(filename);
|
||||||
filename = removeExtname(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) {
|
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" />
|
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
|
||||||
<br />
|
<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" />
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -12,8 +12,12 @@ import { Picture } from '@astrojs/image/components';
|
||||||
<br />
|
<br />
|
||||||
<Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Social image" />
|
<Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Social image" />
|
||||||
<br />
|
<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 />
|
<br />
|
||||||
<Picture id='inline' src={import('../assets/social.jpg')} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Inline social image" />
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -15,3 +15,5 @@ import { Image } from '@astrojs/image/components';
|
||||||
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
|
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
|
||||||
<br />
|
<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" />
|
<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',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
url: '/_image',
|
url: '/_image',
|
||||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
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 }) => {
|
].forEach(({ title, id, url, query }) => {
|
||||||
it(title, () => {
|
it(title, () => {
|
||||||
const image = $(id);
|
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',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
url: '/_image',
|
url: '/_image',
|
||||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
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 }) => {
|
].forEach(({ title, id, url, query }) => {
|
||||||
it(title, () => {
|
it(title, () => {
|
||||||
const image = $(id);
|
const image = $(id);
|
||||||
|
@ -189,12 +234,24 @@ describe('SSG images - build', function () {
|
||||||
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.webp/,
|
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.webp/,
|
||||||
size: { width: 544, height: 184, type: '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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
regex: /^\/hero_\w{4,10}.webp/,
|
regex: /^\/hero_\w{4,10}.webp/,
|
||||||
size: { width: 768, height: 414, type: '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 }) => {
|
].forEach(({ title, id, regex, size }) => {
|
||||||
it(title, () => {
|
it(title, () => {
|
||||||
const image = $(id);
|
const image = $(id);
|
||||||
|
@ -253,12 +310,24 @@ describe('SSG images with subpath - build', function () {
|
||||||
regex: /^\/docs\/googlelogo_color_272x92dp_\w{4,10}.webp/,
|
regex: /^\/docs\/googlelogo_color_272x92dp_\w{4,10}.webp/,
|
||||||
size: { width: 544, height: 184, type: '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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
regex: /^\/docs\/hero_\w{4,10}.webp/,
|
regex: /^\/docs\/hero_\w{4,10}.webp/,
|
||||||
size: { width: 768, height: 414, type: '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 }) => {
|
].forEach(({ title, id, regex, size }) => {
|
||||||
it(title, () => {
|
it(title, () => {
|
||||||
const image = $(id);
|
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',
|
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',
|
title: 'Remote images with search',
|
||||||
id: '#query',
|
id: '#query',
|
||||||
|
@ -62,6 +72,18 @@ describe('SSR images - build', async function () {
|
||||||
url: '/_image',
|
url: '/_image',
|
||||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
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 }) => {
|
].forEach(({ title, id, url, query }) => {
|
||||||
it(title, async () => {
|
it(title, async () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
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',
|
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',
|
title: 'Remote images with search',
|
||||||
id: '#query',
|
id: '#query',
|
||||||
|
@ -156,6 +188,18 @@ describe('SSR images with subpath - build', function () {
|
||||||
url: '/_image',
|
url: '/_image',
|
||||||
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
|
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 }) => {
|
].forEach(({ title, id, url, query }) => {
|
||||||
it(title, async () => {
|
it(title, async () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
|
|
@ -58,6 +58,17 @@ describe('SSR images - dev', function () {
|
||||||
},
|
},
|
||||||
contentType: 'image/webp',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
@ -70,6 +81,19 @@ describe('SSR images - dev', function () {
|
||||||
},
|
},
|
||||||
contentType: 'image/webp',
|
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 }) => {
|
].forEach(({ title, id, url, query, contentType }) => {
|
||||||
it(title, async () => {
|
it(title, async () => {
|
||||||
const image = $(id);
|
const image = $(id);
|
||||||
|
@ -149,6 +173,17 @@ describe('SSR images with subpath - dev', function () {
|
||||||
},
|
},
|
||||||
contentType: 'image/webp',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
@ -161,6 +196,19 @@ describe('SSR images with subpath - dev', function () {
|
||||||
},
|
},
|
||||||
contentType: 'image/webp',
|
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 }) => {
|
].forEach(({ title, id, url, query, contentType }) => {
|
||||||
it(title, async () => {
|
it(title, async () => {
|
||||||
const image = $(id);
|
const image = $(id);
|
||||||
|
|
|
@ -56,6 +56,19 @@ describe('SSG pictures - dev', function () {
|
||||||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||||
alt: 'Hero image',
|
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 }) => {
|
].forEach(({ title, id, url, query, alt }) => {
|
||||||
it(title, () => {
|
it(title, () => {
|
||||||
const sources = $(`${id} source`);
|
const sources = $(`${id} source`);
|
||||||
|
@ -200,6 +213,13 @@ describe('SSG pictures - build', function () {
|
||||||
size: { width: 544, height: 184, type: 'png' },
|
size: { width: 544, height: 184, type: 'png' },
|
||||||
alt: 'Google logo',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
@ -288,6 +308,13 @@ describe('SSG pictures with subpath - build', function () {
|
||||||
size: { width: 544, height: 184, type: 'png' },
|
size: { width: 544, height: 184, type: 'png' },
|
||||||
alt: 'Google logo',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
|
|
@ -42,6 +42,17 @@ describe('SSR pictures - build', function () {
|
||||||
},
|
},
|
||||||
alt: 'Google logo',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
@ -49,6 +60,19 @@ describe('SSR pictures - build', function () {
|
||||||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||||
alt: 'Hero image',
|
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 }) => {
|
].forEach(({ title, id, url, query }) => {
|
||||||
it(title, async () => {
|
it(title, async () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
@ -122,6 +146,17 @@ describe('SSR pictures with subpath - build', function () {
|
||||||
},
|
},
|
||||||
alt: 'Google logo',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
@ -129,6 +164,19 @@ describe('SSR pictures with subpath - build', function () {
|
||||||
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
|
||||||
alt: 'Hero image',
|
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 }) => {
|
].forEach(({ title, id, url, query }) => {
|
||||||
it(title, async () => {
|
it(title, async () => {
|
||||||
const app = await fixture.loadTestAdapterApp();
|
const app = await fixture.loadTestAdapterApp();
|
||||||
|
|
|
@ -54,6 +54,19 @@ describe('SSR pictures - dev', function () {
|
||||||
contentType: 'image/png',
|
contentType: 'image/png',
|
||||||
alt: 'Google logo',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
@ -67,6 +80,20 @@ describe('SSR pictures - dev', function () {
|
||||||
contentType: 'image/jpeg',
|
contentType: 'image/jpeg',
|
||||||
alt: 'Hero image',
|
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 }) => {
|
].forEach(({ title, id, url, query, alt, contentType }) => {
|
||||||
it(title, async () => {
|
it(title, async () => {
|
||||||
const sources = $(`${id} source`);
|
const sources = $(`${id} source`);
|
||||||
|
@ -148,6 +175,20 @@ describe('SSR pictures with subpath - dev', function () {
|
||||||
contentType: 'image/png',
|
contentType: 'image/png',
|
||||||
alt: 'Google logo',
|
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',
|
title: 'Public images',
|
||||||
id: '#hero',
|
id: '#hero',
|
||||||
|
|
|
@ -14,6 +14,7 @@ describe('Sharp service', () => {
|
||||||
['width & height', { src, height: 400, width: 200 }],
|
['width & height', { src, height: 400, width: 200 }],
|
||||||
['aspect ratio string', { src, aspectRatio: '16:9' }],
|
['aspect ratio string', { src, aspectRatio: '16:9' }],
|
||||||
['aspect ratio float', { src, aspectRatio: 1.7 }],
|
['aspect ratio float', { src, aspectRatio: 1.7 }],
|
||||||
|
['background color', { src, format: 'jpeg', background: '#333333' }],
|
||||||
].forEach(([description, props]) => {
|
].forEach(([description, props]) => {
|
||||||
it(description, async () => {
|
it(description, async () => {
|
||||||
const { searchParams } = await sharp.serializeTransform(props);
|
const { searchParams } = await sharp.serializeTransform(props);
|
||||||
|
@ -31,6 +32,7 @@ describe('Sharp service', () => {
|
||||||
verifyProp(props.width, 'w');
|
verifyProp(props.width, 'w');
|
||||||
verifyProp(props.height, 'h');
|
verifyProp(props.height, 'h');
|
||||||
verifyProp(props.aspectRatio, 'ar');
|
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 }],
|
['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 string', `ar=16:9&href=${href}`, { src, aspectRatio: '16:9' }],
|
||||||
['aspect ratio float', `ar=1.7&href=${href}`, { src, aspectRatio: 1.7 }],
|
['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]) => {
|
].forEach(([description, params, expected]) => {
|
||||||
it(description, async () => {
|
it(description, async () => {
|
||||||
const searchParams = new URLSearchParams(params);
|
const searchParams = new URLSearchParams(params);
|
||||||
|
|
|
@ -49,6 +49,12 @@ describe('Images in MDX - build', function () {
|
||||||
regex: /^\/hero_\w{4,10}.webp/,
|
regex: /^\/hero_\w{4,10}.webp/,
|
||||||
size: { width: 768, height: 414, type: '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 }) => {
|
].forEach(({ title, id, regex, size }) => {
|
||||||
it(title, () => {
|
it(title, () => {
|
||||||
const image = $(id);
|
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