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

@ -29,16 +29,16 @@ The `astro add` command-line tool automates the installation for you. Run one of
```sh
# Using NPM
npm run astro add image
npx astro add image
# Using Yarn
yarn astro add image
# Using PNPM
pnpm astro add image
```
Then, restart the dev server by typing `CTRL-C` and then `npm run astro dev` in the terminal window that was running Astro.
Finally, in the terminal window running Astro, press `CTRL+C` and then restart the dev server.
Because this command is new, it might not properly set things up. If that happens, [feel free to log an issue on our GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/withastro/astro/issues) and try the manual installation steps below.
### Manual Install
@ -190,6 +190,24 @@ A `string` can be provided in the form of `{width}:{height}`, ex: `16:9` or `3:4
A `number` can also be provided, useful when the aspect ratio is calculated at build time. This can be an inline number such as `1.777` or inlined as a JSX expression like `aspectRatio={16/9}`.
#### background
<p>
**Type:** `ColorDefinition`<br>
**Default:** `undefined`
</p>
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
as default replacement for transparent pixels.
The parameter accepts a `string` as value.
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
`rgb(100,100,100)`.
### `<Picture /`>
#### src
@ -271,6 +289,24 @@ A `number` can also be provided, useful when the aspect ratio is calculated at b
The output formats to be used in the optimized image. If not provided, `webp` and `avif` will be used in addition to the original image format.
#### background
<p>
**Type:** `ColorDefinition`<br>
**Default:** `undefined`
</p>
The background color to use for replacing the alpha channel with `sharp`'s `flatten` method. In case the output format
doesn't support transparency (i.e. `jpeg`), it's advisable to include a background color, otherwise black will be used
as default replacement for transparent pixels.
The parameter accepts a `string` as value.
The parameter can be a [named HTML color](https://www.w3schools.com/tags/ref_colornames.asp), a hexadecimal
color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`, or an RGB definition in the form
`rgb(100,100,100)`.
### `getImage`
This is the helper function used by the `<Image />` component to build `<img />` attributes for the transformed image. This helper can be used directly for more complex use cases that aren't currently supported by the `<Image />` component.

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