Merge branch 'main' into feat/squoosh-lib

This commit is contained in:
Tony Sullivan 2022-09-08 11:03:51 -05:00
commit 27a7c4b2ea
121 changed files with 2239 additions and 1455 deletions

View file

@ -1,5 +0,0 @@
---
"astro": patch
---
Improve third-party Astro package support

View file

@ -1,5 +0,0 @@
---
'@astrojs/vue': patch
---
Mark vueperslides as a default noExternal

View file

@ -1,5 +0,0 @@
---
'@astrojs/image': patch
---
Parallelize image transforms

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Remove regression when there is duplicate client/server CSS

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Correctly escape paths in file names

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Ensure SSR module is loaded before testing if it's CSS in dev

View file

@ -0,0 +1,6 @@
---
'astro': patch
'@astrojs/react': patch
---
Fix framework components on Vercel Edge

View file

@ -1,5 +0,0 @@
---
'@astrojs/alpinejs': patch
---
Update homepage link

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5"
"astro": "^1.1.7"
}
}

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"@astrojs/mdx": "^0.11.1",
"@astrojs/rss": "^1.0.0",
"@astrojs/sitemap": "^1.0.0"

View file

@ -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

View file

@ -1,2 +0,0 @@
# Expose Astro dependencies for `pnpm` users
shamefully-hoist=true

View file

@ -1,6 +0,0 @@
{
"startCommand": "npm start",
"env": {
"ENABLE_CJS_IMPORTS": true
}
}

View file

@ -1,12 +1,13 @@
# Astro Starter Kit: Component
# Astro Starter Kit: Component Package
This is a template for an Astro component library. Use this template for writing components to use in multiple projects or publish to NPM.
```
npm init astro -- --template component
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/component)
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/non-html-pages)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
@ -14,34 +15,19 @@ Inside of your Astro project, you'll see the following folders and files:
```
/
├── demo/
│ ├── public/
│ └── src/
│ └── pages/
│ └── index.astro
└── packages/
└── my-component/
├── index.js
└── package.json
├── index.ts
├── src
│ └── MyComponent.astro
├── tsconfig.json
├── package.json
```
This project uses **workspaces** to develop a single package, `@example/my-component`, from `packages/my-component`. It also includes a `demo` Astro site for testing and demonstrating the component.
The `index.ts` file is the "entry point" for your package. Export your components in `index.ts` to make them importable from your package.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :--------------------- | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:3000` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
| `npm link` | Registers this package locally. Run `npm link my-component-library` in an Astro project to install your components
| `npm publish` | [Publishes](https://docs.npmjs.com/creating-and-publishing-unscoped-public-packages#publishing-unscoped-public-packages) this package to NPM. Requires you to be [logged in](https://docs.npmjs.com/cli/v8/commands/npm-adduser)

View file

@ -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"
}
}

View file

@ -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

View file

@ -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>

View 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;

View file

@ -1,13 +1,23 @@
{
"name": "@example/component",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "astro --root demo dev",
"build": "astro --root demo build",
"serve": "astro --root demo preview"
"version": "0.0.1",
"type": "module",
"exports": {
".": "./index.ts"
},
"dependencies": {
"astro": "^1.1.5"
"files": [
"src",
"index.ts"
],
"keywords": [
"astro-component"
],
"scripts": {},
"devDependencies": {
"astro": "^1.1.7"
},
"peerDependencies": {
"astro": "^1.1.7"
}
}

View file

@ -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>

View file

@ -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>

View file

@ -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>
```

View file

@ -1,2 +0,0 @@
export { default as Button } from './Button.astro';
export { default as Heading } from './Heading.astro';

View file

@ -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"
]
}

View file

@ -1,3 +0,0 @@
packages:
- 'packages/**/*'
- 'demo'

View file

@ -1,11 +0,0 @@
{
"infiniteLoopProtection": true,
"hardReloadOnChange": false,
"view": "browser",
"template": "node",
"container": {
"port": 3000,
"startScript": "start",
"node": "14"
}
}

View 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>

View file

@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/base"
}

View file

@ -11,12 +11,12 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"preact": "^10.7.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"@astrojs/react": "^1.1.0",
"@astrojs/preact": "^1.0.2",
"@astrojs/preact": "^1.1.0",
"@algolia/client-search": "^4.13.1",
"@docsearch/css": "^3.1.0",
"@docsearch/react": "^3.1.0",

View file

@ -10,9 +10,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"alpinejs": "^3.10.2",
"@astrojs/alpinejs": "^0.1.1",
"@astrojs/alpinejs": "^0.1.2",
"@types/alpinejs": "^3.7.0"
}
}

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"lit": "^2.2.5",
"@astrojs/lit": "^1.0.0",
"@webcomponents/template-shadowroot": "^0.1.0"

View file

@ -10,17 +10,17 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"preact": "^10.7.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"solid-js": "^1.4.3",
"svelte": "^3.48.0",
"vue": "^3.2.37",
"@astrojs/preact": "^1.0.2",
"@astrojs/preact": "^1.1.0",
"@astrojs/react": "^1.1.0",
"@astrojs/solid-js": "^1.1.0",
"@astrojs/svelte": "^1.0.0",
"@astrojs/vue": "^1.0.0"
"@astrojs/vue": "^1.0.1"
}
}

View file

@ -10,8 +10,8 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"preact": "^10.7.3",
"@astrojs/preact": "^1.0.2"
"@astrojs/preact": "^1.1.0"
}
}

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"@astrojs/react": "^1.1.0",

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"solid-js": "^1.4.3",
"@astrojs/solid-js": "^1.1.0"
}

View file

@ -12,6 +12,6 @@
"dependencies": {
"svelte": "^3.48.0",
"@astrojs/svelte": "^1.0.0",
"astro": "^1.1.5"
"astro": "^1.1.7"
}
}

View file

@ -10,8 +10,8 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"vue": "^3.2.37",
"@astrojs/vue": "^1.0.0"
"@astrojs/vue": "^1.0.1"
}
}

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5"
"astro": "^1.1.7"
}
}

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5"
"astro": "^1.1.7"
}
}

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5"
"astro": "^1.1.7"
}
}

View file

@ -12,7 +12,7 @@
},
"devDependencies": {},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"svelte": "^3.48.0",
"@astrojs/svelte": "^1.0.0",
"@astrojs/node": "^1.0.1",

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"@astrojs/markdown-remark": "^1.1.0",
"hast-util-select": "5.0.1",
"rehype-autolink-headings": "^6.1.1",

View file

@ -10,6 +10,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5"
"astro": "^1.1.7"
}
}

View file

@ -10,9 +10,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"preact": "^10.6.5",
"@astrojs/preact": "^1.0.2",
"@astrojs/preact": "^1.1.0",
"@astrojs/mdx": "^0.11.1"
}
}

View file

@ -10,9 +10,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"preact": "^10.7.3",
"@astrojs/preact": "^1.0.2",
"@astrojs/preact": "^1.1.0",
"nanostores": "^0.5.12",
"@nanostores/preact": "^0.1.3"
}

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"@astrojs/tailwind": "^1.0.0",
"autoprefixer": "^10.4.7",
"canvas-confetti": "^1.5.1",

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"vite-plugin-pwa": "0.11.11",
"workbox-window": "^6.5.3"
}

View file

@ -12,7 +12,7 @@
"test": "vitest"
},
"dependencies": {
"astro": "^1.1.5",
"astro": "^1.1.7",
"vitest": "^0.20.3"
}
}

View file

@ -1,5 +1,27 @@
# astro
## 1.1.7
### Patch Changes
- [#4646](https://github.com/withastro/astro/pull/4646) [`98f242cdc`](https://github.com/withastro/astro/commit/98f242cdcd860679ad787ffb387558cb1dc93b87) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Add cyclic ref detection when serializing props
- [#4656](https://github.com/withastro/astro/pull/4656) [`6d845c353`](https://github.com/withastro/astro/commit/6d845c353d5688f30787c4361f86c605fb638dd9) Thanks [@matthewp](https://github.com/matthewp)! - Fix bug with using `assert` as import identifier
- [#4403](https://github.com/withastro/astro/pull/4403) [`d31e72c3b`](https://github.com/withastro/astro/commit/d31e72c3ba8270d1e8d33c533502b3c4c6390a15) Thanks [@JohnDaly](https://github.com/JohnDaly)! - Fix for components, declared with JSXMemberExpression nodes, that failed to hydrate due to incomplete 'component-export' metadata
## 1.1.6
### Patch Changes
- [#4623](https://github.com/withastro/astro/pull/4623) [`eb1862b4e`](https://github.com/withastro/astro/commit/eb1862b4e68b399eecc7267ea9e0bee36983b0cb) Thanks [@delucis](https://github.com/delucis)! - Improve third-party Astro package support
- [#4643](https://github.com/withastro/astro/pull/4643) [`307b7b97c`](https://github.com/withastro/astro/commit/307b7b97ce79d076ceb4fdc25fd28a27077deb34) Thanks [@matthewp](https://github.com/matthewp)! - Remove regression when there is duplicate client/server CSS
- [#4584](https://github.com/withastro/astro/pull/4584) [`29a5fdc15`](https://github.com/withastro/astro/commit/29a5fdc1535fc389035d8107025f7490bfa976ed) Thanks [@bluwy](https://github.com/bluwy)! - Correctly escape paths in file names
- [#4621](https://github.com/withastro/astro/pull/4621) [`0068afb87`](https://github.com/withastro/astro/commit/0068afb876342ae76154e552dfc5bb6832b665ed) Thanks [@AllanChain](https://github.com/AllanChain)! - Ensure SSR module is loaded before testing if it's CSS in dev
## 1.1.5
### Patch Changes

View 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');
});
});

View file

@ -1,10 +1,7 @@
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
// https://astro.build/config
export default defineConfig({
vite: {
ssr: {
noExternal: ['@example/my-component'],
},
},
integrations: [preact()],
});

View file

@ -0,0 +1,9 @@
{
"name": "@e2e/error-cyclic",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*",
"@astrojs/preact": "workspace:*"
}
}

View file

@ -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>
);
}

View file

@ -0,0 +1,8 @@
---
import { PreactCounter } from '../components/PreactCounter'
const cycle: any = { foo: ['bar'] }
cycle.foo.push(cycle)
---
<PreactCounter client:load cycle={cycle} />

View file

@ -1,7 +1,8 @@
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import mdx from "@astrojs/mdx";
// https://astro.build/config
export default defineConfig({
integrations: [preact()],
integrations: [preact(), mdx()]
});

View file

@ -3,6 +3,7 @@
"version": "0.0.0",
"private": true,
"devDependencies": {
"@astrojs/mdx": "workspace:*",
"@astrojs/preact": "workspace:*",
"astro": "workspace:*"
},

View file

@ -1,5 +1,6 @@
---
import * as ns from '../components/PreactCounter.tsx';
import { components } from '../components/PreactCounter.tsx';
---
<html lang="en">
@ -10,9 +11,13 @@ import * as ns from '../components/PreactCounter.tsx';
</head>
<body>
<main>
<ns.components.PreactCounter id="preact-counter" client:load>
<h1>preact</h1>
<ns.components.PreactCounter id="preact-counter-namespace" client:load>
<h1>preact (namespace import)</h1>
</ns.components.PreactCounter>
<components.PreactCounter id="preact-counter-named" client:load>
<h1>preact (named import)</h1>
</components.PreactCounter>
</main>
</body>
</html>

View file

@ -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>

View file

@ -19,18 +19,68 @@ test.describe('Hydrating namespaced components', () => {
test('Preact Component', async ({ page }) => {
await page.goto('/');
const counter = await page.locator('#preact-counter');
await expect(counter, 'component is visible').toBeVisible();
// Counter declared with: <ns.components.PreactCounter id="preact-counter-namespace" client:load>
const namespacedCounter = await page.locator('#preact-counter-namespace');
await expect(namespacedCounter, 'component is visible').toBeVisible();
const count = await counter.locator('pre');
await expect(count, 'initial count is 0').toHaveText('0');
const namespacedCount = await namespacedCounter.locator('pre');
await expect(namespacedCount, 'initial count is 0').toHaveText('0');
const children = await counter.locator('.children');
await expect(children, 'children exist').toHaveText('preact');
const namespacedChildren = await namespacedCounter.locator('.children');
await expect(namespacedChildren, 'children exist').toHaveText('preact (namespace import)');
const increment = await counter.locator('.increment');
await increment.click();
const namespacedIncrement = await namespacedCounter.locator('.increment');
await namespacedIncrement.click();
await expect(count, 'count incremented by 1').toHaveText('1');
await expect(namespacedCount, 'count incremented by 1').toHaveText('1');
// Counter declared with: <components.PreactCounterTwo id="preact-counter-named" client:load>
const namedCounter = await page.locator('#preact-counter-named');
await expect(namedCounter, 'component is visible').toBeVisible();
const namedCount = await namedCounter.locator('pre');
await expect(namedCount, 'initial count is 0').toHaveText('0');
const namedChildren = await namedCounter.locator('.children');
await expect(namedChildren, 'children exist').toHaveText('preact (named import)');
const namedIncrement = await namedCounter.locator('.increment');
await namedIncrement.click();
await expect(namedCount, 'count incremented by 1').toHaveText('1');
});
test('MDX', async ({ page }) => {
await page.goto('/mdx');
// Counter declared with: <ns.components.PreactCounter id="preact-counter-namespace" client:load>
const namespacedCounter = await page.locator('#preact-counter-namespace');
await expect(namespacedCounter, 'component is visible').toBeVisible();
const namespacedCount = await namespacedCounter.locator('pre');
await expect(namespacedCount, 'initial count is 0').toHaveText('0');
const namespacedChildren = await namespacedCounter.locator('.children');
await expect(namespacedChildren, 'children exist').toHaveText('preact (namespace import)');
const namespacedIncrement = await namespacedCounter.locator('.increment');
await namespacedIncrement.click();
await expect(namespacedCount, 'count incremented by 1').toHaveText('1');
// Counter declared with: <components.PreactCounterTwo id="preact-counter-named" client:load>
const namedCounter = await page.locator('#preact-counter-named');
await expect(namedCounter, 'component is visible').toBeVisible();
const namedCount = await namedCounter.locator('pre');
await expect(namedCount, 'initial count is 0').toHaveText('0');
const namedChildren = await namedCounter.locator('.children');
await expect(namedChildren, 'children exist').toHaveText('preact (named import)');
const namedIncrement = await namedCounter.locator('.increment');
await namedIncrement.click();
await expect(namedCount, 'count incremented by 1').toHaveText('1');
});
});

View file

@ -1,6 +1,6 @@
{
"name": "astro",
"version": "1.1.5",
"version": "1.1.7",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",
@ -94,7 +94,7 @@
"test:e2e:match": "playwright test -g"
},
"dependencies": {
"@astrojs/compiler": "^0.23.4",
"@astrojs/compiler": "^0.23.5",
"@astrojs/language-server": "^0.23.0",
"@astrojs/markdown-remark": "^1.1.1",
"@astrojs/telemetry": "^1.0.0",

View file

@ -243,7 +243,7 @@ class DependencyWalker {
dir = parentDir;
}
} catch (e) {
} catch {
// Give up! Who knows where the `package.json` is…
}
}

View file

@ -207,7 +207,8 @@ export default function astroJSX(): PluginObj {
break;
}
if (namespace.at(0) === local) {
path.setData('import', { name: imported, path: source });
const name = imported === '*' ? imported : tagName;
path.setData('import', { name, path: source });
break;
}
}

View file

@ -146,7 +146,7 @@ export async function generateHydrateScript(
if (renderer.clientEntrypoint) {
island.props['component-export'] = componentExport.value;
island.props['renderer-url'] = await result.resolve(decodeURI(renderer.clientEntrypoint));
island.props['props'] = escapeHTML(serializeProps(props));
island.props['props'] = escapeHTML(serializeProps(props, metadata));
}
island.props['ssr'] = '';

View file

@ -303,7 +303,8 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
// Include componentExport name, componentUrl, and props in hash to dedupe identical islands
const astroId = shorthash(
`<!--${metadata.componentExport!.value}:${metadata.componentUrl}-->\n${html}\n${serializeProps(
props
props,
metadata
)}`
);

View file

@ -79,7 +79,10 @@ export async function renderPage(
controller.enqueue(encoder.encode('<!DOCTYPE html>\n'));
}
}
controller.enqueue(encoder.encode(html));
// Convert HTML object to string
// for environments that won't "toString" automatically
// (ex. Cloudflare and Vercel Edge)
controller.enqueue(encoder.encode(String(html)));
i++;
}
controller.close();

View file

@ -1,3 +1,5 @@
import type { AstroComponentMetadata } from '../../@types/astro';
type ValueOf<T> = T[keyof T];
const PROP_TYPE = {
@ -11,19 +13,31 @@ const PROP_TYPE = {
URL: 7,
};
function serializeArray(value: any[]): any[] {
return value.map((v) => convertToSerializedForm(v));
function serializeArray(value: any[], metadata: AstroComponentMetadata): any[] {
return value.map((v) => convertToSerializedForm(v, metadata));
}
function serializeObject(value: Record<any, any>): Record<any, any> {
function serializeObject(
value: Record<any, any>,
metadata: AstroComponentMetadata
): Record<any, any> {
if (cyclicRefs.has(value)) {
throw new Error(`Cyclic reference detected while serializing props for <${metadata.displayName} client:${metadata.hydrate}>!
Cyclic references cannot be safely serialized for client-side usage. Please remove the cyclic reference.`);
}
cyclicRefs.add(value);
return Object.fromEntries(
Object.entries(value).map(([k, v]) => {
return [k, convertToSerializedForm(v)];
return [k, convertToSerializedForm(v, metadata)];
})
);
}
function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
function convertToSerializedForm(
value: any,
metadata: AstroComponentMetadata
): [ValueOf<typeof PROP_TYPE>, any] {
const tag = Object.prototype.toString.call(value);
switch (tag) {
case '[object Date]': {
@ -33,10 +47,16 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
return [PROP_TYPE.RegExp, (value as RegExp).source];
}
case '[object Map]': {
return [PROP_TYPE.Map, JSON.stringify(serializeArray(Array.from(value as Map<any, any>)))];
return [
PROP_TYPE.Map,
JSON.stringify(serializeArray(Array.from(value as Map<any, any>), metadata)),
];
}
case '[object Set]': {
return [PROP_TYPE.Set, JSON.stringify(serializeArray(Array.from(value as Set<any>)))];
return [
PROP_TYPE.Set,
JSON.stringify(serializeArray(Array.from(value as Set<any>), metadata)),
];
}
case '[object BigInt]': {
return [PROP_TYPE.BigInt, (value as bigint).toString()];
@ -45,11 +65,11 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
return [PROP_TYPE.URL, (value as URL).toString()];
}
case '[object Array]': {
return [PROP_TYPE.JSON, JSON.stringify(serializeArray(value))];
return [PROP_TYPE.JSON, JSON.stringify(serializeArray(value, metadata))];
}
default: {
if (value !== null && typeof value === 'object') {
return [PROP_TYPE.Value, serializeObject(value)];
return [PROP_TYPE.Value, serializeObject(value, metadata)];
} else {
return [PROP_TYPE.Value, value];
}
@ -57,6 +77,9 @@ function convertToSerializedForm(value: any): [ValueOf<typeof PROP_TYPE>, any] {
}
}
export function serializeProps(props: any) {
return JSON.stringify(serializeObject(props));
let cyclicRefs = new WeakSet<any>();
export function serializeProps(props: any, metadata: AstroComponentMetadata) {
const serialized = JSON.stringify(serializeObject(props, metadata));
cyclicRefs = new WeakSet<any>();
return serialized;
}

View file

@ -120,6 +120,7 @@ async function handle500Response(
) {
res.on('close', () => setTimeout(() => viteServer.ws.send(getViteErrorPayload(err)), 200));
if (res.headersSent) {
res.write(`<script type="module" src="/@vite/client"></script>`);
res.end();
} else {
writeHtmlResponse(

View file

@ -1,5 +1,11 @@
# @astrojs/alpinejs
## 0.1.2
### Patch Changes
- [#4622](https://github.com/withastro/astro/pull/4622) [`63cd9d89e`](https://github.com/withastro/astro/commit/63cd9d89e8b83ce5e39cdae84a8342e28d1940cc) Thanks [@mohammed-elhaouari](https://github.com/mohammed-elhaouari)! - Update homepage link
## 0.1.1
### Patch Changes

View file

@ -18,14 +18,16 @@ The `astro add` command-line tool automates the installation for you. Run one of
```sh
# Using NPM
npm run astro add alpinejs
npx astro add alpinejs
# Using Yarn
yarn astro add alpinejs
# Using PNPM
pnpm astro add alpinejs
```
Finally, in the terminal window running Astro, press `CTRL+C` and then type `npm run astro dev` to restart the dev server.
Finally, in the terminal window running Astro, press `CTRL+C` and then restart the dev server.
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
### Manual Install

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/alpinejs",
"description": "The official Alpine.js integration for Astro.",
"version": "0.1.1",
"version": "0.1.2",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",

View file

@ -1,5 +1,21 @@
# @astrojs/image
## 0.6.0
### Minor Changes
- [#4642](https://github.com/withastro/astro/pull/4642) [`e4348a4eb`](https://github.com/withastro/astro/commit/e4348a4eb49466579204eb5f7fb8823736f467c0) Thanks [@beeb](https://github.com/beeb)! - Added a `background` option to specify a background color to replace transparent pixels (alpha layer).
### Patch Changes
- [#4649](https://github.com/withastro/astro/pull/4649) [`db70afdcd`](https://github.com/withastro/astro/commit/db70afdcd5b7d6b39c9953e88dbdadc5e3a93175) Thanks [@tony-sull](https://github.com/tony-sull)! - Fixes a bug related to filenames for remote images in SSG builds
## 0.5.1
### Patch Changes
- [#4626](https://github.com/withastro/astro/pull/4626) [`494c2b835`](https://github.com/withastro/astro/commit/494c2b8353d1975d840c5acaf70cb513b99c58e5) Thanks [@altano](https://github.com/altano)! - Parallelize image transforms
## 0.5.0
### Minor Changes

View file

@ -24,24 +24,24 @@ This integration provides `<Image />` and `<Picture>` components as well as a ba
### Quick Install
The `astro add` command-line tool automates the installation for you. Run one of the following commands in a new terminal window. (If you aren't sure which package manager you're using, run the first command.) Then, follow the prompts, and type "y" in the terminal (meaning "yes") for each one.
```sh
# Using NPM
npm run astro add image
npx astro add image
# Using Yarn
yarn astro add image
# Using PNPM
pnpm astro add image
```
Then, restart the dev server by typing `CTRL-C` and then `npm run astro dev` in the terminal window that was running Astro.
Because this command is new, it might not properly set things up. If that happens, [feel free to log an issue on our GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
Finally, in the terminal window running Astro, press `CTRL+C` and then restart the dev server.
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
### Manual Install
First, install the `@astrojs/image` package using your package manager. If you're using npm or aren't sure, run this in the terminal:
```sh
npm install @astrojs/image
@ -57,7 +57,7 @@ export default {
// ...
integrations: [image()],
}
```
```
Then, restart the dev server.
### Update `env.d.ts`
@ -190,6 +190,24 @@ A `string` can be provided in the form of `{width}:{height}`, ex: `16:9` or `3:4
A `number` can also be provided, useful when the aspect ratio is calculated at build time. This can be an inline number such as `1.777` or inlined as a JSX expression like `aspectRatio={16/9}`.
#### background
<p>
**Type:** `ColorDefinition`<br>
**Default:** `undefined`
</p>
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
as default replacement for transparent pixels.
The parameter accepts a `string` as value.
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
`rgb(100,100,100)`.
### `<Picture /`>
#### src
@ -271,6 +289,24 @@ A `number` can also be provided, useful when the aspect ratio is calculated at b
The output formats to be used in the optimized image. If not provided, `webp` and `avif` will be used in addition to the original image format.
#### background
<p>
**Type:** `ColorDefinition`<br>
**Default:** `undefined`
</p>
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
as default replacement for transparent pixels.
The parameter accepts a `string` as value.
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
`rgb(100,100,100)`.
### `getImage`
This is the helper function used by the `<Image />` component to build `<img />` attributes for the transformed image. This helper can be used directly for more complex use cases that aren't currently supported by the `<Image />` component.
@ -307,7 +343,7 @@ The integration can be configured to run with a different image service, either
### config.serviceEntryPoint
The `serviceEntryPoint` should resolve to the image service installed from NPM. The default entry point is `@astrojs/image/sharp`, which resolves to the entry point exported from this integration's `package.json`.
```js
@ -342,7 +378,7 @@ export default {
## Examples
### Local images
Image files in your project's `src` directory can be imported in frontmatter and passed directly to the `<Image />` component. All other properties are optional and will default to the original image file's properties if not provided.
```astro
@ -371,7 +407,7 @@ import heroImage from '../assets/hero.png';
Files in the `/public` directory are always served or copied as-is, with no processing. We recommend that local images are always kept in `src/` so that Astro can transform, optimize and bundle them. But if you absolutely must keep an image in `public/`, use its relative URL path as the image's `src=` attribute. It will be treated as a remote image, which requires an `aspectRatio` attribute.
Alternatively, you can import an image from your `public/` directory in your frontmatter and use a variable in your `src=` attribute. You cannot, however, import this directly inside the component as its `src` value.
Alternatively, you can import an image from your `public/` directory in your frontmatter and use a variable in your `src=` attribute. You cannot, however, import this directly inside the component as its `src` value.
For example, use an image located at `public/social.png` in either static or SSR builds like so:
@ -386,7 +422,7 @@ import socialImage from '/social.png';
```
### Remote images
Remote images can be transformed with the `<Image />` component. The `<Image />` component needs to know the final dimensions for the `<img />` element to avoid content layout shifts. For remote images, this means you must either provide `width` and `height`, or one of the dimensions plus the required `aspectRatio`.
```astro

View file

@ -17,7 +17,7 @@ interface RemoteImageProps extends TransformOptions, astroHTML.JSX.ImgHTMLAttrib
src: string;
/** Defines an alternative text description of the image. Set to an empty string (alt="") if the image is not a key part of the content (it's decoration or a tracking pixel). */
alt: string;
format: OutputFormat;
format?: OutputFormat;
width: number;
height: number;
}

View file

@ -27,6 +27,7 @@ interface RemoteImageProps
widths: number[];
aspectRatio: TransformOptions['aspectRatio'];
formats?: OutputFormat[];
background: TransformOptions['background'];
}
export type Props = LocalImageProps | RemoteImageProps;
@ -37,6 +38,7 @@ const {
sizes,
widths,
aspectRatio,
background,
formats = ['avif', 'webp'],
loading = 'lazy',
decoding = 'async',
@ -47,7 +49,7 @@ if (alt === undefined || alt === null) {
warnForMissingAlt();
}
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio });
const { image, sources } = await getPicture({ src, widths, formats, aspectRatio, background });
---
<picture {...attrs}>

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/image",
"description": "Load and transform images in your Astro site.",
"version": "0.5.0",
"version": "0.6.0",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",

View file

@ -1,5 +1,10 @@
/// <reference types="astro/astro-jsx" />
import type { ImageService, OutputFormat, TransformOptions } from '../loaders/index.js';
import type {
ColorDefinition,
ImageService,
OutputFormat,
TransformOptions,
} from '../loaders/index.js';
import { isSSRService, parseAspectRatio } from '../loaders/index.js';
import sharp from '../loaders/squoosh.js';
import { isRemoteImage } from '../utils/paths.js';
@ -63,7 +68,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
// resolve the metadata promise, usually when the ESM import is inlined
const metadata = 'then' in input.src ? (await input.src).default : input.src;
let { width, height, aspectRatio, format = metadata.format, ...rest } = input;
let { width, height, aspectRatio, background, format = metadata.format, ...rest } = input;
if (!width && !height) {
// neither dimension was provided, use the file metadata
@ -86,6 +91,7 @@ async function resolveTransform(input: GetImageTransform): Promise<TransformOpti
height,
aspectRatio,
format: format as OutputFormat,
background: background as ColorDefinition | undefined,
};
}

View file

@ -10,6 +10,7 @@ export interface GetPictureParams {
widths: number[];
formats: OutputFormat[];
aspectRatio?: TransformOptions['aspectRatio'];
background?: TransformOptions['background'];
}
export interface GetPictureResult {
@ -36,7 +37,7 @@ async function resolveFormats({ src, formats }: GetPictureParams) {
unique.add(extname(metadata.src).replace('.', '') as OutputFormat);
}
return [...unique];
return Array.from(unique).filter(Boolean);
}
export async function getPicture(params: GetPictureParams): Promise<GetPictureResult> {
@ -64,6 +65,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
format,
width,
height: Math.round(width / aspectRatio!),
background: params.background,
});
return `${img.src} ${width}w`;
})
@ -83,6 +85,7 @@ export async function getPicture(params: GetPictureParams): Promise<GetPictureRe
width: Math.max(...widths),
aspectRatio,
format: allFormats[allFormats.length - 1],
background: params.background,
});
const sources = await Promise.all(allFormats.map((format) => getSource(format)));

View 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',
];

View file

@ -1,3 +1,5 @@
import { htmlColorNames, type NamedColor } from './colornames.js';
/// <reference types="astro/astro-jsx" />
export type InputFormat =
| 'heic'
@ -10,16 +12,35 @@ export type InputFormat =
| 'webp'
| 'gif';
export type OutputFormat = 'avif' | 'jpeg' | 'jpg' | 'png' | 'webp';
export type OutputFormatSupportsAlpha = 'avif' | 'png' | 'webp';
export type OutputFormat = OutputFormatSupportsAlpha | 'jpeg' | 'jpg';
export type ColorDefinition =
| NamedColor
| `#${string}`
| `rgb(${number}, ${number}, ${number})`
| `rgb(${number},${number},${number})`;
export function isOutputFormat(value: string): value is OutputFormat {
return ['avif', 'jpeg', 'jpg', 'png', 'webp'].includes(value);
}
export function isOutputFormatSupportsAlpha(value: string): value is OutputFormatSupportsAlpha {
return ['avif', 'png', 'webp'].includes(value);
}
export function isAspectRatioString(value: string): value is `${number}:${number}` {
return /^\d*:\d*$/.test(value);
}
export function isColor(value: string): value is ColorDefinition {
return (
(htmlColorNames as string[]).includes(value.toLowerCase()) ||
/^#[0-9a-f]{3}([0-9a-f]{3})?$/i.test(value) ||
/^rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)$/i.test(value)
);
}
export function parseAspectRatio(aspectRatio: TransformOptions['aspectRatio']) {
if (!aspectRatio) {
return undefined;
@ -75,6 +96,15 @@ export interface TransformOptions {
* @example "16:9" - strings can be used in the format of `{ratioWidth}:{ratioHeight}`.
*/
aspectRatio?: number | `${number}:${number}`;
/**
* The background color to use when converting from a transparent image format to a
* non-transparent format. This is useful for converting PNGs to JPEGs.
*
* @example "white" - a named color
* @example "#ffffff" - a hex color
* @example "rgb(255, 255, 255)" - an rgb color
*/
background?: ColorDefinition;
}
export interface HostedImageService<T extends TransformOptions = TransformOptions> {
@ -161,6 +191,10 @@ export abstract class BaseSSRService implements SSRImageService {
searchParams.append('ar', transform.aspectRatio.toString());
}
if (transform.background) {
searchParams.append('bg', transform.background);
}
searchParams.append('href', transform.src);
return { searchParams };
@ -202,6 +236,10 @@ export abstract class BaseSSRService implements SSRImageService {
}
}
if (searchParams.has('bg')) {
transform.background = searchParams.get('bg') as ColorDefinition;
}
return transform;
}

View file

@ -15,6 +15,11 @@ class SharpService extends BaseSSRService {
sharpImage.resize(width, height);
}
// remove alpha channel and replace with background color if requested
if (transform.background) {
sharpImage.flatten({ background: transform.background });
}
if (transform.format) {
sharpImage.toFormat(transform.format, { quality: transform.quality });
}

View file

@ -14,7 +14,7 @@ function extname(src: string, format?: OutputFormat) {
const index = src.lastIndexOf('.');
if (index <= 0) {
return undefined;
return '';
}
return src.substring(index);
@ -38,11 +38,12 @@ export function propsToFilename(transform: TransformOptions) {
// strip off the querystring first, then remove the file extension
let filename = removeQueryString(transform.src);
filename = basename(filename);
const ext = extname(filename);
filename = removeExtname(filename);
const ext = transform.format || extname(transform.src)?.substring(1);
const outputExt = transform.format ? `.${transform.format}` : ext;
return `/${filename}_${shorthash(JSON.stringify(transform))}.${ext}`;
return `/${filename}_${shorthash(JSON.stringify(transform))}${outputExt}`;
}
export function appendForwardSlash(path: string) {

View file

@ -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);
});
});
});

View file

@ -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);
}
}
});
});
});

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View 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>

View file

@ -20,5 +20,9 @@ import { Image } from '@astrojs/image/components';
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
<br />
<Image id="query" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png?token=abc" width={544} height={184} format="webp" alt="query" />
<br />
<Image id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="jpeg" alt="Google" background="#333333" />
<br />
<Image id="ipsum" src="https://picsum.photos/200/300" width={200} height={300} alt="ipsum" format="jpeg" />
</body>
</html>

View file

@ -12,8 +12,12 @@ import { Picture } from '@astrojs/image/components';
<br />
<Picture id="social-jpg" src={socialJpg} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Social image" />
<br />
<Picture id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} alt="Google logo" />
<Picture id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} alt="Google logo" formats={["avif", "webp", "png"]} />
<br />
<Picture id='inline' src={import('../assets/social.jpg')} sizes="(min-width: 640px) 50vw, 100vw" widths={[253, 506]} alt="Inline social image" />
<br />
<Picture id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" sizes="(min-width: 640px) 50vw, 100vw" widths={[272, 544]} aspectRatio={544/184} alt="Google logo" background="rgb(51, 51, 51)" formats={['avif', 'jpeg']} />
<br />
<Picture id="ipsum" src="https://picsum.photos/200/300" sizes="100vw" widths={[100, 200]} aspectRatio={2/3} formats={["avif", "webp", "jpg"]} alt="ipsum" />
</body>
</html>

View file

@ -15,3 +15,5 @@ import { Image } from '@astrojs/image/components';
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />
<br />
<Image id="query" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png?token=abc" width={544} height={184} format="webp" alt="query" />
<br />
<Image id="bg-color" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="jpeg" alt="Google" background="#333333" />

View file

@ -50,12 +50,34 @@ describe('SSG images - dev', function () {
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
},
{
title: 'Public images',
id: '#hero',
url: '/_image',
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'jpeg',
w: '544',
h: '184',
bg: '#333333',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
].forEach(({ title, id, url, query }) => {
it(title, () => {
const image = $(id);
@ -120,12 +142,35 @@ describe('SSG images with subpath - dev', function () {
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
},
{
title: 'Public images',
id: '#hero',
url: '/_image',
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'jpeg',
w: '544',
h: '184',
bg: '#333333',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
].forEach(({ title, id, url, query }) => {
it(title, () => {
const image = $(id);
@ -189,12 +234,24 @@ describe('SSG images - build', function () {
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.webp/,
size: { width: 544, height: 184, type: 'webp' },
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
},
{
title: 'Public images',
id: '#hero',
regex: /^\/hero_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Remote images',
id: '#bg-color',
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
size: { width: 544, height: 184, type: 'jpg' },
},
].forEach(({ title, id, regex, size }) => {
it(title, () => {
const image = $(id);
@ -253,12 +310,24 @@ describe('SSG images with subpath - build', function () {
regex: /^\/docs\/googlelogo_color_272x92dp_\w{4,10}.webp/,
size: { width: 544, height: 184, type: 'webp' },
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/docs\/300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
},
{
title: 'Public images',
id: '#hero',
regex: /^\/docs\/hero_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Remote images',
id: '#bg-color',
regex: /^\/docs\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
size: { width: 544, height: 184, type: 'jpg' },
},
].forEach(({ title, id, regex, size }) => {
it(title, () => {
const image = $(id);

View file

@ -45,6 +45,16 @@ describe('SSR images - build', async function () {
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
},
{
title: 'Remote images with search',
id: '#query',
@ -62,6 +72,18 @@ describe('SSR images - build', async function () {
url: '/_image',
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'jpeg',
w: '544',
h: '184',
bg: '#333333',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
].forEach(({ title, id, url, query }) => {
it(title, async () => {
const app = await fixture.loadTestAdapterApp();
@ -139,6 +161,16 @@ describe('SSR images with subpath - build', function () {
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
},
{
title: 'Remote images with search',
id: '#query',
@ -156,6 +188,18 @@ describe('SSR images with subpath - build', function () {
url: '/_image',
query: { f: 'webp', w: '768', h: '414', href: '/hero.jpg' },
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'jpeg',
w: '544',
h: '184',
bg: '#333333',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
},
].forEach(({ title, id, url, query }) => {
it(title, async () => {
const app = await fixture.loadTestAdapterApp();

View file

@ -58,6 +58,17 @@ describe('SSR images - dev', function () {
},
contentType: 'image/webp',
},
{
title: 'Remote wihtout file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
contentType: 'image/jpeg',
},
{
title: 'Public images',
id: '#hero',
@ -70,6 +81,19 @@ describe('SSR images - dev', function () {
},
contentType: 'image/webp',
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'jpeg',
w: '544',
h: '184',
bg: '#333333',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
contentType: 'image/jpeg',
},
].forEach(({ title, id, url, query, contentType }) => {
it(title, async () => {
const image = $(id);
@ -149,6 +173,17 @@ describe('SSR images with subpath - dev', function () {
},
contentType: 'image/webp',
},
{
title: 'Remote wihtout file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
contentType: 'image/jpeg',
},
{
title: 'Public images',
id: '#hero',
@ -161,6 +196,19 @@ describe('SSR images with subpath - dev', function () {
},
contentType: 'image/webp',
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'jpeg',
w: '544',
h: '184',
bg: '#333333',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
contentType: 'image/jpeg',
},
].forEach(({ title, id, url, query, contentType }) => {
it(title, async () => {
const image = $(id);

View file

@ -56,6 +56,19 @@ describe('SSG pictures - dev', function () {
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
alt: 'Hero image',
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'png',
w: '544',
h: '184',
bg: 'rgb(51, 51, 51)',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
alt: 'Google logo',
},
].forEach(({ title, id, url, query, alt }) => {
it(title, () => {
const sources = $(`${id} source`);
@ -200,6 +213,13 @@ describe('SSG pictures - build', function () {
size: { width: 544, height: 184, type: 'png' },
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
alt: 'ipsum',
},
{
title: 'Public images',
id: '#hero',
@ -288,6 +308,13 @@ describe('SSG pictures with subpath - build', function () {
size: { width: 544, height: 184, type: 'png' },
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
regex: /^\/docs\/300_\w{4,10}/,
size: { width: 200, height: 300, type: 'jpg' },
alt: 'ipsum',
},
{
title: 'Public images',
id: '#hero',

View file

@ -42,6 +42,17 @@ describe('SSR pictures - build', function () {
},
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
alt: 'ipsum',
},
{
title: 'Public images',
id: '#hero',
@ -49,6 +60,19 @@ describe('SSR pictures - build', function () {
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
alt: 'Hero image',
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'png',
w: '544',
h: '184',
bg: 'rgb(51, 51, 51)',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
alt: 'Google logo',
},
].forEach(({ title, id, url, query }) => {
it(title, async () => {
const app = await fixture.loadTestAdapterApp();
@ -122,6 +146,17 @@ describe('SSR pictures with subpath - build', function () {
},
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
alt: 'ipsum',
},
{
title: 'Public images',
id: '#hero',
@ -129,6 +164,19 @@ describe('SSR pictures with subpath - build', function () {
query: { f: 'jpg', w: '768', h: '414', href: '/hero.jpg' },
alt: 'Hero image',
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'png',
w: '544',
h: '184',
bg: 'rgb(51, 51, 51)',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
alt: 'Google logo',
},
].forEach(({ title, id, url, query }) => {
it(title, async () => {
const app = await fixture.loadTestAdapterApp();

View file

@ -54,6 +54,19 @@ describe('SSR pictures - dev', function () {
contentType: 'image/png',
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
f: 'jpg',
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
contentType: 'image/jpeg',
alt: 'ipsum',
},
{
title: 'Public images',
id: '#hero',
@ -67,6 +80,20 @@ describe('SSR pictures - dev', function () {
contentType: 'image/jpeg',
alt: 'Hero image',
},
{
title: 'Background color',
id: '#bg-color',
url: '/_image',
query: {
f: 'png',
w: '544',
h: '184',
bg: 'rgb(51, 51, 51)',
href: 'https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png',
},
contentType: 'image/png',
alt: 'Google logo',
},
].forEach(({ title, id, url, query, alt, contentType }) => {
it(title, async () => {
const sources = $(`${id} source`);
@ -148,6 +175,20 @@ describe('SSR pictures with subpath - dev', function () {
contentType: 'image/png',
alt: 'Google logo',
},
{
title: 'Remote without file extension',
id: '#ipsum',
url: '/_image',
query: {
f: 'jpg',
w: '200',
h: '300',
href: 'https://picsum.photos/200/300',
},
contentType: 'image/jpeg',
alt: 'ipsum',
},
,
{
title: 'Public images',
id: '#hero',

View file

@ -14,6 +14,7 @@ describe('Sharp service', () => {
['width & height', { src, height: 400, width: 200 }],
['aspect ratio string', { src, aspectRatio: '16:9' }],
['aspect ratio float', { src, aspectRatio: 1.7 }],
['background color', { src, format: 'jpeg', background: '#333333' }],
].forEach(([description, props]) => {
it(description, async () => {
const { searchParams } = await sharp.serializeTransform(props);
@ -31,6 +32,7 @@ describe('Sharp service', () => {
verifyProp(props.width, 'w');
verifyProp(props.height, 'h');
verifyProp(props.aspectRatio, 'ar');
verifyProp(props.background, 'bg');
});
});
});
@ -48,6 +50,11 @@ describe('Sharp service', () => {
['width & height', `w=200&h=400&href=${href}`, { src, height: 400, width: 200 }],
['aspect ratio string', `ar=16:9&href=${href}`, { src, aspectRatio: '16:9' }],
['aspect ratio float', `ar=1.7&href=${href}`, { src, aspectRatio: 1.7 }],
[
'background color',
`f=jpeg&bg=%23333333&href=${href}`,
{ src, format: 'jpeg', background: '#333333' },
],
].forEach(([description, params, expected]) => {
it(description, async () => {
const searchParams = new URLSearchParams(params);

View file

@ -49,6 +49,12 @@ describe('Images in MDX - build', function () {
regex: /^\/hero_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'Background color',
id: '#bg-color',
regex: /^\/googlelogo_color_272x92dp_\w{4,10}.jpeg/,
size: { width: 544, height: 184, type: 'jpg' },
},
].forEach(({ title, id, regex, size }) => {
it(title, () => {
const image = $(id);

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