Compare commits

...

49 commits

Author SHA1 Message Date
Matthew Phillips
0d57cafd04 Implement getting hoisted scripts for propagation 2023-02-03 12:22:48 -05:00
bholmesdev
0ae1c91b8d chore: clarify analyzer comments 2023-02-03 12:22:08 -05:00
Matthew Phillips
5885a7e67c updated 2023-02-03 12:22:06 -05:00
Houston (Bot)
70342a8306 [ci] update lockfile (#6115)
Co-authored-by: FredKSchott <FredKSchott@users.noreply.github.com>
2023-02-03 12:20:38 -05:00
matthewp
f1b42d81bb [ci] format 2023-02-03 12:20:38 -05:00
Matthew Phillips
ffd09a91aa Refactor build into plugins (#6077)
* Refactor build into plugins

* maybe fix internals

* Await post-build hooks

* Use extendManualChunks

* Remove commented out code
2023-02-03 12:20:36 -05:00
Bjorn Lu
91b3307b98 Fix scanning sourcemap handling (#6114) 2023-02-03 12:18:19 -05:00
Houston (Bot)
d04dbdfa19 [ci] release (#6109)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
matthewp
f4cb1b70b5 [ci] format 2023-02-03 12:18:19 -05:00
Matthew Phillips
3ec76dc571 Node adapter: handle prerendering and serving with query params (#6110)
* Node adapter: handle prerendering and serving with query params

* Adding a changeset
2023-02-03 12:18:19 -05:00
Matthew Phillips
331e394ca1 Upgrade preact's dependencies to fix security issue (#6108) 2023-02-03 12:18:19 -05:00
Houston (Bot)
a1966ff1d6 [ci] release (#6094)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
matthewp
ac487a4ced [ci] format 2023-02-03 12:18:19 -05:00
Matthew Phillips
c8d601ebeb Prevent eager rendering of head content in multi-level MDX layout (#6107)
* Prevent eager rendering of head content in multi-level MDX layout

* Adding a changeset

* Remove old comment

* Keep track of slot position as well
2023-02-03 12:18:19 -05:00
Yasser Hennawi
cd425bf448 chore: bump vitefu for @astrojs/solid-js (#6104)
Co-authored-by: Yasser Hennawi <yasser.hennawi@nordsec.com>
2023-02-03 12:18:19 -05:00
Houston (Bot)
631a773dbb [ci] update lockfile (#6100)
Co-authored-by: FredKSchott <FredKSchott@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
bholmesdev
a342882859 [ci] format 2023-02-03 12:18:19 -05:00
Daniel
39aae03429 fix: solidjs integration for vercel edge build (adopting same mechanics as cloudflare) (#6085)
* fix solidjs integration for vercel deployment

* downgrade change to patch

---------

Co-authored-by: AirBorne04 <>
2023-02-03 12:18:19 -05:00
Houston (Bot)
2934c3a9e9 [ci] release (#6089)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
Matthew Phillips
af4dc9ffe2 Move @netlify/functions to be a dep (#6090)
* Move @netlify/functions to be a de

* Adding a changeset
2023-02-03 12:18:19 -05:00
Shiina
a84e934d21 Fix incorrent encoded when path has other language characters (#6088)
* fix: Incorrent encoded when path has Chinese

* chore: exec changeset
2023-02-03 12:18:19 -05:00
Houston (Bot)
e88ea2c7cf [ci] release (#6048)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
matthewp
095158bb86 [ci] format 2023-02-03 12:18:19 -05:00
Elliott Marquez
b5c6a9355c [Lit] Forwards compatiblity for streaming Declarative Shadow DOM (#6055)
* Forwards compatiblity for streaming DSD

* add shadowrootmode

* update tests

* add changeset
2023-02-03 12:18:19 -05:00
Houston (Bot)
7944d28482 [ci] update lockfile (#6060)
Co-authored-by: FredKSchott <FredKSchott@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
Ben Holmes
80dc1a5cbd [Content collections] Apply MDX components on render (#6064)
* fix: apply MDX components during render()

* test: MDX components export in SSG and SSR

* chore: changeset
2023-02-03 12:18:19 -05:00
bholmesdev
a784e603a9 [ci] format 2023-02-03 12:18:19 -05:00
Daniel
1126c750ed fix: Failed to execute 'encode' on 'TextEncoder': parameter 1 is not of type 'String' in Edge Runtime SSR (#6070)
* minor fixes for errors related to vercel SSR in core

* yielding empty string instead of nothing, to not exit the iterator

---------

Co-authored-by: AirBorne04 <>
2023-02-03 12:18:19 -05:00
matthewp
7747f0099a [ci] format 2023-02-03 12:18:19 -05:00
Elliott Marquez
262a24b5c3 [Lit] Fix hydration not having the same reactive values as server (#6080)
* Fix lit hydration not having the same reactive values

* add changeset

* add clientEntrypoint to package exports

* update tests

* add changeset

* only add defer-hydration when strictly necessary

* remove second changest

* fix test typos
2023-02-03 12:18:19 -05:00
Yan Thomas
6386abd30b Add JSDOC @message to fix docs error pages (#6076) 2023-02-03 12:18:19 -05:00
Bryce Russell
4f5b224cda Add required alt to Picture example (#6074)
Co-authored-by: BryceRussell <19967622+BryceRussell@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
Nick Buk[0vec]
6a4262f962 Add links to sharp docs in @astro/image README (#6017)
Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
2023-02-03 12:18:19 -05:00
Ben Holmes
9af39e7628 [MDX] Syntax highlighting after user plugins (#6050)
* fix: load MDX syntax highlighting after user plugins

* chore: changeset
2023-02-03 12:18:19 -05:00
Happydev
6daf548688 Correct usage of getImage function (#6068) 2023-02-03 12:18:19 -05:00
Chris Swithinbank
81f6ece5ca Wrap tag in example error message in code backticks (#6063) 2023-02-03 12:18:19 -05:00
Chris Swithinbank
0c3c216ef2 Revert MDX README changes (#6062) 2023-02-03 12:18:19 -05:00
Princesseuh
680e235f44 [ci] format 2023-02-03 12:18:19 -05:00
Mayank
bbe50ec11c error overlay: show cause if available (#6052)
* show `cause` in error overlay

* add extra check for string

* add changeset
2023-02-03 12:18:19 -05:00
matthewp
31820e4122 [ci] format 2023-02-03 12:18:19 -05:00
Alexey Shmalko
c54a115e29 fix(image): allow usage of image from any directory (#5932)
Currently, @astrojs/image allows *importing* images from srcDir
only. Importing images from outside srcDir fails miserably *in dev
mode* and produces incorrect src.

This happens because `path.relative(fileURLToPath(config.srcDir), id)`
resolves to "../something" and when joined with '/@astroimage' cancels
it out (`join('/@astroimage', '../../something')` => `'/something'`).

Rework /@astroimage URL scheme to be similar to "/@fs/" scheme—always
export absolute path to the target file.
2023-02-03 12:18:19 -05:00
Renato Lacerda
dc37849f1d Fix GetPictureResult interface (#5894) 2023-02-03 12:18:19 -05:00
Houston (Bot)
7de72f3417 [ci] release (#6041)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
natemoo-re
7418ce4737 [ci] format 2023-02-03 12:18:19 -05:00
Nate Moore
0f007f3d56 Handle invalid argument errors (#6045)
* fix: add error handling for invalid arguments

* chore: add changeset

* Update packages/astro/src/core/errors/errors-data.ts

Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com>

---------

Co-authored-by: Nate Moore <nate@astro.build>
Co-authored-by: Yan Thomas <61414485+Yan-Thomas@users.noreply.github.com>
2023-02-03 12:18:19 -05:00
bholmesdev
7f4529f12f chore: unskip 2023-01-30 15:13:11 -05:00
bholmesdev
6e904db135 chore: stray console logs 2023-01-30 15:12:10 -05:00
bholmesdev
d556df728d chore: clarify analyzer comments 2023-01-30 15:11:45 -05:00
bholmesdev
0577e4e9e4 wip: store propagated scripts separately 2023-01-30 15:07:22 -05:00
132 changed files with 1904 additions and 981 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fix sourcemap generation when scanning files

View file

@ -1,5 +0,0 @@
---
'@astrojs/cloudflare': patch
---
Cloudflare fix for building to directory mode

View file

@ -1,5 +0,0 @@
---
'astro': patch
---
Improve error handling when top-level `return` is present

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
}
}

View file

@ -11,8 +11,8 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"@astrojs/mdx": "^0.15.2",
"astro": "^2.0.6",
"@astrojs/mdx": "^0.16.0",
"@astrojs/rss": "^2.1.0",
"@astrojs/sitemap": "^1.0.1"
}

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
},
"peerDependencies": {
"astro": "^2.0.0-beta.0"

View file

@ -10,7 +10,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
},
"devDependencies": {
"@astrojs/deno": "^4.0.0"

View file

@ -11,12 +11,12 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"preact": "^10.7.3",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"@astrojs/react": "^2.0.2",
"@astrojs/preact": "^2.0.1",
"@astrojs/preact": "^2.0.2",
"@algolia/client-search": "^4.13.1",
"@docsearch/css": "^3.1.0",
"@docsearch/react": "^3.1.0",

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"alpinejs": "^3.10.2",
"@astrojs/alpinejs": "^0.1.3",
"@types/alpinejs": "^3.7.0"

View file

@ -11,9 +11,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"lit": "^2.2.5",
"@astrojs/lit": "^1.1.1",
"@astrojs/lit": "^1.1.2",
"@webcomponents/template-shadowroot": "^0.1.0"
}
}

View file

@ -11,16 +11,16 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"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": "^2.0.1",
"@astrojs/preact": "^2.0.2",
"@astrojs/react": "^2.0.2",
"@astrojs/solid-js": "^2.0.1",
"@astrojs/solid-js": "^2.0.2",
"@astrojs/svelte": "^2.0.1",
"@astrojs/vue": "^2.0.1"
}

View file

@ -11,9 +11,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"preact": "^10.7.3",
"@astrojs/preact": "^2.0.1",
"@astrojs/preact": "^2.0.2",
"@preact/signals": "^1.1.0"
}
}

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"react": "^18.1.0",
"react-dom": "^18.1.0",
"@astrojs/react": "^2.0.2",

View file

@ -11,8 +11,8 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"solid-js": "^1.4.3",
"@astrojs/solid-js": "^2.0.1"
"@astrojs/solid-js": "^2.0.2"
}
}

View file

@ -13,6 +13,6 @@
"dependencies": {
"svelte": "^3.48.0",
"@astrojs/svelte": "^2.0.1",
"astro": "^2.0.3"
"astro": "^2.0.6"
}
}

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"vue": "^3.2.37",
"@astrojs/vue": "^2.0.1"
}

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/node": "^5.0.1",
"astro": "^2.0.3"
"@astrojs/node": "^5.0.3",
"astro": "^2.0.6"
}
}

View file

@ -15,7 +15,7 @@
],
"scripts": {},
"devDependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
},
"peerDependencies": {
"astro": "^2.0.0-beta.0"

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
}
}

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
}
}

View file

@ -12,10 +12,10 @@
"server": "node dist/server/entry.mjs"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"svelte": "^3.48.0",
"@astrojs/svelte": "^2.0.1",
"@astrojs/node": "^5.0.1",
"@astrojs/node": "^5.0.3",
"concurrently": "^7.2.1",
"unocss": "^0.15.6",
"vite-imagetools": "^4.0.4"

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"@astrojs/markdown-remark": "^2.0.1",
"hast-util-select": "5.0.1",
"rehype-autolink-headings": "^6.1.1",

View file

@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3"
"astro": "^2.0.6"
}
}

View file

@ -11,9 +11,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"preact": "^10.6.5",
"@astrojs/preact": "^2.0.1",
"@astrojs/mdx": "^0.15.2"
"@astrojs/preact": "^2.0.2",
"@astrojs/mdx": "^0.16.0"
}
}

View file

@ -11,9 +11,9 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"preact": "^10.7.3",
"@astrojs/preact": "^2.0.1",
"@astrojs/preact": "^2.0.2",
"nanostores": "^0.5.12",
"@nanostores/preact": "^0.1.3"
}

View file

@ -11,10 +11,10 @@
"astro": "astro"
},
"dependencies": {
"@astrojs/mdx": "^0.15.2",
"@astrojs/mdx": "^0.16.0",
"@astrojs/tailwind": "^3.0.1",
"@types/canvas-confetti": "^1.4.3",
"astro": "^2.0.3",
"astro": "^2.0.6",
"autoprefixer": "^10.4.7",
"canvas-confetti": "^1.5.1",
"postcss": "^8.4.14",

View file

@ -11,7 +11,7 @@
"astro": "astro"
},
"dependencies": {
"astro": "^2.0.3",
"astro": "^2.0.6",
"vite-plugin-pwa": "0.11.11",
"workbox-window": "^6.5.3"
}

View file

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

View file

@ -1,5 +1,31 @@
# astro
## 2.0.6
### Patch Changes
- [#6107](https://github.com/withastro/astro/pull/6107) [`9bec6bc41`](https://github.com/withastro/astro/commit/9bec6bc410f324a41c67e5d185fa86f78d7625f2) Thanks [@matthewp](https://github.com/matthewp)! - Fixes head contents being placed in body in MDX components
## 2.0.5
### Patch Changes
- [#6052](https://github.com/withastro/astro/pull/6052) [`9793f19ec`](https://github.com/withastro/astro/commit/9793f19ecd4e64cbf3140454fe52aeee2c22c8c9) Thanks [@mayank99](https://github.com/mayank99)! - Error overlay will now show the error's `cause` if available.
- [#6070](https://github.com/withastro/astro/pull/6070) [`f91615f5c`](https://github.com/withastro/astro/commit/f91615f5c04fde36f115dad9110dd75254efd61d) Thanks [@AirBorne04](https://github.com/AirBorne04)! - \* safe guard against TextEncode.encode(HTMLString) [errors on vercel edge]
- safe guard against html.replace when html is undefined
- [#6064](https://github.com/withastro/astro/pull/6064) [`2fb72c887`](https://github.com/withastro/astro/commit/2fb72c887f71c0a69ab512870d65b8c867774766) Thanks [@bholmesdev](https://github.com/bholmesdev)! - Apply MDX `components` export when rendering as a content collection entry
## 2.0.4
### Patch Changes
- [#6045](https://github.com/withastro/astro/pull/6045) [`41e97158b`](https://github.com/withastro/astro/commit/41e97158ba90d23d346b6e3ff6c7c14b5ecbe903) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve error handling when an Astro component is rendered manually
- [#6036](https://github.com/withastro/astro/pull/6036) [`e779c6242`](https://github.com/withastro/astro/commit/e779c6242418d1d4102e683ca5b851b764c89688) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Improve error handling when top-level `return` is present
## 2.0.3
### Patch Changes

View file

@ -0,0 +1,35 @@
import { LitElement, html } from 'lit';
export default class NonDeferredCounter extends LitElement {
static get properties() {
return {
count: {
type: Number,
// All set properties are reflected to attributes so its hydration is
// not deferred.
reflect: true,
},
};
}
constructor() {
super();
this.count = 0;
}
increment() {
this.count++;
}
render() {
return html`
<div>
<p>Count: ${this.count}</p>
<button type="button" @click=${this.increment}>Increment</button>
</div>
`;
}
}
customElements.define('non-deferred-counter', NonDeferredCounter);

View file

@ -1,8 +1,9 @@
---
import MyCounter from '../components/Counter.js';
import NonDeferredCounter from '../components/NonDeferredCounter.js';
const someProps = {
count: 0,
count: 10,
};
---
@ -15,6 +16,9 @@ const someProps = {
<h1>Hello, client:idle!</h1>
</MyCounter>
<NonDeferredCounter id="non-deferred" client:idle {...someProps}>
</NonDeferredCounter>
<MyCounter id="client-load" {...someProps} client:load>
<h1>Hello, client:load!</h1>
</MyCounter>

View file

@ -2,7 +2,7 @@
import MyCounter from '../components/Counter.js';
const someProps = {
count: 0,
count: 10,
};
---

View file

@ -2,7 +2,7 @@
import MyCounter from '../components/Counter.js';
const someProps = {
count: 0,
count: 10,
};
---

View file

@ -32,12 +32,25 @@ test.describe('Lit components', () => {
await expect(counter).toHaveCount(1);
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
await expect(count, 'initial count is 10').toHaveText('Count: 10');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
await expect(count, 'count incremented by 1').toHaveText('Count: 11');
});
t('non-deferred attribute serialization', async ({ page, astro }) => {
await page.goto(astro.resolveUrl('/'));
const counter = page.locator('#non-deferred');
const count = counter.locator('p');
await expect(count, 'initial count is 10').toHaveText('Count: 10');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 11');
});
t('client:load', async ({ page, astro }) => {
@ -47,12 +60,12 @@ test.describe('Lit components', () => {
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
await expect(count, 'initial count is 10').toHaveText('Count: 10');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
await expect(count, 'count incremented by 1').toHaveText('Count: 11');
});
t('client:visible', async ({ page, astro }) => {
@ -64,12 +77,12 @@ test.describe('Lit components', () => {
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
await expect(count, 'initial count is 10').toHaveText('Count: 10');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
await expect(count, 'count incremented by 1').toHaveText('Count: 11');
});
t('client:media', async ({ page, astro }) => {
@ -79,18 +92,18 @@ test.describe('Lit components', () => {
await expect(counter, 'component is visible').toBeVisible();
const count = counter.locator('p');
await expect(count, 'initial count is 0').toHaveText('Count: 0');
await expect(count, 'initial count is 10').toHaveText('Count: 10');
const inc = counter.locator('button');
await inc.click();
await expect(count, 'component not hydrated yet').toHaveText('Count: 0');
await expect(count, 'component not hydrated yet').toHaveText('Count: 10');
// Reset the viewport to hydrate the component (max-width: 50rem)
await page.setViewportSize({ width: 414, height: 1124 });
await inc.click();
await expect(count, 'count incremented by 1').toHaveText('Count: 1');
await expect(count, 'count incremented by 1').toHaveText('Count: 11');
});
t.skip('HMR', async ({ page, astro }) => {

View file

@ -1,6 +1,6 @@
{
"name": "astro",
"version": "2.0.3",
"version": "2.0.6",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module",
"author": "withastro",

View file

@ -1449,6 +1449,10 @@ export interface SSRResult {
): AstroGlobal;
resolve: (s: string) => Promise<string>;
response: ResponseInit;
// Bits 1 = astro, 2 = jsx, 4 = slot
// As rendering occurs these bits are manipulated to determine where content
// is within a slot. This is used for head injection.
scope: number;
_metadata: SSRMetadata;
}

View file

@ -2,7 +2,6 @@ export { createContentTypesGenerator } from './types-generator.js';
export { contentObservable, getContentPaths, getDotAstroTypeReference } from './utils.js';
export {
astroContentAssetPropagationPlugin,
astroContentProdBundlePlugin,
} from './vite-plugin-content-assets.js';
export { astroContentServerPlugin } from './vite-plugin-content-server.js';
export { astroContentVirtualModPlugin } from './vite-plugin-content-virtual-mod.js';

View file

@ -1,3 +1,4 @@
import { AstroError, AstroErrorData } from '../core/errors/index.js';
import { prependForwardSlash } from '../core/path.js';
import {
@ -120,21 +121,32 @@ async function render({
id: string;
collectionToRenderEntryMap: CollectionToEntryMap;
}) {
const lazyImport = collectionToRenderEntryMap[collection]?.[id];
if (!lazyImport) throw new Error(`${String(collection)}${String(id)} does not exist.`);
const UnexpectedRenderError = new AstroError({
...AstroErrorData.UnknownContentCollectionError,
message: `Unexpected error while rendering ${String(collection)}${String(id)}.`,
});
const mod = await lazyImport();
const lazyImport = collectionToRenderEntryMap[collection]?.[id];
if (typeof lazyImport !== 'function') throw UnexpectedRenderError;
const baseMod = await lazyImport();
if (baseMod == null || typeof baseMod !== 'object') throw UnexpectedRenderError;
const { collectedStyles, collectedLinks, collectedScripts, getMod } = baseMod;
if (typeof getMod !== 'function') throw UnexpectedRenderError;
const mod = await getMod();
if (mod == null || typeof mod !== 'object') throw UnexpectedRenderError;
const Content = createComponent({
factory(result, props, slots) {
factory(result, baseProps, slots) {
let styles = '',
links = '',
scripts = '';
if (Array.isArray(mod?.collectedStyles)) {
styles = mod.collectedStyles.map((style: any) => renderStyleElement(style)).join('');
if (Array.isArray(collectedStyles)) {
styles = collectedStyles.map((style: any) => renderStyleElement(style)).join('');
}
if (Array.isArray(mod?.collectedLinks)) {
links = mod.collectedLinks
if (Array.isArray(collectedLinks)) {
links = collectedLinks
.map((link: any) => {
return renderUniqueStylesheet(result, {
href: prependForwardSlash(link),
@ -142,8 +154,17 @@ async function render({
})
.join('');
}
if (Array.isArray(mod?.collectedScripts)) {
scripts = mod.collectedScripts.map((script: any) => renderScriptElement(script)).join('');
if (Array.isArray(collectedScripts)) {
scripts = collectedScripts.map((script: any) => renderScriptElement(script)).join('');
}
let props = baseProps;
// Auto-apply MDX components export
if (id.endsWith('mdx')) {
props = {
components: mod.components ?? {},
...baseProps,
};
}
return createHeadAndContent(

View file

@ -1,7 +1,9 @@
import { pathToFileURL } from 'url';
import npath from 'node:path';
import type { Plugin } from 'vite';
import { moduleIsTopLevelPage, walkParentInfos } from '../core/build/graph.js';
import { BuildInternals, getPageDataByViteID } from '../core/build/internal.js';
import { AstroBuildPlugin } from '../core/build/plugin.js';
import type { ModuleLoader } from '../core/module-loader/loader.js';
import { createViteLoader } from '../core/module-loader/vite.js';
import { getStylesForURL } from '../core/render/dev/css.js';
@ -13,6 +15,8 @@ import {
SCRIPTS_PLACEHOLDER,
STYLES_PLACEHOLDER,
} from './consts.js';
import type { RollupOutput, OutputChunk, StaticBuildOptions } from '../core/build/types';
import { prependForwardSlash } from '../core/path.js';
function isPropagatedAsset(viteId: string): boolean {
const url = new URL(viteId, 'file://');
@ -36,7 +40,9 @@ export function astroContentAssetPropagationPlugin({ mode }: { mode: string }):
if (isPropagatedAsset(id)) {
const basePath = id.split('?')[0];
const code = `
export { Content, getHeadings, frontmatter } from ${JSON.stringify(basePath)};
export async function getMod() {
return import(${JSON.stringify(basePath)});
}
export const collectedLinks = ${JSON.stringify(LINKS_PLACEHOLDER)};
export const collectedStyles = ${JSON.stringify(STYLES_PLACEHOLDER)};
export const collectedScripts = ${JSON.stringify(SCRIPTS_PLACEHOLDER)};
@ -70,26 +76,92 @@ export function astroContentAssetPropagationPlugin({ mode }: { mode: string }):
};
}
export function astroContentProdBundlePlugin({ internals }: { internals: BuildInternals }): Plugin {
export function astroConfigBuildPlugin(options: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin {
let ssrPluginContext: any = undefined;
return {
name: 'astro:content-prod-bundle',
async generateBundle(_options, bundle) {
for (const [_, chunk] of Object.entries(bundle)) {
if (chunk.type === 'chunk' && chunk.code.includes(LINKS_PLACEHOLDER)) {
for (const id of Object.keys(chunk.modules)) {
for (const [pageInfo, depth, order] of walkParentInfos(id, this)) {
if (moduleIsTopLevelPage(pageInfo)) {
const pageViteID = pageInfo.id;
const pageData = getPageDataByViteID(internals, pageViteID);
if (!pageData) continue;
const entryCss = pageData.contentCollectionCss?.get(id);
if (!entryCss) continue;
chunk.code = chunk.code.replace(
JSON.stringify(LINKS_PLACEHOLDER),
JSON.stringify([...entryCss])
);
build: 'ssr',
hooks: {
'build:before': ({ build }) => {
return {
vitePlugin: {
name: 'astro:content-build-plugin',
generateBundle() {
if(build === 'ssr') {
ssrPluginContext = this;
}
}
},
};
},
'build:post': ({ ssrOutputs, clientOutputs, mutate }) => {
const outputs = ssrOutputs.flatMap(o => o.output);
for (const chunk of outputs) {
if (
chunk.type === 'chunk' &&
(chunk.code.includes(LINKS_PLACEHOLDER) || chunk.code.includes(SCRIPTS_PLACEHOLDER))
) {
let entryCSS = new Set<string>();
let entryScripts = new Set<string>();
for (const id of Object.keys(chunk.modules)) {
for (const [pageInfo] of walkParentInfos(id, ssrPluginContext)) {
if (moduleIsTopLevelPage(pageInfo)) {
const pageViteID = pageInfo.id;
const pageData = getPageDataByViteID(internals, pageViteID);
if (!pageData) continue;
const _entryCss = pageData.contentCollectionCss?.get(id);
const _entryScripts = pageData.propagatedScripts?.get(id);
if(_entryCss) {
for(const value of _entryCss) {
entryCSS.add(value);
}
}
if(_entryScripts) {
for(const value of _entryScripts) {
entryScripts.add(value);
}
}
}
}
}
let newCode = chunk.code;
if (entryCSS.size) {
newCode = newCode.replace(
JSON.stringify(LINKS_PLACEHOLDER),
JSON.stringify([...entryCSS])
);
}
if (entryScripts.size) {
const entryFileNames = new Set<string>();
for(const output of clientOutputs) {
for(const clientChunk of output.output) {
if(clientChunk.type !== 'chunk') continue;
for(const [id] of Object.entries(clientChunk.modules)) {
if(entryScripts.has(id)) {
entryFileNames.add(clientChunk.fileName);
}
}
}
}
newCode = newCode.replace(
JSON.stringify(SCRIPTS_PLACEHOLDER),
JSON.stringify(
[...entryFileNames].map((src) => ({
props: {
src: prependForwardSlash(npath.posix.join(
options.settings.config.base,
src
)),
type: 'module'
},
children: '',
}))
)
);
}
mutate(chunk, 'server', newCode);
}
}
}

View file

@ -40,9 +40,10 @@ export function moduleIsTopLevelPage(info: ModuleInfo): boolean {
// This could be a .astro page, a .markdown or a .md (or really any file extension for markdown files) page.
export function* getTopLevelPages(
id: string,
ctx: { getModuleInfo: GetModuleInfo }
ctx: { getModuleInfo: GetModuleInfo },
walkUntil?: (importer: string) => boolean
): Generator<[ModuleInfo, number, number], void, unknown> {
for (const res of walkParentInfos(id, ctx)) {
for (const res of walkParentInfos(id, ctx, walkUntil)) {
if (moduleIsTopLevelPage(res[0])) {
yield res;
}

View file

@ -55,6 +55,7 @@ export async function collectPagesData(
moduleSpecifier: '',
css: new Map(),
contentCollectionCss: new Map(),
propagatedScripts: new Map(),
hoistedScript: undefined,
};
@ -77,6 +78,7 @@ export async function collectPagesData(
moduleSpecifier: '',
css: new Map(),
contentCollectionCss: new Map(),
propagatedScripts: new Map(),
hoistedScript: undefined,
};
}

View file

@ -0,0 +1,120 @@
import type { Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from './internal';
import type { StaticBuildOptions, ViteBuildReturn } from './types';
type RollupOutputArray = Extract<ViteBuildReturn, Array<any>>;
type OutputChunkorAsset = RollupOutputArray[number]['output'][number];
type OutputChunk = Extract<OutputChunkorAsset, { type: 'chunk' }>;
type MutateChunk = (chunk: OutputChunk, build: 'server' | 'client', newCode: string) => void;
export type AstroBuildPlugin = {
build: 'ssr' | 'client' | 'both';
hooks?: {
'build:before'?: (opts: { build: 'ssr' | 'client'; input: Set<string> }) => {
enforce?: 'after-user-plugins';
vitePlugin: VitePlugin | VitePlugin[] | undefined;
};
'build:post'?: (opts: {
ssrOutputs: RollupOutputArray;
clientOutputs: RollupOutputArray;
mutate: MutateChunk;
}) => void | Promise<void>;
};
};
export function createPluginContainer(options: StaticBuildOptions, internals: BuildInternals) {
const clientPlugins: AstroBuildPlugin[] = [];
const ssrPlugins: AstroBuildPlugin[] = [];
const allPlugins = new Set<AstroBuildPlugin>();
return {
options,
internals,
register(plugin: AstroBuildPlugin) {
allPlugins.add(plugin);
switch (plugin.build) {
case 'client': {
clientPlugins.push(plugin);
break;
}
case 'ssr': {
ssrPlugins.push(plugin);
break;
}
case 'both': {
clientPlugins.push(plugin);
ssrPlugins.push(plugin);
break;
}
}
},
// Hooks
runBeforeHook(build: 'ssr' | 'client', input: Set<string>) {
let plugins = build === 'ssr' ? ssrPlugins : clientPlugins;
let vitePlugins: Array<VitePlugin | VitePlugin[]> = [];
let lastVitePlugins: Array<VitePlugin | VitePlugin[]> = [];
for (const plugin of plugins) {
if (plugin.hooks?.['build:before']) {
let result = plugin.hooks['build:before']({ build, input });
if (result.vitePlugin) {
vitePlugins.push(result.vitePlugin);
}
}
}
return {
vitePlugins,
lastVitePlugins,
};
},
async runPostHook(ssrReturn: ViteBuildReturn, clientReturn: ViteBuildReturn | null) {
const mutations = new Map<
string,
{
build: 'server' | 'client';
code: string;
}
>();
const ssrOutputs: RollupOutputArray = [];
const clientOutputs: RollupOutputArray = [];
if (Array.isArray(ssrReturn)) {
ssrOutputs.push(...ssrReturn);
} else if ('output' in ssrReturn) {
ssrOutputs.push(ssrReturn);
}
if (Array.isArray(clientReturn)) {
clientOutputs.push(...clientReturn);
} else if (clientReturn && 'output' in clientReturn) {
clientOutputs.push(clientReturn);
}
const mutate: MutateChunk = (chunk, build, newCode) => {
chunk.code = newCode;
mutations.set(chunk.fileName, {
build,
code: newCode,
});
};
for (const plugin of allPlugins) {
const postHook = plugin.hooks?.['build:post'];
if (postHook) {
await postHook({
ssrOutputs,
clientOutputs,
mutate,
});
}
}
return mutations;
},
};
}
export type AstroBuildPluginContainer = ReturnType<typeof createPluginContainer>;

View file

@ -0,0 +1,22 @@
import { astroConfigBuildPlugin } from '../../../content/vite-plugin-content-assets.js';
import type { AstroBuildPluginContainer } from '../plugin';
import { pluginAliasResolve } from './plugin-alias-resolve.js';
import { pluginAnalyzer } from './plugin-analyzer.js';
import { pluginCSS } from './plugin-css.js';
import { pluginHoistedScripts } from './plugin-hoisted-scripts.js';
import { pluginInternals } from './plugin-internals.js';
import { pluginPages } from './plugin-pages.js';
import { pluginPrerender } from './plugin-prerender.js';
import { pluginSSR } from './plugin-ssr.js';
export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) {
register(pluginAliasResolve(internals));
register(pluginAnalyzer(internals));
register(pluginInternals(internals));
register(pluginPages(options, internals));
register(pluginCSS(options, internals));
register(pluginPrerender(options, internals));
register(astroConfigBuildPlugin(options, internals));
register(pluginHoistedScripts(options, internals));
register(pluginSSR(options, internals));
}

View file

@ -1,5 +1,6 @@
import type { Alias, Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from '../../core/build/internal.js';
import type { BuildInternals } from '../internal.js';
import { AstroBuildPlugin } from '../plugin.js';
/**
* `@rollup/plugin-alias` doesn't resolve aliases in Rollup input by default. This plugin fixes it
@ -48,3 +49,16 @@ function matches(pattern: string | RegExp, importee: string) {
}
return importee.startsWith(pattern + '/');
}
export function pluginAliasResolve(internals: BuildInternals): AstroBuildPlugin {
return {
build: 'client',
hooks: {
'build:before': () => {
return {
vitePlugin: vitePluginAliasResolve(internals),
};
},
},
};
}

View file

@ -1,16 +1,28 @@
import type { PluginContext } from 'rollup';
import type { Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from '../../core/build/internal.js';
import type { PluginMetadata as AstroPluginMetadata } from '../../vite-plugin-astro/types';
import type { PluginMetadata as AstroPluginMetadata } from '../../../vite-plugin-astro/types';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import { prependForwardSlash } from '../../core/path.js';
import { getTopLevelPages } from './graph.js';
import { getPageDataByViteID, trackClientOnlyPageDatas } from './internal.js';
import { prependForwardSlash } from '../../../core/path.js';
import { getTopLevelPages, moduleIsTopLevelPage, walkParentInfos } from '../graph.js';
import { getPageDataByViteID, trackClientOnlyPageDatas } from '../internal.js';
import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js';
function isPropagatedAsset(id: string) {
return new URL('file://' + id).searchParams.has(PROPAGATED_ASSET_FLAG);
}
export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
function hoistedScriptScanner() {
const uniqueHoistedIds = new Map<string, string>();
const pageScripts = new Map<string, Set<string>>();
const pageScripts = new Map<
string,
{
hoistedSet: Set<string>;
propagatedMapByImporter: Map<string, Set<string>>;
}
>();
return {
scan(this: PluginContext, scripts: AstroPluginMetadata['astro']['scripts'], from: string) {
@ -21,13 +33,36 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
}
if (hoistedScripts.size) {
for (const [pageInfo] of getTopLevelPages(from, this)) {
const pageId = pageInfo.id;
for (const hid of hoistedScripts) {
if (pageScripts.has(pageId)) {
pageScripts.get(pageId)?.add(hid);
} else {
pageScripts.set(pageId, new Set([hid]));
for (const [parentInfo] of walkParentInfos(from, this, function until(importer) {
return isPropagatedAsset(importer);
})) {
if (isPropagatedAsset(parentInfo.id)) {
for (const [nestedParentInfo] of walkParentInfos(from, this)) {
if (moduleIsTopLevelPage(nestedParentInfo)) {
for (const hid of hoistedScripts) {
if (!pageScripts.has(nestedParentInfo.id)) {
pageScripts.set(nestedParentInfo.id, {
hoistedSet: new Set(),
propagatedMapByImporter: new Map(),
});
}
const entry = pageScripts.get(nestedParentInfo.id)!;
if (!entry.propagatedMapByImporter.has(parentInfo.id)) {
entry.propagatedMapByImporter.set(parentInfo.id, new Set());
}
entry.propagatedMapByImporter.get(parentInfo.id)!.add(hid);
}
}
}
} else if (moduleIsTopLevelPage(parentInfo)) {
for (const hid of hoistedScripts) {
if (!pageScripts.has(parentInfo.id)) {
pageScripts.set(parentInfo.id, {
hoistedSet: new Set(),
propagatedMapByImporter: new Map(),
});
}
pageScripts.get(parentInfo.id)?.hoistedSet.add(hid);
}
}
}
@ -35,14 +70,14 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
},
finalize() {
for (const [pageId, hoistedScripts] of pageScripts) {
for (const [pageId, { hoistedSet, propagatedMapByImporter }] of pageScripts) {
const pageData = getPageDataByViteID(internals, pageId);
if (!pageData) continue;
const { component } = pageData;
const astroModuleId = prependForwardSlash(component);
const uniqueHoistedId = JSON.stringify(Array.from(hoistedScripts).sort());
const uniqueHoistedId = JSON.stringify(Array.from(hoistedSet).sort());
let moduleId: string;
// If we're already tracking this set of hoisted scripts, get the unique id
@ -55,13 +90,26 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
}
internals.discoveredScripts.add(moduleId);
// TODO: map raw URL (?type=script) to client build ready URL
// This will inject the raw URL as a script tag as-is,
// which will fail to map with the client build output.
pageData.propagatedScripts = propagatedMapByImporter;
// Add propagated scripts to client build,
// but DON'T add to pages -> hoisted script map.
for (const propagatedScripts of propagatedMapByImporter.values()) {
for (const propagatedScript of propagatedScripts) {
internals.discoveredScripts.add(propagatedScript);
}
}
// Make sure to track that this page uses this set of hoisted scripts
if (internals.hoistedScriptIdToPagesMap.has(moduleId)) {
const pages = internals.hoistedScriptIdToPagesMap.get(moduleId);
pages!.add(astroModuleId);
} else {
internals.hoistedScriptIdToPagesMap.set(moduleId, new Set([astroModuleId]));
internals.hoistedScriptIdToHoistedMap.set(moduleId, hoistedScripts);
internals.hoistedScriptIdToHoistedMap.set(moduleId, hoistedSet);
}
}
},
@ -122,3 +170,16 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
},
};
}
export function pluginAnalyzer(internals: BuildInternals): AstroBuildPlugin {
return {
build: 'ssr',
hooks: {
'build:before': () => {
return {
vitePlugin: vitePluginAnalyzer(internals),
};
}
},
};
}

View file

@ -2,20 +2,22 @@ import * as crypto from 'node:crypto';
import * as npath from 'node:path';
import type { GetModuleInfo } from 'rollup';
import { Plugin as VitePlugin, ResolvedConfig, transformWithEsbuild } from 'vite';
import { isCSSRequest } from '../render/util.js';
import type { BuildInternals } from './internal';
import type { PageBuildData, StaticBuildOptions } from './types';
import { isCSSRequest } from '../../render/util.js';
import type { BuildInternals } from '../internal';
import type { AstroBuildPlugin } from '../plugin';
import type { PageBuildData, StaticBuildOptions } from '../types';
import { PROPAGATED_ASSET_FLAG } from '../../content/consts.js';
import * as assetName from './css-asset-name.js';
import { moduleIsTopLevelPage, walkParentInfos } from './graph.js';
import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js';
import * as assetName from '../css-asset-name.js';
import { moduleIsTopLevelPage, walkParentInfos } from '../graph.js';
import {
eachPageData,
getPageDataByViteID,
getPageDatasByClientOnlyID,
getPageDatasByHoistedScriptId,
isHoistedScript,
} from './internal.js';
} from '../internal.js';
import { extendManualChunks } from './util.js';
interface PluginOptions {
internals: BuildInternals;
@ -54,40 +56,30 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
name: 'astro:rollup-plugin-build-css',
outputOptions(outputOptions) {
const manualChunks = outputOptions.manualChunks || Function.prototype;
const assetFileNames = outputOptions.assetFileNames;
const namingIncludesHash = assetFileNames?.toString().includes('[hash]');
const createNameForParentPages = namingIncludesHash
? assetName.shortHashedName
: assetName.createSlugger(settings);
outputOptions.manualChunks = function (id, ...args) {
// Defer to user-provided `manualChunks`, if it was provided.
if (typeof manualChunks == 'object') {
if (id in manualChunks) {
return manualChunks[id];
}
} else if (typeof manualChunks === 'function') {
const outid = manualChunks.call(this, id, ...args);
if (outid) {
return outid;
}
}
// For CSS, create a hash of all of the pages that use it.
// This causes CSS to be built into shared chunks when used by multiple pages.
if (isCSSRequest(id)) {
for (const [pageInfo] of walkParentInfos(id, {
getModuleInfo: args[0].getModuleInfo,
})) {
if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) {
// Split delayed assets to separate modules
// so they can be injected where needed
return createNameHash(id, [id]);
extendManualChunks(outputOptions, {
after(id, meta) {
// For CSS, create a hash of all of the pages that use it.
// This causes CSS to be built into shared chunks when used by multiple pages.
if (isCSSRequest(id)) {
for (const [pageInfo] of walkParentInfos(id, {
getModuleInfo: meta.getModuleInfo,
})) {
if (new URL(pageInfo.id, 'file://').searchParams.has(PROPAGATED_ASSET_FLAG)) {
// Split delayed assets to separate modules
// so they can be injected where needed
return createNameHash(id, [id]);
}
}
return createNameForParentPages(id, meta);
}
return createNameForParentPages(id, args[0]);
}
};
},
});
},
async generateBundle(_outputOptions, bundle) {
@ -272,3 +264,25 @@ export function rollupPluginAstroBuildCSS(options: PluginOptions): VitePlugin[]
},
];
}
export function pluginCSS(
options: StaticBuildOptions,
internals: BuildInternals
): AstroBuildPlugin {
return {
build: 'both',
hooks: {
'build:before': ({ build }) => {
let plugins = rollupPluginAstroBuildCSS({
buildOptions: options,
internals,
target: build === 'ssr' ? 'server' : 'client',
});
return {
vitePlugin: plugins,
};
},
},
};
}

View file

@ -1,8 +1,10 @@
import type { Plugin as VitePlugin } from 'vite';
import type { AstroSettings } from '../../@types/astro';
import type { BuildInternals } from '../../core/build/internal.js';
import { viteID } from '../util.js';
import { getPageDataByViteID } from './internal.js';
import type { AstroSettings } from '../../../@types/astro';
import { viteID } from '../../util.js';
import type { BuildInternals } from '../internal.js';
import { getPageDataByViteID } from '../internal.js';
import { AstroBuildPlugin } from '../plugin';
import { StaticBuildOptions } from '../types';
function virtualHoistedEntry(id: string) {
return id.startsWith('/astro/hoisted.js?q=');
@ -91,3 +93,19 @@ export function vitePluginHoistedScripts(
},
};
}
export function pluginHoistedScripts(
options: StaticBuildOptions,
internals: BuildInternals
): AstroBuildPlugin {
return {
build: 'client',
hooks: {
'build:before': () => {
return {
vitePlugin: vitePluginHoistedScripts(options.settings, internals),
};
},
},
};
}

View file

@ -1,5 +1,6 @@
import type { Plugin as VitePlugin, UserConfig } from 'vite';
import type { BuildInternals } from './internal.js';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin';
export function vitePluginInternals(input: Set<string>, internals: BuildInternals): VitePlugin {
return {
@ -60,3 +61,16 @@ export function vitePluginInternals(input: Set<string>, internals: BuildInternal
},
};
}
export function pluginInternals(internals: BuildInternals): AstroBuildPlugin {
return {
build: 'both',
hooks: {
'build:before': ({ input }) => {
return {
vitePlugin: vitePluginInternals(input, internals),
};
},
},
};
}

View file

@ -1,8 +1,10 @@
import type { Plugin as VitePlugin } from 'vite';
import { pagesVirtualModuleId, resolvedPagesVirtualModuleId } from '../app/index.js';
import { addRollupInput } from './add-rollup-input.js';
import { BuildInternals, eachPageData, hasPrerenderedPages } from './internal.js';
import type { StaticBuildOptions } from './types';
import type { AstroBuildPlugin } from '../plugin';
import type { StaticBuildOptions } from '../types';
import { pagesVirtualModuleId, resolvedPagesVirtualModuleId } from '../../app/index.js';
import { addRollupInput } from '../add-rollup-input.js';
import { BuildInternals, eachPageData, hasPrerenderedPages } from '../internal.js';
export function vitePluginPages(opts: StaticBuildOptions, internals: BuildInternals): VitePlugin {
return {
@ -53,3 +55,16 @@ export const renderers = [${rendererItems}];`;
},
};
}
export function pluginPages(opts: StaticBuildOptions, internals: BuildInternals): AstroBuildPlugin {
return {
build: 'ssr',
hooks: {
'build:before': () => {
return {
vitePlugin: vitePluginPages(opts, internals),
};
},
},
};
}

View file

@ -0,0 +1,51 @@
import type { Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import type { StaticBuildOptions } from '../types';
import { extendManualChunks } from './util.js';
export function vitePluginPrerender(
opts: StaticBuildOptions,
internals: BuildInternals
): VitePlugin {
return {
name: 'astro:rollup-plugin-prerender',
outputOptions(outputOptions) {
extendManualChunks(outputOptions, {
after(id, meta) {
// Split the Astro runtime into a separate chunk for readability
if (id.includes('astro/dist')) {
return 'astro';
}
const pageInfo = internals.pagesByViteID.get(id);
if (pageInfo) {
// prerendered pages should be split into their own chunk
// Important: this can't be in the `pages/` directory!
if (meta.getModuleInfo(id)?.meta.astro?.pageOptions?.prerender) {
return 'prerender';
}
// dynamic pages should all go in their own chunk in the pages/* directory
return `pages/all`;
}
},
});
},
};
}
export function pluginPrerender(
opts: StaticBuildOptions,
internals: BuildInternals
): AstroBuildPlugin {
return {
build: 'ssr',
hooks: {
'build:before': () => {
return {
vitePlugin: vitePluginPrerender(opts, internals),
};
},
},
};
}

View file

@ -1,21 +1,21 @@
import type { Plugin as VitePlugin } from 'vite';
import type { AstroAdapter } from '../../@types/astro';
import type { SerializedRouteInfo, SerializedSSRManifest } from '../app/types';
import type { BuildInternals } from './internal.js';
import type { StaticBuildOptions } from './types';
import type { AstroAdapter } from '../../../@types/astro';
import type { SerializedRouteInfo, SerializedSSRManifest } from '../../app/types';
import type { BuildInternals } from '../internal.js';
import type { StaticBuildOptions } from '../types';
import glob from 'fast-glob';
import * as fs from 'fs';
import { fileURLToPath } from 'url';
import { getContentPaths } from '../../content/index.js';
import { runHookBuildSsr } from '../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../vite-plugin-scripts/index.js';
import { pagesVirtualModuleId } from '../app/index.js';
import { removeLeadingForwardSlash, removeTrailingForwardSlash } from '../path.js';
import { serializeRouteData } from '../routing/index.js';
import { addRollupInput } from './add-rollup-input.js';
import { getOutFile, getOutFolder } from './common.js';
import { eachPrerenderedPageData, eachServerPageData, sortedCSS } from './internal.js';
import { getContentPaths } from '../../../content/index.js';
import { runHookBuildSsr } from '../../../integrations/index.js';
import { BEFORE_HYDRATION_SCRIPT_ID, PAGE_SCRIPT_ID } from '../../../vite-plugin-scripts/index.js';
import { pagesVirtualModuleId } from '../../app/index.js';
import { removeLeadingForwardSlash, removeTrailingForwardSlash } from '../../path.js';
import { serializeRouteData } from '../../routing/index.js';
import { addRollupInput } from '../add-rollup-input.js';
import { getOutFile, getOutFolder } from '../common.js';
import { eachPrerenderedPageData, eachServerPageData, sortedCSS } from '../internal.js';
import { AstroBuildPlugin } from '../plugin';
export const virtualModuleId = '@astrojs-ssr-virtual-entry';
const resolvedVirtualModuleId = '\0' + virtualModuleId;
@ -29,7 +29,7 @@ export function vitePluginSSR(internals: BuildInternals, adapter: AstroAdapter):
options(opts) {
return addRollupInput(opts, [virtualModuleId]);
},
resolveId(id) {
resolveId(id, parent) {
if (id === virtualModuleId) {
return resolvedVirtualModuleId;
}
@ -114,12 +114,10 @@ export async function injectManifest(buildOpts: StaticBuildOptions, internals: B
const chunk = internals.ssrEntryChunk;
const code = chunk.code;
chunk.code = code.replace(replaceExp, () => {
return code.replace(replaceExp, () => {
return JSON.stringify(manifest);
});
const serverEntryURL = new URL(buildOpts.buildConfig.serverEntry, buildOpts.buildConfig.server);
await fs.promises.mkdir(new URL('./', serverEntryURL), { recursive: true });
await fs.promises.writeFile(serverEntryURL, chunk.code, 'utf-8');
}
function buildManifest(
@ -220,3 +218,37 @@ function buildManifest(
return ssrManifest;
}
export function pluginSSR(
options: StaticBuildOptions,
internals: BuildInternals
): AstroBuildPlugin {
const ssr = options.settings.config.output === 'server';
return {
build: 'ssr',
hooks: {
'build:before': () => {
let vitePlugin = ssr ? vitePluginSSR(internals, options.settings.adapter!) : undefined;
return {
enforce: 'after-user-plugins',
vitePlugin,
};
},
'build:post': async ({ mutate }) => {
if (!ssr) {
return;
}
if (!internals.ssrEntryChunk) {
throw new Error(`Did not generate an entry chunk for SSR`);
}
// Mutate the filename
internals.ssrEntryChunk.fileName = options.settings.config.build.serverEntry;
const code = await injectManifest(options, internals);
mutate(internals.ssrEntryChunk, 'server', code);
},
},
};
}

View file

@ -0,0 +1,40 @@
import type { Plugin as VitePlugin } from 'vite';
// eslint-disable-next-line @typescript-eslint/ban-types
type OutputOptionsHook = Extract<VitePlugin['outputOptions'], Function>;
type OutputOptions = Parameters<OutputOptionsHook>[0];
type ExtendManualChunksHooks = {
before?: (id: string, meta: any) => string | undefined;
after?: (id: string, meta: any) => string | undefined;
};
export function extendManualChunks(outputOptions: OutputOptions, hooks: ExtendManualChunksHooks) {
const manualChunks = outputOptions.manualChunks;
outputOptions.manualChunks = function (id, meta) {
if (hooks.before) {
let value = hooks.before(id, meta);
if (value) {
return value;
}
}
// Defer to user-provided `manualChunks`, if it was provided.
if (typeof manualChunks == 'object') {
if (id in manualChunks) {
let value = manualChunks[id];
return value[0];
}
} else if (typeof manualChunks === 'function') {
const outid = manualChunks.call(this, id, meta);
if (outid) {
return outid;
}
}
if (hooks.after) {
return hooks.after(id, meta) || null;
}
return null;
};
}

View file

@ -4,7 +4,6 @@ import fs from 'fs';
import { bgGreen, bgMagenta, black, dim } from 'kleur/colors';
import { fileURLToPath } from 'url';
import * as vite from 'vite';
import { astroContentProdBundlePlugin } from '../../content/index.js';
import {
BuildInternals,
createBuildInternals,
@ -21,16 +20,10 @@ import { info } from '../logger/core.js';
import { getOutDirWithinCwd } from './common.js';
import { generatePages } from './generate.js';
import { trackPageData } from './internal.js';
import { AstroBuildPluginContainer, createPluginContainer } from './plugin.js';
import { registerAllPlugins } from './plugins/index.js';
import type { PageBuildData, StaticBuildOptions } from './types';
import { getTimeStat } from './util.js';
import { vitePluginAliasResolve } from './vite-plugin-alias-resolve.js';
import { vitePluginAnalyzer } from './vite-plugin-analyzer.js';
import { rollupPluginAstroBuildCSS } from './vite-plugin-css.js';
import { vitePluginHoistedScripts } from './vite-plugin-hoisted-scripts.js';
import { vitePluginInternals } from './vite-plugin-internals.js';
import { vitePluginPages } from './vite-plugin-pages.js';
import { vitePluginPrerender } from './vite-plugin-prerender.js';
import { injectManifest, vitePluginSSR } from './vite-plugin-ssr.js';
export async function staticBuild(opts: StaticBuildOptions) {
const { allPages, settings } = opts;
@ -70,10 +63,14 @@ export async function staticBuild(opts: StaticBuildOptions) {
// condition, so we are doing it ourselves
emptyDir(settings.config.outDir, new Set('.git'));
// Register plugins
const container = createPluginContainer(opts, internals);
registerAllPlugins(container);
// Build your project (SSR application code, assets, client JS, etc.)
timer.ssr = performance.now();
info(opts.logging, 'build', `Building ${settings.config.output} entrypoints...`);
await ssrBuild(opts, internals, pageInput);
const ssrOutput = await ssrBuild(opts, internals, pageInput, container);
info(opts.logging, 'build', dim(`Completed in ${getTimeStat(timer.ssr, performance.now())}.`));
const rendererClientEntrypoints = settings.renderers
@ -93,9 +90,11 @@ export async function staticBuild(opts: StaticBuildOptions) {
// Run client build first, so the assets can be fed into the SSR rendered version.
timer.clientBuild = performance.now();
await clientBuild(opts, internals, clientInput);
const clientOutput = await clientBuild(opts, internals, clientInput, container);
timer.generate = performance.now();
await runPostBuildHooks(container, ssrOutput, clientOutput);
switch (settings.config.output) {
case 'static': {
await generatePages(opts, internals);
@ -103,7 +102,6 @@ export async function staticBuild(opts: StaticBuildOptions) {
return;
}
case 'server': {
await injectManifest(opts, internals);
await generatePages(opts, internals);
await cleanStaticOutput(opts, internals);
info(opts.logging, null, `\n${bgMagenta(black(' finalizing server assets '))}\n`);
@ -113,11 +111,18 @@ export async function staticBuild(opts: StaticBuildOptions) {
}
}
async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, input: Set<string>) {
async function ssrBuild(
opts: StaticBuildOptions,
internals: BuildInternals,
input: Set<string>,
container: AstroBuildPluginContainer
) {
const { settings, viteConfig } = opts;
const ssr = settings.config.output === 'server';
const out = ssr ? opts.buildConfig.server : getOutDirWithinCwd(settings.config.outDir);
const { lastVitePlugins, vitePlugins } = container.runBeforeHook('ssr', input);
const viteBuildConfig: vite.InlineConfig = {
...viteConfig,
mode: viteConfig.mode || 'production',
@ -154,21 +159,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
modulePreload: { polyfill: false },
reportCompressedSize: false,
},
plugins: [
vitePluginAnalyzer(internals),
vitePluginInternals(input, internals),
vitePluginPages(opts, internals),
rollupPluginAstroBuildCSS({
buildOptions: opts,
internals,
target: 'server',
}),
vitePluginPrerender(opts, internals),
...(viteConfig.plugins || []),
astroContentProdBundlePlugin({ internals }),
// SSR needs to be last
ssr && vitePluginSSR(internals, settings.adapter!),
],
plugins: [...vitePlugins, ...(viteConfig.plugins || []), ...lastVitePlugins],
envPrefix: viteConfig.envPrefix ?? 'PUBLIC_',
base: settings.config.base,
};
@ -187,7 +178,8 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
async function clientBuild(
opts: StaticBuildOptions,
internals: BuildInternals,
input: Set<string>
input: Set<string>,
container: AstroBuildPluginContainer
) {
const { settings, viteConfig } = opts;
const timer = performance.now();
@ -204,6 +196,7 @@ async function clientBuild(
return null;
}
const { lastVitePlugins, vitePlugins } = container.runBeforeHook('client', input);
info(opts.logging, null, `\n${bgGreen(black(' building client '))}`);
const viteBuildConfig: vite.InlineConfig = {
@ -228,17 +221,7 @@ async function clientBuild(
preserveEntrySignatures: 'exports-only',
},
},
plugins: [
vitePluginAliasResolve(internals),
vitePluginInternals(input, internals),
vitePluginHoistedScripts(settings, internals),
rollupPluginAstroBuildCSS({
buildOptions: opts,
internals,
target: 'client',
}),
...(viteConfig.plugins || []),
],
plugins: [...vitePlugins, ...(viteConfig.plugins || []), ...lastVitePlugins],
envPrefix: viteConfig.envPrefix ?? 'PUBLIC_',
base: settings.config.base,
};
@ -256,6 +239,24 @@ async function clientBuild(
return buildResult;
}
async function runPostBuildHooks(
container: AstroBuildPluginContainer,
ssrReturn: Awaited<ReturnType<typeof ssrBuild>>,
clientReturn: Awaited<ReturnType<typeof clientBuild>>
) {
const mutations = await container.runPostHook(ssrReturn, clientReturn);
const config = container.options.settings.config;
const buildConfig = container.options.settings.config.build;
for (const [fileName, mutation] of mutations) {
const root = config.output === 'server' ?
mutation.build === 'server' ? buildConfig.server : buildConfig.client :
config.outDir;
const fileURL = new URL(fileName, root);
await fs.promises.mkdir(new URL('./', fileURL), { recursive: true });
await fs.promises.writeFile(fileURL, mutation.code, 'utf-8');
}
}
/**
* For each statically prerendered page, replace their SSR file with a noop.
* This allows us to run the SSR build only once, but still remove dependencies for statically rendered routes.

View file

@ -1,4 +1,4 @@
import type { InlineConfig } from 'vite';
import type { default as vite, InlineConfig } from 'vite';
import type {
AstroConfig,
AstroSettings,
@ -22,6 +22,7 @@ export interface PageBuildData {
moduleSpecifier: string;
css: Map<string, { depth: number; order: number }>;
contentCollectionCss: Map<string, Set<string>>;
propagatedScripts: Map<string, Set<string>>;
hoistedScript: { type: 'inline' | 'external'; value: string } | undefined;
}
export type AllPagesData = Record<ComponentPath, PageBuildData>;
@ -44,3 +45,10 @@ export interface SingleFileBuiltModule {
pageMap: Map<ComponentPath, ComponentInstance>;
renderers: SSRLoadedRenderer[];
}
export type ViteBuildReturn = Awaited<ReturnType<typeof vite.build>>;
export type RollupOutput = Extract<
Extract<ViteBuildReturn, Exclude<ViteBuildReturn, Array<any>>>,
{ output: any }
>;
export type OutputChunk = Extract<RollupOutput['output'][number], { type: 'chunk' }>;

View file

@ -1,43 +0,0 @@
import type { Plugin as VitePlugin } from 'vite';
import type { BuildInternals } from './internal.js';
import type { StaticBuildOptions } from './types';
export function vitePluginPrerender(
opts: StaticBuildOptions,
internals: BuildInternals
): VitePlugin {
return {
name: 'astro:rollup-plugin-prerender',
outputOptions(outputOptions) {
const manualChunks = outputOptions.manualChunks || Function.prototype;
outputOptions.manualChunks = function (id, api, ...args) {
// Defer to user-provided `manualChunks`, if it was provided.
if (typeof manualChunks == 'object') {
if (id in manualChunks) {
return manualChunks[id];
}
} else if (typeof manualChunks === 'function') {
const outid = manualChunks.call(this, id, api, ...args);
if (outid) {
return outid;
}
}
// Split the Astro runtime into a separate chunk for readability
if (id.includes('astro/dist')) {
return 'astro';
}
const pageInfo = internals.pagesByViteID.get(id);
if (pageInfo) {
// prerendered pages should be split into their own chunk
// Important: this can't be in the `pages/` directory!
if (api.getModuleInfo(id)?.meta.astro?.pageOptions?.prerender) {
return `prerender`;
}
// dynamic pages should all go in their own chunk in the pages/* directory
return `pages/all`;
}
};
},
};
}

View file

@ -118,6 +118,7 @@ export interface AstroErrorPayload {
line?: number;
column?: number;
};
cause?: unknown;
};
}
@ -174,6 +175,7 @@ export async function getViteErrorPayload(err: ErrorWithMetadata): Promise<Astro
},
plugin,
stack: err.stack,
cause: err.cause,
},
};

View file

@ -415,6 +415,20 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
},
hint: 'Mutable values declared at runtime are not supported. Please make sure to use exactly `export const prerender = true`.',
},
/**
* @docs
* @message
* **Example error messages:**<br/>
* InvalidComponentArgs: Invalid arguments passed to `<MyAstroComponent>` component.
* @description
* Astro components cannot be rendered manually via a function call, such as `Component()` or `{items.map(Component)}`. Prefer the component syntax `<Component />` or `{items.map(item => <Component {...item} />)}`.
*/
InvalidComponentArgs: {
title: 'Invalid component arguments.',
code: 3020,
message: (name: string) => `Invalid arguments passed to${name ? ` <${name}>` : ''} component.`,
hint: 'Astro components cannot be rendered directly via function call, such as `Component()` or `{items.map(Component)}`.',
},
// Vite Errors - 4xxx
UnknownViteError: {
title: 'Unknown Vite Error.',
@ -615,6 +629,7 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
},
/**
* @docs
* @message `COLLECTION_NAME` `ENTRY_ID` has an invalid slug. `slug` must be a string.
* @see
* - [The reserved entry `slug` field](https://docs.astro.build/en/guides/content-collections/)
* @description
@ -632,6 +647,7 @@ See https://docs.astro.build/en/guides/server-side-rendering/ for more informati
},
/**
* @docs
* @message A content collection schema should not contain `slug` since it is reserved for slug generation. Remove this from your `COLLECTION_NAME` collection schema.
* @see
* - [The reserved entry `slug` field](https://docs.astro.build/en/guides/content-collections/)
* @description

View file

@ -321,7 +321,8 @@ const style = /* css */ `
#message-hints,
#stack,
#code {
#code,
#cause {
border-radius: var(--roundiness);
background-color: var(--box-background);
}
@ -471,7 +472,8 @@ const style = /* css */ `
color: var(--error-text);
}
#stack h2 {
#stack h2,
#cause h2 {
color: var(--title-text);
font-family: var(--font-normal);
font-size: 22px;
@ -480,7 +482,8 @@ const style = /* css */ `
border-bottom: 1px solid var(--border);
}
#stack-content {
#stack-content,
#cause-content {
font-size: 14px;
white-space: pre;
line-height: 21px;
@ -488,6 +491,10 @@ const style = /* css */ `
padding: 24px;
color: var(--stack-text);
}
#cause {
display: none;
}
`;
const overlayTemplate = /* html */ `
@ -552,6 +559,11 @@ ${style.trim()}
<h2>Stack Trace</h2>
<div id="stack-content"></div>
</section>
<section id="cause">
<h2>Cause</h2>
<div id="cause-content"></div>
</section>
</div>
</div>
`;
@ -593,6 +605,17 @@ class ErrorOverlay extends HTMLElement {
this.text('#title', err.title);
this.text('#message-content', err.message, true);
const cause = this.root.querySelector<HTMLElement>('#cause');
if (cause && err.cause) {
if (typeof err.cause === 'string') {
this.text('#cause-content', err.cause);
cause.style.display = 'block';
} else {
this.text('#cause-content', JSON.stringify(err.cause, null, 2));
cause.style.display = 'block';
}
}
const hint = this.root.querySelector<HTMLElement>('#hint');
if (hint && err.hint) {
this.text('#hint-content', err.hint, true);

View file

@ -156,6 +156,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
propagation: args.propagation ?? new Map(),
propagators: new Map(),
extraHead: [],
scope: 0,
cookies,
/** This function returns the `Astro` faux-global */
createAstro(

View file

@ -1,11 +1,28 @@
import type { PropagationHint } from '../../@types/astro';
import { AstroError, AstroErrorData } from '../../core/errors/index.js';
import type { AstroComponentFactory } from './render/index.js';
function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string) {
function validateArgs(args: unknown[]): args is Parameters<AstroComponentFactory> {
if (args.length !== 3) return false;
if (!args[0] || typeof args[0] !== 'object') return false;
return true;
}
function baseCreateComponent(cb: AstroComponentFactory, moduleId?: string): AstroComponentFactory {
const name = moduleId?.split('/').pop()?.replace('.astro', '') ?? '';
const fn = (...args: Parameters<AstroComponentFactory>) => {
if (!validateArgs(args)) {
throw new AstroError({
...AstroErrorData.InvalidComponentArgs,
message: AstroErrorData.InvalidComponentArgs.message(name),
});
}
return cb(...args);
};
Object.defineProperty(fn, 'name', { value: name, writable: false });
// Add a flag to this callback to mark it as an Astro component
cb.isAstroComponentFactory = true;
cb.moduleId = moduleId;
return cb;
fn.isAstroComponentFactory = true;
fn.moduleId = moduleId;
return fn;
}
interface CreateComponentOptions {

View file

@ -12,6 +12,7 @@ import {
} from './index.js';
import { HTMLParts } from './render/common.js';
import type { ComponentIterable } from './render/component';
import { ScopeFlags } from './render/util.js';
const ClientOnlyPlaceholder = 'astro-client-only';
@ -94,6 +95,7 @@ Did you forget to import the component or is it possible there is a typo?`);
props[key] = value;
}
}
result.scope |= ScopeFlags.JSX;
return markHTMLString(await renderToString(result, vnode.type as any, props, slots));
}
case !vnode.type && (vnode.type as any) !== 0:

View file

@ -3,6 +3,7 @@ import type { HeadAndContent } from './head-and-content';
import type { RenderTemplateResult } from './render-template';
import { HTMLParts } from '../common.js';
import { ScopeFlags } from '../util.js';
import { isHeadAndContent } from './head-and-content.js';
import { renderAstroTemplateResult } from './render-template.js';
@ -27,6 +28,7 @@ export async function renderToString(
props: any,
children: any
): Promise<string> {
result.scope |= ScopeFlags.Astro;
const factoryResult = await componentFactory(result, props, children);
if (factoryResult instanceof Response) {

View file

@ -93,5 +93,7 @@ export function chunkToByteArray(
if (chunk instanceof Uint8Array) {
return chunk as Uint8Array;
}
return encoder.encode(stringifyChunk(result, chunk));
// stringify chunk might return a HTMLString
let stringified = stringifyChunk(result, chunk);
return encoder.encode(stringified.toString());
}

View file

@ -261,8 +261,10 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
if (isPage || renderer?.name === 'astro:jsx') {
yield html;
} else {
} else if (html && html.length > 0) {
yield markHTMLString(html.replace(/\<\/?astro-slot\>/g, ''));
} else {
yield '';
}
})();
}

View file

@ -1,7 +1,7 @@
import type { SSRResult } from '../../../@types/astro';
import { markHTMLString } from '../escape.js';
import { renderElement } from './util.js';
import { renderElement, ScopeFlags } from './util.js';
// Filter out duplicate elements in our set
const uniqueElements = (item: any, index: number, all: any[]) => {
@ -52,6 +52,14 @@ export function* maybeRenderHead(result: SSRResult) {
return;
}
// Don't render the head inside of a JSX component that's inside of an Astro component
// as the Astro component will be the one to render the head.
switch (result.scope) {
case ScopeFlags.JSX | ScopeFlags.Slot | ScopeFlags.Astro: {
return;
}
}
// This is an instruction informing the page rendering that head might need rendering.
// This allows the page to deduplicate head injections.
yield { type: 'head', result } as const;

View file

@ -3,6 +3,7 @@ import type { RenderInstruction } from './types.js';
import { HTMLString, markHTMLString } from '../escape.js';
import { renderChild } from './any.js';
import { ScopeFlags } from './util.js';
const slotString = Symbol.for('astro:slot-string');
@ -20,8 +21,13 @@ export function isSlotString(str: string): str is any {
return !!(str as any)[slotString];
}
export async function renderSlot(_result: any, slotted: string, fallback?: any): Promise<string> {
export async function renderSlot(
result: SSRResult,
slotted: string,
fallback?: any
): Promise<string> {
if (slotted) {
result.scope |= ScopeFlags.Slot;
let iterator = renderChild(slotted);
let content = '';
let instructions: null | RenderInstruction[] = null;
@ -35,6 +41,8 @@ export async function renderSlot(_result: any, slotted: string, fallback?: any):
content += chunk;
}
}
// Remove the flag since we are now outside of the scope.
result.scope &= ~ScopeFlags.Slot;
return markHTMLString(new SlotString(content, instructions));
}
return fallback;

View file

@ -128,3 +128,9 @@ export function renderElement(
}
return `<${name}${internalSpreadAttributes(props, shouldEscape)}>${children}</${name}>`;
}
export const ScopeFlags = {
Astro: 1 << 0,
JSX: 1 << 1,
Slot: 1 << 2,
};

View file

@ -29,6 +29,7 @@ export default function astroScannerPlugin({ settings }: { settings: AstroSettin
const { meta = {} } = this.getModuleInfo(id) ?? {};
return {
code,
map: null,
meta: {
...meta,
astro: {

View file

@ -46,7 +46,7 @@ describe('Content Collections - render()', () => {
// Includes hoisted script
expect(
[...allScripts].find((script) =>
$(script).text().includes('document.querySelector("#update-me")')
$(script).attr('src')?.includes('WithScripts')
),
'`WithScripts.astro` hoisted script missing from head.'
).to.not.be.undefined;
@ -55,9 +55,7 @@ describe('Content Collections - render()', () => {
expect($('script[data-is-inline]')).to.have.a.lengthOf(1);
});
// TODO: Script bleed isn't solved for prod builds.
// Tackling in separate PR.
it.skip('Excludes component scripts for non-rendered entries', async () => {
it('Excludes component scripts for non-rendered entries', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
@ -71,6 +69,15 @@ describe('Content Collections - render()', () => {
'`WithScripts.astro` hoisted script included unexpectedly.'
).to.be.undefined;
});
it('Applies MDX components export', async () => {
const html = await fixture.readFile('/launch-week-components-export/index.html');
const $ = cheerio.load(html);
const h2 = $('h2');
expect(h2).to.have.a.lengthOf(1);
expect(h2.attr('data-components-export-applied')).to.equal('true');
});
});
describe('Build - SSR', () => {
@ -110,6 +117,18 @@ describe('Content Collections - render()', () => {
// Includes styles
expect($('link[rel=stylesheet]')).to.have.a.lengthOf(0);
});
it('Applies MDX components export', async () => {
const app = await fixture.loadTestAdapterApp();
const request = new Request('http://example.com/launch-week-components-export');
const response = await app.render(request);
const html = await response.text();
const $ = cheerio.load(html);
const h2 = $('h2');
expect(h2).to.have.a.lengthOf(1);
expect(h2.attr('data-components-export-applied')).to.equal('true');
});
});
describe('Dev - SSG', () => {
@ -162,5 +181,17 @@ describe('Content Collections - render()', () => {
// Includes inline script
expect($('script[data-is-inline]')).to.have.a.lengthOf(1);
});
it('Applies MDX components export', async () => {
const response = await fixture.fetch('/launch-week-components-export', { method: 'GET' });
expect(response.status).to.equal(200);
const html = await response.text();
const $ = cheerio.load(html);
const h2 = $('h2');
expect(h2).to.have.a.lengthOf(1);
expect(h2.attr('data-components-export-applied')).to.equal('true');
});
});
});

View file

@ -23,7 +23,7 @@ describe('Custom Elements', () => {
expect($('my-element')).to.have.lengthOf(1);
// test 2: shadow rendered
expect($('my-element template[shadowroot=open]')).to.have.lengthOf(1);
expect($('my-element template[shadowroot=open][shadowrootmode=open]')).to.have.lengthOf(1);
});
it('Works with exported tagName', async () => {
@ -34,7 +34,7 @@ describe('Custom Elements', () => {
expect($('my-element')).to.have.lengthOf(1);
// test 2: shadow rendered
expect($('my-element template[shadowroot=open]')).to.have.lengthOf(1);
expect($('my-element template[shadowroot=open][shadowrootmode=open]')).to.have.lengthOf(1);
});
it.skip('Hydration works with exported tagName', async () => {
@ -46,7 +46,7 @@ describe('Custom Elements', () => {
expect($('my-element')).to.have.lengthOf(1);
// test 2: shadow rendered
expect($('my-element template[shadowroot=open]')).to.have.lengthOf(1);
expect($('my-element template[shadowroot=open][shadowrootmode=open]')).to.have.lengthOf(1);
// Hydration
// test 3: Component and polyfill scripts bundled separately

View file

@ -0,0 +1,4 @@
---
---
<h2 data-components-export-applied="true"><slot /></h2>

View file

@ -0,0 +1,18 @@
---
title: 'Launch week!'
description: 'Join us for the exciting launch of SPACE BLOG'
publishedDate: 'Sat May 21 2022 00:00:00 GMT-0400 (Eastern Daylight Time)'
tags: ['announcement']
---
import H2 from '../../../components/H2.astro';
export const components = { h2: H2 };
Join us for the space blog launch!
## Details
- THIS THURSDAY
- Houston, TX
- Dress code: **interstellar casual** ✨

View file

@ -0,0 +1,14 @@
---
import { getEntryBySlug } from 'astro:content';
const entry = await getEntryBySlug('blog', 'promo/launch-week-components-export');
const { Content } = await entry.render();
---
<html>
<head>
<title>Launch Week</title>
</head>
<body>
<Content />
</body>
</html>

View file

@ -18,7 +18,7 @@ function renderToStaticMarkup(component, props, innerHTML) {
const Component = getConstructor(component);
const el = new Component();
el.connectedCallback();
const html = `<${el.localName}><template shadowroot="open">${el.shadowRoot.innerHTML}</template>${el.innerHTML}</${el.localName}>`
const html = `<${el.localName}><template shadowroot="open" shadowrootmode="open">${el.shadowRoot.innerHTML}</template>${el.innerHTML}</${el.localName}>`
return {
html
};

View file

@ -0,0 +1,23 @@
import { LitElement, html } from 'lit';
import { property, customElement } from 'lit/decorators.js';
@customElement('non-deferred-counter')
export class NonDeferredCounter extends LitElement {
// All set properties are reflected to attributes so its hydration is not
// hydration-deferred should always be set.
@property({ type: Number, reflect: true }) count = 0;
increment() {
this.count++;
}
render() {
return html`
<div>
<p>Count: ${this.count}</p>
<button type="button" @click=${this.increment}>Increment</button>
</div>
`;
}
}

View file

@ -1,18 +1,25 @@
---
import {MyElement} from '../components/my-element.js';
import {NonDeferredCounter} from '../components/non-deferred-element.js';
---
<html>
<head>
<title>LitElements</title>
<title>LitElements</title>
</head>
<body>
<MyElement
foo="bar"
str-attr={'initialized'}
bool={false}
obj={{data: 1}}
reflectedStrProp={'initialized reflected'}>
</MyElement>
<MyElement
id="default"
foo="bar"
str-attr={'initialized'}
bool={false}
obj={{data: 1}}
reflectedStrProp={'initialized reflected'}>
</MyElement>
<NonDeferredCounter
id="non-deferred"
count={10}
foo="bar">
</NonDeferredCounter>
</body>
</html>

View file

@ -30,36 +30,57 @@ describe('LitElement test', function () {
const $ = cheerio.load(html);
// test 1: attributes rendered non reactive properties
expect($('my-element').attr('foo')).to.equal('bar');
expect($('#default').attr('foo')).to.equal('bar');
// test 2: shadow rendered
expect($('my-element').html()).to.include(`<div>Testing...</div>`);
expect($('#default').html()).to.include(`<div>Testing...</div>`);
// test 3: string reactive property set
expect(stripExpressionMarkers($('my-element').html())).to.include(
expect(stripExpressionMarkers($('#default').html())).to.include(
`<div id="str">initialized</div>`
);
// test 4: boolean reactive property correctly set
// <my-element bool="false"> Lit will equate to true because it uses
// this.hasAttribute to determine its value
expect(stripExpressionMarkers($('my-element').html())).to.include(`<div id="bool">B</div>`);
expect(stripExpressionMarkers($('#default').html())).to.include(`<div id="bool">B</div>`);
// test 5: object reactive property set
// by default objects will be stringified to [object Object]
expect(stripExpressionMarkers($('my-element').html())).to.include(
`<div id="data">data: 1</div>`
);
expect(stripExpressionMarkers($('#default').html())).to.include(`<div id="data">data: 1</div>`);
// test 6: reactive properties are not rendered as attributes
expect($('my-element').attr('obj')).to.equal(undefined);
expect($('my-element').attr('bool')).to.equal(undefined);
expect($('my-element').attr('str')).to.equal(undefined);
expect($('#default').attr('obj')).to.equal(undefined);
expect($('#default').attr('bool')).to.equal(undefined);
expect($('#default').attr('str')).to.equal(undefined);
// test 7: reflected reactive props are rendered as attributes
expect($('my-element').attr('reflectedbool')).to.equal('');
expect($('my-element').attr('reflected-str')).to.equal('default reflected string');
expect($('my-element').attr('reflected-str-prop')).to.equal('initialized reflected');
expect($('#default').attr('reflectedbool')).to.equal('');
expect($('#default').attr('reflected-str')).to.equal('default reflected string');
expect($('#default').attr('reflected-str-prop')).to.equal('initialized reflected');
});
it('Sets defer-hydration on element only when necessary', async () => {
// @lit-labs/ssr/ requires Node 13.9 or higher
if (NODE_VERSION < 13.9) {
return;
}
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
// test 1: reflected reactive props are rendered as attributes
expect($('#non-deferred').attr('count')).to.equal('10');
// test 2: non-reactive props are set as attributes
expect($('#non-deferred').attr('foo')).to.equal('bar');
// test 3: components with only reflected reactive props set are not
// deferred because their state can be completely serialized via attributes
expect($('#non-deferred').attr('defer-hydration')).to.equal(undefined);
// test 4: components with non-reflected reactive props set are deferred because
// their state needs to be synced with the server on the client.
expect($('#default').attr('defer-hydration')).to.equal('');
});
it('Correctly passes child slots', async () => {
@ -74,7 +95,7 @@ describe('LitElement test', function () {
const $slottedMyElement = $('#slotted');
const $slottedSlottedMyElement = $('#slotted-slotted');
expect($('my-element').length).to.equal(3);
expect($('#default').length).to.equal(3);
// Root my-element
expect($rootMyElement.children('.default').length).to.equal(2);

View file

@ -1,5 +1,14 @@
# @astrojs/cloudflare
## 6.1.1
### Patch Changes
- [#6046](https://github.com/withastro/astro/pull/6046) [`df3201165`](https://github.com/withastro/astro/commit/df320116528e00ab082396531b4deffbb0707b78) Thanks [@matthewp](https://github.com/matthewp)! - Cloudflare fix for building to directory mode
- Updated dependencies [[`41e97158b`](https://github.com/withastro/astro/commit/41e97158ba90d23d346b6e3ff6c7c14b5ecbe903), [`e779c6242`](https://github.com/withastro/astro/commit/e779c6242418d1d4102e683ca5b851b764c89688)]:
- astro@2.0.4
## 6.1.0
### Minor Changes

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/cloudflare",
"description": "Deploy your site to cloudflare workers or cloudflare pages",
"version": "6.1.0",
"version": "6.1.1",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -38,7 +38,7 @@
"tiny-glob": "^0.2.9"
},
"peerDependencies": {
"astro": "workspace:^2.0.3"
"astro": "workspace:^2.0.6"
},
"devDependencies": {
"astro": "workspace:*",

View file

@ -32,7 +32,7 @@
"esbuild": "^0.15.18"
},
"peerDependencies": {
"astro": "workspace:^2.0.3"
"astro": "workspace:^2.0.6"
},
"devDependencies": {
"astro": "workspace:*",

View file

@ -1,5 +1,18 @@
# @astrojs/image
## 0.14.0
### Minor Changes
- [#5932](https://github.com/withastro/astro/pull/5932) [`b3e65991f`](https://github.com/withastro/astro/commit/b3e65991f731f5320ba5826c731934a8e8482493) Thanks [@rasendubi](https://github.com/rasendubi)! - Allow images from outside srcDir
### Patch Changes
- [#5894](https://github.com/withastro/astro/pull/5894) [`ca91976ed`](https://github.com/withastro/astro/commit/ca91976edbfd34adbb31096516a266f31d8f6216) Thanks [@ralacerda](https://github.com/ralacerda)! - `getPicture()` return object with the correct image type
- Updated dependencies [[`9793f19ec`](https://github.com/withastro/astro/commit/9793f19ecd4e64cbf3140454fe52aeee2c22c8c9), [`f91615f5c`](https://github.com/withastro/astro/commit/f91615f5c04fde36f115dad9110dd75254efd61d), [`2fb72c887`](https://github.com/withastro/astro/commit/2fb72c887f71c0a69ab512870d65b8c867774766)]:
- astro@2.0.5
## 0.13.1
### Patch Changes

View file

@ -259,7 +259,7 @@ color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`,
**Default:** `'cover'`
</p>
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead.
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead. Read more about [how `sharp` resizes images](https://sharp.pixelplumbing.com/api-resize).
How the image should be resized to fit both `height` and `width`.
@ -271,7 +271,7 @@ How the image should be resized to fit both `height` and `width`.
**Default:** `'centre'`
</p>
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead.
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead. Read more about [how `sharp` resizes images](https://sharp.pixelplumbing.com/api-resize).
Position of the crop when fit is `cover` or `contain`.
@ -333,7 +333,7 @@ The list of sizes that should be built for responsive images. This is combined w
```astro
// Builds three images: 400x400, 800x800, and 1200x1200
<Picture src={...} widths={[400, 800, 1200]} aspectRatio="1:1" />
<Picture src={...} widths={[400, 800, 1200]} aspectRatio="1:1" alt="descriptive text" />
```
#### aspectRatio
@ -392,7 +392,7 @@ color representation with 3 or 6 hexadecimal characters in the form `#123[abc]`,
**Default:** `'cover'`
</p>
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead.
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead. Read more about [how `sharp` resizes images](https://sharp.pixelplumbing.com/api-resize).
How the image should be resized to fit both `height` and `width`.
@ -406,7 +406,7 @@ How the image should be resized to fit both `height` and `width`.
**Default:** `'centre'`
</p>
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead.
> This is not supported by the default Squoosh service. See the [installation section](#installing-sharp-optional) for details on using the `sharp` service instead. Read more about [how `sharp` resizes images](https://sharp.pixelplumbing.com/api-resize).
Position of the crop when fit is `cover` or `contain`.
@ -422,7 +422,10 @@ This can be helpful if you need to add preload links to a page's `<head>`.
---
import { getImage } from '@astrojs/image';
const { src } = await getImage({src: '../assets/hero.png'});
const { src } = await getImage({
src: import('../assets/hero.png'),
alt: "My hero image"
});
---
<html>

View file

@ -1,7 +1,7 @@
{
"name": "@astrojs/image",
"description": "Load and transform images in your Astro site.",
"version": "0.13.1",
"version": "0.14.0",
"type": "module",
"types": "./dist/index.d.ts",
"author": "withastro",
@ -47,8 +47,7 @@
"image-size": "^1.0.2",
"kleur": "^4.1.5",
"magic-string": "^0.27.0",
"mime": "^3.0.0",
"slash": "^4.0.0"
"mime": "^3.0.0"
},
"devDependencies": {
"@types/http-cache-semantics": "^4.0.1",
@ -64,7 +63,7 @@
"vite": "^4.0.3"
},
"peerDependencies": {
"astro": "workspace:^2.0.3",
"astro": "workspace:^2.0.6",
"sharp": ">=0.31.0"
},
"peerDependenciesMeta": {

View file

@ -17,7 +17,7 @@ export interface GetPictureParams {
}
export interface GetPictureResult {
image: astroHTML.JSX.HTMLAttributes;
image: astroHTML.JSX.ImgHTMLAttributes;
sources: { type: string; srcset: string }[];
}

View file

@ -1,10 +1,9 @@
import type { AstroConfig } from 'astro';
import MagicString from 'magic-string';
import fs from 'node:fs/promises';
import path, { basename, extname, join } from 'node:path';
import { basename, extname } from 'node:path';
import { Readable } from 'node:stream';
import { fileURLToPath, pathToFileURL } from 'node:url';
import slash from 'slash';
import { pathToFileURL } from 'node:url';
import type { Plugin, ResolvedConfig } from 'vite';
import type { IntegrationOptions } from './index.js';
import type { InputFormat } from './loaders/index.js';
@ -65,12 +64,7 @@ export function createPlugin(config: AstroConfig, options: Required<IntegrationO
meta.src = `__ASTRO_IMAGE_ASSET__${handle}__`;
} else {
const relId = path.relative(fileURLToPath(config.srcDir), id);
meta.src = join('/@astroimage', relId);
// Windows compat
meta.src = slash(meta.src);
meta.src = '/@astroimage' + url.pathname;
}
return `export default ${JSON.stringify(meta)}`;
@ -78,9 +72,9 @@ export function createPlugin(config: AstroConfig, options: Required<IntegrationO
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
if (req.url?.startsWith('/@astroimage/')) {
const [, id] = req.url.split('/@astroimage/');
// Reconstructing URL to get rid of query parameters in path
const url = new URL(req.url.slice('/@astroimage'.length), 'file:');
const url = new URL(id, config.srcDir);
const file = await fs.readFile(url);
const meta = await metadata(url);

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -1,6 +1,7 @@
---
import socialJpg from '../assets/social.jpg';
import introJpg from '../assets/blog/introducing astro.jpg';
import outsideSrc from '../../social.png';
import { Image } from '@astrojs/image/components';
const publicImage = new URL('./hero.jpg', Astro.url);
---
@ -18,6 +19,8 @@ const publicImage = new URL('./hero.jpg', Astro.url);
<br />
<Image id="no-transforms" src={socialJpg} alt="no-transforms" />
<br />
<Image id="outside-src" src={outsideSrc} alt="outside-src" />
<br />
<Image id="google" src="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" width={544} height={184} format="webp" alt="Google" />
<br />
<Image id="inline" src={import('../assets/social.jpg')} width={506} alt="inline" />

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

View file

@ -1,6 +1,7 @@
---
import socialJpg from '../assets/social.jpg';
import introJpg from '../assets/blog/introducing astro.jpg';
import outsideSrc from '../../social.png';
import { Picture } from '@astrojs/image/components';
const publicImage = new URL('./hero.jpg', Astro.url);
---
@ -14,6 +15,8 @@ const publicImage = new URL('./hero.jpg', Astro.url);
<br />
<Picture id="spaces" src={introJpg} sizes="100vw" widths={[384, 768]} aspectRatio={768/414} alt="spaces" />
<br />
<Picture id="outside-src" src={outsideSrc} sizes="100vw" widths={[384, 768]} aspectRatio={768/414} alt="outside-src" />
<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" formats={["avif", "webp", "png"]} />

View file

@ -2,9 +2,14 @@ import { expect } from 'chai';
import * as cheerio from 'cheerio';
import sizeOf from 'image-size';
import fs from 'fs/promises';
import { fileURLToPath } from 'url';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { join } from 'node:path';
import { loadFixture } from './test-utils.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const toAstroImage = (relpath) =>
'/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-image', relpath)).pathname;
describe('SSG images - dev', function () {
let fixture;
let devServer;
@ -25,25 +30,32 @@ describe('SSG images - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
},
{
title: 'Local image no transforms',
id: '#no-transforms',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: {},
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { f: 'webp', w: '768', h: '414' },
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
},
{
@ -123,19 +135,32 @@ describe('SSG images with subpath - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
},
{
title: 'Local image no transforms',
id: '#no-transforms',
url: toAstroImage('src/assets/social.jpg'),
query: {},
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { f: 'webp', w: '768', h: '414' },
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
},
{
@ -210,8 +235,11 @@ describe('SSG images - build', function () {
});
function verifyImage(pathname, expected) {
const url = new URL('./fixtures/basic-image/dist/' + pathname, import.meta.url);
const dist = fileURLToPath(url);
const dist = join(
fileURLToPath(new URL('.', import.meta.url)),
'fixtures/basic-image/dist',
pathname
);
const result = sizeOf(dist);
expect(result).to.deep.equal(expected);
}
@ -229,6 +257,12 @@ describe('SSG images - build', function () {
regex: /^\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'File outside src',
id: '#outside-src',
regex: /^\/_astro\/social.\w{8}_\w{4,10}.png/,
size: { type: 'png', width: 2024, height: 1012 },
},
{
title: 'Inline imports',
id: '#inline',
@ -311,6 +345,12 @@ describe('SSG images with subpath - build', function () {
regex: /^\/docs\/_astro\/introducing astro.\w{8}_\w{4,10}.webp/,
size: { width: 768, height: 414, type: 'webp' },
},
{
title: 'File outside src',
id: '#outside-src',
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.png/,
size: { type: 'png', width: 2024, height: 1012 },
},
{
title: 'Inline imports',
id: '#inline',

View file

@ -1,8 +1,14 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { join } from 'node:path';
import { loadFixture } from './test-utils.js';
import testAdapter from '../../../astro/test/test-adapter.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const toAstroImage = (relpath) =>
'/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-image', relpath)).pathname;
describe('SSR images - dev', function () {
let fixture;
let devServer;
@ -28,28 +34,35 @@ describe('SSR images - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
},
{
title: 'Local image no transforms',
id: '#no-transforms',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: {},
contentType: 'image/jpeg',
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { f: 'webp', w: '768', h: '414' },
contentType: 'image/webp',
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
},
@ -150,21 +163,28 @@ describe('SSR images with subpath - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { f: 'webp', w: '768', h: '414' },
contentType: 'image/webp',
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '2024', h: '1012' },
contentType: 'image/png',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
},

View file

@ -3,9 +3,14 @@ import * as cheerio from 'cheerio';
import fs from 'fs';
import sizeOf from 'image-size';
import path from 'path';
import { fileURLToPath } from 'url';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { join } from 'node:path';
import { loadFixture } from './test-utils.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const toAstroImage = (relpath) =>
'/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-picture', relpath)).pathname;
describe('SSG pictures - dev', function () {
let fixture;
let devServer;
@ -26,21 +31,28 @@ describe('SSG pictures - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
alt: 'Social image',
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { f: 'jpg', w: '768', h: '414' },
alt: 'spaces',
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '768', h: '414' },
alt: 'outside-src',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
alt: 'Inline social image',
},
@ -120,21 +132,28 @@ describe('SSG pictures with subpath - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
alt: 'Social image',
},
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { f: 'jpg', w: '768', h: '414' },
alt: 'spaces',
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '768', h: '414' },
alt: 'outside-src',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
alt: 'Inline social image',
},
@ -222,6 +241,13 @@ describe('SSG pictures - build', function () {
size: { width: 768, height: 414, type: 'jpg' },
alt: 'spaces',
},
{
title: 'File outside src',
id: '#outside-src',
regex: /^\/_astro\/social.\w{8}_\w{4,10}.png/,
size: { type: 'png', width: 768, height: 414 },
alt: 'outside-src',
},
{
title: 'Inline images',
id: '#inline',
@ -322,6 +348,13 @@ describe('SSG pictures with subpath - build', function () {
size: { width: 506, height: 253, type: 'jpg' },
alt: 'Social image',
},
{
title: 'File outside src',
id: '#outside-src',
regex: /^\/docs\/_astro\/social.\w{8}_\w{4,10}.png/,
size: { type: 'png', width: 768, height: 414 },
alt: 'outside-src',
},
{
title: 'Inline images',
id: '#inline',

View file

@ -30,6 +30,13 @@ describe('SSR pictures - build', function () {
query: { w: '768', h: '414', f: 'jpg', href: /^\/_astro\/introducing astro.\w{8}.jpg/ },
alt: 'spaces',
},
{
title: 'File outside src',
id: '#outside-src',
url: '/_image',
query: { w: '768', h: '414', f: 'png', href: /^\/_astro\/social.\w{8}.png/ },
alt: 'outside-src',
},
{
title: 'Inline imports',
id: '#inline',
@ -141,6 +148,13 @@ describe('SSR pictures with subpath - build', function () {
query: { w: '768', h: '414', f: 'jpg', href: /^\/docs\/_astro\/introducing astro.\w{8}.jpg/ },
alt: 'spaces',
},
{
title: 'File outside src',
id: '#outside-src',
url: '/_image',
query: { w: '768', h: '414', f: 'png', href: /^\/docs\/_astro\/social.\w{8}.png/ },
alt: 'outside-src',
},
{
title: 'Inline imports',
id: '#inline',

View file

@ -1,8 +1,14 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { join } from 'node:path';
import { loadFixture } from './test-utils.js';
import testAdapter from '../../../astro/test/test-adapter.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const toAstroImage = (relpath) =>
'/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/basic-picture', relpath)).pathname;
describe('SSR pictures - dev', function () {
let fixture;
let devServer;
@ -28,7 +34,7 @@ describe('SSR pictures - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
alt: 'Social image',
@ -36,15 +42,23 @@ describe('SSR pictures - dev', function () {
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { w: '768', h: '414', f: 'jpg' },
contentType: 'image/jpeg',
alt: 'spaces',
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '768', h: '414' },
contentType: 'image/png',
alt: 'outside-src',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
alt: 'Inline social image',
@ -157,7 +171,7 @@ describe('SSR pictures with subpath - dev', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
alt: 'Social image',
@ -165,15 +179,23 @@ describe('SSR pictures with subpath - dev', function () {
{
title: 'Filename with spaces',
id: '#spaces',
url: '/@astroimage/assets/blog/introducing astro.jpg',
url: toAstroImage('src/assets/blog/introducing astro.jpg'),
query: { w: '768', h: '414', f: 'jpg' },
contentType: 'image/jpeg',
alt: 'spaces',
},
{
title: 'File outside src',
id: '#outside-src',
url: toAstroImage('social.png'),
query: { f: 'png', w: '768', h: '414' },
contentType: 'image/png',
alt: 'outside-src',
},
{
title: 'Inline imports',
id: '#inline',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
contentType: 'image/jpeg',
alt: 'Inline social image',

View file

@ -1,7 +1,13 @@
import { expect } from 'chai';
import * as cheerio from 'cheerio';
import { fileURLToPath, pathToFileURL } from 'node:url';
import { join } from 'node:path';
import { loadFixture } from './test-utils.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const toAstroImage = (relpath) =>
'/@astroimage' + pathToFileURL(join(__dirname, 'fixtures/squoosh-service', relpath)).pathname;
describe('Squoosh service', function () {
let fixture;
let devServer;
@ -22,7 +28,7 @@ describe('Squoosh service', function () {
{
title: 'Local images',
id: '#social-jpg',
url: '/@astroimage/assets/social.jpg',
url: toAstroImage('src/assets/social.jpg'),
query: { f: 'jpg', w: '506', h: '253' },
},
{

View file

@ -1,5 +1,13 @@
# @astrojs/lit
## 1.1.2
### Patch Changes
- [#6080](https://github.com/withastro/astro/pull/6080) [`0db220415`](https://github.com/withastro/astro/commit/0db22041531d981a813a07f4c4e00cfb7ebddd51) Thanks [@e111077](https://github.com/e111077)! - Fixes Lit hydration not having the same reactive values as server (losing state upon hydration)
- [#6055](https://github.com/withastro/astro/pull/6055) [`2567aa48b`](https://github.com/withastro/astro/commit/2567aa48bba8751cf7e10429555f1e85830c9169) Thanks [@e111077](https://github.com/e111077)! - Add forwards compatibility for streaming Declarative Shadow DOM
## 1.1.1
### Patch Changes

View file

@ -8,9 +8,13 @@ async function polyfill() {
}
const polyfillCheckEl = new DOMParser()
.parseFromString(`<p><template shadowroot="open"></template></p>`, 'text/html', {
includeShadowRoots: true,
})
.parseFromString(
`<p><template shadowroot="open" shadowrootmode="open"></template></p>`,
'text/html',
{
includeShadowRoots: true,
}
)
.querySelector('p');
if (!polyfillCheckEl || !polyfillCheckEl.shadowRoot) {

View file

@ -8,7 +8,7 @@ var b = (t, n) => {
function s() {
if (d === void 0) {
let t = document.createElement('div');
(t.innerHTML = '<div><template shadowroot="open"></template></div>'),
(t.innerHTML = '<div><template shadowroot="open" shadowrootmode="open"></template></div>'),
(d = !!t.firstElementChild.shadowRoot);
}
return d;
@ -80,8 +80,12 @@ async function g() {
window.addEventListener('DOMContentLoaded', () => t(document.body), { once: true });
}
var x = new DOMParser()
.parseFromString('<p><template shadowroot="open"></template></p>', 'text/html', {
includeShadowRoots: !0,
})
.parseFromString(
'<p><template shadowroot="open" shadowrootmode="open"></template></p>',
'text/html',
{
includeShadowRoots: !0,
}
)
.querySelector('p');
(!x || !x.shadowRoot) && g();

View file

@ -1,6 +1,6 @@
{
"name": "@astrojs/lit",
"version": "1.1.1",
"version": "1.1.2",
"description": "Use Lit components within Astro",
"type": "module",
"types": "./dist/index.d.ts",
@ -23,6 +23,7 @@
".": "./dist/index.js",
"./server.js": "./server.js",
"./client-shim.js": "./client-shim.js",
"./dist/client.js": "./dist/client.js",
"./hydration-support.js": "./hydration-support.js",
"./package.json": "./package.json"
},

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