diff --git a/.changeset/five-rocks-vanish.md b/.changeset/five-rocks-vanish.md new file mode 100644 index 000000000000..7113deb2969b --- /dev/null +++ b/.changeset/five-rocks-vanish.md @@ -0,0 +1,42 @@ +--- +'astro': minor +--- + +Experimental Server Islands + +Server Islands allow you to specify components that should run on the server, allowing the rest of the page to be more aggressively cached, or even generated statically. Turn any `.astro` component into a server island by adding the `server:defer` directive and optionally, fallback placeholder content: + +```astro +--- +import Avatar from '../components/Avatar.astro'; +import GenericUser from '../components/GenericUser.astro'; +--- + +
+

Page Title

+
+ + + +
+
+``` + +The `server:defer` directive can be used on any Astro component in a project using `hybrid` or `server` mode with an adapter. There are no special APIs needed inside of the island. + +Enable server islands by adding the experimental flag to your Astro config with an appropriate `output` mode and adatper: + +```js +import { defineConfig } from 'astro/config'; +import netlify from '@astrojs/netlify'; + +export default defineConfig({ + output: 'hybrid', + adapter: netlify(), + experimental { + serverIslands: true, + }, +}); +``` + +For more information, see the [server islands documentation](https://docs.astro.build/en/reference/configuration-reference/#experimentalserverislands). diff --git a/examples/server-islands/astro.config.mjs b/examples/server-islands/astro.config.mjs new file mode 100644 index 000000000000..c0d6b918a52e --- /dev/null +++ b/examples/server-islands/astro.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig } from 'astro/config'; +import nodejs from '@astrojs/node'; +import react from '@astrojs/react'; +import tailwind from '@astrojs/tailwind'; + +// https://astro.build/config +export default defineConfig({ + output: 'server', + adapter: nodejs({ mode: 'standalone' }), + integrations: [ + react(), + tailwind({ applyBaseStyles: false }) + ], + devToolbar: { enabled: false }, + experimental: { + serverIslands: true, + } +}); diff --git a/examples/server-islands/package.json b/examples/server-islands/package.json new file mode 100644 index 000000000000..b80361b5e65b --- /dev/null +++ b/examples/server-islands/package.json @@ -0,0 +1,26 @@ +{ + "name": "@example/server-islands", + "version": "0.0.1", + "private": true, + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro" + }, + "devDependencies": { + "@astrojs/node": "^8.2.6", + "@astrojs/react": "^3.6.0", + "@astrojs/tailwind": "^5.1.0", + "@fortawesome/fontawesome-free": "^6.5.2", + "@tailwindcss/forms": "^0.5.7", + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "astro": "workspace:*", + "postcss": "^8.4.38", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "tailwindcss": "^3.4.3" + } +} diff --git a/examples/server-islands/public/assets/css/main.css b/examples/server-islands/public/assets/css/main.css new file mode 100644 index 000000000000..e30382e7c369 --- /dev/null +++ b/examples/server-islands/public/assets/css/main.css @@ -0,0 +1,1766 @@ +/* @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"); +@import url("https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap"); */ + +/* ! tailwindcss v3.0.23 | MIT License | https://tailwindcss.com */ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input:-ms-input-placeholder, textarea:-ms-input-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +/* +Ensure the default browser behavior of the `hidden` attribute. +*/ + +[hidden] { + display: none; +} + +[type='text'],[type='email'],[type='url'],[type='password'],[type='number'],[type='date'],[type='datetime-local'],[type='month'],[type='search'],[type='tel'],[type='time'],[type='week'],[multiple],textarea,select { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + border-radius: 0px; + padding-top: 0.5rem; + padding-right: 0.75rem; + padding-bottom: 0.5rem; + padding-left: 0.75rem; + font-size: 1rem; + line-height: 1.5rem; + --tw-shadow: 0 0 #0000; +} + +[type='text']:focus, [type='email']:focus, [type='url']:focus, [type='password']:focus, [type='number']:focus, [type='date']:focus, [type='datetime-local']:focus, [type='month']:focus, [type='search']:focus, [type='tel']:focus, [type='time']:focus, [type='week']:focus, [multiple]:focus, textarea:focus, select:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + border-color: #2563eb; +} + +input::-moz-placeholder, textarea::-moz-placeholder { + color: #6b7280; + opacity: 1; +} + +input:-ms-input-placeholder, textarea:-ms-input-placeholder { + color: #6b7280; + opacity: 1; +} + +input::placeholder,textarea::placeholder { + color: #6b7280; + opacity: 1; +} + +::-webkit-datetime-edit-fields-wrapper { + padding: 0; +} + +::-webkit-date-and-time-value { + min-height: 1.5em; +} + +::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field { + padding-top: 0; + padding-bottom: 0; +} + +select { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + -webkit-print-color-adjust: exact; + color-adjust: exact; +} + +[multiple] { + background-image: initial; + background-position: initial; + background-repeat: unset; + background-size: initial; + padding-right: 0.75rem; + -webkit-print-color-adjust: unset; + color-adjust: unset; +} + +[type='checkbox'],[type='radio'] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 0; + -webkit-print-color-adjust: exact; + color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + flex-shrink: 0; + height: 1rem; + width: 1rem; + color: #2563eb; + background-color: #fff; + border-color: #6b7280; + border-width: 1px; + --tw-shadow: 0 0 #0000; +} + +[type='checkbox'] { + border-radius: 0px; +} + +[type='radio'] { + border-radius: 100%; +} + +[type='checkbox']:focus,[type='radio']:focus { + outline: 2px solid transparent; + outline-offset: 2px; + --tw-ring-inset: var(--tw-empty,/*!*/ /*!*/); + --tw-ring-offset-width: 2px; + --tw-ring-offset-color: #fff; + --tw-ring-color: #2563eb; + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); +} + +[type='checkbox']:checked,[type='radio']:checked { + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); +} + +[type='radio']:checked { + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e"); +} + +[type='checkbox']:checked:hover,[type='checkbox']:checked:focus,[type='radio']:checked:hover,[type='radio']:checked:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='checkbox']:indeterminate { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e"); + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; +} + +[type='checkbox']:indeterminate:hover,[type='checkbox']:indeterminate:focus { + border-color: transparent; + background-color: currentColor; +} + +[type='file'] { + background: unset; + border-color: inherit; + border-width: 0; + border-radius: 0; + padding: 0; + font-size: unset; + line-height: inherit; +} + +[type='file']:focus { + outline: 1px auto -webkit-focus-ring-color; +} + +body { + font-family: Poppins, sans-serif; +} + +h1, h2, h3, h4, h5, h6 { + font-family: Roboto, sans-serif; +} + +*, ::before, ::after { + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.container { + width: 100%; + margin-right: auto; + margin-left: auto; + padding-right: 1rem; + padding-left: 1rem; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.size-selector input:checked + label { + --tw-bg-opacity: 1; + background-color: rgb(253 61 87 / var(--tw-bg-opacity)); + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.color-selector input:checked + label { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); + --tw-ring-opacity: 1; + --tw-ring-color: rgb(253 61 87 / var(--tw-ring-opacity)); +} + +.input-box { + display: block; + width: 100%; + border-radius: 0.25rem; + border-width: 1px; + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); + padding-left: 1rem; + padding-right: 1rem; + padding-top: 0.75rem; + padding-bottom: 0.75rem; + font-size: 0.875rem; + line-height: 1.25rem; + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.input-box::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.input-box:-ms-input-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.input-box::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.input-box:focus { + --tw-border-opacity: 1; + border-color: rgb(253 61 87 / var(--tw-border-opacity)); + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.invisible { + visibility: hidden; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.inset-0 { + top: 0px; + right: 0px; + bottom: 0px; + left: 0px; +} + +.left-4 { + left: 1rem; +} + +.top-3 { + top: 0.75rem; +} + +.right-0 { + right: 0px; +} + +.-top-1 { + top: -0.25rem; +} + +.-right-3 { + right: -0.75rem; +} + +.left-0 { + left: 0px; +} + +.top-full { + top: 100%; +} + +.-left-8 { + left: -2rem; +} + +.top-0 { + top: 0px; +} + +.z-10 { + z-index: 10; +} + +.col-span-1 { + grid-column: span 1 / span 1; +} + +.col-span-2 { + grid-column: span 2 / span 2; +} + +.col-span-3 { + grid-column: span 3 / span 3; +} + +.col-span-9 { + grid-column: span 9 / span 9; +} + +.col-span-8 { + grid-column: span 8 / span 8; +} + +.col-span-4 { + grid-column: span 4 / span 4; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.mx-3 { + margin-left: 0.75rem; + margin-right: 0.75rem; +} + +.ml-2 { + margin-left: 0.5rem; +} + +.ml-6 { + margin-left: 1.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mt-12 { + margin-top: 3rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-1 { + margin-bottom: 0.25rem; +} + +.ml-3 { + margin-left: 0.75rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mt-1 { + margin-top: 0.25rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.ml-auto { + margin-left: auto; +} + +.block { + display: block; +} + +.flex { + display: flex; +} + +.table { + display: table; +} + +.grid { + display: grid; +} + +.hidden { + display: none; +} + +.h-5 { + height: 1.25rem; +} + +.h-12 { + height: 3rem; +} + +.h-8 { + height: 2rem; +} + +.h-14 { + height: 3.5rem; +} + +.h-3 { + height: 0.75rem; +} + +.h-6 { + height: 1.5rem; +} + +.h-9 { + height: 2.25rem; +} + +.w-32 { + width: 8rem; +} + +.w-full { + width: 100%; +} + +.w-5 { + width: 1.25rem; +} + +.w-10\/12 { + width: 83.333333%; +} + +.w-12 { + width: 3rem; +} + +.w-9 { + width: 2.25rem; +} + +.w-14 { + width: 3.5rem; +} + +.w-3 { + width: 0.75rem; +} + +.w-1\/2 { + width: 50%; +} + +.w-6 { + width: 1.5rem; +} + +.w-max { + width: -webkit-max-content; + width: -moz-max-content; + width: max-content; +} + +.w-8 { + width: 2rem; +} + +.w-3\/5 { + width: 60%; +} + +.w-40 { + width: 10rem; +} + +.w-44 { + width: 11rem; +} + +.w-10 { + width: 2.5rem; +} + +.w-28 { + width: 7rem; +} + +.w-1\/3 { + width: 33.333333%; +} + +.max-w-xl { + max-width: 36rem; +} + +.max-w-lg { + max-width: 32rem; +} + +.flex-shrink-0 { + flex-shrink: 0; +} + +.flex-grow { + flex-grow: 1; +} + +.table-auto { + table-layout: auto; +} + +.border-collapse { + border-collapse: collapse; +} + +.cursor-pointer { + cursor: pointer; +} + +.cursor-not-allowed { + cursor: not-allowed; +} + +.select-none { + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); +} + +.grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); +} + +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.items-baseline { + align-items: baseline; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.gap-6 { + gap: 1.5rem; +} + +.gap-5 { + gap: 1.25rem; +} + +.gap-3 { + gap: 0.75rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-1 { + gap: 0.25rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-8 { + gap: 2rem; +} + +.space-x-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1rem * var(--tw-space-x-reverse)); + margin-left: calc(1rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-6 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1.5rem * var(--tw-space-x-reverse)); + margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-x-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(0.5rem * var(--tw-space-x-reverse)); + margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-4 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1rem * var(--tw-space-y-reverse)); +} + +.space-x-5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-x-reverse: 0; + margin-right: calc(1.25rem * var(--tw-space-x-reverse)); + margin-left: calc(1.25rem * calc(1 - var(--tw-space-x-reverse))); +} + +.space-y-1 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.25rem * var(--tw-space-y-reverse)); +} + +.space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} + +.space-y-2 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); +} + +.space-y-5 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(1.25rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(1.25rem * var(--tw-space-y-reverse)); +} + +.divide-y > :not([hidden]) ~ :not([hidden]) { + --tw-divide-y-reverse: 0; + border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); +} + +.divide-x > :not([hidden]) ~ :not([hidden]) { + --tw-divide-x-reverse: 0; + border-right-width: calc(1px * var(--tw-divide-x-reverse)); + border-left-width: calc(1px * calc(1 - var(--tw-divide-x-reverse))); +} + +.divide-dashed > :not([hidden]) ~ :not([hidden]) { + border-style: dashed; +} + +.divide-gray-300 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-divide-opacity)); +} + +.divide-gray-200 > :not([hidden]) ~ :not([hidden]) { + --tw-divide-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-divide-opacity)); +} + +.overflow-hidden { + overflow: hidden; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-sm { + border-radius: 0.125rem; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-l-md { + border-top-left-radius: 0.375rem; + border-bottom-left-radius: 0.375rem; +} + +.rounded-r-md { + border-top-right-radius: 0.375rem; + border-bottom-right-radius: 0.375rem; +} + +.rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.border { + border-width: 1px; +} + +.border-r-0 { + border-right-width: 0px; +} + +.border-t { + border-top-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-b-2 { + border-bottom-width: 2px; +} + +.border-primary { + --tw-border-opacity: 1; + border-color: rgb(253 61 87 / var(--tw-border-opacity)); +} + +.border-gray-100 { + --tw-border-opacity: 1; + border-color: rgb(243 244 246 / var(--tw-border-opacity)); +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + +.border-gray-300 { + --tw-border-opacity: 1; + border-color: rgb(209 213 219 / var(--tw-border-opacity)); +} + +.border-red-400 { + --tw-border-opacity: 1; + border-color: rgb(248 113 113 / var(--tw-border-opacity)); +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-primary { + --tw-bg-opacity: 1; + background-color: rgb(253 61 87 / var(--tw-bg-opacity)); +} + +.bg-gray-800 { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.bg-black { + --tw-bg-opacity: 1; + background-color: rgb(0 0 0 / var(--tw-bg-opacity)); +} + +.bg-blue-800 { + --tw-bg-opacity: 1; + background-color: rgb(30 64 175 / var(--tw-bg-opacity)); +} + +.bg-red-600 { + --tw-bg-opacity: 1; + background-color: rgb(220 38 38 / var(--tw-bg-opacity)); +} + +.bg-red-400 { + --tw-bg-opacity: 1; + background-color: rgb(248 113 113 / var(--tw-bg-opacity)); +} + +.bg-opacity-40 { + --tw-bg-opacity: 0.4; +} + +.bg-cover { + background-size: cover; +} + +.bg-center { + background-position: center; +} + +.bg-no-repeat { + background-repeat: no-repeat; +} + +.object-contain { + -o-object-fit: contain; + object-fit: contain; +} + +.object-cover { + -o-object-fit: cover; + object-fit: cover; +} + +.p-1 { + padding: 0.25rem; +} + +.p-4 { + padding: 1rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-5 { + padding-top: 1.25rem; + padding-bottom: 1.25rem; +} + +.py-36 { + padding-top: 9rem; + padding-bottom: 9rem; +} + +.py-16 { + padding-top: 4rem; + padding-bottom: 4rem; +} + +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; +} + +.py-7 { + padding-top: 1.75rem; + padding-bottom: 1.75rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.pl-12 { + padding-left: 3rem; +} + +.pr-3 { + padding-right: 0.75rem; +} + +.pb-16 { + padding-bottom: 4rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.pb-3 { + padding-bottom: 0.75rem; +} + +.pt-16 { + padding-top: 4rem; +} + +.pb-12 { + padding-bottom: 3rem; +} + +.pl-8 { + padding-left: 2rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + +.pb-8 { + padding-bottom: 2rem; +} + +.pb-5 { + padding-bottom: 1.25rem; +} + +.pt-5 { + padding-top: 1.25rem; +} + +.pb-7 { + padding-bottom: 1.75rem; +} + +.pb-6 { + padding-bottom: 1.5rem; +} + +.text-left { + text-align: left; +} + +.text-center { + text-align: center; +} + +.font-roboto { + font-family: Roboto, sans-serif; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-6xl { + font-size: 3.75rem; + line-height: 1; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.font-medium { + font-weight: 500; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.capitalize { + text-transform: capitalize; +} + +.leading-3 { + line-height: .75rem; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +.text-gray-400 { + --tw-text-opacity: 1; + color: rgb(156 163 175 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.text-gray-700 { + --tw-text-opacity: 1; + color: rgb(55 65 81 / var(--tw-text-opacity)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.text-gray-200 { + --tw-text-opacity: 1; + color: rgb(229 231 235 / var(--tw-text-opacity)); +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.text-primary { + --tw-text-opacity: 1; + color: rgb(253 61 87 / var(--tw-text-opacity)); +} + +.text-yellow-400 { + --tw-text-opacity: 1; + color: rgb(250 204 21 / var(--tw-text-opacity)); +} + +.text-green-600 { + --tw-text-opacity: 1; + color: rgb(22 163 74 / var(--tw-text-opacity)); +} + +.text-red-600 { + --tw-text-opacity: 1; + color: rgb(220 38 38 / var(--tw-text-opacity)); +} + +.line-through { + -webkit-text-decoration-line: line-through; + text-decoration-line: line-through; +} + +.placeholder-gray-400::-moz-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.placeholder-gray-400:-ms-input-placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.placeholder-gray-400::placeholder { + --tw-placeholder-opacity: 1; + color: rgb(156 163 175 / var(--tw-placeholder-opacity)); +} + +.opacity-0 { + opacity: 0; +} + +.shadow-sm { + --tw-shadow: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-md { + --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.transition { + transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-300 { + transition-duration: 300ms; +} + +.hover\:bg-transparent:hover { + background-color: transparent; +} + +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.hover\:bg-gray-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); +} + +.hover\:bg-blue-700:hover { + --tw-bg-opacity: 1; + background-color: rgb(29 78 216 / var(--tw-bg-opacity)); +} + +.hover\:bg-red-500:hover { + --tw-bg-opacity: 1; + background-color: rgb(239 68 68 / var(--tw-bg-opacity)); +} + +.hover\:text-primary:hover { + --tw-text-opacity: 1; + color: rgb(253 61 87 / var(--tw-text-opacity)); +} + +.hover\:text-white:hover { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.hover\:text-gray-500:hover { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.focus\:border-primary:focus { + --tw-border-opacity: 1; + border-color: rgb(253 61 87 / var(--tw-border-opacity)); +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +.focus\:ring-0:focus { + --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); + --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); + box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); +} + +.focus\:ring-primary:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(253 61 87 / var(--tw-ring-opacity)); +} + +.group:hover .group-hover\:visible { + visibility: visible; +} + +.group:hover .group-hover\:bg-opacity-60 { + --tw-bg-opacity: 0.6; +} + +.group:hover .group-hover\:opacity-100 { + opacity: 1; +} + +@media (min-width: 768px) { + .md\:block { + display: block; + } + + .md\:flex { + display: flex; + } + + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .md\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .md\:gap-8 { + gap: 2rem; + } + + .md\:pl-12 { + padding-left: 3rem; + } +} diff --git a/examples/server-islands/public/assets/images/avatar.png b/examples/server-islands/public/assets/images/avatar.png new file mode 100644 index 000000000000..4e8512382cdd Binary files /dev/null and b/examples/server-islands/public/assets/images/avatar.png differ diff --git a/examples/server-islands/public/assets/images/banner-bg.jpg b/examples/server-islands/public/assets/images/banner-bg.jpg new file mode 100644 index 000000000000..0ce119a45584 Binary files /dev/null and b/examples/server-islands/public/assets/images/banner-bg.jpg differ diff --git a/examples/server-islands/public/assets/images/category/category-1.jpg b/examples/server-islands/public/assets/images/category/category-1.jpg new file mode 100644 index 000000000000..e45bf4ae01ec Binary files /dev/null and b/examples/server-islands/public/assets/images/category/category-1.jpg differ diff --git a/examples/server-islands/public/assets/images/category/category-2.jpg b/examples/server-islands/public/assets/images/category/category-2.jpg new file mode 100644 index 000000000000..56249e6a3f7d Binary files /dev/null and b/examples/server-islands/public/assets/images/category/category-2.jpg differ diff --git a/examples/server-islands/public/assets/images/category/category-3.jpg b/examples/server-islands/public/assets/images/category/category-3.jpg new file mode 100644 index 000000000000..5ef6659465e9 Binary files /dev/null and b/examples/server-islands/public/assets/images/category/category-3.jpg differ diff --git a/examples/server-islands/public/assets/images/category/category-4.jpg b/examples/server-islands/public/assets/images/category/category-4.jpg new file mode 100644 index 000000000000..b12e648303c9 Binary files /dev/null and b/examples/server-islands/public/assets/images/category/category-4.jpg differ diff --git a/examples/server-islands/public/assets/images/category/category-5.jpg b/examples/server-islands/public/assets/images/category/category-5.jpg new file mode 100644 index 000000000000..f8e804f8c066 Binary files /dev/null and b/examples/server-islands/public/assets/images/category/category-5.jpg differ diff --git a/examples/server-islands/public/assets/images/category/category-6.jpg b/examples/server-islands/public/assets/images/category/category-6.jpg new file mode 100644 index 000000000000..e1991d35cbbd Binary files /dev/null and b/examples/server-islands/public/assets/images/category/category-6.jpg differ diff --git a/examples/server-islands/public/assets/images/complete.png b/examples/server-islands/public/assets/images/complete.png new file mode 100644 index 000000000000..ea3fe9681e09 Binary files /dev/null and b/examples/server-islands/public/assets/images/complete.png differ diff --git a/examples/server-islands/public/assets/images/favicon/about.txt b/examples/server-islands/public/assets/images/favicon/about.txt new file mode 100644 index 000000000000..7229927f7072 --- /dev/null +++ b/examples/server-islands/public/assets/images/favicon/about.txt @@ -0,0 +1,6 @@ +This favicon was generated using the following font: + +- Font Title: Roboto +- Font Author: Copyright 2011 Google Inc. All Rights Reserved. +- Font Source: http://fonts.gstatic.com/s/roboto/v29/KFOlCnqEu92Fr1MmWUlvAx05IsDqlA.ttf +- Font License: Apache License, version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.html)) diff --git a/examples/server-islands/public/assets/images/favicon/android-chrome-192x192.png b/examples/server-islands/public/assets/images/favicon/android-chrome-192x192.png new file mode 100644 index 000000000000..6809c797894d Binary files /dev/null and b/examples/server-islands/public/assets/images/favicon/android-chrome-192x192.png differ diff --git a/examples/server-islands/public/assets/images/favicon/android-chrome-512x512.png b/examples/server-islands/public/assets/images/favicon/android-chrome-512x512.png new file mode 100644 index 000000000000..27d7db79c6a0 Binary files /dev/null and b/examples/server-islands/public/assets/images/favicon/android-chrome-512x512.png differ diff --git a/examples/server-islands/public/assets/images/favicon/apple-touch-icon.png b/examples/server-islands/public/assets/images/favicon/apple-touch-icon.png new file mode 100644 index 000000000000..ff578ae53911 Binary files /dev/null and b/examples/server-islands/public/assets/images/favicon/apple-touch-icon.png differ diff --git a/examples/server-islands/public/assets/images/favicon/favicon-16x16.png b/examples/server-islands/public/assets/images/favicon/favicon-16x16.png new file mode 100644 index 000000000000..470a85966941 Binary files /dev/null and b/examples/server-islands/public/assets/images/favicon/favicon-16x16.png differ diff --git a/examples/server-islands/public/assets/images/favicon/favicon-32x32.png b/examples/server-islands/public/assets/images/favicon/favicon-32x32.png new file mode 100644 index 000000000000..56afd053d926 Binary files /dev/null and b/examples/server-islands/public/assets/images/favicon/favicon-32x32.png differ diff --git a/examples/server-islands/public/assets/images/favicon/favicon.ico b/examples/server-islands/public/assets/images/favicon/favicon.ico new file mode 100644 index 000000000000..77bb5fa1839f Binary files /dev/null and b/examples/server-islands/public/assets/images/favicon/favicon.ico differ diff --git a/examples/server-islands/public/assets/images/favicon/site.webmanifest b/examples/server-islands/public/assets/images/favicon/site.webmanifest new file mode 100644 index 000000000000..45dc8a20658b --- /dev/null +++ b/examples/server-islands/public/assets/images/favicon/site.webmanifest @@ -0,0 +1 @@ +{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} \ No newline at end of file diff --git a/examples/server-islands/public/assets/images/icons/bed-2.svg b/examples/server-islands/public/assets/images/icons/bed-2.svg new file mode 100644 index 000000000000..09b790da78db --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/bed-2.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/server-islands/public/assets/images/icons/bed.svg b/examples/server-islands/public/assets/images/icons/bed.svg new file mode 100644 index 000000000000..d66adb50136e --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/bed.svg @@ -0,0 +1,2 @@ + + diff --git a/examples/server-islands/public/assets/images/icons/delivery-van.svg b/examples/server-islands/public/assets/images/icons/delivery-van.svg new file mode 100644 index 000000000000..a6c08a447c8c --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/delivery-van.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/server-islands/public/assets/images/icons/money-back.svg b/examples/server-islands/public/assets/images/icons/money-back.svg new file mode 100644 index 000000000000..8722d553853c --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/money-back.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/examples/server-islands/public/assets/images/icons/office.svg b/examples/server-islands/public/assets/images/icons/office.svg new file mode 100644 index 000000000000..27451293b5d7 --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/office.svg @@ -0,0 +1,2 @@ + + diff --git a/examples/server-islands/public/assets/images/icons/outdoor-cafe.svg b/examples/server-islands/public/assets/images/icons/outdoor-cafe.svg new file mode 100644 index 000000000000..2f014997946d --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/outdoor-cafe.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/server-islands/public/assets/images/icons/phone.svg b/examples/server-islands/public/assets/images/icons/phone.svg new file mode 100644 index 000000000000..1ab3dafd2160 --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/phone.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/examples/server-islands/public/assets/images/icons/restaurant.svg b/examples/server-islands/public/assets/images/icons/restaurant.svg new file mode 100644 index 000000000000..859ca4457609 --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/restaurant.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/server-islands/public/assets/images/icons/service-hours.svg b/examples/server-islands/public/assets/images/icons/service-hours.svg new file mode 100644 index 000000000000..311820ec5c45 --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/service-hours.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/server-islands/public/assets/images/icons/sofa.svg b/examples/server-islands/public/assets/images/icons/sofa.svg new file mode 100644 index 000000000000..61608eb42e03 --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/sofa.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/server-islands/public/assets/images/icons/terrace.svg b/examples/server-islands/public/assets/images/icons/terrace.svg new file mode 100644 index 000000000000..40cd91ff4fc9 --- /dev/null +++ b/examples/server-islands/public/assets/images/icons/terrace.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/server-islands/public/assets/images/logo.svg b/examples/server-islands/public/assets/images/logo.svg new file mode 100644 index 000000000000..4a698caaad39 --- /dev/null +++ b/examples/server-islands/public/assets/images/logo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/examples/server-islands/public/assets/images/methods.png b/examples/server-islands/public/assets/images/methods.png new file mode 100644 index 000000000000..6ab2499aa3cb Binary files /dev/null and b/examples/server-islands/public/assets/images/methods.png differ diff --git a/examples/server-islands/public/assets/images/offer.jpg b/examples/server-islands/public/assets/images/offer.jpg new file mode 100644 index 000000000000..1b735cdb8801 Binary files /dev/null and b/examples/server-islands/public/assets/images/offer.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product1.jpg b/examples/server-islands/public/assets/images/products/product1.jpg new file mode 100644 index 000000000000..523c18d1ce82 Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product1.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product10.jpg b/examples/server-islands/public/assets/images/products/product10.jpg new file mode 100644 index 000000000000..234c4b1bacf0 Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product10.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product11.jpg b/examples/server-islands/public/assets/images/products/product11.jpg new file mode 100644 index 000000000000..8f23255cf6fb Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product11.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product12.jpg b/examples/server-islands/public/assets/images/products/product12.jpg new file mode 100644 index 000000000000..f1f32d6ddf4b Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product12.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product2.jpg b/examples/server-islands/public/assets/images/products/product2.jpg new file mode 100644 index 000000000000..ab2fc5d8b59d Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product2.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product3.jpg b/examples/server-islands/public/assets/images/products/product3.jpg new file mode 100644 index 000000000000..dd2e27bc4d5a Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product3.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product4.jpg b/examples/server-islands/public/assets/images/products/product4.jpg new file mode 100644 index 000000000000..4e31e49b8008 Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product4.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product5.jpg b/examples/server-islands/public/assets/images/products/product5.jpg new file mode 100644 index 000000000000..e950c71fd260 Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product5.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product6.jpg b/examples/server-islands/public/assets/images/products/product6.jpg new file mode 100644 index 000000000000..fb68277067b4 Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product6.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product7.jpg b/examples/server-islands/public/assets/images/products/product7.jpg new file mode 100644 index 000000000000..e0c355a9bb3d Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product7.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product8.jpg b/examples/server-islands/public/assets/images/products/product8.jpg new file mode 100644 index 000000000000..bca29d380cf1 Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product8.jpg differ diff --git a/examples/server-islands/public/assets/images/products/product9.jpg b/examples/server-islands/public/assets/images/products/product9.jpg new file mode 100644 index 000000000000..7e4558371a70 Binary files /dev/null and b/examples/server-islands/public/assets/images/products/product9.jpg differ diff --git a/examples/server-islands/src/base.css b/examples/server-islands/src/base.css new file mode 100644 index 000000000000..90e97c7ff114 --- /dev/null +++ b/examples/server-islands/src/base.css @@ -0,0 +1,32 @@ +@import url("https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap"); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply font-poppins; + } + h1, + h2, + h3, + h4, + h5, + h6 { + @apply font-roboto; + } +} + +@layer components { + .size-selector input:checked + label { + @apply bg-primary text-white; + } + .color-selector input:checked + label { + @apply ring-2 ring-primary; + } + + .input-box { + @apply block w-full border border-gray-300 px-4 py-3 text-gray-600 text-sm rounded placeholder-gray-400 focus:border-primary focus:ring-0; + } +} diff --git a/examples/server-islands/src/cart.ts b/examples/server-islands/src/cart.ts new file mode 100644 index 000000000000..355074a38c3b --- /dev/null +++ b/examples/server-islands/src/cart.ts @@ -0,0 +1,20 @@ + +const channel = new MessageChannel(); + +function onNewCartItem(cb: (m: any) => void) { + let onMessage = (ev: MessageEvent) => { + cb(ev.data); + }; + channel.port2.addEventListener('message', onMessage); + channel.port2.start(); + return () => channel.port2.removeEventListener('message', onMessage); +} + +function addToCart(item: any) { + channel.port1.postMessage(item); +} + +export { + onNewCartItem, + addToCart +} diff --git a/examples/server-islands/src/components/AddToCart.tsx b/examples/server-islands/src/components/AddToCart.tsx new file mode 100644 index 000000000000..5fcfc4eaa172 --- /dev/null +++ b/examples/server-islands/src/components/AddToCart.tsx @@ -0,0 +1,27 @@ +import { addToCart } from '../cart'; + +export default function({ small }) { + function onClick(ev: Event) { + ev.preventDefault(); + let item = { name: 'Sofa' }; + addToCart(item); + + } + + if(small) { + return ( + Add + to cart + ) + } + + return ( + + Add to cart + + ) +} diff --git a/examples/server-islands/src/components/CartCount.tsx b/examples/server-islands/src/components/CartCount.tsx new file mode 100644 index 000000000000..5c3d3e3928d3 --- /dev/null +++ b/examples/server-islands/src/components/CartCount.tsx @@ -0,0 +1,14 @@ +import { useEffect, useState } from 'react'; +import { onNewCartItem } from '../cart'; + +export default function({ count: initialCount }) { + const [count, setCount] = useState(initialCount); + useEffect(() => { + return onNewCartItem(() => setCount(count + 1)); + }, [count]); + + return ( +
+{count}
+ ); +} diff --git a/examples/server-islands/src/components/PersonalBar.astro b/examples/server-islands/src/components/PersonalBar.astro new file mode 100644 index 000000000000..197ade129793 --- /dev/null +++ b/examples/server-islands/src/components/PersonalBar.astro @@ -0,0 +1,32 @@ +--- +import CartCount from './CartCount'; + +const { placeholder } = Astro.props; +let wishlist = 0; +let cart = 0; + +if(!placeholder) { + await new Promise(resolve => setTimeout(resolve, 3000)); +} +--- + +
+ +
+
Wishlist
+
{ wishlist }
+
+ +
+ +
+
Cart
+ +
+ +
+ +
+
Account
+
diff --git a/examples/server-islands/src/pages/index.astro b/examples/server-islands/src/pages/index.astro new file mode 100644 index 000000000000..a36d5df05f4a --- /dev/null +++ b/examples/server-islands/src/pages/index.astro @@ -0,0 +1,543 @@ +--- +import '../base.css'; +import AddToCart from '../components/AddToCart'; +import PersonalBar from '../components/PersonalBar.astro'; +import '@fortawesome/fontawesome-free/css/all.min.css'; +--- + + + + + + + + Product - Ecommerce Tailwind + + + + + + + + + + +
+
+ + Logo + + +
+ + + + + +
+ +
+ + + +
+
+
+ + + + + + + +
+ + + + + + +

Product

+
+ + + +
+
+ product +
+ product2 + product2 + product2 + product2 + product2 +
+
+ +
+

Italian L Shape Sofa

+
+
+ + + + + +
+
(150 Reviews)
+
+
+

+ Availability: + In Stock +

+

+ Brand: + Apex +

+

+ Category: + Sofa +

+

+ SKU: + BE45VGRT +

+
+
+

$45.00

+

$55.00

+
+ +

Lorem ipsum dolor sit amet consectetur adipisicing elit. Eos eius eum + reprehenderit dolore vel mollitia optio consequatur hic asperiores inventore suscipit, velit + consequuntur, voluptate doloremque iure necessitatibus adipisci magnam porro.

+ +
+

Size

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+

Color

+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+

Quantity

+
+
-
+
4
+
+
+
+
+ + + + +
+
+ + + +
+

Product details

+
+
+

Lorem, ipsum dolor sit amet consectetur adipisicing elit. Tenetur necessitatibus deleniti natus + dolore cum maiores suscipit optio itaque voluptatibus veritatis tempora iste facilis non aut + sapiente dolor quisquam, ex ab.

+

Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolorum, quae accusantium voluptatem + blanditiis sapiente voluptatum. Autem ab, dolorum assumenda earum veniam eius illo fugiat possimus + illum dolor totam, ducimus excepturi.

+

Lorem ipsum dolor sit amet consectetur adipisicing elit. Error quia modi ut expedita! Iure molestiae + labore cumque nobis quasi fuga, quibusdam rem? Temporibus consectetur corrupti rerum veritatis + numquam labore amet.

+
+ + + + + + + + + + + + + + +
ColorBlank, Brown, Red
MaterialLatex
Weight55kg
+
+
+ + + +
+

Related products

+
+
+
+ product 1 + +
+
+ +

Guyer + Chair

+
+
+

$45.00

+

$55.90

+
+
+
+ + + + + +
+
(150)
+
+
+ +
+
+
+ product 1 + +
+
+ +

Bed + King Size

+
+
+

$45.00

+

$55.90

+
+
+
+ + + + + +
+
(150)
+
+
+ +
+
+
+ product 1 + +
+
+ +

+ Couple Sofa

+
+
+

$45.00

+

$55.90

+
+
+
+ + + + + +
+
(150)
+
+
+ +
+
+
+ product 1 + +
+
+ +

+ Mattrass X

+
+
+

$45.00

+

$55.90

+
+
+
+ + + + + +
+
(150)
+
+
+ +
+
+
+ + + + + + + +
+
+

© TailCommerce - All Right Reserved

+
+ methods +
+
+
+ + + + + diff --git a/examples/server-islands/tailwind.config.cjs b/examples/server-islands/tailwind.config.cjs new file mode 100644 index 000000000000..4076218af7f3 --- /dev/null +++ b/examples/server-islands/tailwind.config.cjs @@ -0,0 +1,27 @@ +module.exports = { + content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'], + theme: { + screen: { + sm: "576px", + md: "768px", + lg: "992px", + xl: "1200px", + }, + container: { + center: true, + padding: "1rem", + }, + extend: { + fontFamily: { + poppins: ["Poppins", "sans-serif"], + roboto: ["Roboto", "sans-serif"], + }, + colors: { + primary: "#fd3d57", + }, + }, + }, + plugins: [ + require("@tailwindcss/forms"), + ], +}; diff --git a/packages/astro/e2e/fixtures/server-islands/astro.config.mjs b/packages/astro/e2e/fixtures/server-islands/astro.config.mjs new file mode 100644 index 000000000000..f03c53335d30 --- /dev/null +++ b/packages/astro/e2e/fixtures/server-islands/astro.config.mjs @@ -0,0 +1,14 @@ +import mdx from '@astrojs/mdx'; +import react from '@astrojs/react'; +import { defineConfig } from 'astro/config'; +import nodejs from '@astrojs/node'; + +// https://astro.build/config +export default defineConfig({ + output: 'hybrid', + adapter: nodejs({ mode: 'standalone' }), + integrations: [react(), mdx()], + experimental: { + serverIslands: true, + } +}); diff --git a/packages/astro/e2e/fixtures/server-islands/package.json b/packages/astro/e2e/fixtures/server-islands/package.json new file mode 100644 index 000000000000..9958ee287857 --- /dev/null +++ b/packages/astro/e2e/fixtures/server-islands/package.json @@ -0,0 +1,16 @@ +{ + "name": "@e2e/server-islands", + "version": "0.0.0", + "private": true, + "scripts": { + "dev": "astro dev" + }, + "dependencies": { + "@astrojs/react": "workspace:*", + "astro": "workspace:*", + "@astrojs/mdx": "workspace:*", + "@astrojs/node": "workspace:*", + "react": "^18.3.1", + "react-dom": "^18.3.1" + } +} diff --git a/packages/astro/e2e/fixtures/server-islands/src/components/Island.astro b/packages/astro/e2e/fixtures/server-islands/src/components/Island.astro new file mode 100644 index 000000000000..b7c376f517ad --- /dev/null +++ b/packages/astro/e2e/fixtures/server-islands/src/components/Island.astro @@ -0,0 +1,4 @@ +--- +--- +

I am an island

+ diff --git a/packages/astro/e2e/fixtures/server-islands/src/pages/index.astro b/packages/astro/e2e/fixtures/server-islands/src/pages/index.astro new file mode 100644 index 000000000000..71b7d0e266c8 --- /dev/null +++ b/packages/astro/e2e/fixtures/server-islands/src/pages/index.astro @@ -0,0 +1,14 @@ +--- +import Island from '../components/Island.astro'; +--- + + + + + + + +

children

+
+ + diff --git a/packages/astro/e2e/fixtures/server-islands/src/pages/mdx.mdx b/packages/astro/e2e/fixtures/server-islands/src/pages/mdx.mdx new file mode 100644 index 000000000000..1a0a0ac6f3f9 --- /dev/null +++ b/packages/astro/e2e/fixtures/server-islands/src/pages/mdx.mdx @@ -0,0 +1,3 @@ +import Island from '../components/Island.astro'; + + diff --git a/packages/astro/e2e/server-islands.test.js b/packages/astro/e2e/server-islands.test.js new file mode 100644 index 000000000000..1479b807b121 --- /dev/null +++ b/packages/astro/e2e/server-islands.test.js @@ -0,0 +1,66 @@ +import { expect } from '@playwright/test'; +import { testFactory } from './test-utils.js'; + +const test = testFactory({ root: './fixtures/server-islands/' }); + +test.describe('Server islands', () => { + test.describe('Development', () => { + let devServer; + + test.beforeAll(async ({ astro }) => { + devServer = await astro.startDevServer(); + }); + + test.afterAll(async () => { + await devServer.stop(); + }); + + test('Load content from the server', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + let el = page.locator('#island'); + + await expect(el, 'element rendered').toBeVisible(); + await expect(el, 'should have content').toHaveText('I am an island'); + }); + + test('Can be in an MDX file', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/mdx')); + let el = page.locator('#island'); + + await expect(el, 'element rendered').toBeVisible(); + await expect(el, 'should have content').toHaveText('I am an island'); + }); + + test('Slots are provided back to the server islands', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + let el = page.locator('#children'); + + await expect(el, 'element rendered').toBeVisible(); + }); + }); + + test.describe('Production', () => { + let previewServer; + + test.beforeAll(async ({ astro }) => { + // Playwright's Node version doesn't have these functions, so stub them. + process.stdout.clearLine = () => {}; + process.stdout.cursorTo = () => {}; + await astro.build(); + previewServer = await astro.preview(); + }); + + test.afterAll(async () => { + await previewServer.stop(); + }); + + test('Only one component in prod', async ({ page, astro }) => { + await page.goto(astro.resolveUrl('/')); + + let el = page.locator('#island'); + + await expect(el, 'element rendered').toBeVisible(); + await expect(el, 'should have content').toHaveText('I am an island'); + }); + }); +}); diff --git a/packages/astro/package.json b/packages/astro/package.json index a83a148861cb..bd24ecaac792 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -125,7 +125,7 @@ "test:node": "astro-scripts test \"test/**/*.test.js\"" }, "dependencies": { - "@astrojs/compiler": "^2.8.2", + "@astrojs/compiler": "^2.9.0", "@astrojs/internal-helpers": "workspace:*", "@astrojs/markdown-remark": "workspace:*", "@astrojs/telemetry": "workspace:*", diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts index 81199ea94361..33cb5f0d0b13 100644 --- a/packages/astro/src/@types/astro.ts +++ b/packages/astro/src/@types/astro.ts @@ -17,7 +17,7 @@ import type { ActionInputSchema, } from '../actions/runtime/virtual/server.js'; import type { RemotePattern } from '../assets/utils/remotePattern.js'; -import type { AssetsPrefix, SerializedSSRManifest } from '../core/app/types.js'; +import type { AssetsPrefix, SSRManifest, SerializedSSRManifest } from '../core/app/types.js'; import type { PageBuildData } from '../core/build/types.js'; import type { AstroConfigType } from '../core/config/index.js'; import type { AstroTimer } from '../core/config/timer.js'; @@ -90,6 +90,7 @@ export interface AstroBuiltinProps { 'client:media'?: string; 'client:visible'?: ClientVisibleOptions | boolean; 'client:only'?: boolean | string; + 'server:defer'?: boolean; } export type ClientVisibleOptions = Pick; @@ -2202,6 +2203,71 @@ export interface AstroUserConfig { */ validateSecrets?: boolean; }; + + /** + * @docs + * @name experimental.serverIslands + * @type {boolean} + * @default `false` + * @version 4.12.0 + * @description + * + * Enables experimental Server Island features. + * Server Islands offer the ability to defer a component to render asynchronously after the page has already rendered. + * + * To enable, configure an [on-demand server rendering `output` mode](https://docs.astro.build/en/basics/rendering-modes/#on-demand-rendered) with an adapter, and add the `serverIslands` flag to the `experimental` object: + * + * ```js + * { + * output: 'hybrid', // or 'server' + * adapter: nodejs({ mode: 'standalone' }), + * experimental: { + * serverIslands: true, + * }, + * } + * ``` + * + * Use the `server:defer` directive on any Astro component to delay initial rendering: + * + * ```astro "server:defer" + * --- + * import Avatar from '~/components/Avatar.astro'; + * --- + * + * ``` + * + * The outer page will be rendered, either at build-time (`hybrid`) or at runtime (`server`) with the island content omitted and a ``) + } + } +} diff --git a/packages/astro/src/runtime/server/render/slot.ts b/packages/astro/src/runtime/server/render/slot.ts index 95f9008250aa..fae7edc9eb9b 100644 --- a/packages/astro/src/runtime/server/render/slot.ts +++ b/packages/astro/src/runtime/server/render/slot.ts @@ -1,8 +1,8 @@ import type { SSRResult } from '../../../@types/astro.js'; -import type { renderTemplate } from './astro/render-template.js'; +import { renderTemplate } from './astro/render-template.js'; import type { RenderInstruction } from './instruction.js'; -import { HTMLString, markHTMLString } from '../escape.js'; +import { HTMLString, markHTMLString, unescapeHTML } from '../escape.js'; import { renderChild } from './any.js'; import { type RenderDestination, type RenderInstance, chunkToString } from './common.js'; @@ -103,3 +103,9 @@ export async function renderSlots( } return { slotInstructions, children }; } + +export function createSlotValueFromString(content: string): ComponentSlotValue { + return function() { + return renderTemplate`${unescapeHTML(content)}`; + }; +} diff --git a/packages/astro/src/vite-plugin-astro-server/pipeline.ts b/packages/astro/src/vite-plugin-astro-server/pipeline.ts index 4ad3c48c8ba6..c41d5bbcd973 100644 --- a/packages/astro/src/vite-plugin-astro-server/pipeline.ts +++ b/packages/astro/src/vite-plugin-astro-server/pipeline.ts @@ -12,13 +12,13 @@ import type { } from '../@types/astro.js'; import { getInfoOutput } from '../cli/info/index.js'; import { type HeadElements } from '../core/base-pipeline.js'; -import { ASTRO_VERSION, DEFAULT_404_COMPONENT } from '../core/constants.js'; +import { ASTRO_VERSION } from '../core/constants.js'; import { enhanceViteSSRError } from '../core/errors/dev/index.js'; import { AggregateError, CSSError, MarkdownError } from '../core/errors/index.js'; import type { Logger } from '../core/logger/core.js'; import type { ModuleLoader } from '../core/module-loader/index.js'; import { Pipeline, loadRenderer } from '../core/render/index.js'; -import { default404Page } from '../core/routing/astro-designed-error-pages.js'; +import { createDefaultRoutes } from '../core/routing/default.js'; import { findRouteToRewrite } from '../core/routing/rewrite.js'; import { isPage, isServerLikeOutput, viteID } from '../core/util.js'; import { resolveIdToUrl } from '../core/viteUtils.js'; @@ -45,13 +45,16 @@ export class DevPipeline extends Pipeline { readonly logger: Logger, readonly manifest: SSRManifest, readonly settings: AstroSettings, - readonly config = settings.config + readonly config = settings.config, + readonly defaultRoutes = createDefaultRoutes(manifest, config.root) ) { const mode = 'development'; const resolve = createResolve(loader, config.root); const serverLike = isServerLikeOutput(config); const streaming = true; super(logger, manifest, mode, [], resolve, serverLike, streaming); + manifest.serverIslandMap = settings.serverIslandMap; + manifest.serverIslandNameMap = settings.serverIslandNameMap; } static create( @@ -153,8 +156,12 @@ export class DevPipeline extends Pipeline { async preload(routeData: RouteData, filePath: URL) { const { loader } = this; - if (filePath.href === new URL(DEFAULT_404_COMPONENT, this.config.root).href) { - return { default: default404Page } as any as ComponentInstance; + + // First check built-in routes + for(const route of this.defaultRoutes) { + if(route.matchesComponent(filePath)) { + return route.instance; + } } // Important: This needs to happen first, in case a renderer provides polyfills. @@ -216,4 +223,16 @@ export class DevPipeline extends Pipeline { setManifestData(manifestData: ManifestData) { this.manifestData = manifestData; } + + rewriteKnownRoute(route: string, sourceRoute: RouteData): ComponentInstance { + if (isServerLikeOutput(this.config) && sourceRoute.prerender) { + for(let def of this.defaultRoutes) { + if(route === def.route) { + return def.instance; + } + } + } + + throw new Error('Unknown route'); + } } diff --git a/packages/astro/src/vite-plugin-astro-server/plugin.ts b/packages/astro/src/vite-plugin-astro-server/plugin.ts index 9df0502ea320..ba94d7cbf27c 100644 --- a/packages/astro/src/vite-plugin-astro-server/plugin.ts +++ b/packages/astro/src/vite-plugin-astro-server/plugin.ts @@ -9,8 +9,8 @@ import { AstroError, AstroErrorData } from '../core/errors/index.js'; import { patchOverlay } from '../core/errors/overlay.js'; import type { Logger } from '../core/logger/core.js'; import { createViteLoader } from '../core/module-loader/index.js'; -import { ensure404Route } from '../core/routing/astro-designed-error-pages.js'; import { createRouteManifest } from '../core/routing/index.js'; +import { injectDefaultRoutes } from '../core/routing/default.js'; import { toRoutingStrategy } from '../i18n/utils.js'; import { baseMiddleware } from './base.js'; import { createController } from './controller.js'; @@ -35,8 +35,8 @@ export default function createVitePluginAstroServer({ configureServer(viteServer) { const loader = createViteLoader(viteServer); const manifest = createDevelopmentManifest(settings); - let manifestData: ManifestData = ensure404Route( - createRouteManifest({ settings, fsMod }, logger) + let manifestData: ManifestData = injectDefaultRoutes( + createRouteManifest({ settings, fsMod }, logger), ); const pipeline = DevPipeline.create(manifestData, { loader, logger, manifest, settings }); const controller = createController({ loader }); @@ -46,7 +46,9 @@ export default function createVitePluginAstroServer({ function rebuildManifest(needsManifestRebuild: boolean) { pipeline.clearRouteCache(); if (needsManifestRebuild) { - manifestData = ensure404Route(createRouteManifest({ settings }, logger)); + manifestData = injectDefaultRoutes( + createRouteManifest({ settings }, logger), + ); pipeline.setManifestData(manifestData); } } diff --git a/packages/astro/src/vite-plugin-astro-server/route.ts b/packages/astro/src/vite-plugin-astro-server/route.ts index 36449ea24359..9f087b621c15 100644 --- a/packages/astro/src/vite-plugin-astro-server/route.ts +++ b/packages/astro/src/vite-plugin-astro-server/route.ts @@ -12,7 +12,6 @@ import { loadMiddleware } from '../core/middleware/loadMiddleware.js'; import { RenderContext } from '../core/render-context.js'; import { type SSROptions, getProps } from '../core/render/index.js'; import { createRequest } from '../core/request.js'; -import { default404Page } from '../core/routing/astro-designed-error-pages.js'; import { matchAllRoutes } from '../core/routing/index.js'; import { getSortedPreloadedMatches } from '../prerender/routing.js'; import type { DevPipeline } from './pipeline.js'; @@ -106,19 +105,6 @@ export async function matchRoute( const custom404 = getCustom404Route(manifestData); - if (custom404 && custom404.component === DEFAULT_404_COMPONENT) { - const component: ComponentInstance = { - default: default404Page, - }; - return { - route: custom404, - filePath: new URL(`file://${custom404.component}`), - resolvedPathname: pathname, - preloadedComponent: component, - mod: component, - }; - } - if (custom404) { const filePath = new URL(`./${custom404.component}`, config.root); const preloadedComponent = await pipeline.preload(custom404, filePath); diff --git a/packages/astro/src/vite-plugin-astro/index.ts b/packages/astro/src/vite-plugin-astro/index.ts index cedf49e95bce..456b7677de63 100644 --- a/packages/astro/src/vite-plugin-astro/index.ts +++ b/packages/astro/src/vite-plugin-astro/index.ts @@ -214,6 +214,7 @@ export default function astro({ settings, logger }: AstroPluginOptions): vite.Pl const astroMetadata: AstroPluginMetadata['astro'] = { clientOnlyComponents: transformResult.clientOnlyComponents, hydratedComponents: transformResult.hydratedComponents, + serverComponents: transformResult.serverComponents, scripts: transformResult.scripts, containsHead: transformResult.containsHead, propagation: transformResult.propagation ? 'self' : 'none', diff --git a/packages/astro/src/vite-plugin-astro/metadata.ts b/packages/astro/src/vite-plugin-astro/metadata.ts index d0a2b3644b22..3fa0068a5a40 100644 --- a/packages/astro/src/vite-plugin-astro/metadata.ts +++ b/packages/astro/src/vite-plugin-astro/metadata.ts @@ -7,3 +7,15 @@ export function getAstroMetadata(modInfo: ModuleInfo): PluginMetadata['astro'] | } return undefined; } + +export function createDefaultAstroMetadata(): PluginMetadata['astro'] { + return { + hydratedComponents: [], + clientOnlyComponents: [], + serverComponents: [], + scripts: [], + propagation: 'none', + containsHead: false, + pageOptions: {}, + }; +} diff --git a/packages/astro/src/vite-plugin-astro/types.ts b/packages/astro/src/vite-plugin-astro/types.ts index 8e82165f5ebd..ee5003941526 100644 --- a/packages/astro/src/vite-plugin-astro/types.ts +++ b/packages/astro/src/vite-plugin-astro/types.ts @@ -10,6 +10,7 @@ export interface PluginMetadata { astro: { hydratedComponents: TransformResult['hydratedComponents']; clientOnlyComponents: TransformResult['clientOnlyComponents']; + serverComponents: TransformResult['serverComponents']; scripts: TransformResult['scripts']; containsHead: TransformResult['containsHead']; propagation: PropagationHint; diff --git a/packages/astro/src/vite-plugin-markdown/index.ts b/packages/astro/src/vite-plugin-markdown/index.ts index 98362c89d443..25a5ab64844c 100644 --- a/packages/astro/src/vite-plugin-markdown/index.ts +++ b/packages/astro/src/vite-plugin-markdown/index.ts @@ -13,9 +13,9 @@ import { AstroError, AstroErrorData } from '../core/errors/index.js'; import type { Logger } from '../core/logger/core.js'; import { isMarkdownFile } from '../core/util.js'; import { shorthash } from '../runtime/server/shorthash.js'; -import type { PluginMetadata } from '../vite-plugin-astro/types.js'; import { getFileInfo } from '../vite-plugin-utils/index.js'; import { type MarkdownImagePath, getMarkdownCodeForImages } from './images.js'; +import { createDefaultAstroMetadata } from '../vite-plugin-astro/metadata.js'; interface AstroPluginOptions { settings: AstroSettings; @@ -159,14 +159,7 @@ export default function markdown({ settings, logger }: AstroPluginOptions): Plug return { code, meta: { - astro: { - hydratedComponents: [], - clientOnlyComponents: [], - scripts: [], - propagation: 'none', - containsHead: false, - pageOptions: {}, - } as PluginMetadata['astro'], + astro: createDefaultAstroMetadata(), vite: { lang: 'ts', }, diff --git a/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs b/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs new file mode 100644 index 000000000000..70d0e6d6aadb --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/hybrid/astro.config.mjs @@ -0,0 +1,13 @@ +import svelte from '@astrojs/svelte'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'hybrid', + integrations: [ + svelte() + ], + experimental: { + serverIslands: true, + } +}); + diff --git a/packages/astro/test/fixtures/server-islands/hybrid/package.json b/packages/astro/test/fixtures/server-islands/hybrid/package.json new file mode 100644 index 000000000000..fdb447b0e071 --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/hybrid/package.json @@ -0,0 +1,10 @@ +{ + "name": "@test/server-islands-hybrid", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/svelte": "workspace:*", + "astro": "workspace:*", + "svelte": "^4.2.18" + } +} diff --git a/packages/astro/test/fixtures/server-islands/hybrid/src/components/Island.astro b/packages/astro/test/fixtures/server-islands/hybrid/src/components/Island.astro new file mode 100644 index 000000000000..49a5a87ae0d5 --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/hybrid/src/components/Island.astro @@ -0,0 +1,4 @@ +--- + +--- +

I'm an island

diff --git a/packages/astro/test/fixtures/server-islands/hybrid/src/pages/index.astro b/packages/astro/test/fixtures/server-islands/hybrid/src/pages/index.astro new file mode 100644 index 000000000000..d42973294e6d --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/hybrid/src/pages/index.astro @@ -0,0 +1,12 @@ +--- +import Island from '../components/Island.astro'; +--- + + + Testing + + +

Testing

+ + + diff --git a/packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs b/packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs new file mode 100644 index 000000000000..8eb474b04853 --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/ssr/astro.config.mjs @@ -0,0 +1,13 @@ +import svelte from '@astrojs/svelte'; +import { defineConfig } from 'astro/config'; + +export default defineConfig({ + output: 'server', + integrations: [ + svelte() + ], + experimental: { + serverIslands: true, + } +}); + diff --git a/packages/astro/test/fixtures/server-islands/ssr/package.json b/packages/astro/test/fixtures/server-islands/ssr/package.json new file mode 100644 index 000000000000..fa6e000dda49 --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/ssr/package.json @@ -0,0 +1,10 @@ +{ + "name": "@test/server-islands-ssr", + "version": "0.0.0", + "private": true, + "dependencies": { + "@astrojs/svelte": "workspace:*", + "astro": "workspace:*", + "svelte": "^4.2.18" + } +} diff --git a/packages/astro/test/fixtures/server-islands/ssr/src/components/Island.astro b/packages/astro/test/fixtures/server-islands/ssr/src/components/Island.astro new file mode 100644 index 000000000000..49a5a87ae0d5 --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/ssr/src/components/Island.astro @@ -0,0 +1,4 @@ +--- + +--- +

I'm an island

diff --git a/packages/astro/test/fixtures/server-islands/ssr/src/pages/index.astro b/packages/astro/test/fixtures/server-islands/ssr/src/pages/index.astro new file mode 100644 index 000000000000..d42973294e6d --- /dev/null +++ b/packages/astro/test/fixtures/server-islands/ssr/src/pages/index.astro @@ -0,0 +1,12 @@ +--- +import Island from '../components/Island.astro'; +--- + + + Testing + + +

Testing

+ + + diff --git a/packages/astro/test/server-islands.test.js b/packages/astro/test/server-islands.test.js new file mode 100644 index 000000000000..60fece1e46d4 --- /dev/null +++ b/packages/astro/test/server-islands.test.js @@ -0,0 +1,120 @@ + +import assert from 'node:assert/strict'; +import { after, before, describe, it } from 'node:test'; +import * as cheerio from 'cheerio'; +import testAdapter from './test-adapter.js'; +import { loadFixture } from './test-utils.js'; + +describe('Server islands', () => { + describe('SSR', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + before(async () => { + fixture = await loadFixture({ + root: './fixtures/server-islands/ssr', + adapter: testAdapter(), + }); + }); + + describe('dev', () => { + let devServer; + + before(async () => { + devServer = await fixture.startDevServer(); + }); + + after(async () => { + await devServer.stop(); + }); + + it('omits the islands HTML', async () => { + const res = await fixture.fetch('/'); + assert.equal(res.status, 200); + const html = await res.text(); + const $ = cheerio.load(html); + const serverIslandEl = $('h2#island'); + assert.equal(serverIslandEl.length, 0); + }); + }); + + describe('prod', () => { + before(async () => { + await fixture.build(); + }); + + it('omits the islands HTML', async () => { + const app = await fixture.loadTestAdapterApp(); + const request = new Request('http://example.com/'); + const response = await app.render(request); + const html = await response.text(); + + const $ = cheerio.load(html); + const serverIslandEl = $('h2#island'); + assert.equal(serverIslandEl.length, 0); + + const serverIslandScript = $('script[data-island-id]'); + assert.equal(serverIslandScript.length, 1, 'has the island script'); + }); + }); + }); + + describe('Hybrid mode', () => { + /** @type {import('./test-utils').Fixture} */ + let fixture; + before(async () => { + fixture = await loadFixture({ + root: './fixtures/server-islands/hybrid', + adapter: testAdapter(), + }); + }); + + describe('build', () => { + before(async () => { + await fixture.build(); + }); + + it('Omits the island HTML from the static HTML', async () => { + let html = await fixture.readFile('/client/index.html'); + + const $ = cheerio.load(html); + const serverIslandEl = $('h2#island'); + assert.equal(serverIslandEl.length, 0); + + const serverIslandScript = $('script[data-island-id]'); + assert.equal(serverIslandScript.length, 1, 'has the island script'); + }); + + describe('prod', () => { + async function fetchIsland() { + const app = await fixture.loadTestAdapterApp(); + const request = new Request('http://example.com/_server-islands/Island', { + method: 'POST', + body: JSON.stringify({ + componentExport: 'default', + props: {}, + slots: {}, + }) + }); + return app.render(request); + } + + it('Island returns its HTML', async () => { + const response = await fetchIsland(); + const html = await response.text(); + const $ = cheerio.load(html); + + const serverIslandEl = $('h2#island'); + assert.equal(serverIslandEl.length, 1); + }); + + it('Island does not include the doctype', async () => { + const response = await fetchIsland(); + const html = await response.text(); + console.log(html); + + assert.ok(!/doctype/i.test(html), 'html does not include doctype'); + }); + }); + }); + }); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d527f0ffea9..f16d08abf03b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -373,6 +373,45 @@ importers: specifier: ^4.11.6 version: link:../../packages/astro + examples/server-islands: + devDependencies: + '@astrojs/node': + specifier: ^8.2.6 + version: link:../../packages/integrations/node + '@astrojs/react': + specifier: ^3.6.0 + version: link:../../packages/integrations/react + '@astrojs/tailwind': + specifier: ^5.1.0 + version: link:../../packages/integrations/tailwind + '@fortawesome/fontawesome-free': + specifier: ^6.5.2 + version: 6.5.2 + '@tailwindcss/forms': + specifier: ^0.5.7 + version: 0.5.7(tailwindcss@3.4.5) + '@types/react': + specifier: ^18.3.3 + version: 18.3.3 + '@types/react-dom': + specifier: ^18.3.0 + version: 18.3.0 + astro: + specifier: workspace:* + version: link:../../packages/astro + postcss: + specifier: ^8.4.38 + version: 8.4.39 + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + tailwindcss: + specifier: ^3.4.3 + version: 3.4.5 + examples/ssr: dependencies: '@astrojs/node': @@ -529,8 +568,8 @@ importers: packages/astro: dependencies: '@astrojs/compiler': - specifier: ^2.8.2 - version: 2.8.2 + specifier: ^2.9.0 + version: 2.9.1 '@astrojs/internal-helpers': specifier: workspace:* version: link:../internal-helpers @@ -1554,6 +1593,27 @@ importers: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + packages/astro/e2e/fixtures/server-islands: + dependencies: + '@astrojs/mdx': + specifier: workspace:* + version: link:../../../../integrations/mdx + '@astrojs/node': + specifier: workspace:* + version: link:../../../../integrations/node + '@astrojs/react': + specifier: workspace:* + version: link:../../../../integrations/react + astro: + specifier: workspace:* + version: link:../../.. + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + packages/astro/e2e/fixtures/solid-circular: dependencies: '@astrojs/solid-js': @@ -3592,6 +3652,30 @@ importers: specifier: workspace:* version: link:../../.. + packages/astro/test/fixtures/server-islands/hybrid: + dependencies: + '@astrojs/svelte': + specifier: workspace:* + version: link:../../../../../integrations/svelte + astro: + specifier: workspace:* + version: link:../../../.. + svelte: + specifier: ^4.2.18 + version: 4.2.18 + + packages/astro/test/fixtures/server-islands/ssr: + dependencies: + '@astrojs/svelte': + specifier: workspace:* + version: link:../../../../../integrations/svelte + astro: + specifier: workspace:* + version: link:../../../.. + svelte: + specifier: ^4.2.18 + version: 4.2.18 + packages/astro/test/fixtures/set-html: dependencies: astro: @@ -5968,8 +6052,8 @@ packages: '@astrojs/compiler@1.8.2': resolution: {integrity: sha512-o/ObKgtMzl8SlpIdzaxFnt7SATKPxu4oIP/1NL+HDJRzxfJcAkOTAb/ZKMRyULbz4q+1t2/DAebs2Z1QairkZw==} - '@astrojs/compiler@2.8.2': - resolution: {integrity: sha512-2v2N2oDnMH6+CX1Wn6f45Afa4tdkUMutdx8pJaokfaOYnAU+u6+UK7o7sXqydKro1cLwVmmOIJv6AqiXnAdLDA==} + '@astrojs/compiler@2.9.1': + resolution: {integrity: sha512-s8Ge2lWHx/s3kl4UoerjL/iPtwdtogNM/BLOaGCwQA6crMOVYpphy5wUkYlKyuh8GAeGYH/5haLAFBsgNy9AQQ==} '@astrojs/language-server@2.11.1': resolution: {integrity: sha512-WSIBBUK9lSeVD4KhPiZk2u3wsXdj7WEYvYPPs8ZsgbSVIOzUJWAKVcITHiXmcXlzZB5ubK44YUN/Hq+f2GeMyQ==} @@ -6744,6 +6828,10 @@ packages: '@fontsource/montserrat@5.0.18': resolution: {integrity: sha512-85JBs2rCdFK/VBdSb401e2lXk5gynVo2zi3Rh2Guem4WNtT2q52+V90o3KzjmajY3TPJvCZA/kI7R05ev7148g==} + '@fortawesome/fontawesome-free@6.5.2': + resolution: {integrity: sha512-hRILoInAx8GNT5IMkrtIt9blOdrqHOnPBH+k70aWUAqPZPgopb9G5EQJFpaBx/S8zp2fC+mPW349Bziuk1o28Q==} + engines: {node: '>=6'} + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} @@ -7239,6 +7327,11 @@ packages: svelte: ^4.0.0 || ^5.0.0-next.0 vite: ^5.0.0 + '@tailwindcss/forms@0.5.7': + resolution: {integrity: sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==} + peerDependencies: + tailwindcss: '>=3.0.0 || >= 3.0.0-alpha.1' + '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} @@ -9687,6 +9780,10 @@ packages: resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} engines: {node: '>=12'} + mini-svg-data-uri@1.4.4: + resolution: {integrity: sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg==} + hasBin: true + minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} @@ -11759,11 +11856,11 @@ snapshots: '@astrojs/compiler@1.8.2': {} - '@astrojs/compiler@2.8.2': {} + '@astrojs/compiler@2.9.1': {} '@astrojs/language-server@2.11.1(prettier-plugin-astro@0.14.0)(prettier@3.3.3)(typescript@5.5.3)': dependencies: - '@astrojs/compiler': 2.8.2 + '@astrojs/compiler': 2.9.1 '@jridgewell/sourcemap-codec': 1.4.15 '@volar/kit': 2.4.0-alpha.16(typescript@5.5.3) '@volar/language-core': 2.4.0-alpha.16 @@ -12645,6 +12742,8 @@ snapshots: '@fontsource/montserrat@5.0.18': {} + '@fortawesome/fontawesome-free@6.5.2': {} + '@humanwhocodes/module-importer@1.0.1': {} '@humanwhocodes/retry@0.3.0': {} @@ -13156,6 +13255,11 @@ snapshots: transitivePeerDependencies: - supports-color + '@tailwindcss/forms@0.5.7(tailwindcss@3.4.5)': + dependencies: + mini-svg-data-uri: 1.4.4 + tailwindcss: 3.4.5 + '@trysound/sax@0.2.0': {} '@ts-morph/common@0.20.0': @@ -16148,6 +16252,8 @@ snapshots: mimic-fn@4.0.0: {} + mini-svg-data-uri@1.4.4: {} + minimatch@3.1.2: dependencies: brace-expansion: 1.1.11