Astro Integration System (#2820)
* update examples * add initial integrations * update tests * update astro * update ci * get final tests working * update injectelement todo * update ben code review * respond to final code review feedback
This commit is contained in:
parent
0f376a7c52
commit
6386c14d00
266 changed files with 3619 additions and 1117 deletions
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
|
@ -99,7 +99,8 @@ jobs:
|
|||
with:
|
||||
name: artifacts
|
||||
path: |
|
||||
packages/**/dist/**
|
||||
packages/*/dist/**
|
||||
packages/*/*/dist/**
|
||||
packages/webapi/mod.js
|
||||
packages/webapi/mod.js.map
|
||||
if-no-files-found: error
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the Preact renderer to support Preact JSX components.
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
// Enable the Preact integration to support Preact JSX components.
|
||||
integrations: [preact()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/preact": "^0.0.1",
|
||||
"astro": "^0.24.3",
|
||||
"sass": "^1.49.9"
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
integrations: [preact()],
|
||||
buildOptions: {
|
||||
site: 'https://example.com/',
|
||||
},
|
||||
|
|
|
@ -10,6 +10,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"astro": "^0.24.3",
|
||||
"@astrojs/renderer-preact": "^0.5.0"
|
||||
"@astrojs/preact": "^0.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Comment out "renderers: []" to enable Astro's default component support.
|
||||
renderers: [],
|
||||
});
|
||||
export default defineConfig({});
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
renderers: [
|
||||
// Enable the Preact renderer to support Preact JSX components.
|
||||
'@astrojs/renderer-preact',
|
||||
// Enable the React renderer, for the Algolia search component
|
||||
'@astrojs/renderer-react',
|
||||
integrations: [
|
||||
// Enable Preact to support Preact JSX components.
|
||||
preact(),
|
||||
// Enable React for the Algolia search component.
|
||||
react(),
|
||||
],
|
||||
});
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
"react-dom": "^17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/renderer-react": "^0.5.0",
|
||||
"@astrojs/preact": "^0.0.1",
|
||||
"@astrojs/react": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
renderers: [],
|
||||
});
|
||||
export default defineConfig({});
|
||||
|
|
|
@ -2,6 +2,6 @@ import { defineConfig } from 'astro/config';
|
|||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// No renderers are needed for AlpineJS support, just use Astro components!
|
||||
renderers: [],
|
||||
// No integrations are needed for AlpineJS support, just use Astro components!
|
||||
integrations: [],
|
||||
});
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import lit from '@astrojs/lit';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the lit renderer to support LitHTML components and templates.
|
||||
renderers: ['@astrojs/renderer-lit'],
|
||||
// Enable Lit to support LitHTML components and templates.
|
||||
integrations: [lit()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-lit": "^0.4.0",
|
||||
"@astrojs/lit": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
import react from '@astrojs/react';
|
||||
import svelte from '@astrojs/svelte';
|
||||
import vue from '@astrojs/vue';
|
||||
import solid from '@astrojs/solid-js';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable many renderers to support all different kinds of components.
|
||||
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-solid'],
|
||||
// Enable many frameworks to support all different kinds of components.
|
||||
integrations: [preact(), react(), svelte(), vue(), solid()],
|
||||
});
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-lit": "^0.4.0",
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/renderer-react": "^0.5.0",
|
||||
"@astrojs/renderer-solid": "^0.4.0",
|
||||
"@astrojs/renderer-svelte": "^0.5.2",
|
||||
"@astrojs/renderer-vue": "^0.4.0",
|
||||
"@astrojs/lit": "^0.0.1",
|
||||
"@astrojs/preact": "^0.0.1",
|
||||
"@astrojs/react": "^0.0.1",
|
||||
"@astrojs/solid-js": "^0.0.1",
|
||||
"@astrojs/svelte": "^0.0.1",
|
||||
"@astrojs/vue": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the Preact renderer to support Preact JSX components.
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
// Enable Preact to support Preact JSX components.
|
||||
integrations: [preact()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/preact": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the React renderer to support React JSX components.
|
||||
renderers: ['@astrojs/renderer-react'],
|
||||
// Enable React to support React JSX components.
|
||||
integrations: [react()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-react": "^0.5.0",
|
||||
"@astrojs/react": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import solid from '@astrojs/solid-js';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the Solid renderer to support Solid JSX components.
|
||||
renderers: ['@astrojs/renderer-solid'],
|
||||
// Enable Solid to support Solid JSX components.
|
||||
integrations: [solid()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-solid": "^0.4.0",
|
||||
"@astrojs/solid-js": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import svelte from '@astrojs/svelte';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the Svelte renderer to support Svelte components.
|
||||
renderers: ['@astrojs/renderer-svelte'],
|
||||
// Enable Svelte to support Svelte components.
|
||||
integrations: [svelte()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-svelte": "^0.5.2",
|
||||
"@astrojs/svelte": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import vue from '@astrojs/vue';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the Vue renderer to support Vue components.
|
||||
renderers: ['@astrojs/renderer-vue'],
|
||||
// Enable Vue to support Vue components.
|
||||
integrations: [vue()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-vue": "^0.4.0",
|
||||
"@astrojs/vue": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
17
examples/integrations-playground/.gitignore
vendored
Normal file
17
examples/integrations-playground/.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
# build output
|
||||
dist
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
2
examples/integrations-playground/.npmrc
Normal file
2
examples/integrations-playground/.npmrc
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Expose Astro dependencies for `pnpm` users
|
||||
shamefully-hoist=true
|
6
examples/integrations-playground/.stackblitzrc
Normal file
6
examples/integrations-playground/.stackblitzrc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"startCommand": "npm start",
|
||||
"env": {
|
||||
"ENABLE_CJS_IMPORTS": true
|
||||
}
|
||||
}
|
7
examples/integrations-playground/README.md
Normal file
7
examples/integrations-playground/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Integration Playground
|
||||
|
||||
```
|
||||
npm init astro -- --template integration-playground
|
||||
```
|
||||
|
||||
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/integration-playground)
|
12
examples/integrations-playground/astro.config.mjs
Normal file
12
examples/integrations-playground/astro.config.mjs
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
import lit from '@astrojs/lit';
|
||||
import react from '@astrojs/react';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
import turbolinks from '@astrojs/turbolinks';
|
||||
import sitemap from '@astrojs/sitemap';
|
||||
import partytown from '@astrojs/partytown';
|
||||
|
||||
export default defineConfig({
|
||||
integrations: [lit(), react(), tailwind(), turbolinks(), partytown(), sitemap()],
|
||||
});
|
20
examples/integrations-playground/package.json
Normal file
20
examples/integrations-playground/package.json
Normal file
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"name": "@example/integrations-playground",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"start": "astro dev",
|
||||
"build": "astro build",
|
||||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/lit": "^0.0.1",
|
||||
"@astrojs/react": "^0.0.1",
|
||||
"@astrojs/partytown": "^0.0.1",
|
||||
"@astrojs/sitemap": "^0.0.1",
|
||||
"@astrojs/tailwind": "^0.0.1",
|
||||
"@astrojs/turbolinks": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
12
examples/integrations-playground/public/assets/logo.svg
Normal file
12
examples/integrations-playground/public/assets/logo.svg
Normal file
|
@ -0,0 +1,12 @@
|
|||
<svg width="193" height="256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<style>
|
||||
#flame { fill: #FF5D01; }
|
||||
#a { fill: #000014; }
|
||||
@media (prefers-color-scheme: dark) {
|
||||
#a { fill: #fff; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<path id="a" fill-rule="evenodd" clip-rule="evenodd" d="M131.496 18.929c1.943 2.413 2.935 5.67 4.917 12.181l43.309 142.27a180.277 180.277 0 00-51.778-17.53L99.746 60.56a3.67 3.67 0 00-7.042.01l-27.857 95.232a180.224 180.224 0 00-52.01 17.557l43.52-142.281c1.989-6.502 2.983-9.752 4.927-12.16a15.999 15.999 0 016.484-4.798c2.872-1.154 6.271-1.154 13.07-1.154h31.085c6.807 0 10.211 0 13.085 1.157a16 16 0 016.488 4.806z" fill="url(#paint0_linear)"/>
|
||||
<path id="flame" fill-rule="evenodd" clip-rule="evenodd" d="M136.678 180.151c-7.14 6.105-21.39 10.268-37.804 10.268-20.147 0-37.033-6.272-41.513-14.707-1.602 4.835-1.962 10.367-1.962 13.902 0 0-1.055 17.355 11.016 29.426 0-6.268 5.081-11.349 11.349-11.349 10.743 0 10.731 9.373 10.721 16.977v.679c0 11.542 7.054 21.436 17.086 25.606a23.27 23.27 0 01-2.339-10.2c0-11.008 6.463-15.107 13.973-19.87 5.977-3.79 12.616-8.001 17.192-16.449a31.013 31.013 0 003.744-14.82c0-3.299-.513-6.479-1.463-9.463z" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
BIN
examples/integrations-playground/public/favicon.ico
Normal file
BIN
examples/integrations-playground/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.2 KiB |
2
examples/integrations-playground/public/robots.txt
Normal file
2
examples/integrations-playground/public/robots.txt
Normal file
|
@ -0,0 +1,2 @@
|
|||
User-agent: *
|
||||
Disallow: /
|
11
examples/integrations-playground/sandbox.config.json
Normal file
11
examples/integrations-playground/sandbox.config.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"infiniteLoopProtection": true,
|
||||
"hardReloadOnChange": false,
|
||||
"view": "browser",
|
||||
"template": "node",
|
||||
"container": {
|
||||
"port": 3000,
|
||||
"startScript": "start",
|
||||
"node": "14"
|
||||
}
|
||||
}
|
34
examples/integrations-playground/src/components/Counter.js
Normal file
34
examples/integrations-playground/src/components/Counter.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
import { LitElement, html } from 'lit';
|
||||
|
||||
export const tagName = 'my-counter';
|
||||
|
||||
class Counter extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
count: {
|
||||
type: Number,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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(tagName, Counter);
|
3
examples/integrations-playground/src/components/Link.jsx
Normal file
3
examples/integrations-playground/src/components/Link.jsx
Normal file
|
@ -0,0 +1,3 @@
|
|||
export default function Link({ to, text }) {
|
||||
return <a href={to}>{text}</a>;
|
||||
}
|
66
examples/integrations-playground/src/components/Lorem.astro
Normal file
66
examples/integrations-playground/src/components/Lorem.astro
Normal file
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
//hey
|
||||
---
|
||||
<style>
|
||||
p {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi quam arcu, rhoncus et dui at, volutpat viverra augue. Suspendisse placerat libero tellus, ut consequat ligula
|
||||
rutrum id. Vestibulum lectus libero, viverra in lacus eget, porttitor tincidunt leo. Integer sit amet turpis et felis fringilla lacinia in id nibh. Proin vitae dapibus odio.
|
||||
Mauris ornare eget urna id volutpat. Duis tellus nisi, hendrerit id sodales in, rutrum a quam. Proin tempor velit turpis, et tempor lacus sagittis in. Sed congue mauris quis nibh
|
||||
posuere, nec semper lacus auctor. Morbi sit amet enim sit amet arcu ullamcorper sollicitudin. Donec dignissim posuere tincidunt. Donec ultrices quam nec orci venenatis suscipit.
|
||||
Maecenas sapien quam, pretium sit amet ullamcorper at, vulputate sit amet urna. Suspendisse potenti. Integer in sapien turpis. Nulla accumsan viverra diam, quis convallis magna
|
||||
finibus eget. Integer sed eros bibendum, consequat velit sit amet, tincidunt orci. Mauris varius id metus in fringilla. Vestibulum dignissim massa eget erat luctus, ac congue
|
||||
mauris pellentesque. In et tempor dolor. Cras blandit congue lorem at facilisis. Aenean vel lacinia quam. Pellentesque luctus metus ut scelerisque efficitur. Mauris laoreet
|
||||
sodales libero eget luctus. Proin at congue dui, a cursus risus. Pellentesque lorem sem, rhoncus fermentum arcu ut, euismod fermentum ligula. Nullam eu orci posuere, laoreet leo
|
||||
in, commodo dolor. Fusce at felis elementum, commodo justo at, placerat justo. Nam feugiat scelerisque arcu, ut fermentum tellus elementum in. Sed ut vulputate ante. Morbi cursus
|
||||
arcu quis odio convallis egestas. Donec vulputate vestibulum dolor eget tristique. Nullam tempor semper augue, vitae lobortis neque tempor ac. Pellentesque massa leo, congue id
|
||||
ligula auctor, sollicitudin pharetra lorem. Curabitur a lacus porttitor, venenatis est quis, mattis velit. Fusce hendrerit lobortis mi ac efficitur. Mauris ornare, lorem sed
|
||||
varius faucibus, nisi dui pretium urna, sit amet lacinia nibh ligula in ipsum. Phasellus gravida, metus eget ornare ultrices, dolor ipsum consectetur erat, ac aliquet eros metus
|
||||
sed lectus. Nullam eleifend posuere rhoncus. Curabitur semper ligula vel ante posuere, at blandit orci accumsan. Vivamus accumsan metus in lorem laoreet, a luctus arcu tempus.
|
||||
Donec posuere sollicitudin nulla at vulputate. Nulla condimentum imperdiet purus, et lobortis ligula iaculis in. Donec suscipit viverra neque, ut elementum eros lacinia ut. Fusce
|
||||
at odio enim. Donec rutrum lectus sit amet est auctor, ac rhoncus lorem imperdiet. Curabitur commodo ex est, non tempus massa pulvinar nec. Sed fermentum, lectus eget ultricies
|
||||
luctus, enim sem sodales quam, sed laoreet tortor sem feugiat nisi. Morbi molestie vehicula viverra. Integer accumsan mi in orci ultrices posuere. Integer mi quam, faucibus et
|
||||
aliquet imperdiet, ornare ac ex. Nunc mattis molestie nisi, eu venenatis nibh vehicula at. Aliquam ut elit consectetur, finibus lorem sed, condimentum sapien. Praesent fermentum
|
||||
iaculis orci, vitae tincidunt est viverra nec. Morbi semper turpis sed lectus ornare tristique. Sed congue dui ex. Maecenas orci ligula, imperdiet sit amet accumsan et, finibus a
|
||||
velit. Ut vitae blandit eros. Nam gravida nec ipsum non volutpat. Integer quam metus, porttitor id ante sed, rutrum porta quam. Aenean at mattis ante. Morbi id libero eget risus
|
||||
sagittis gravida. Proin consequat sapien a dignissim posuere. Ut luctus sed metus ut elementum. Mauris tincidunt condimentum risus at bibendum. Aenean a sapien justo. Morbi vel
|
||||
neque in eros venenatis scelerisque vitae nec justo. Vestibulum lacinia, dui eu sollicitudin ornare, est elit vestibulum arcu, nec ultrices augue turpis in massa. Duis commodo
|
||||
lectus sed est posuere, et mollis nisi dapibus. Sed id ultrices arcu. Praesent tempor sodales aliquet. Donec suscipit ipsum eu odio cursus, quis sodales metus sodales. Nunc
|
||||
vestibulum massa at felis ullamcorper cursus. Pellentesque facilisis ante ut lectus vulputate vestibulum. Nullam pharetra felis ac lacus sodales, vel suscipit metus faucibus.
|
||||
Donec facilisis imperdiet risus, in volutpat odio tincidunt a. Aliquam vitae leo lorem. Proin scelerisque efficitur velit, vel cursus ipsum accumsan id. Morbi nibh nulla, pretium
|
||||
quis venenatis et, pharetra et sapien. Cras lobortis, massa sit amet blandit pulvinar, mi magna condimentum ex, quis commodo ipsum est quis metus. Maecenas pulvinar, leo sit amet
|
||||
congue pulvinar, neque magna ultrices mi, et rhoncus massa sapien quis libero. Etiam a nunc et ipsum faucibus pretium. Nulla facilisi. Nunc nec dolor velit. In semper semper mi
|
||||
non condimentum. Pellentesque vehicula volutpat odio, a semper sem porta a. In sit amet lectus rutrum, sollicitudin augue auctor, maximus quam. Mauris congue, nisl non fermentum
|
||||
iaculis, leo erat interdum lorem, quis bibendum arcu eros et elit. Fusce tortor ante, gravida a arcu in, lacinia finibus ante. Phasellus facilisis lectus vitae sapien feugiat
|
||||
laoreet. Curabitur ultricies libero sit amet condimentum suscipit. Duis at vestibulum mi. Suspendisse at neque augue. Duis ornare a mauris id efficitur. Suspendisse in dui nec
|
||||
dolor dignissim venenatis. Curabitur a magna turpis. Aliquam at commodo tellus. In id sem interdum, suscipit felis at, mattis velit. Proin accumsan sodales felis a lacinia.
|
||||
Curabitur at magna a massa varius maximus. Vestibulum in auctor ante. Donec aliquam tortor sed nulla rutrum, et egestas mi efficitur. Sed viverra quam tellus, quis vulputate
|
||||
felis ultrices sed. Mauris sagittis, neque quis laoreet gravida, nisi est ultrices mi, at tempus nunc justo non dui. Suspendisse porttitor tortor nulla, eget luctus quam finibus
|
||||
id. Proin sodales eros mollis tellus euismod luctus a eu mi. Quisque consectetur iaculis nibh, at mollis tellus volutpat eu. Aenean a nulla vel lectus rhoncus aliquam. Donec
|
||||
vitae lacinia neque. Donec non lectus eget sem finibus ultrices vel nec felis. Proin fringilla mi a leo rhoncus aliquam sit amet quis augue. Duis congue ligula at est suscipit
|
||||
fringilla. Proin aliquam erat ut consequat dapibus. Suspendisse non nisi orci. Donec ac erat vel libero egestas laoreet. Nullam felis odio, tincidunt eget eleifend a, porttitor
|
||||
eu nisi. Suspendisse tristique eros at dolor scelerisque hendrerit. Etiam id dignissim lectus. Fusce lacinia metus eu risus placerat, et eleifend nunc ultrices. Ut gravida a dui
|
||||
sed volutpat. Sed semper quis erat sed ornare. Pellentesque sapien sem, fermentum vel nunc at, auctor posuere nisl. Maecenas aliquet lobortis leo. Vivamus tellus urna, dignissim
|
||||
consectetur sapien vitae, hendrerit varius sem. Nunc dictum tristique fermentum. Duis eu suscipit odio. Curabitur quis egestas neque. Fusce eu fringilla orci, vitae euismod
|
||||
sapien. Donec sit amet iaculis urna. Phasellus maximus nisl in libero bibendum volutpat. Nulla at vehicula lorem. Phasellus varius, elit ac suscipit pretium, turpis ipsum
|
||||
porttitor lectus, vitae ullamcorper orci velit ut ligula. Proin mollis, orci vel commodo auctor, sapien ipsum vulputate enim, sit amet aliquam nulla sapien ut sapien. Proin
|
||||
tincidunt ex non massa aliquet, quis aliquam nulla egestas. Maecenas mollis turpis dapibus, dignissim lectus tincidunt, egestas ligula. Suspendisse in lobortis purus. Sed tellus
|
||||
tellus, mollis eget tempor sed, interdum ut lectus. Nulla sed ex efficitur, porta dui cursus, tristique elit. Maecenas tincidunt tortor vitae massa laoreet ultricies. Mauris ac
|
||||
elit vitae orci eleifend ornare non eu ligula. Curabitur venenatis nulla ut neque tristique, non tincidunt justo pretium. Suspendisse mattis semper dui, eget vestibulum risus
|
||||
elementum sed. In consequat nisi sit amet nulla euismod, at convallis tortor tincidunt. Aliquam hendrerit venenatis risus in interdum. Duis ullamcorper imperdiet elit sit amet
|
||||
blandit. Mauris placerat lacinia velit id pharetra. Nam nec iaculis dui. Etiam odio mi, fringilla in rutrum in, viverra quis tellus. Aliquam egestas mauris id nisi facilisis, in
|
||||
laoreet nibh malesuada. Ut eu dui laoreet, venenatis tellus ac, feugiat mauris. Nunc in velit laoreet, venenatis tellus quis, blandit dolor. Nulla ultrices et neque id placerat.
|
||||
Nulla eu interdum nulla. Aliquam molestie enim quis rutrum finibus. Nulla bibendum orci vel scelerisque posuere. Praesent quis magna molestie, luctus tortor tincidunt, gravida
|
||||
neque. Quisque et ligula eget magna viverra interdum at a sapien. Mauris ornare efficitur nunc sed vulputate. Praesent laoreet mollis tincidunt. Vestibulum id arcu vulputate,
|
||||
eleifend enim vel, accumsan turpis. Morbi faucibus convallis tellus, semper laoreet justo lacinia nec. Sed sodales ligula consectetur dui rhoncus, et convallis metus accumsan.
|
||||
Sed ullamcorper non ex sit amet ultricies. Donec finibus nulla nec blandit porttitor. Etiam aliquam quis leo a imperdiet. Cras at lobortis est. In convallis semper enim, ac porta
|
||||
ligula fringilla at. Donec augue est, facilisis et odio sit amet, viverra ullamcorper nisl. Ut porta velit nec sem lacinia, sit amet mollis magna auctor. Nulla lobortis lacinia
|
||||
mauris nec sagittis. Suspendisse rutrum ex vel nisi interdum hendrerit et ut purus. Sed consectetur sodales nibh eget tempus. Aenean egestas luctus viverra. Integer fermentum
|
||||
tincidunt tellus, nec rhoncus velit hendrerit vitae. Proin quis neque porttitor, scelerisque risus gravida, volutpat sem. Fusce nec ex rhoncus, tempor libero nec, pellentesque
|
||||
ex. Integer quis iaculis purus. Nullam vitae imperdiet orci. Sed sit amet eros condimentum, scelerisque turpis facilisis, dignissim ante. Proin quis tristique lacus, sed sagittis
|
||||
nisl. Cras pharetra ultrices purus, sed ullamcorper nisi fringilla eu. Praesent risus turpis, auctor in fringilla a, fringilla eu dolor. Phasellus auctor tristique enim, eleifend
|
||||
molestie diam venenatis ut. Mauris dapibus, enim eget pharetra semper, nulla dui porttitor mi, auctor hendrerit augue nulla quis urna. Aliquam in cursus justo.
|
||||
</p>
|
19
examples/integrations-playground/src/components/Test.js
Normal file
19
examples/integrations-playground/src/components/Test.js
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { LitElement, html } from 'lit';
|
||||
|
||||
export const tagName = 'calc-add';
|
||||
|
||||
class CalcAdd extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
num: {
|
||||
type: Number,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
return html` <div>Number: ${this.num}</div> `;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define(tagName, CalcAdd);
|
15
examples/integrations-playground/src/pages/foo.astro
Normal file
15
examples/integrations-playground/src/pages/foo.astro
Normal file
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
// Page 2!
|
||||
import Link from '../components/Link.jsx';
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<title>Demo: Page 2</title>
|
||||
</head>
|
||||
<body>
|
||||
<Link to="/" text="Go Home" />
|
||||
</body>
|
||||
</html>
|
53
examples/integrations-playground/src/pages/index.astro
Normal file
53
examples/integrations-playground/src/pages/index.astro
Normal file
|
@ -0,0 +1,53 @@
|
|||
---
|
||||
import Lorem from '../components/Lorem.astro';
|
||||
import Link from '../components/Link.jsx';
|
||||
import '../components/Test.js';
|
||||
import '../components/Counter.js';
|
||||
---
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<title>Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="px-4 py-4">Test app</h1>
|
||||
<h2 class="partytown-status">
|
||||
<strong>Party Mode!</strong>
|
||||
Colors changing = partytown is enabled
|
||||
</h2>
|
||||
<my-counter client:load></my-counter>
|
||||
<Link to="/foo" text="Go to Page 2" />
|
||||
<Lorem />
|
||||
<calc-add num={33}></calc-add>
|
||||
|
||||
|
||||
<script type="text/partytown">
|
||||
// Remove `type="text/partytown"` to see this block the page
|
||||
// and cause the page to become unresponsive
|
||||
console.log('start partytown blocking script')
|
||||
const now = Date.now()
|
||||
let count = 1;
|
||||
while (Date.now() - now < 10000) {
|
||||
if (Date.now() - now > count * 1000) {
|
||||
console.log('blocking', count);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
console.log('end partytown blocking script')
|
||||
</script>
|
||||
|
||||
<script>
|
||||
setInterval(() => {
|
||||
const randomColor = Math.floor(Math.random()*16777215).toString(16);
|
||||
document.querySelector('.partytown-status').style.color = "#" + randomColor;
|
||||
}, 100);
|
||||
</script>
|
||||
<style>
|
||||
h1, h2 {
|
||||
color: blue;
|
||||
}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
5
examples/integrations-playground/tsconfig.json
Normal file
5
examples/integrations-playground/tsconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node"
|
||||
}
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Comment out "renderers: []" to enable Astro's default component support.
|
||||
renderers: [],
|
||||
});
|
||||
export default defineConfig({});
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Comment out "renderers: []" to enable Astro's default component support.
|
||||
renderers: [],
|
||||
});
|
||||
export default defineConfig({});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/render-preact';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the Preact renderer to support Preact JSX components.
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
integrations: [preact()],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/preact": "^0.0.1",
|
||||
"astro": "^0.24.3",
|
||||
"sass": "^1.49.9"
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import svelte from '@astrojs/svelte';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
renderers: ['@astrojs/renderer-svelte'],
|
||||
integrations: [svelte()],
|
||||
vite: {
|
||||
server: {
|
||||
cors: {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
"server": "node server/server.mjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-svelte": "^0.5.2",
|
||||
"@astrojs/svelte": "^0.0.1",
|
||||
"astro": "^0.24.3",
|
||||
"concurrently": "^7.0.0",
|
||||
"lightcookie": "^1.0.25",
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Set "renderers" to "[]" to disable all default, builtin component support.
|
||||
renderers: [],
|
||||
});
|
||||
export default defineConfig({});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import react from '@astrojs/react';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Comment out "renderers: []" to enable Astro's default component support.
|
||||
integrations: [react()],
|
||||
buildOptions: {
|
||||
site: 'http://example.com/blog',
|
||||
},
|
||||
renderers: ['@astrojs/renderer-react'],
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-react": "^0.5.0",
|
||||
"@astrojs/react": "^0.0.1",
|
||||
"astro": "^0.24.3",
|
||||
"sass": "^1.49.9"
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@ import addClasses from './add-classes.mjs';
|
|||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable Custom Markdown options, plugins, etc.
|
||||
renderers: [],
|
||||
markdownOptions: {
|
||||
render: [
|
||||
astroRemark,
|
||||
|
|
|
@ -4,7 +4,6 @@ import astroRemark from '@astrojs/markdown-remark';
|
|||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable Custom Markdown options, plugins, etc.
|
||||
renderers: [],
|
||||
markdownOptions: {
|
||||
render: [
|
||||
astroRemark,
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
import react from '@astrojs/react';
|
||||
import svelte from '@astrojs/svelte';
|
||||
import vue from '@astrojs/vue';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
|
||||
// Enable many frameworks to support all different kinds of components.
|
||||
integrations: [preact(), react(), svelte(), vue()],
|
||||
});
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/markdown-remark": "^0.6.4",
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/renderer-react": "^0.5.0",
|
||||
"@astrojs/renderer-svelte": "^0.5.2",
|
||||
"@astrojs/renderer-vue": "^0.4.0",
|
||||
"@astrojs/preact": "^0.0.1",
|
||||
"@astrojs/react": "^0.0.1",
|
||||
"@astrojs/svelte": "^0.0.1",
|
||||
"@astrojs/vue": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import preact from '@astrojs/preact';
|
||||
import react from '@astrojs/react';
|
||||
import svelte from '@astrojs/svelte';
|
||||
import vue from '@astrojs/vue';
|
||||
import solid from '@astrojs/solid-js';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable many renderers to support all different kinds of components.
|
||||
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-solid'],
|
||||
// Enable many frameworks to support all different kinds of components.
|
||||
integrations: [preact(), react(), svelte(), vue(), solid()],
|
||||
});
|
||||
|
|
|
@ -20,11 +20,11 @@
|
|||
"vue": "^3.2.31"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/renderer-react": "^0.5.0",
|
||||
"@astrojs/renderer-solid": "^0.4.0",
|
||||
"@astrojs/renderer-svelte": "^0.5.2",
|
||||
"@astrojs/renderer-vue": "^0.4.0",
|
||||
"@astrojs/preact": "^0.0.1",
|
||||
"@astrojs/react": "^0.0.1",
|
||||
"@astrojs/solid-js": "^0.0.1",
|
||||
"@astrojs/svelte": "^0.0.1",
|
||||
"@astrojs/vue": "^0.0.1",
|
||||
"astro": "^0.24.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { defineConfig } from 'astro/config';
|
||||
import tailwind from '@astrojs/tailwind';
|
||||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
// Enable the Preact renderer to support Preact JSX components.
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
integrations: [tailwind()],
|
||||
});
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
"preview": "astro preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/tailwind": "^0.0.1",
|
||||
"astro": "^0.24.3",
|
||||
"autoprefixer": "^10.4.4",
|
||||
"canvas-confetti": "^1.5.1",
|
||||
"postcss": "^8.4.12",
|
||||
"tailwindcss": "^3.0.23"
|
||||
}
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: path.join(__dirname, 'tailwind.config.js'), // update this if your path differs!
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
|
@ -1,10 +1,11 @@
|
|||
---
|
||||
let { type = 'button' } = Astro.props;
|
||||
// Click button, get confetti!
|
||||
// Styled by Tailwind :)
|
||||
---
|
||||
|
||||
<button
|
||||
class="py-2 px-4 bg-purple-500 text-white font-semibold rounded-lg shadow-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:ring-opacity-75"
|
||||
{type}
|
||||
>
|
||||
<button class="py-2 px-4 bg-purple-500 text-white font-semibold rounded-lg shadow-md hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:ring-opacity-75">
|
||||
<slot />
|
||||
</button>
|
||||
<script hoist>
|
||||
import confetti from 'canvas-confetti';
|
||||
document.body.querySelector('button').addEventListener("click", () => confetti());
|
||||
</script>
|
|
@ -1,7 +1,6 @@
|
|||
---
|
||||
// Component Imports
|
||||
import Button from '../components/Button.astro';
|
||||
import '../styles/global.css';
|
||||
|
||||
// Full Astro Component Syntax:
|
||||
// https://docs.astro.build/core-concepts/astro-components/
|
||||
|
@ -15,6 +14,8 @@ import '../styles/global.css';
|
|||
</head>
|
||||
|
||||
<body>
|
||||
<Button>I’m a Tailwind Button!</Button>
|
||||
<div class="grid place-items-center h-screen">
|
||||
<Button>Click Me!</Button>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
module.exports = {
|
||||
content: ['./src/**/*.{astro,html,js,jsx,svelte,ts,tsx,vue}'],
|
||||
};
|
|
@ -3,7 +3,6 @@ import { VitePWA } from 'vite-plugin-pwa';
|
|||
|
||||
// https://astro.build/config
|
||||
export default defineConfig({
|
||||
renderers: [],
|
||||
vite: {
|
||||
plugins: [VitePWA()],
|
||||
},
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
"compiled/*",
|
||||
"packages/markdown/*",
|
||||
"packages/renderers/*",
|
||||
"packages/integrations/*",
|
||||
"packages/*",
|
||||
"examples/*",
|
||||
"examples/component/demo",
|
||||
|
|
|
@ -57,7 +57,7 @@
|
|||
"dev": "astro-scripts dev \"src/**/*.ts\"",
|
||||
"postbuild": "astro-scripts copy \"src/**/*.astro\"",
|
||||
"benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js",
|
||||
"test": "mocha --parallel --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
|
||||
"test": "mocha --exit --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
|
||||
"test:match": "mocha --timeout 20000 -g"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -65,10 +65,6 @@
|
|||
"@astrojs/language-server": "^0.8.10",
|
||||
"@astrojs/markdown-remark": "^0.6.4",
|
||||
"@astrojs/prism": "0.4.0",
|
||||
"@astrojs/renderer-preact": "^0.5.0",
|
||||
"@astrojs/renderer-react": "0.5.0",
|
||||
"@astrojs/renderer-svelte": "0.5.2",
|
||||
"@astrojs/renderer-vue": "0.4.0",
|
||||
"@astrojs/webapi": "^0.11.0",
|
||||
"@babel/core": "^7.17.7",
|
||||
"@babel/traverse": "^7.17.3",
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import type { AddressInfo } from 'net';
|
||||
import type * as babel from '@babel/core';
|
||||
import type * as vite from 'vite';
|
||||
import type { z } from 'zod';
|
||||
import type { AstroConfigSchema } from '../core/config';
|
||||
import type { AstroComponentFactory, Metadata } from '../runtime/server';
|
||||
import type { AstroRequest } from '../core/render/request';
|
||||
import type * as vite from 'vite';
|
||||
|
||||
export interface AstroBuiltinProps {
|
||||
'client:load'?: boolean;
|
||||
|
@ -127,21 +128,30 @@ export interface AstroUserConfig {
|
|||
|
||||
/**
|
||||
* @docs
|
||||
* @name renderers
|
||||
* @type {string[]}
|
||||
* @default `['@astrojs/renderer-svelte','@astrojs/renderer-vue','@astrojs/renderer-react','@astrojs/renderer-preact']`
|
||||
* @name integrations
|
||||
* @type {AstroIntegration[]}
|
||||
* @default `[]`
|
||||
* @description
|
||||
* Set the UI framework renderers for your project. Framework renderers are what power Astro's ability to use other frameworks inside of your project, like React, Svelte, and Vue.
|
||||
* Add Integrations to your project to extend Astro.
|
||||
*
|
||||
* Setting this configuration will disable Astro's default framework support, so you will need to provide a renderer for every framework that you want to use.
|
||||
* Integrations are your one-stop shop to add new frameworks (like Solid.js), new features (like sitemaps), and new libraries (like Partytown and Turbolinks).
|
||||
*
|
||||
* Setting this configuration will disable Astro's default integration, so it is recommended to provide a renderer for every framework that you use:
|
||||
*
|
||||
* Note: Integrations are currently under active development, and only first-party integrations are supported. In the future, 3rd-party integrations will be allowed.
|
||||
*
|
||||
* ```js
|
||||
* import react from '@astrojs/react';
|
||||
* import vue from '@astrojs/vue';
|
||||
* {
|
||||
* // Use Astro + React, with no other frameworks.
|
||||
* renderers: ['@astrojs/renderer-react']
|
||||
* // Example: Use Astro with Vue + React, and no other frameworks.
|
||||
* integrations: [react(), vue()]
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
integrations?: AstroIntegration[];
|
||||
|
||||
/** @deprecated - Use "integrations" instead. Run Astro to learn more about migrating. */
|
||||
renderers?: string[];
|
||||
|
||||
/**
|
||||
|
@ -170,6 +180,7 @@ export interface AstroUserConfig {
|
|||
* }
|
||||
* ```
|
||||
*/
|
||||
/** Options for rendering markdown content */
|
||||
markdownOptions?: {
|
||||
render?: MarkdownRenderOptions;
|
||||
};
|
||||
|
@ -379,7 +390,7 @@ export interface AstroUserConfig {
|
|||
|
||||
/**
|
||||
* @docs
|
||||
* @name devOptions.vite
|
||||
* @name vite
|
||||
* @type {vite.UserConfig}
|
||||
* @description
|
||||
*
|
||||
|
@ -421,11 +432,33 @@ export interface AstroUserConfig {
|
|||
// export interface AstroUserConfig extends z.input<typeof AstroConfigSchema> {
|
||||
// }
|
||||
|
||||
/**
|
||||
* IDs for different stages of JS script injection:
|
||||
* - "before-hydration": Imported client-side, before the hydration script runs. Processed & resolved by Vite.
|
||||
* - "head-inline": Injected into a script tag in the `<head>` of every page. Not processed or resolved by Vite.
|
||||
* - "page": Injected into the JavaScript bundle of every page. Processed & resolved by Vite.
|
||||
* - "page-ssr": Injected into the frontmatter of every Astro page. Processed & resolved by Vite.
|
||||
*/
|
||||
type InjectedScriptStage = 'before-hydration' | 'head-inline' | 'page' | 'page-ssr';
|
||||
|
||||
/**
|
||||
* Resolved Astro Config
|
||||
* Config with user settings along with all defaults filled in.
|
||||
*/
|
||||
export type AstroConfig = z.output<typeof AstroConfigSchema>;
|
||||
export interface AstroConfig extends z.output<typeof AstroConfigSchema> {
|
||||
// Public:
|
||||
// This is a more detailed type than zod validation gives us.
|
||||
// TypeScript still confirms zod validation matches this type.
|
||||
integrations: AstroIntegration[];
|
||||
// Private:
|
||||
// We have a need to pass context based on configured state,
|
||||
// that is different from the user-exposed configuration.
|
||||
// TODO: Create an AstroConfig class to manage this, long-term.
|
||||
_ctx: {
|
||||
renderers: AstroRenderer[];
|
||||
scripts: { stage: InjectedScriptStage; content: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
export type AsyncRendererComponentFn<U> = (Component: any, props: any, children: string | undefined, metadata?: AstroComponentMetadata) => Promise<U>;
|
||||
|
||||
|
@ -560,38 +593,51 @@ export interface EndpointHandler {
|
|||
[method: string]: (params: any, request: AstroRequest) => EndpointOutput | Response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Astro Renderer
|
||||
* Docs: https://docs.astro.build/reference/renderer-reference/
|
||||
*/
|
||||
export interface Renderer {
|
||||
/** Name of the renderer (required) */
|
||||
export interface AstroRenderer {
|
||||
/** Name of the renderer. */
|
||||
name: string;
|
||||
/** Import statement for renderer */
|
||||
source?: string;
|
||||
/** Import statement for the server renderer */
|
||||
serverEntry: string;
|
||||
/** Scripts to be injected before component */
|
||||
polyfills?: string[];
|
||||
/** Polyfills that need to run before hydration ever occurs */
|
||||
hydrationPolyfills?: string[];
|
||||
/** Import entrypoint for the client/browser renderer. */
|
||||
clientEntrypoint?: string;
|
||||
/** Import entrypoint for the server/build/ssr renderer. */
|
||||
serverEntrypoint: string;
|
||||
/** JSX identifier (e.g. 'react' or 'solid-js') */
|
||||
jsxImportSource?: string;
|
||||
/** Babel transform options */
|
||||
jsxTransformOptions?: JSXTransformFn;
|
||||
/** Utilies for server-side rendering */
|
||||
}
|
||||
|
||||
export interface SSRLoadedRenderer extends AstroRenderer {
|
||||
ssr: {
|
||||
check: AsyncRendererComponentFn<boolean>;
|
||||
renderToStaticMarkup: AsyncRendererComponentFn<{
|
||||
html: string;
|
||||
}>;
|
||||
};
|
||||
/** Return configuration object for Vite ("options" should match https://vitejs.dev/guide/api-plugin.html#config) */
|
||||
viteConfig?: (options: { mode: 'string'; command: 'build' | 'serve' }) => Promise<vite.InlineConfig>;
|
||||
/** @deprecated Don’t try and build these dependencies for client (deprecated in 0.21) */
|
||||
external?: string[];
|
||||
/** @deprecated Clientside requirements (deprecated in 0.21) */
|
||||
knownEntrypoints?: string[];
|
||||
}
|
||||
|
||||
export interface AstroIntegration {
|
||||
/** The name of the integration. */
|
||||
name: string;
|
||||
/** The different hooks available to extend. */
|
||||
hooks: {
|
||||
'astro:config:setup'?: (options: {
|
||||
config: AstroConfig;
|
||||
command: 'dev' | 'build';
|
||||
updateConfig: (newConfig: Record<string, any>) => void;
|
||||
addRenderer: (renderer: AstroRenderer) => void;
|
||||
injectScript: (stage: InjectedScriptStage, content: string) => void;
|
||||
// TODO: Add support for `injectElement()` for full HTML element injection, not just scripts.
|
||||
// This may require some refactoring of `scripts`, `styles`, and `links` into something
|
||||
// more generalized. Consider the SSR use-case as well.
|
||||
// injectElement: (stage: vite.HtmlTagDescriptor, element: string) => void;
|
||||
}) => void;
|
||||
'astro:config:done'?: (options: { config: AstroConfig }) => void | Promise<void>;
|
||||
'astro:server:setup'?: (options: { server: vite.ViteDevServer }) => void | Promise<void>;
|
||||
'astro:server:start'?: (options: { address: AddressInfo }) => void | Promise<void>;
|
||||
'astro:server:done'?: () => void | Promise<void>;
|
||||
'astro:build:start'?: () => void | Promise<void>;
|
||||
'astro:build:done'?: (options: { pages: { pathname: string }[]; dir: URL }) => void | Promise<void>;
|
||||
};
|
||||
}
|
||||
|
||||
export type RouteType = 'page' | 'endpoint';
|
||||
|
@ -665,7 +711,7 @@ export interface SSRElement {
|
|||
}
|
||||
|
||||
export interface SSRMetadata {
|
||||
renderers: Renderer[];
|
||||
renderers: SSRLoadedRenderer[];
|
||||
pathname: string;
|
||||
legacyBuild: boolean;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { ComponentInstance, ManifestData, RouteData, Renderer } from '../../@types/astro';
|
||||
import type { ComponentInstance, ManifestData, RouteData, SSRLoadedRenderer } from '../../@types/astro';
|
||||
import type { SSRManifest as Manifest, RouteInfo } from './types';
|
||||
|
||||
import { defaultLogOptions } from '../logger.js';
|
||||
|
@ -6,7 +6,6 @@ import { matchRoute } from '../routing/match.js';
|
|||
import { render } from '../render/core.js';
|
||||
import { RouteCache } from '../render/route-cache.js';
|
||||
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
|
||||
import { createRenderer } from '../render/renderer.js';
|
||||
import { prependForwardSlash } from '../path.js';
|
||||
|
||||
export class App {
|
||||
|
@ -15,7 +14,7 @@ export class App {
|
|||
#rootFolder: URL;
|
||||
#routeDataToRouteInfo: Map<RouteData, RouteInfo>;
|
||||
#routeCache: RouteCache;
|
||||
#renderersPromise: Promise<Renderer[]>;
|
||||
#renderersPromise: Promise<SSRLoadedRenderer[]>;
|
||||
|
||||
constructor(manifest: Manifest, rootFolder: URL) {
|
||||
this.#manifest = manifest;
|
||||
|
@ -84,18 +83,11 @@ export class App {
|
|||
status: 200,
|
||||
});
|
||||
}
|
||||
async #loadRenderers(): Promise<Renderer[]> {
|
||||
const rendererNames = this.#manifest.renderers;
|
||||
async #loadRenderers(): Promise<SSRLoadedRenderer[]> {
|
||||
return await Promise.all(
|
||||
rendererNames.map(async (rendererName) => {
|
||||
return createRenderer(rendererName, {
|
||||
renderer(name) {
|
||||
return import(name);
|
||||
},
|
||||
server(entry) {
|
||||
return import(entry);
|
||||
},
|
||||
});
|
||||
this.#manifest.renderers.map(async (renderer) => {
|
||||
const mod = (await import(renderer.serverEntrypoint)) as { default: SSRLoadedRenderer['ssr'] };
|
||||
return { ...renderer, ssr: mod.default };
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { RouteData, SerializedRouteData, MarkdownRenderOptions } from '../../@types/astro';
|
||||
import type { RouteData, SerializedRouteData, MarkdownRenderOptions, AstroRenderer } from '../../@types/astro';
|
||||
|
||||
export interface RouteInfo {
|
||||
routeData: RouteData;
|
||||
|
@ -17,7 +17,7 @@ export interface SSRManifest {
|
|||
markdown: {
|
||||
render: MarkdownRenderOptions;
|
||||
};
|
||||
renderers: string[];
|
||||
renderers: AstroRenderer[];
|
||||
entryModules: Record<string, string>;
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ import { collectPagesData } from './page-data.js';
|
|||
import { build as scanBasedBuild } from './scan-based-build.js';
|
||||
import { staticBuild } from './static-build.js';
|
||||
import { RouteCache } from '../render/route-cache.js';
|
||||
import { runHookBuildDone, runHookBuildStart, runHookConfigDone, runHookConfigSetup } from '../../integrations/index.js';
|
||||
|
||||
export interface BuildOptions {
|
||||
mode?: string;
|
||||
|
@ -57,23 +58,23 @@ class AstroBuilder {
|
|||
const timer: Record<string, number> = {};
|
||||
timer.init = performance.now();
|
||||
timer.viteStart = performance.now();
|
||||
this.config = await runHookConfigSetup({ config: this.config, command: 'build' });
|
||||
const viteConfig = await createVite(
|
||||
vite.mergeConfig(
|
||||
{
|
||||
mode: this.mode,
|
||||
server: {
|
||||
hmr: false,
|
||||
middlewareMode: 'ssr',
|
||||
},
|
||||
{
|
||||
mode: this.mode,
|
||||
server: {
|
||||
hmr: false,
|
||||
middlewareMode: 'ssr',
|
||||
},
|
||||
this.config.vite || {}
|
||||
),
|
||||
},
|
||||
{ astroConfig: this.config, logging, mode: 'build' }
|
||||
);
|
||||
await runHookConfigDone({ config: this.config });
|
||||
this.viteConfig = viteConfig;
|
||||
const viteServer = await vite.createServer(viteConfig);
|
||||
this.viteServer = viteServer;
|
||||
debug('build', timerMessage('Vite started', timer.viteStart));
|
||||
await runHookBuildStart({ config: this.config });
|
||||
|
||||
timer.loadStart = performance.now();
|
||||
const { assets, allPages } = await collectPagesData({
|
||||
|
@ -160,6 +161,8 @@ class AstroBuilder {
|
|||
|
||||
// You're done! Time to clean up.
|
||||
await viteServer.close();
|
||||
await runHookBuildDone({ config: this.config, pages: pageNames });
|
||||
|
||||
if (logging.level && levels[logging.level] <= levels['info']) {
|
||||
await this.printStats({ logging, timeStart: timer.init, pageCount: pageNames.length });
|
||||
}
|
||||
|
|
|
@ -1,31 +1,28 @@
|
|||
import type { OutputChunk, OutputAsset, RollupOutput } from 'rollup';
|
||||
import type { Plugin as VitePlugin, UserConfig, Manifest as ViteManifest } from 'vite';
|
||||
import type { AstroConfig, ComponentInstance, EndpointHandler, ManifestData, Renderer, RouteType } from '../../@types/astro';
|
||||
import type { AllPagesData } from './types';
|
||||
import type { LogOptions } from '../logger';
|
||||
import type { ViteConfigWithSSR } from '../create-vite';
|
||||
import type { PageBuildData } from './types';
|
||||
import type { BuildInternals } from '../../core/build/internal.js';
|
||||
import type { RenderOptions } from '../../core/render/core';
|
||||
import type { SerializedSSRManifest, SerializedRouteInfo } from '../app/types';
|
||||
|
||||
import glob from 'fast-glob';
|
||||
import fs from 'fs';
|
||||
import npath from 'path';
|
||||
import type { OutputAsset, OutputChunk, RollupOutput } from 'rollup';
|
||||
import { fileURLToPath } from 'url';
|
||||
import glob from 'fast-glob';
|
||||
import type { Manifest as ViteManifest, Plugin as VitePlugin, UserConfig } from 'vite';
|
||||
import * as vite from 'vite';
|
||||
import { debug, error } from '../../core/logger.js';
|
||||
import { prependForwardSlash, appendForwardSlash } from '../../core/path.js';
|
||||
import { emptyDir, removeDir, resolveDependency } from '../../core/util.js';
|
||||
import type { AstroConfig, AstroRenderer, ComponentInstance, EndpointHandler, ManifestData, RouteType, SSRLoadedRenderer } from '../../@types/astro';
|
||||
import type { BuildInternals } from '../../core/build/internal.js';
|
||||
import { createBuildInternals } from '../../core/build/internal.js';
|
||||
import { debug, error } from '../../core/logger.js';
|
||||
import { appendForwardSlash, prependForwardSlash } from '../../core/path.js';
|
||||
import type { RenderOptions } from '../../core/render/core';
|
||||
import { emptyDir, removeDir, resolveDependency } from '../../core/util.js';
|
||||
import { rollupPluginAstroBuildCSS } from '../../vite-plugin-build-css/index.js';
|
||||
import { vitePluginHoistedScripts } from './vite-plugin-hoisted-scripts.js';
|
||||
import { RouteCache } from '../render/route-cache.js';
|
||||
import type { SerializedRouteInfo, SerializedSSRManifest } from '../app/types';
|
||||
import type { ViteConfigWithSSR } from '../create-vite';
|
||||
import { call as callEndpoint } from '../endpoint/index.js';
|
||||
import { serializeRouteData } from '../routing/index.js';
|
||||
import type { LogOptions } from '../logger';
|
||||
import { render } from '../render/core.js';
|
||||
import { RouteCache } from '../render/route-cache.js';
|
||||
import { createLinkStylesheetElementSet, createModuleScriptElementWithSrcSet } from '../render/ssr-element.js';
|
||||
import { createRequest } from '../render/request.js';
|
||||
import { serializeRouteData } from '../routing/index.js';
|
||||
import type { AllPagesData, PageBuildData } from './types';
|
||||
import { vitePluginHoistedScripts } from './vite-plugin-hoisted-scripts.js';
|
||||
|
||||
export interface StaticBuildOptions {
|
||||
allPages: AllPagesData;
|
||||
|
@ -116,17 +113,8 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
|||
// about that page, such as its paths.
|
||||
const facadeIdToPageDataMap = new Map<string, PageBuildData>();
|
||||
|
||||
// Collects polyfills and passes them as top-level inputs
|
||||
const polyfills = getRenderers(opts).flatMap((renderer) => {
|
||||
return (renderer.polyfills || []).concat(renderer.hydrationPolyfills || []);
|
||||
});
|
||||
for (const polyfill of polyfills) {
|
||||
jsInput.add(polyfill);
|
||||
}
|
||||
|
||||
// Build internals needed by the CSS plugin
|
||||
const internals = createBuildInternals();
|
||||
|
||||
for (const [component, pageData] of Object.entries(allPages)) {
|
||||
const astroModuleURL = new URL('./' + component, astroConfig.projectRoot);
|
||||
const astroModuleId = prependForwardSlash(component);
|
||||
|
@ -145,7 +133,7 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
|||
// Any hydration directive like astro/client/idle.js
|
||||
...metadata.hydrationDirectiveSpecifiers(),
|
||||
// The client path for each renderer
|
||||
...renderers.filter((renderer) => !!renderer.source).map((renderer) => renderer.source!),
|
||||
...renderers.filter((renderer) => !!renderer.clientEntrypoint).map((renderer) => renderer.clientEntrypoint!),
|
||||
]);
|
||||
|
||||
// Add hoisted scripts
|
||||
|
@ -172,6 +160,7 @@ export async function staticBuild(opts: StaticBuildOptions) {
|
|||
|
||||
// Build your project (SSR application code, assets, client JS, etc.)
|
||||
const ssrResult = (await ssrBuild(opts, internals, pageInput)) as RollupOutput;
|
||||
|
||||
await clientBuild(opts, internals, jsInput);
|
||||
|
||||
// SSG mode, generate pages.
|
||||
|
@ -189,10 +178,11 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
|||
const { astroConfig, viteConfig } = opts;
|
||||
const ssr = astroConfig.buildOptions.experimentalSsr;
|
||||
const out = ssr ? getServerRoot(astroConfig) : getOutRoot(astroConfig);
|
||||
|
||||
// TODO: use vite.mergeConfig() here?
|
||||
return await vite.build({
|
||||
logLevel: 'warn',
|
||||
logLevel: 'error',
|
||||
mode: 'production',
|
||||
css: viteConfig.css,
|
||||
build: {
|
||||
...viteConfig.build,
|
||||
emptyOutDir: false,
|
||||
|
@ -200,6 +190,9 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
|
|||
outDir: fileURLToPath(out),
|
||||
ssr: true,
|
||||
rollupOptions: {
|
||||
// onwarn(warn) {
|
||||
// console.log(warn);
|
||||
// },
|
||||
input: Array.from(input),
|
||||
output: {
|
||||
format: 'esm',
|
||||
|
@ -240,9 +233,12 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
|
|||
}
|
||||
|
||||
const out = astroConfig.buildOptions.experimentalSsr ? getClientRoot(astroConfig) : getOutRoot(astroConfig);
|
||||
|
||||
// TODO: use vite.mergeConfig() here?
|
||||
return await vite.build({
|
||||
logLevel: 'warn',
|
||||
logLevel: 'error',
|
||||
mode: 'production',
|
||||
css: viteConfig.css,
|
||||
build: {
|
||||
emptyOutDir: false,
|
||||
minify: 'esbuild',
|
||||
|
@ -275,38 +271,20 @@ async function clientBuild(opts: StaticBuildOptions, internals: BuildInternals,
|
|||
});
|
||||
}
|
||||
|
||||
function getRenderers(opts: StaticBuildOptions) {
|
||||
// All of the PageDatas have the same renderers, so just grab one.
|
||||
const pageData = Object.values(opts.allPages)[0];
|
||||
// These renderers have been loaded through Vite. To generate pages
|
||||
// we need the ESM loaded version. This creates that.
|
||||
const viteLoadedRenderers = pageData.preload[0];
|
||||
|
||||
return viteLoadedRenderers;
|
||||
async function loadRenderer(renderer: AstroRenderer, config: AstroConfig): Promise<SSRLoadedRenderer> {
|
||||
const mod = (await import(resolveDependency(renderer.serverEntrypoint, config))) as { default: SSRLoadedRenderer['ssr'] };
|
||||
return { ...renderer, ssr: mod.default };
|
||||
}
|
||||
|
||||
async function collectRenderers(opts: StaticBuildOptions): Promise<Renderer[]> {
|
||||
const viteLoadedRenderers = getRenderers(opts);
|
||||
|
||||
const renderers = await Promise.all(
|
||||
viteLoadedRenderers.map(async (r) => {
|
||||
const mod = await import(resolveDependency(r.serverEntry, opts.astroConfig));
|
||||
return Object.create(r, {
|
||||
ssr: {
|
||||
value: mod.default,
|
||||
},
|
||||
}) as Renderer;
|
||||
})
|
||||
);
|
||||
|
||||
return renderers;
|
||||
async function loadRenderers(config: AstroConfig): Promise<SSRLoadedRenderer[]> {
|
||||
return Promise.all(config._ctx.renderers.map((r) => loadRenderer(r, config)));
|
||||
}
|
||||
|
||||
async function generatePages(result: RollupOutput, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>) {
|
||||
debug('build', 'Finish build. Begin generating.');
|
||||
|
||||
// Get renderers to be shared for each page generation.
|
||||
const renderers = await collectRenderers(opts);
|
||||
const renderers = await loadRenderers(opts.astroConfig);
|
||||
|
||||
for (let output of result.output) {
|
||||
if (chunkIsPage(opts.astroConfig, output, internals)) {
|
||||
|
@ -315,7 +293,13 @@ async function generatePages(result: RollupOutput, opts: StaticBuildOptions, int
|
|||
}
|
||||
}
|
||||
|
||||
async function generatePage(output: OutputChunk, opts: StaticBuildOptions, internals: BuildInternals, facadeIdToPageDataMap: Map<string, PageBuildData>, renderers: Renderer[]) {
|
||||
async function generatePage(
|
||||
output: OutputChunk,
|
||||
opts: StaticBuildOptions,
|
||||
internals: BuildInternals,
|
||||
facadeIdToPageDataMap: Map<string, PageBuildData>,
|
||||
renderers: SSRLoadedRenderer[]
|
||||
) {
|
||||
const { astroConfig } = opts;
|
||||
|
||||
let url = new URL('./' + output.fileName, getOutRoot(astroConfig));
|
||||
|
@ -359,7 +343,7 @@ interface GeneratePathOptions {
|
|||
linkIds: string[];
|
||||
hoistedId: string | null;
|
||||
mod: ComponentInstance;
|
||||
renderers: Renderer[];
|
||||
renderers: SSRLoadedRenderer[];
|
||||
}
|
||||
|
||||
async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: GeneratePathOptions) {
|
||||
|
@ -377,6 +361,16 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
|
|||
const links = createLinkStylesheetElementSet(linkIds.reverse(), site);
|
||||
const scripts = createModuleScriptElementWithSrcSet(hoistedId ? [hoistedId] : [], site);
|
||||
|
||||
// Add all injected scripts to the page.
|
||||
for (const script of astroConfig._ctx.scripts) {
|
||||
if (script.stage === 'head-inline') {
|
||||
scripts.add({
|
||||
props: {},
|
||||
children: script.content,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const options: RenderOptions = {
|
||||
legacyBuild: false,
|
||||
|
@ -391,6 +385,14 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
|
|||
async resolve(specifier: string) {
|
||||
const hashedFilePath = internals.entrySpecifierToBundleMap.get(specifier);
|
||||
if (typeof hashedFilePath !== 'string') {
|
||||
// If no "astro:scripts/before-hydration.js" script exists in the build,
|
||||
// then we can assume that no before-hydration scripts are needed.
|
||||
// Return this as placeholder, which will be ignored by the browser.
|
||||
// TODO: In the future, we hope to run this entire script through Vite,
|
||||
// removing the need to maintain our own custom Vite-mimic resolve logic.
|
||||
if (specifier === 'astro:scripts/before-hydration.js') {
|
||||
return 'data:text/javascript;charset=utf-8,//[no before-hydration script]';
|
||||
}
|
||||
throw new Error(`Cannot find the built path for ${specifier}`);
|
||||
}
|
||||
const relPath = npath.posix.relative(pathname, '/' + hashedFilePath);
|
||||
|
@ -480,7 +482,7 @@ async function generateManifest(result: RollupOutput, opts: StaticBuildOptions,
|
|||
markdown: {
|
||||
render: astroConfig.markdownOptions.render,
|
||||
},
|
||||
renderers: astroConfig.renderers,
|
||||
renderers: astroConfig._ctx.renderers,
|
||||
entryModules: Object.fromEntries(internals.entrySpecifierToBundleMap.entries()),
|
||||
};
|
||||
|
||||
|
@ -628,8 +630,8 @@ export function vitePluginNewBuild(input: Set<string>, internals: BuildInternals
|
|||
}
|
||||
await Promise.all(promises);
|
||||
for (const [, chunk] of Object.entries(bundle)) {
|
||||
if (chunk.type === 'chunk' && chunk.facadeModuleId && mapping.has(chunk.facadeModuleId)) {
|
||||
const specifier = mapping.get(chunk.facadeModuleId)!;
|
||||
if (chunk.type === 'chunk' && chunk.facadeModuleId) {
|
||||
const specifier = mapping.get(chunk.facadeModuleId) || chunk.facadeModuleId;
|
||||
internals.entrySpecifierToBundleMap.set(specifier, chunk.fileName);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,48 @@
|
|||
import type { AstroConfig, AstroUserConfig, CLIFlags } from '../@types/astro';
|
||||
import type { Arguments as Flags } from 'yargs-parser';
|
||||
import type * as Postcss from 'postcss';
|
||||
|
||||
import * as colors from 'kleur/colors';
|
||||
import path from 'path';
|
||||
import { pathToFileURL, fileURLToPath } from 'url';
|
||||
import { mergeConfig as mergeViteConfig } from 'vite';
|
||||
import { z } from 'zod';
|
||||
import load from '@proload/core';
|
||||
import loadTypeScript from '@proload/plugin-tsm';
|
||||
import postcssrc from 'postcss-load-config';
|
||||
import { arraify, isObject } from './util.js';
|
||||
|
||||
load.use([loadTypeScript]);
|
||||
|
||||
interface PostCSSConfigResult {
|
||||
options: Postcss.ProcessOptions;
|
||||
plugins: Postcss.Plugin[];
|
||||
}
|
||||
|
||||
async function resolvePostcssConfig(inlineOptions: any, root: URL): Promise<PostCSSConfigResult> {
|
||||
if (isObject(inlineOptions)) {
|
||||
const options = { ...inlineOptions };
|
||||
delete options.plugins;
|
||||
return {
|
||||
options,
|
||||
plugins: inlineOptions.plugins || [],
|
||||
};
|
||||
}
|
||||
const searchPath = typeof inlineOptions === 'string' ? inlineOptions : fileURLToPath(root);
|
||||
try {
|
||||
// @ts-ignore
|
||||
return await postcssrc({}, searchPath);
|
||||
} catch (err: any) {
|
||||
if (!/No PostCSS Config found/.test(err.message)) {
|
||||
throw err;
|
||||
}
|
||||
return {
|
||||
options: {},
|
||||
plugins: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export const AstroConfigSchema = z.object({
|
||||
projectRoot: z
|
||||
.string()
|
||||
|
@ -36,7 +69,31 @@ export const AstroConfigSchema = z.object({
|
|||
.optional()
|
||||
.default('./dist')
|
||||
.transform((val) => new URL(val)),
|
||||
renderers: z.array(z.string()).optional().default(['@astrojs/renderer-svelte', '@astrojs/renderer-vue', '@astrojs/renderer-react', '@astrojs/renderer-preact']),
|
||||
integrations: z.preprocess(
|
||||
// preprocess
|
||||
(val) => (Array.isArray(val) ? val.flat(Infinity).filter(Boolean) : val),
|
||||
// validate
|
||||
z
|
||||
.array(z.object({ name: z.string(), hooks: z.object({}).passthrough().default({}) }))
|
||||
.default([])
|
||||
// validate: first-party integrations only
|
||||
// TODO: Add `To use 3rd-party integrations or to create your own, use the --experimental-integrations flag.`,
|
||||
.refine((arr) => arr.every((integration) => integration.name.startsWith('@astrojs/')), {
|
||||
message: `Astro integrations are still experimental, and only official integrations are currently supported`,
|
||||
})
|
||||
),
|
||||
styleOptions: z
|
||||
.object({
|
||||
postcss: z
|
||||
.object({
|
||||
options: z.any(),
|
||||
plugins: z.array(z.any()),
|
||||
})
|
||||
.optional()
|
||||
.default({ options: {}, plugins: [] }),
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
markdownOptions: z
|
||||
.object({
|
||||
render: z.any().optional().default(['@astrojs/markdown-remark', {}]),
|
||||
|
@ -81,6 +138,37 @@ export const AstroConfigSchema = z.object({
|
|||
/** Turn raw config values into normalized values */
|
||||
export async function validateConfig(userConfig: any, root: string): Promise<AstroConfig> {
|
||||
const fileProtocolRoot = pathToFileURL(root + path.sep);
|
||||
// Manual deprecation checks
|
||||
/* eslint-disable no-console */
|
||||
if (userConfig.hasOwnProperty('renderers')) {
|
||||
console.error('Astro "renderers" are now "integrations"!');
|
||||
console.error('Update your configuration and install new dependencies:');
|
||||
try {
|
||||
const rendererKeywords = userConfig.renderers.map((r: string) => r.replace('@astrojs/renderer-', ''));
|
||||
const rendererImports = rendererKeywords.map((r: string) => ` import ${r} from '@astrojs/${r}';`).join('\n');
|
||||
const rendererIntegrations = rendererKeywords.map((r: string) => ` ${r}(),`).join('\n');
|
||||
console.error('');
|
||||
console.error(colors.dim(' // astro.config.js'));
|
||||
if (rendererImports.length > 0) {
|
||||
console.error(colors.green(rendererImports));
|
||||
}
|
||||
console.error('');
|
||||
console.error(colors.dim(' // ...'));
|
||||
if (rendererIntegrations.length > 0) {
|
||||
console.error(colors.green(' integrations: ['));
|
||||
console.error(colors.green(rendererIntegrations));
|
||||
console.error(colors.green(' ],'));
|
||||
} else {
|
||||
console.error(colors.green(' integrations: [],'));
|
||||
}
|
||||
console.error('');
|
||||
} catch (err) {
|
||||
// We tried, better to just exit.
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
/* eslint-enable no-console */
|
||||
|
||||
// We need to extend the global schema to add transforms that are relative to root.
|
||||
// This is type checked against the global schema to make sure we still match.
|
||||
const AstroConfigRelativeSchema = AstroConfigSchema.extend({
|
||||
|
@ -104,8 +192,26 @@ export async function validateConfig(userConfig: any, root: string): Promise<Ast
|
|||
.string()
|
||||
.default('./dist')
|
||||
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
|
||||
styleOptions: z
|
||||
.object({
|
||||
postcss: z.preprocess(
|
||||
(val) => resolvePostcssConfig(val, fileProtocolRoot),
|
||||
z
|
||||
.object({
|
||||
options: z.any(),
|
||||
plugins: z.array(z.any()),
|
||||
})
|
||||
.optional()
|
||||
.default({ options: {}, plugins: [] })
|
||||
),
|
||||
})
|
||||
.optional()
|
||||
.default({}),
|
||||
});
|
||||
return AstroConfigRelativeSchema.parseAsync(userConfig);
|
||||
return {
|
||||
...(await AstroConfigRelativeSchema.parseAsync(userConfig)),
|
||||
_ctx: { scripts: [], renderers: [] },
|
||||
};
|
||||
}
|
||||
|
||||
/** Adds '/' to end of string but doesn’t double-up */
|
||||
|
@ -175,7 +281,11 @@ export async function loadConfig(configOptions: LoadConfigOptions): Promise<Astr
|
|||
if (config) {
|
||||
userConfig = config.value;
|
||||
}
|
||||
// normalize, validate, and return
|
||||
return resolveConfig(userConfig, root, flags);
|
||||
}
|
||||
|
||||
/** Attempt to resolve an Astro configuration object. Normalize, validate, and return. */
|
||||
export async function resolveConfig(userConfig: AstroUserConfig, root: string, flags: CLIFlags = {}): Promise<AstroConfig> {
|
||||
const mergedConfig = mergeCLIFlags(userConfig, flags);
|
||||
const validatedConfig = await validateConfig(mergedConfig, root);
|
||||
return validatedConfig;
|
||||
|
@ -185,3 +295,42 @@ export function formatConfigError(err: z.ZodError) {
|
|||
const errorList = err.issues.map((issue) => ` ! ${colors.bold(issue.path.join('.'))} ${colors.red(issue.message + '.')}`);
|
||||
return `${colors.red('[config]')} Astro found issue(s) with your configuration:\n${errorList.join('\n')}`;
|
||||
}
|
||||
|
||||
function mergeConfigRecursively(defaults: Record<string, any>, overrides: Record<string, any>, rootPath: string) {
|
||||
const merged: Record<string, any> = { ...defaults };
|
||||
for (const key in overrides) {
|
||||
const value = overrides[key];
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const existing = merged[key];
|
||||
|
||||
if (existing == null) {
|
||||
merged[key] = value;
|
||||
continue;
|
||||
}
|
||||
|
||||
// fields that require special handling:
|
||||
if (key === 'vite' && rootPath === '') {
|
||||
merged[key] = mergeViteConfig(existing, value);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Array.isArray(existing) || Array.isArray(value)) {
|
||||
merged[key] = [...arraify(existing ?? []), ...arraify(value ?? [])];
|
||||
continue;
|
||||
}
|
||||
if (isObject(existing) && isObject(value)) {
|
||||
merged[key] = mergeConfigRecursively(existing, value, rootPath ? `${rootPath}.${key}` : key);
|
||||
continue;
|
||||
}
|
||||
|
||||
merged[key] = value;
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
export function mergeConfig(defaults: Record<string, any>, overrides: Record<string, any>, isRoot = true): Record<string, any> {
|
||||
return mergeConfigRecursively(defaults, overrides, isRoot ? '' : '.');
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import { builtinModules } from 'module';
|
|||
import { fileURLToPath } from 'url';
|
||||
import fs from 'fs';
|
||||
import * as vite from 'vite';
|
||||
import { runHookServerSetup } from '../integrations/index.js';
|
||||
import astroVitePlugin from '../vite-plugin-astro/index.js';
|
||||
import astroViteServerPlugin from '../vite-plugin-astro-server/index.js';
|
||||
import astroPostprocessVitePlugin from '../vite-plugin-astro-postprocess/index.js';
|
||||
|
@ -12,7 +13,8 @@ import configAliasVitePlugin from '../vite-plugin-config-alias/index.js';
|
|||
import markdownVitePlugin from '../vite-plugin-markdown/index.js';
|
||||
import jsxVitePlugin from '../vite-plugin-jsx/index.js';
|
||||
import envVitePlugin from '../vite-plugin-env/index.js';
|
||||
import { resolveDependency } from './util.js';
|
||||
import astroScriptsPlugin from '../vite-plugin-scripts/index.js';
|
||||
import astroIntegrationsContainerPlugin from '../vite-plugin-integrations-container/index.js';
|
||||
|
||||
// Some packages are just external, and that’s the way it goes.
|
||||
const ALWAYS_EXTERNAL = new Set([
|
||||
|
@ -41,12 +43,11 @@ interface CreateViteOptions {
|
|||
}
|
||||
|
||||
/** Return a common starting point for all Vite actions */
|
||||
export async function createVite(inlineConfig: ViteConfigWithSSR, { astroConfig, logging, mode }: CreateViteOptions): Promise<ViteConfigWithSSR> {
|
||||
export async function createVite(commandConfig: ViteConfigWithSSR, { astroConfig, logging, mode }: CreateViteOptions): Promise<ViteConfigWithSSR> {
|
||||
// Scan for any third-party Astro packages. Vite needs these to be passed to `ssr.noExternal`.
|
||||
const astroPackages = await getAstroPackages(astroConfig);
|
||||
|
||||
// Start with the Vite configuration that Astro core needs
|
||||
let viteConfig: ViteConfigWithSSR = {
|
||||
const commonConfig: ViteConfigWithSSR = {
|
||||
cacheDir: fileURLToPath(new URL('./node_modules/.vite/', astroConfig.projectRoot)), // using local caches allows Astro to be used in monorepos, etc.
|
||||
clearScreen: false, // we want to control the output, not Vite
|
||||
logLevel: 'warn', // log warnings and errors only
|
||||
|
@ -56,6 +57,7 @@ export async function createVite(inlineConfig: ViteConfigWithSSR, { astroConfig,
|
|||
plugins: [
|
||||
configAliasVitePlugin({ config: astroConfig }),
|
||||
astroVitePlugin({ config: astroConfig, logging }),
|
||||
astroScriptsPlugin({ config: astroConfig }),
|
||||
// The server plugin is for dev only and having it run during the build causes
|
||||
// the build to run very slow as the filewatcher is triggered often.
|
||||
mode === 'dev' && astroViteServerPlugin({ config: astroConfig, logging }),
|
||||
|
@ -63,6 +65,7 @@ export async function createVite(inlineConfig: ViteConfigWithSSR, { astroConfig,
|
|||
markdownVitePlugin({ config: astroConfig }),
|
||||
jsxVitePlugin({ config: astroConfig, logging }),
|
||||
astroPostprocessVitePlugin({ config: astroConfig }),
|
||||
astroIntegrationsContainerPlugin({ config: astroConfig }),
|
||||
],
|
||||
publicDir: fileURLToPath(astroConfig.public),
|
||||
root: fileURLToPath(astroConfig.projectRoot),
|
||||
|
@ -75,6 +78,9 @@ export async function createVite(inlineConfig: ViteConfigWithSSR, { astroConfig,
|
|||
// add proxies here
|
||||
},
|
||||
},
|
||||
css: {
|
||||
postcss: astroConfig.styleOptions.postcss || {},
|
||||
},
|
||||
// Note: SSR API is in beta (https://vitejs.dev/guide/ssr.html)
|
||||
ssr: {
|
||||
external: [...ALWAYS_EXTERNAL],
|
||||
|
@ -82,26 +88,16 @@ export async function createVite(inlineConfig: ViteConfigWithSSR, { astroConfig,
|
|||
},
|
||||
};
|
||||
|
||||
// Add in Astro renderers, which will extend the base config
|
||||
for (const name of astroConfig.renderers) {
|
||||
try {
|
||||
const { default: renderer } = await import(resolveDependency(name, astroConfig));
|
||||
if (!renderer) continue;
|
||||
// if a renderer provides viteConfig(), call it and pass in results
|
||||
if (renderer.viteConfig) {
|
||||
if (typeof renderer.viteConfig !== 'function') {
|
||||
throw new Error(`${name}: viteConfig(options) must be a function! Got ${typeof renderer.viteConfig}.`);
|
||||
}
|
||||
const rendererConfig = await renderer.viteConfig({ mode: inlineConfig.mode, command: inlineConfig.mode === 'production' ? 'build' : 'serve' }); // is this command true?
|
||||
viteConfig = vite.mergeConfig(viteConfig, rendererConfig) as ViteConfigWithSSR;
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`${name}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
viteConfig = vite.mergeConfig(viteConfig, inlineConfig); // merge in inline Vite config
|
||||
return viteConfig;
|
||||
// Merge configs: we merge vite configuration objects together in the following order,
|
||||
// where future values will override previous values.
|
||||
// 1. common vite config
|
||||
// 2. user-provided vite config, via AstroConfig
|
||||
// 3. integration-provided vite config, via the `config:setup` hook
|
||||
// 4. command vite config, passed as the argument to this function
|
||||
let result = commonConfig;
|
||||
result = vite.mergeConfig(result, astroConfig.vite || {});
|
||||
result = vite.mergeConfig(result, commandConfig);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Scans `projectRoot` for third-party Astro packages that could export an `.astro` file
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import type { AstroConfig } from '../../@types/astro';
|
||||
import type { AddressInfo } from 'net';
|
||||
|
||||
import { performance } from 'perf_hooks';
|
||||
import { apply as applyPolyfill } from '../polyfill.js';
|
||||
import { createVite } from '../create-vite.js';
|
||||
import { defaultLogOptions, info, warn, LogOptions } from '../logger.js';
|
||||
import * as vite from 'vite';
|
||||
import type { AstroConfig } from '../../@types/astro';
|
||||
import { runHookConfigDone, runHookConfigSetup, runHookServerDone, runHookServerSetup, runHookServerStart } from '../../integrations/index.js';
|
||||
import { createVite } from '../create-vite.js';
|
||||
import { defaultLogOptions, info, LogOptions, warn } from '../logger.js';
|
||||
import * as msg from '../messages.js';
|
||||
import { apply as applyPolyfill } from '../polyfill.js';
|
||||
import { getResolvedHostForVite } from './util.js';
|
||||
|
||||
export interface DevOptions {
|
||||
|
@ -22,31 +22,36 @@ export interface DevServer {
|
|||
export default async function dev(config: AstroConfig, options: DevOptions = { logging: defaultLogOptions }): Promise<DevServer> {
|
||||
const devStart = performance.now();
|
||||
applyPolyfill();
|
||||
|
||||
// TODO: remove call once --hostname is baselined
|
||||
const host = getResolvedHostForVite(config);
|
||||
const viteUserConfig = vite.mergeConfig(
|
||||
config = await runHookConfigSetup({ config, command: 'dev' });
|
||||
const viteConfig = await createVite(
|
||||
{
|
||||
mode: 'development',
|
||||
server: { host },
|
||||
// TODO: remove call once --hostname is baselined
|
||||
server: { host: getResolvedHostForVite(config) },
|
||||
},
|
||||
config.vite || {}
|
||||
{ astroConfig: config, logging: options.logging, mode: 'dev' }
|
||||
);
|
||||
const viteConfig = await createVite(viteUserConfig, { astroConfig: config, logging: options.logging, mode: 'dev' });
|
||||
await runHookConfigDone({ config });
|
||||
const viteServer = await vite.createServer(viteConfig);
|
||||
runHookServerSetup({ config, server: viteServer });
|
||||
await viteServer.listen(config.devOptions.port);
|
||||
|
||||
const devServerAddressInfo = viteServer.httpServer!.address() as AddressInfo;
|
||||
const site = config.buildOptions.site ? new URL(config.buildOptions.site) : undefined;
|
||||
info(options.logging, null, msg.devStart({ startupTime: performance.now() - devStart, config, devServerAddressInfo, site, https: !!viteUserConfig.server?.https }));
|
||||
info(options.logging, null, msg.devStart({ startupTime: performance.now() - devStart, config, devServerAddressInfo, site, https: !!viteConfig.server?.https }));
|
||||
|
||||
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0';
|
||||
if (currentVersion.includes('-')) {
|
||||
warn(options.logging, null, msg.prerelease({ currentVersion }));
|
||||
}
|
||||
|
||||
await runHookServerStart({ config, address: devServerAddressInfo });
|
||||
|
||||
return {
|
||||
address: devServerAddressInfo,
|
||||
stop: () => viteServer.close(),
|
||||
stop: async () => {
|
||||
await viteServer.close();
|
||||
await runHookServerDone({ config });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, Renderer, RouteData, SSRElement } from '../../@types/astro';
|
||||
import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, SSRLoadedRenderer, RouteData, SSRElement } from '../../@types/astro';
|
||||
import type { LogOptions } from '../logger.js';
|
||||
import type { AstroRequest } from './request';
|
||||
|
||||
|
@ -66,7 +66,7 @@ export interface RenderOptions {
|
|||
pathname: string;
|
||||
scripts: Set<SSRElement>;
|
||||
resolve: (s: string) => Promise<string>;
|
||||
renderers: Renderer[];
|
||||
renderers: SSRLoadedRenderer[];
|
||||
route?: RouteData;
|
||||
routeCache: RouteCache;
|
||||
site?: string;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type * as vite from 'vite';
|
||||
|
||||
import path from 'path';
|
||||
import { viteID } from '../../util.js';
|
||||
import { unwrapId, viteID } from '../../util.js';
|
||||
|
||||
// https://vitejs.dev/guide/features.html#css-pre-processors
|
||||
export const STYLE_EXTENSIONS = new Set(['.css', '.pcss', '.postcss', '.scss', '.sass', '.styl', '.stylus', '.less']);
|
||||
|
@ -13,41 +13,50 @@ const cssRe = new RegExp(
|
|||
);
|
||||
export const isCSSRequest = (request: string): boolean => cssRe.test(request);
|
||||
|
||||
/**
|
||||
* getStylesForURL
|
||||
* Given a filePath URL, crawl Vite’s module graph to find style files
|
||||
*/
|
||||
/** Given a filePath URL, crawl Vite’s module graph to find all style imports. */
|
||||
export function getStylesForURL(filePath: URL, viteServer: vite.ViteDevServer): Set<string> {
|
||||
const css = new Set<string>();
|
||||
const importedCssUrls = new Set<string>();
|
||||
|
||||
// recursively crawl module graph to get all style files imported by parent id
|
||||
function crawlCSS(id: string, scanned = new Set<string>()) {
|
||||
// note: use .getModulesByFile() to get all related nodes of the same URL
|
||||
// using .getModuleById() could cause missing style imports on initial server load
|
||||
const relatedMods = viteServer.moduleGraph.getModulesByFile(id) ?? new Set();
|
||||
/** recursively crawl the module graph to get all style files imported by parent id */
|
||||
function crawlCSS(_id: string, isFile: boolean, scanned = new Set<string>()) {
|
||||
const id = unwrapId(_id);
|
||||
const importedModules = new Set<vite.ModuleNode>();
|
||||
const moduleEntriesForId = isFile
|
||||
? // If isFile = true, then you are at the root of your module import tree.
|
||||
// The `id` arg is a filepath, so use `getModulesByFile()` to collect all
|
||||
// nodes for that file. This is needed for advanced imports like Tailwind.
|
||||
viteServer.moduleGraph.getModulesByFile(id) ?? new Set()
|
||||
: // Otherwise, you are following an import in the module import tree.
|
||||
// You are safe to use getModuleById() here because Vite has already
|
||||
// resolved the correct `id` for you, by creating the import you followed here.
|
||||
new Set([viteServer.moduleGraph.getModuleById(id)!]);
|
||||
|
||||
for (const relatedMod of relatedMods) {
|
||||
if (id === relatedMod.id) {
|
||||
// Collect all imported modules for the module(s).
|
||||
for (const entry of moduleEntriesForId) {
|
||||
if (id === entry.id) {
|
||||
scanned.add(id);
|
||||
for (const importedMod of relatedMod.importedModules) {
|
||||
importedModules.add(importedMod);
|
||||
for (const importedModule of entry.importedModules) {
|
||||
importedModules.add(importedModule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scan importedModules
|
||||
// scan imported modules for CSS imports & add them to our collection.
|
||||
// Then, crawl that file to follow and scan all deep imports as well.
|
||||
for (const importedModule of importedModules) {
|
||||
if (!importedModule.id || scanned.has(importedModule.id)) continue;
|
||||
const ext = path.extname(importedModule.url.toLowerCase());
|
||||
if (STYLE_EXTENSIONS.has(ext)) {
|
||||
css.add(importedModule.url); // note: return `url`s for HTML (not .id, which will break Windows)
|
||||
if (!importedModule.id || scanned.has(importedModule.id)) {
|
||||
continue;
|
||||
}
|
||||
crawlCSS(importedModule.id, scanned);
|
||||
const ext = path.extname(importedModule.url).toLowerCase();
|
||||
if (STYLE_EXTENSIONS.has(ext)) {
|
||||
// NOTE: We use the `url` property here. `id` would break Windows.
|
||||
importedCssUrls.add(importedModule.url);
|
||||
}
|
||||
crawlCSS(importedModule.id, false, scanned);
|
||||
}
|
||||
}
|
||||
|
||||
crawlCSS(viteID(filePath));
|
||||
|
||||
return css;
|
||||
// Crawl your import graph for CSS files, populating `importedCssUrls` as a result.
|
||||
crawlCSS(viteID(filePath), true);
|
||||
return importedCssUrls;
|
||||
}
|
||||
|
|
|
@ -1,19 +1,15 @@
|
|||
import type * as vite from 'vite';
|
||||
import type { AstroConfig, ComponentInstance, Renderer, RouteData, RuntimeMode, SSRElement } from '../../../@types/astro';
|
||||
import type { AstroRequest } from '../request';
|
||||
|
||||
import { LogOptions } from '../../logger.js';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { getStylesForURL } from './css.js';
|
||||
import { injectTags } from './html.js';
|
||||
import type * as vite from 'vite';
|
||||
import type { AstroConfig, AstroRenderer, ComponentInstance, RouteData, RuntimeMode, SSRElement, SSRLoadedRenderer } from '../../../@types/astro';
|
||||
import { LogOptions } from '../../logger.js';
|
||||
import { render as coreRender } from '../core.js';
|
||||
import { prependForwardSlash } from '../../../core/path.js';
|
||||
import { RouteCache } from '../route-cache.js';
|
||||
import { resolveRenderers } from './renderers.js';
|
||||
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
|
||||
import { getStylesForURL } from './css.js';
|
||||
import { errorHandler } from './error.js';
|
||||
import { getHmrScript } from './hmr.js';
|
||||
import { prependForwardSlash } from '../../path.js';
|
||||
import { render as coreRender } from '../core.js';
|
||||
import { createModuleScriptElementWithSrcSet } from '../ssr-element.js';
|
||||
|
||||
import { injectTags } from './html.js';
|
||||
export interface SSROptions {
|
||||
/** an instance of the AstroConfig */
|
||||
astroConfig: AstroConfig;
|
||||
|
@ -39,15 +35,25 @@ export interface SSROptions {
|
|||
headers: Headers;
|
||||
}
|
||||
|
||||
export type ComponentPreload = [Renderer[], ComponentInstance];
|
||||
export type ComponentPreload = [SSRLoadedRenderer[], ComponentInstance];
|
||||
|
||||
export type RenderResponse = { type: 'html'; html: string } | { type: 'response'; response: Response };
|
||||
|
||||
const svelteStylesRE = /svelte\?svelte&type=style/;
|
||||
|
||||
async function loadRenderer(viteServer: vite.ViteDevServer, renderer: AstroRenderer): Promise<SSRLoadedRenderer> {
|
||||
const { url } = await viteServer.moduleGraph.ensureEntryFromUrl(renderer.serverEntrypoint);
|
||||
const mod = (await viteServer.ssrLoadModule(url)) as { default: SSRLoadedRenderer['ssr'] };
|
||||
return { ...renderer, ssr: mod.default };
|
||||
}
|
||||
|
||||
export async function loadRenderers(viteServer: vite.ViteDevServer, astroConfig: AstroConfig): Promise<SSRLoadedRenderer[]> {
|
||||
return Promise.all(astroConfig._ctx.renderers.map((r) => loadRenderer(viteServer, r)));
|
||||
}
|
||||
|
||||
export async function preload({ astroConfig, filePath, viteServer }: Pick<SSROptions, 'astroConfig' | 'filePath' | 'viteServer'>): Promise<ComponentPreload> {
|
||||
// Important: This needs to happen first, in case a renderer provides polyfills.
|
||||
const renderers = await resolveRenderers(viteServer, astroConfig);
|
||||
const renderers = await loadRenderers(viteServer, astroConfig);
|
||||
// Load the module from the Vite SSR Runtime.
|
||||
const mod = (await viteServer.ssrLoadModule(fileURLToPath(filePath))) as ComponentInstance;
|
||||
|
||||
|
@ -55,7 +61,7 @@ export async function preload({ astroConfig, filePath, viteServer }: Pick<SSROpt
|
|||
}
|
||||
|
||||
/** use Vite to SSR */
|
||||
export async function render(renderers: Renderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<RenderResponse> {
|
||||
export async function render(renderers: SSRLoadedRenderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<RenderResponse> {
|
||||
const { astroConfig, filePath, logging, mode, origin, pathname, method, headers, route, routeCache, viteServer } = ssrOpts;
|
||||
const legacy = astroConfig.buildOptions.legacyBuild;
|
||||
|
||||
|
@ -69,10 +75,19 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
|
|||
children: '',
|
||||
});
|
||||
scripts.add({
|
||||
props: { type: 'module', src: new URL('../../../runtime/client/hmr.js', import.meta.url).pathname },
|
||||
props: { type: 'module', src: '/@id/astro/client/hmr.js' },
|
||||
children: '',
|
||||
});
|
||||
}
|
||||
// TODO: We should allow adding generic HTML elements to the head, not just scripts
|
||||
for (const script of astroConfig._ctx.scripts) {
|
||||
if (script.stage === 'head-inline') {
|
||||
scripts.add({
|
||||
props: {},
|
||||
children: script.content,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Pass framework CSS in as link tags to be appended to the page.
|
||||
let links = new Set<SSRElement>();
|
||||
|
@ -105,13 +120,22 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
|
|||
origin,
|
||||
pathname,
|
||||
scripts,
|
||||
// Resolves specifiers in the inline hydrated scripts, such as "@astrojs/renderer-preact/client.js"
|
||||
// Resolves specifiers in the inline hydrated scripts, such as "@astrojs/preact/client.js"
|
||||
// TODO: Can we pass the hydration code more directly through Vite, so that we
|
||||
// don't need to copy-paste and maintain Vite's import resolution here?
|
||||
async resolve(s: string) {
|
||||
// The legacy build needs these to remain unresolved so that vite HTML
|
||||
// Can do the resolution. Without this condition the build output will be
|
||||
// broken in the legacy build. This can be removed once the legacy build is removed.
|
||||
if (!astroConfig.buildOptions.legacyBuild) {
|
||||
const [, resolvedPath] = await viteServer.moduleGraph.resolveUrl(s);
|
||||
const [resolvedUrl, resolvedPath] = await viteServer.moduleGraph.resolveUrl(s);
|
||||
if (resolvedPath.includes('node_modules/.vite')) {
|
||||
return resolvedPath.replace(/.*?node_modules\/\.vite/, '/node_modules/.vite');
|
||||
}
|
||||
// NOTE: This matches the same logic that Vite uses to add the `/@id/` prefix.
|
||||
if (!resolvedUrl.startsWith('.') && !resolvedUrl.startsWith('/')) {
|
||||
return '/@id' + prependForwardSlash(resolvedUrl);
|
||||
}
|
||||
return '/@fs' + prependForwardSlash(resolvedPath);
|
||||
} else {
|
||||
return s;
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
import type * as vite from 'vite';
|
||||
import type { AstroConfig, Renderer } from '../../../@types/astro';
|
||||
|
||||
import { resolveDependency } from '../../util.js';
|
||||
import { createRenderer } from '../renderer.js';
|
||||
|
||||
const cache = new Map<string, Promise<Renderer>>();
|
||||
|
||||
async function resolveRenderer(viteServer: vite.ViteDevServer, renderer: string, astroConfig: AstroConfig): Promise<Renderer> {
|
||||
const resolvedRenderer: Renderer = await createRenderer(renderer, {
|
||||
renderer(name) {
|
||||
return import(resolveDependency(name, astroConfig));
|
||||
},
|
||||
async server(entry) {
|
||||
const { url } = await viteServer.moduleGraph.ensureEntryFromUrl(entry);
|
||||
const mod = await viteServer.ssrLoadModule(url);
|
||||
return mod;
|
||||
},
|
||||
});
|
||||
|
||||
return resolvedRenderer;
|
||||
}
|
||||
|
||||
export async function resolveRenderers(viteServer: vite.ViteDevServer, astroConfig: AstroConfig): Promise<Renderer[]> {
|
||||
const ids: string[] = astroConfig.renderers;
|
||||
const renderers = await Promise.all(
|
||||
ids.map((renderer) => {
|
||||
if (cache.has(renderer)) return cache.get(renderer)!;
|
||||
let promise = resolveRenderer(viteServer, renderer, astroConfig);
|
||||
cache.set(renderer, promise);
|
||||
return promise;
|
||||
})
|
||||
);
|
||||
|
||||
return renderers;
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
import type { Renderer } from '../../@types/astro';
|
||||
|
||||
import npath from 'path';
|
||||
|
||||
interface RendererResolverImplementation {
|
||||
renderer: (name: string) => Promise<any>;
|
||||
server: (entry: string) => Promise<any>;
|
||||
}
|
||||
|
||||
export async function createRenderer(renderer: string, impl: RendererResolverImplementation) {
|
||||
const resolvedRenderer: any = {};
|
||||
// We can dynamically import the renderer by itself because it shouldn't have
|
||||
// any non-standard imports, the index is just meta info.
|
||||
// The other entrypoints need to be loaded through Vite.
|
||||
const {
|
||||
default: { name, client, polyfills, hydrationPolyfills, server },
|
||||
} = await impl.renderer(renderer); //await import(resolveDependency(renderer, astroConfig));
|
||||
|
||||
resolvedRenderer.name = name;
|
||||
if (client) resolvedRenderer.source = npath.posix.join(renderer, client);
|
||||
resolvedRenderer.serverEntry = npath.posix.join(renderer, server);
|
||||
if (Array.isArray(hydrationPolyfills)) resolvedRenderer.hydrationPolyfills = hydrationPolyfills.map((src: string) => npath.posix.join(renderer, src));
|
||||
if (Array.isArray(polyfills)) resolvedRenderer.polyfills = polyfills.map((src: string) => npath.posix.join(renderer, src));
|
||||
|
||||
const { default: rendererSSR } = await impl.server(resolvedRenderer.serverEntry);
|
||||
resolvedRenderer.ssr = rendererSSR;
|
||||
|
||||
const completedRenderer: Renderer = resolvedRenderer;
|
||||
return completedRenderer;
|
||||
}
|
|
@ -1,12 +1,10 @@
|
|||
import type { AstroGlobal, AstroGlobalPartial, MarkdownParser, MarkdownRenderOptions, Params, Renderer, SSRElement, SSRResult } from '../../@types/astro';
|
||||
import type { AstroRequest } from './request';
|
||||
|
||||
import { bold } from 'kleur/colors';
|
||||
import { createRequest } from './request.js';
|
||||
import { isCSSRequest } from './dev/css.js';
|
||||
import { isScriptRequest } from './script.js';
|
||||
import type { AstroGlobal, AstroGlobalPartial, MarkdownParser, MarkdownRenderOptions, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
|
||||
import { renderSlot } from '../../runtime/server/index.js';
|
||||
import { warn, LogOptions } from '../logger.js';
|
||||
import { LogOptions, warn } from '../logger.js';
|
||||
import { isCSSRequest } from './dev/css.js';
|
||||
import { createRequest } from './request.js';
|
||||
import { isScriptRequest } from './script.js';
|
||||
|
||||
function onlyAvailableInSSR(name: string) {
|
||||
return function () {
|
||||
|
@ -23,7 +21,7 @@ export interface CreateResultArgs {
|
|||
markdownRender: MarkdownRenderOptions;
|
||||
params: Params;
|
||||
pathname: string;
|
||||
renderers: Renderer[];
|
||||
renderers: SSRLoadedRenderer[];
|
||||
resolve: (s: string) => Promise<string>;
|
||||
site: string | undefined;
|
||||
links?: Set<SSRElement>;
|
||||
|
|
|
@ -25,6 +25,16 @@ export function isValidURL(url: string): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
/** Returns true if argument is an object of any prototype/class (but not null). */
|
||||
export function isObject(value: unknown): value is Record<string, any> {
|
||||
return typeof value === 'object' && value != null;
|
||||
}
|
||||
|
||||
/** Wraps an object in an array. If an array is passed, ignore it. */
|
||||
export function arraify<T>(target: T | T[]): T[] {
|
||||
return Array.isArray(target) ? target : [target];
|
||||
}
|
||||
|
||||
/** is a specifier an npm package? */
|
||||
export function parseNpmName(spec: string): { scope?: string; name: string; subpath?: string } | undefined {
|
||||
// not an npm package
|
||||
|
@ -98,6 +108,14 @@ export function viteID(filePath: URL): string {
|
|||
return slash(fileURLToPath(filePath));
|
||||
}
|
||||
|
||||
export const VALID_ID_PREFIX = `/@id/`;
|
||||
|
||||
// Strip valid id prefix. This is prepended to resolved Ids that are
|
||||
// not valid browser import specifiers by the importAnalysis plugin.
|
||||
export function unwrapId(id: string): string {
|
||||
return id.startsWith(VALID_ID_PREFIX) ? id.slice(VALID_ID_PREFIX.length) : id;
|
||||
}
|
||||
|
||||
/** An fs utility, similar to `rimraf` or `rm -rf` */
|
||||
export function removeDir(_dir: URL): void {
|
||||
const dir = fileURLToPath(_dir);
|
||||
|
|
76
packages/astro/src/integrations/index.ts
Normal file
76
packages/astro/src/integrations/index.ts
Normal file
|
@ -0,0 +1,76 @@
|
|||
import type { AddressInfo } from 'net';
|
||||
import type { ViteDevServer } from 'vite';
|
||||
import { AstroConfig, AstroRenderer } from '../@types/astro.js';
|
||||
import { mergeConfig } from '../core/config.js';
|
||||
|
||||
export async function runHookConfigSetup({ config: _config, command }: { config: AstroConfig; command: 'dev' | 'build' }): Promise<AstroConfig> {
|
||||
let updatedConfig: AstroConfig = { ..._config };
|
||||
for (const integration of _config.integrations) {
|
||||
if (integration.hooks['astro:config:setup']) {
|
||||
await integration.hooks['astro:config:setup']({
|
||||
config: updatedConfig,
|
||||
command,
|
||||
addRenderer(renderer: AstroRenderer) {
|
||||
updatedConfig._ctx.renderers.push(renderer);
|
||||
},
|
||||
injectScript: (stage, content) => {
|
||||
updatedConfig._ctx.scripts.push({ stage, content });
|
||||
},
|
||||
updateConfig: (newConfig) => {
|
||||
updatedConfig = mergeConfig(updatedConfig, newConfig) as AstroConfig;
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return updatedConfig;
|
||||
}
|
||||
|
||||
export async function runHookConfigDone({ config }: { config: AstroConfig }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:config:done']) {
|
||||
await integration.hooks['astro:config:done']({
|
||||
config,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function runHookServerSetup({ config, server }: { config: AstroConfig; server: ViteDevServer }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:server:setup']) {
|
||||
await integration.hooks['astro:server:setup']({ server });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function runHookServerStart({ config, address }: { config: AstroConfig; address: AddressInfo }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:server:start']) {
|
||||
await integration.hooks['astro:server:start']({ address });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function runHookServerDone({ config }: { config: AstroConfig }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:server:done']) {
|
||||
await integration.hooks['astro:server:done']();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function runHookBuildStart({ config }: { config: AstroConfig }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:build:start']) {
|
||||
await integration.hooks['astro:build:start']();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function runHookBuildDone({ config, pages }: { config: AstroConfig; pages: string[] }) {
|
||||
for (const integration of config.integrations) {
|
||||
if (integration.hooks['astro:build:done']) {
|
||||
await integration.hooks['astro:build:done']({ pages: pages.map((p) => ({ pathname: p })), dir: config.dist });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import type { AstroComponentMetadata } from '../../@types/astro';
|
||||
import type { AstroComponentMetadata, SSRLoadedRenderer } from '../../@types/astro';
|
||||
import type { SSRElement, SSRResult } from '../../@types/astro';
|
||||
import { hydrationSpecifier, serializeListValue } from './util.js';
|
||||
import serializeJavaScript from 'serialize-javascript';
|
||||
|
@ -81,7 +81,7 @@ export function extractDirectives(inputProps: Record<string | number, any>): Ext
|
|||
}
|
||||
|
||||
interface HydrateScriptOptions {
|
||||
renderer: any;
|
||||
renderer: SSRLoadedRenderer;
|
||||
result: SSRResult;
|
||||
astroId: string;
|
||||
props: Record<string | number, any>;
|
||||
|
@ -96,16 +96,11 @@ export async function generateHydrateScript(scriptOptions: HydrateScriptOptions,
|
|||
throw new Error(`Unable to resolve a componentExport for "${metadata.displayName}"! Please open an issue.`);
|
||||
}
|
||||
|
||||
let hydrationSource = '';
|
||||
if (renderer.hydrationPolyfills) {
|
||||
hydrationSource += `await Promise.all([${(await Promise.all(renderer.hydrationPolyfills.map(async (src: string) => `\n import("${await result.resolve(src)}")`))).join(
|
||||
', '
|
||||
)}]);\n`;
|
||||
}
|
||||
let hydrationSource = ``;
|
||||
|
||||
hydrationSource += renderer.source
|
||||
hydrationSource += renderer.clientEntrypoint
|
||||
? `const [{ ${componentExport.value}: Component }, { default: hydrate }] = await Promise.all([import("${await result.resolve(componentUrl)}"), import("${await result.resolve(
|
||||
renderer.source
|
||||
renderer.clientEntrypoint
|
||||
)}")]);
|
||||
return (el, children) => hydrate(el)(Component, ${serializeProps(props)}, children);
|
||||
`
|
||||
|
@ -116,6 +111,7 @@ export async function generateHydrateScript(scriptOptions: HydrateScriptOptions,
|
|||
const hydrationScript = {
|
||||
props: { type: 'module', 'data-astro-component-hydration': true },
|
||||
children: `import setup from '${await result.resolve(hydrationSpecifier(hydrate))}';
|
||||
${`import '${await result.resolve('astro:scripts/before-hydration.js')}';`}
|
||||
setup("${astroId}", {name:"${metadata.displayName}",${metadata.hydrateArgs ? `value: ${JSON.stringify(metadata.hydrateArgs)}` : ''}}, async () => {
|
||||
${hydrationSource}
|
||||
});
|
||||
|
|
|
@ -1,19 +1,14 @@
|
|||
import type { AstroComponentMetadata, EndpointHandler, Renderer, Params } from '../../@types/astro';
|
||||
import type { AstroGlobalPartial, SSRResult, SSRElement } from '../../@types/astro';
|
||||
import type { AstroRequest } from '../../core/render/request';
|
||||
|
||||
import shorthash from 'shorthash';
|
||||
import type { AstroComponentMetadata, AstroGlobalPartial, EndpointHandler, Params, SSRElement, SSRLoadedRenderer, SSRResult } from '../../@types/astro';
|
||||
import type { AstroRequest } from '../../core/render/request';
|
||||
import { escapeHTML, HTMLString, markHTMLString } from './escape.js';
|
||||
import { extractDirectives, generateHydrateScript, serializeProps } from './hydration.js';
|
||||
import { serializeListValue } from './util.js';
|
||||
import { escapeHTML, HTMLString, markHTMLString } from './escape.js';
|
||||
|
||||
export { markHTMLString, markHTMLString as unescapeHTML } from './escape.js';
|
||||
export type { Metadata } from './metadata';
|
||||
export { createMetadata } from './metadata.js';
|
||||
|
||||
export { markHTMLString } from './escape.js';
|
||||
// TODO(deprecated): This name has been updated in Astro runtime but not yet in the Astro compiler.
|
||||
export { markHTMLString as unescapeHTML } from './escape.js';
|
||||
|
||||
const voidElementNames = /^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
|
||||
const htmlBooleanAttributes =
|
||||
/^(allowfullscreen|async|autofocus|autoplay|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|itemscope)$/i;
|
||||
|
@ -116,14 +111,14 @@ function guessRenderers(componentUrl?: string): string[] {
|
|||
const extname = componentUrl?.split('.').pop();
|
||||
switch (extname) {
|
||||
case 'svelte':
|
||||
return ['@astrojs/renderer-svelte'];
|
||||
return ['@astrojs/svelte'];
|
||||
case 'vue':
|
||||
return ['@astrojs/renderer-vue'];
|
||||
return ['@astrojs/vue'];
|
||||
case 'jsx':
|
||||
case 'tsx':
|
||||
return ['@astrojs/renderer-react', '@astrojs/renderer-preact'];
|
||||
return ['@astrojs/react', '@astrojs/preact'];
|
||||
default:
|
||||
return ['@astrojs/renderer-react', '@astrojs/renderer-preact', '@astrojs/renderer-vue', '@astrojs/renderer-svelte'];
|
||||
return ['@astrojs/react', '@astrojs/preact', '@astrojs/vue', '@astrojs/svelte'];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -171,13 +166,13 @@ export async function renderComponent(result: SSRResult, displayName: string, Co
|
|||
if (Array.isArray(renderers) && renderers.length === 0 && typeof Component !== 'string' && !componentIsHTMLElement(Component)) {
|
||||
const message = `Unable to render ${metadata.displayName}!
|
||||
|
||||
There are no \`renderers\` set in your \`astro.config.mjs\` file.
|
||||
Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '`'))}?`;
|
||||
There are no \`integrations\` set in your \`astro.config.mjs\` file.
|
||||
Did you mean to add ${formatList(probableRendererNames.map((r) => '`' + r + '`'))}?`;
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
// Call the renderers `check` hook to see if any claim this component.
|
||||
let renderer: Renderer | undefined;
|
||||
let renderer: SSRLoadedRenderer | undefined;
|
||||
if (metadata.hydrate !== 'only') {
|
||||
for (const r of renderers) {
|
||||
if (await r.ssr.check(Component, props, children)) {
|
||||
|
@ -195,7 +190,7 @@ Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '
|
|||
// Attempt: use explicitly passed renderer name
|
||||
if (metadata.hydrateArgs) {
|
||||
const rendererName = metadata.hydrateArgs;
|
||||
renderer = renderers.filter(({ name }) => name === `@astrojs/renderer-${rendererName}` || name === rendererName)[0];
|
||||
renderer = renderers.filter(({ name }) => name === `@astrojs/${rendererName}` || name === rendererName)[0];
|
||||
}
|
||||
// Attempt: user only has a single renderer, default to that
|
||||
if (!renderer && renderers.length === 1) {
|
||||
|
@ -204,7 +199,7 @@ Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '
|
|||
// Attempt: can we guess the renderer from the export extension?
|
||||
if (!renderer) {
|
||||
const extname = metadata.componentUrl?.split('.').pop();
|
||||
renderer = renderers.filter(({ name }) => name === `@astrojs/renderer-${extname}` || name === extname)[0];
|
||||
renderer = renderers.filter(({ name }) => name === `@astrojs/${extname}` || name === extname)[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,7 +210,7 @@ Did you mean to enable ${formatList(probableRendererNames.map((r) => '`' + r + '
|
|||
throw new Error(`Unable to render ${metadata.displayName}!
|
||||
|
||||
Using the \`client:only\` hydration strategy, Astro needs a hint to use the correct renderer.
|
||||
Did you mean to pass <${metadata.displayName} client:only="${probableRendererNames.map((r) => r.replace('@astrojs/renderer-', '')).join('|')}" />
|
||||
Did you mean to pass <${metadata.displayName} client:only="${probableRendererNames.map((r) => r.replace('@astrojs/', '')).join('|')}" />
|
||||
`);
|
||||
} else if (typeof Component !== 'string') {
|
||||
const matchingRenderers = renderers.filter((r) => probableRendererNames.includes(r.name));
|
||||
|
@ -264,16 +259,6 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
|||
);
|
||||
}
|
||||
|
||||
// This is used to add polyfill scripts to the page, if the renderer needs them.
|
||||
if (renderer?.polyfills?.length) {
|
||||
for (const src of renderer.polyfills) {
|
||||
result.scripts.add({
|
||||
props: { type: 'module' },
|
||||
children: `import "${await result.resolve(src)}";`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!hydration) {
|
||||
return markHTMLString(html.replace(/\<\/?astro-fragment\>/g, ''));
|
||||
}
|
||||
|
@ -283,7 +268,7 @@ If you're still stuck, please open an issue on GitHub or join us at https://astr
|
|||
|
||||
// Rather than appending this inline in the page, puts this into the `result.scripts` set that will be appended to the head.
|
||||
// INVESTIGATE: This will likely be a problem in streaming because the `<head>` will be gone at this point.
|
||||
result.scripts.add(await generateHydrateScript({ renderer, result, astroId, props }, metadata as Required<AstroComponentMetadata>));
|
||||
result.scripts.add(await generateHydrateScript({ renderer: renderer!, result, astroId, props }, metadata as Required<AstroComponentMetadata>));
|
||||
|
||||
// Render a template if no fragment is provided.
|
||||
const needsAstroTemplate = children && !/<\/?astro-fragment\>/.test(html);
|
||||
|
|
|
@ -122,13 +122,7 @@ export function invalidateCompilation(config: AstroConfig, filename: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export async function cachedCompilation(
|
||||
config: AstroConfig,
|
||||
filename: string,
|
||||
source: string | null,
|
||||
viteTransform: TransformHook,
|
||||
opts: { ssr: boolean }
|
||||
): Promise<CompileResult> {
|
||||
export async function cachedCompilation(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: { ssr: boolean }): Promise<CompileResult> {
|
||||
let cache: CompilationCache;
|
||||
if (!configCache.has(config)) {
|
||||
cache = new Map();
|
||||
|
@ -139,11 +133,6 @@ export async function cachedCompilation(
|
|||
if (cache.has(filename)) {
|
||||
return cache.get(filename)!;
|
||||
}
|
||||
|
||||
if (source === null) {
|
||||
const fileUrl = new URL(`file://${filename}`);
|
||||
source = await fs.promises.readFile(fileUrl, 'utf-8');
|
||||
}
|
||||
const compileResult = await compile(config, filename, source, viteTransform, opts);
|
||||
cache.set(filename, compileResult);
|
||||
return compileResult;
|
||||
|
|
|
@ -35,7 +35,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
return slash(fileURLToPath(url)) + url.search;
|
||||
}
|
||||
|
||||
let isProduction: boolean;
|
||||
let resolvedConfig: vite.ResolvedConfig;
|
||||
let viteTransform: TransformHook;
|
||||
let viteDevServer: vite.ViteDevServer | null = null;
|
||||
|
||||
|
@ -46,9 +46,9 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
return {
|
||||
name: 'astro:build',
|
||||
enforce: 'pre', // run transforms before other plugins can
|
||||
configResolved(resolvedConfig) {
|
||||
configResolved(_resolvedConfig) {
|
||||
resolvedConfig = _resolvedConfig;
|
||||
viteTransform = getViteTransform(resolvedConfig);
|
||||
isProduction = resolvedConfig.isProduction;
|
||||
},
|
||||
configureServer(server) {
|
||||
viteDevServer = server;
|
||||
|
@ -83,20 +83,26 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
}
|
||||
},
|
||||
async load(id, opts) {
|
||||
let { filename, query } = parseAstroRequest(id);
|
||||
const parsedId = parseAstroRequest(id);
|
||||
const query = parsedId.query;
|
||||
if (!id.endsWith('.astro') && !query.astro) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filename = normalizeFilename(parsedId.filename);
|
||||
const fileUrl = new URL(`file://${filename}`);
|
||||
let source = await fs.promises.readFile(fileUrl, 'utf-8');
|
||||
const isPage = filename.startsWith(config.pages.pathname);
|
||||
if (isPage && config._ctx.scripts.some((s) => s.stage === 'page')) {
|
||||
source += `\n<script hoist src="astro:scripts/page.js" />`;
|
||||
}
|
||||
if (query.astro) {
|
||||
if (query.type === 'style') {
|
||||
if (filename.startsWith('/@fs')) {
|
||||
filename = filename.slice('/@fs'.length);
|
||||
} else if (filename.startsWith('/') && !ancestor(filename, config.projectRoot.pathname)) {
|
||||
filename = new URL('.' + filename, config.projectRoot).pathname;
|
||||
}
|
||||
|
||||
if (typeof query.index === 'undefined') {
|
||||
throw new Error(`Requests for Astro CSS must include an index.`);
|
||||
}
|
||||
|
||||
const transformResult = await cachedCompilation(config, normalizeFilename(filename), null, viteTransform, { ssr: Boolean(opts?.ssr) });
|
||||
const transformResult = await cachedCompilation(config, filename, source, viteTransform, { ssr: Boolean(opts?.ssr) });
|
||||
|
||||
// Track any CSS dependencies so that HMR is triggered when they change.
|
||||
await trackCSSDependencies.call(this, { viteDevServer, id, filename, deps: transformResult.rawCSSDeps });
|
||||
|
@ -111,7 +117,7 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
throw new Error(`Requests for hoisted scripts must include an index`);
|
||||
}
|
||||
|
||||
const transformResult = await cachedCompilation(config, normalizeFilename(filename), null, viteTransform, { ssr: Boolean(opts?.ssr) });
|
||||
const transformResult = await cachedCompilation(config, filename, source, viteTransform, { ssr: Boolean(opts?.ssr) });
|
||||
const scripts = transformResult.scripts;
|
||||
const hoistedScript = scripts[query.index];
|
||||
|
||||
|
@ -125,13 +131,8 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
}
|
||||
}
|
||||
|
||||
if (!id.endsWith('.astro')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const source = await fs.promises.readFile(id, { encoding: 'utf-8' });
|
||||
try {
|
||||
const transformResult = await cachedCompilation(config, id, source, viteTransform, { ssr: Boolean(opts?.ssr) });
|
||||
const transformResult = await cachedCompilation(config, filename, source, viteTransform, { ssr: Boolean(opts?.ssr) });
|
||||
|
||||
// Compile all TypeScript to JavaScript.
|
||||
// Also, catches invalid JS/TS in the compiled output before returning.
|
||||
|
@ -143,9 +144,15 @@ export default function astro({ config, logging }: AstroPluginOptions): vite.Plu
|
|||
define: config.vite.define,
|
||||
});
|
||||
|
||||
// Signal to Vite that we accept HMR updates
|
||||
const SUFFIX = isProduction ? '' : `\nif (import.meta.hot) import.meta.hot.accept((mod) => mod);`;
|
||||
|
||||
let SUFFIX = '';
|
||||
// Add HMR handling in dev mode.
|
||||
if (!resolvedConfig.isProduction) {
|
||||
SUFFIX += `\nif (import.meta.hot) import.meta.hot.accept((mod) => mod);`;
|
||||
}
|
||||
// Add handling to inject scripts into each page JS bundle, if needed.
|
||||
if (isPage) {
|
||||
SUFFIX += `\nimport "astro:scripts/page-ssr.js";`;
|
||||
}
|
||||
return {
|
||||
code: `${code}${SUFFIX}`,
|
||||
map,
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { Plugin as VitePlugin, ResolvedConfig } from 'vite';
|
||||
import { AstroConfig } from '../@types/astro.js';
|
||||
import { runHookServerSetup } from '../integrations/index.js';
|
||||
|
||||
/** Connect Astro integrations into Vite, as needed. */
|
||||
export default function astroIntegrationsContainerPlugin({ config }: { config: AstroConfig }): VitePlugin {
|
||||
return {
|
||||
name: 'astro:integration-container',
|
||||
configureServer(server) {
|
||||
runHookServerSetup({ config, server });
|
||||
},
|
||||
};
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import type { TransformResult } from 'rollup';
|
||||
import type { Plugin, ResolvedConfig } from 'vite';
|
||||
import type { AstroConfig, Renderer } from '../@types/astro';
|
||||
import type { AstroConfig, AstroRenderer } from '../@types/astro';
|
||||
import type { LogOptions } from '../core/logger.js';
|
||||
|
||||
import babel from '@babel/core';
|
||||
|
@ -9,9 +9,9 @@ import * as colors from 'kleur/colors';
|
|||
import * as eslexer from 'es-module-lexer';
|
||||
import path from 'path';
|
||||
import { error } from '../core/logger.js';
|
||||
import { parseNpmName, resolveDependency } from '../core/util.js';
|
||||
import { parseNpmName } from '../core/util.js';
|
||||
|
||||
const JSX_RENDERER_CACHE = new WeakMap<AstroConfig, Map<string, Renderer>>();
|
||||
const JSX_RENDERER_CACHE = new WeakMap<AstroConfig, Map<string, AstroRenderer>>();
|
||||
const JSX_EXTENSIONS = new Set(['.jsx', '.tsx']);
|
||||
const IMPORT_STATEMENTS: Record<string, string> = {
|
||||
react: "import React from 'react'",
|
||||
|
@ -28,24 +28,16 @@ function getEsbuildLoader(fileExt: string): string {
|
|||
return fileExt.substr(1);
|
||||
}
|
||||
|
||||
async function importJSXRenderers(config: AstroConfig): Promise<Map<string, Renderer>> {
|
||||
const renderers = new Map<string, Renderer>();
|
||||
await Promise.all(
|
||||
config.renderers.map((name) => {
|
||||
return import(resolveDependency(name, config)).then(({ default: renderer }) => {
|
||||
if (!renderer.jsxImportSource) return;
|
||||
renderers.set(renderer.jsxImportSource, renderer);
|
||||
});
|
||||
})
|
||||
);
|
||||
return renderers;
|
||||
function collectJSXRenderers(renderers: AstroRenderer[]): Map<string, AstroRenderer> {
|
||||
const renderersWithJSXSupport = renderers.filter((r) => r.jsxImportSource);
|
||||
return new Map(renderersWithJSXSupport.map((r) => [r.jsxImportSource, r] as [string, AstroRenderer]));
|
||||
}
|
||||
|
||||
interface TransformJSXOptions {
|
||||
code: string;
|
||||
id: string;
|
||||
mode: string;
|
||||
renderer: Renderer;
|
||||
renderer: AstroRenderer;
|
||||
ssr: boolean;
|
||||
}
|
||||
|
||||
|
@ -100,13 +92,13 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
|
|||
// load renderers (on first run only)
|
||||
if (!jsxRenderers) {
|
||||
jsxRenderers = new Map();
|
||||
const possibleRenderers = await importJSXRenderers(config);
|
||||
const possibleRenderers = await collectJSXRenderers(config._ctx.renderers);
|
||||
if (possibleRenderers.size === 0) {
|
||||
// note: we have filtered out all non-JSX files, so this error should only show if a JSX file is loaded with no matching renderers
|
||||
throw new Error(
|
||||
`${colors.yellow(
|
||||
id
|
||||
)}\nUnable to resolve a renderer that handles JSX transforms! Please include a \`renderer\` plugin which supports JSX in your \`astro.config.mjs\` file.`
|
||||
)}\nUnable to resolve a JSX renderer! Did you forget to include one? Add a JSX integration like \`@astrojs/react\` to your \`astro.config.mjs\` file.`
|
||||
);
|
||||
}
|
||||
for (const [importSource, renderer] of possibleRenderers) {
|
||||
|
@ -173,7 +165,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
|
|||
const jsxRenderer = jsxRenderers.get(importSource);
|
||||
// if renderer not installed for this JSX source, throw error
|
||||
if (!jsxRenderer) {
|
||||
error(logging, 'renderer', `${colors.yellow(id)} No renderer installed for ${importSource}. Try adding \`@astrojs/renderer-${importSource}\` to your dependencies.`);
|
||||
error(logging, 'renderer', `${colors.yellow(id)} No renderer installed for ${importSource}. Try adding \`@astrojs/${importSource}\` to your project.`);
|
||||
return null;
|
||||
}
|
||||
// downlevel any non-standard syntax, but preserve JSX
|
||||
|
@ -183,7 +175,7 @@ export default function jsx({ config, logging }: AstroPluginJSXOptions): Plugin
|
|||
sourcefile: id,
|
||||
sourcemap: 'inline',
|
||||
});
|
||||
return await transformJSX({ code: jsxCode, id, renderer: jsxRenderers.get(importSource) as Renderer, mode, ssr });
|
||||
return await transformJSX({ code: jsxCode, id, renderer: jsxRenderers.get(importSource) as AstroRenderer, mode, ssr });
|
||||
}
|
||||
|
||||
// if we still can’t tell, throw error
|
||||
|
|
59
packages/astro/src/vite-plugin-scripts/index.ts
Normal file
59
packages/astro/src/vite-plugin-scripts/index.ts
Normal file
|
@ -0,0 +1,59 @@
|
|||
import { Plugin as VitePlugin } from 'vite';
|
||||
import { AstroConfig } from '../@types/astro.js';
|
||||
|
||||
// NOTE: We can't use the virtual "\0" ID convention because we need to
|
||||
// inject these as ESM imports into actual code, where they would not
|
||||
// resolve correctly.
|
||||
const SCRIPT_ID_PREFIX = `astro:scripts/`;
|
||||
const BEFORE_HYDRATION_SCRIPT_ID = `${SCRIPT_ID_PREFIX}before-hydration.js`;
|
||||
const PAGE_SCRIPT_ID = `${SCRIPT_ID_PREFIX}page.js`;
|
||||
const PAGE_SSR_SCRIPT_ID = `${SCRIPT_ID_PREFIX}page-ssr.js`;
|
||||
|
||||
export default function astroScriptsPlugin({ config }: { config: AstroConfig }): VitePlugin {
|
||||
return {
|
||||
name: 'astro:scripts',
|
||||
async resolveId(id) {
|
||||
if (id.startsWith(SCRIPT_ID_PREFIX)) {
|
||||
return id;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
||||
async load(id) {
|
||||
if (id === BEFORE_HYDRATION_SCRIPT_ID) {
|
||||
return config._ctx.scripts
|
||||
.filter((s) => s.stage === 'before-hydration')
|
||||
.map((s) => s.content)
|
||||
.join('\n');
|
||||
}
|
||||
if (id === PAGE_SCRIPT_ID) {
|
||||
return config._ctx.scripts
|
||||
.filter((s) => s.stage === 'page')
|
||||
.map((s) => s.content)
|
||||
.join('\n');
|
||||
}
|
||||
if (id === PAGE_SSR_SCRIPT_ID) {
|
||||
return config._ctx.scripts
|
||||
.filter((s) => s.stage === 'page-ssr')
|
||||
.map((s) => s.content)
|
||||
.join('\n');
|
||||
}
|
||||
return null;
|
||||
},
|
||||
buildStart(options) {
|
||||
// We only want to inject this script if we are building
|
||||
// for the frontend AND some hydrated components exist in
|
||||
// the final build. We can detect this by looking for a
|
||||
// `astro/client/*` input, which signifies both conditions are met.
|
||||
const hasHydratedComponents = Array.isArray(options.input) && options.input.some((input) => input.startsWith('astro/client'));
|
||||
const hasHydrationScripts = config._ctx.scripts.some((s) => s.stage === 'before-hydration');
|
||||
if (hasHydratedComponents && hasHydrationScripts) {
|
||||
this.emitFile({
|
||||
type: 'chunk',
|
||||
id: BEFORE_HYDRATION_SCRIPT_ID,
|
||||
name: BEFORE_HYDRATION_SCRIPT_ID,
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
|
@ -12,15 +12,7 @@ let fixture;
|
|||
|
||||
describe('CSS', function () {
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/0-css/',
|
||||
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
|
||||
vite: {
|
||||
build: {
|
||||
assetsInlineLimit: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/0-css/' });
|
||||
});
|
||||
|
||||
// test HTML and CSS contents for accuracy
|
||||
|
|
|
@ -13,11 +13,6 @@ describe('Assets', () => {
|
|||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/astro-assets/',
|
||||
vite: {
|
||||
build: {
|
||||
assetsInlineLimit: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
|
|
@ -6,10 +6,7 @@ describe('Component children', () => {
|
|||
let fixture;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/astro-children/',
|
||||
renderers: ['@astrojs/renderer-preact', '@astrojs/renderer-vue', '@astrojs/renderer-svelte'],
|
||||
});
|
||||
fixture = await loadFixture({ projectRoot: './fixtures/astro-children/' });
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
|
|
|
@ -32,24 +32,11 @@ describe('Dynamic components', () => {
|
|||
const html = await fixture.readFile('/client-only/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
// test 1: <astro-root> is empty
|
||||
// test 1: <astro-root> is empty.
|
||||
expect($('<astro-root>').html()).to.equal('');
|
||||
const script = $('script').text();
|
||||
|
||||
// Grab the svelte import
|
||||
// const exp = /import\("(.+?)"\)/g;
|
||||
// let match, svelteRenderer;
|
||||
// while ((match = exp.exec(result.contents))) {
|
||||
// if (match[1].includes('renderers/renderer-svelte/client.js')) {
|
||||
// svelteRenderer = match[1];
|
||||
// }
|
||||
// }
|
||||
|
||||
// test 2: Svelte renderer is on the page
|
||||
// expect(svelteRenderer).to.be.ok;
|
||||
|
||||
// test 3: Can load svelte renderer
|
||||
// const result = await fixture.fetch(svelteRenderer);
|
||||
// expect(result.status).to.equal(200);
|
||||
// test 2: correct script is being loaded.
|
||||
// because of bundling, we don't have access to the source import,
|
||||
// only the bundled import.
|
||||
expect($('script').html()).to.include(`import setup from '../only`);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,6 @@ describe('Expressions', () => {
|
|||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/astro-expr/',
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
|
|
@ -8,7 +8,6 @@ describe('Dynamic component fallback', () => {
|
|||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/astro-fallback',
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
import { expect } from 'chai';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('JSX', () => {
|
||||
let cwd = './fixtures/astro-jsx/';
|
||||
let orders = [
|
||||
['preact', 'react', 'solid'],
|
||||
['preact', 'solid', 'react'],
|
||||
['react', 'preact', 'solid'],
|
||||
['react', 'solid', 'preact'],
|
||||
['solid', 'react', 'preact'],
|
||||
['solid', 'preact', 'react'],
|
||||
];
|
||||
let fixtures = {};
|
||||
|
||||
before(async () => {
|
||||
await Promise.all(
|
||||
orders.map((renderers, n) =>
|
||||
loadFixture({
|
||||
projectRoot: cwd,
|
||||
renderers: renderers.map((name) => `@astrojs/renderer-${name}`),
|
||||
dist: new URL(`${cwd}dist-${n}/`, import.meta.url),
|
||||
}).then((fixture) => {
|
||||
fixtures[renderers.toString()] = fixture;
|
||||
return fixture.build();
|
||||
})
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('Renderer order', () => {
|
||||
it('JSX renderers can be defined in any order', async () => {
|
||||
if (!Object.values(fixtures).length) {
|
||||
throw new Error(`JSX renderers didn’t build properly`);
|
||||
}
|
||||
|
||||
for (const [name, fixture] of Object.entries(fixtures)) {
|
||||
const html = await fixture.readFile('/index.html');
|
||||
expect(html, name).to.be.ok;
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -10,7 +10,6 @@ describe('Astro Markdown plugins', () => {
|
|||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/astro-markdown-plugins/',
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
markdownOptions: {
|
||||
render: [
|
||||
markdownRemark,
|
||||
|
|
|
@ -8,10 +8,6 @@ describe('Astro Markdown', () => {
|
|||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
projectRoot: './fixtures/astro-markdown/',
|
||||
renderers: ['@astrojs/renderer-preact'],
|
||||
buildOptions: {
|
||||
sitemap: false,
|
||||
},
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
|
|
@ -2,7 +2,7 @@ import { expect } from 'chai';
|
|||
import cheerio from 'cheerio';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
|
||||
describe('Partial HTML ', async () => {
|
||||
describe('Partial HTML', async () => {
|
||||
let fixture;
|
||||
let devServer;
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ describe('Config Validation', () => {
|
|||
|
||||
it('Multiple validation errors can be formatted correctly', async () => {
|
||||
const veryBadConfig = {
|
||||
renderers: [42],
|
||||
integrations: [42],
|
||||
buildOptions: { pageUrlFormat: 'invalid' },
|
||||
pages: {},
|
||||
};
|
||||
|
@ -41,8 +41,34 @@ describe('Config Validation', () => {
|
|||
expect(formattedError).to.equal(
|
||||
`[config] Astro found issue(s) with your configuration:
|
||||
! pages Expected string, received object.
|
||||
! renderers.0 Expected string, received number.
|
||||
! integrations.0 Expected object, received number.
|
||||
! buildOptions.pageUrlFormat Invalid input.`
|
||||
);
|
||||
});
|
||||
|
||||
it('ignores falsey "integration" values', async () => {
|
||||
const result = await validateConfig({ integrations: [0, false, null, undefined] }, process.cwd());
|
||||
expect(result.integrations).to.deep.equal([]);
|
||||
});
|
||||
it('normalizes "integration" values', async () => {
|
||||
const result = await validateConfig({ integrations: [{ name: '@astrojs/a' }] }, process.cwd());
|
||||
expect(result.integrations).to.deep.equal([{ name: '@astrojs/a', hooks: {} }]);
|
||||
});
|
||||
it('flattens array "integration" values', async () => {
|
||||
const result = await validateConfig({ integrations: [{ name: '@astrojs/a' }, [{ name: '@astrojs/b' }, { name: '@astrojs/c' }]] }, process.cwd());
|
||||
expect(result.integrations).to.deep.equal([
|
||||
{ name: '@astrojs/a', hooks: {} },
|
||||
{ name: '@astrojs/b', hooks: {} },
|
||||
{ name: '@astrojs/c', hooks: {} },
|
||||
]);
|
||||
});
|
||||
it('blocks third-party "integration" values', async () => {
|
||||
const configError = await validateConfig({ integrations: [{ name: '@my-plugin/a' }] }, process.cwd()).catch((err) => err);
|
||||
expect(configError instanceof z.ZodError).to.equal(true);
|
||||
const formattedError = stripAnsi(formatConfigError(configError));
|
||||
expect(formattedError).to.equal(
|
||||
`[config] Astro found issue(s) with your configuration:
|
||||
! integrations Astro integrations are still experimental, and only official integrations are currently supported.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,9 +10,24 @@ describe('config', () => {
|
|||
|
||||
before(async () => {
|
||||
[hostnameFixture, hostFixture, portFixture] = await Promise.all([
|
||||
loadFixture({ projectRoot: './fixtures/config-hostname/' }),
|
||||
loadFixture({ projectRoot: './fixtures/config-host/' }),
|
||||
loadFixture({ projectRoot: './fixtures/config-port/' }),
|
||||
loadFixture({
|
||||
projectRoot: './fixtures/config-host/',
|
||||
devOptions: {
|
||||
hostname: '0.0.0.0',
|
||||
},
|
||||
}),
|
||||
loadFixture({
|
||||
projectRoot: './fixtures/config-host/',
|
||||
devOptions: {
|
||||
host: true,
|
||||
},
|
||||
}),
|
||||
loadFixture({
|
||||
projectRoot: './fixtures/config-host/',
|
||||
devOptions: {
|
||||
port: 5006,
|
||||
},
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue