merge next into main (#2705)

* Unflag the static build (#2652)

* Unflag the static build

* Only set legacyBuild to false if experimentalSSR is true

* Use legacy build when we have to

* Put a few more tests into legacy mode

* Last two

* Make astro-basic use the legacy build

* Adds a changeset

* Mark the lit test as legacy

* Update yarn lock

* Update based on feedback

* Add --legacy-build flag

* Move astro-basic test to use static build (#2682)

* Move some tests over to the static build (#2677)

* Move some tests over to the static build

* Fix assets tests

* Fix the assets tests

* Fix for the client:only components

* Moves asset tests to the static build

* Move postcss test over to static build

* Bring back legacy build for astro-basic test

* Move astro-basic test to use static build

* Migrate more tests to the static build (#2693)

* fix: disable HMR during build (#2684)

* Migrate more tests to the static build

* Only prepend links in non-legacy mode

* Add the 0-css tests

* Convert all CSS tests to the static build

* Migrate Astro global tests

* Remove .only

* Fix static build tests

* Migrate a few more

* More tests

* Move the lit test back to legacy

* Increase the test timeout

Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>

* Improve `head` injection behavior (#2436)

* feat: add renderHead util to server

* feat: remove `layouts` from config, Vite plugin

* fix: improve head injection during rendering

* chore: update compiler

* fix: do not escape links

* chore: enter `pre` mode

* Replace `send` with `sirv` (#2713)

* remove send

* Create thick-ravens-chew.md

* I feel like I'm going to screw something up

* working finally!

* rewrite req.url

* Add tiny bit of doc

* Update .gitignore

Co-authored-by: Evan Boehs <evan@boehs.org>

* Move remaining tests to the static build (#2712)

* Move lit test to the static build

* Migrate astro-env plugin to work in the static build

* Do not remove vite:define

* Adds a changeset

* Add a warning when passing the --experimental-static-build flag (#2718)

* Add a warning when passing the --experimental-static-build flag

* Disable the lint warning

* [ci] release (next) (#2721)

* [ci] release (next)

* chore: update changeset

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Nate Moore <nate@skypack.dev>

* 404 page (#2719)

* Fix: build to 404.html in the static build

* Adds a changeset

* fix pnpm install missing peer deps

* fix svelte version in workspace

* fix lockfile

* fix(webapi): add dev script

* improve preview reliability (#2739)

* improve preview reliability - fix broken tests

* shamefully hoist to unblock

* remove lit from test running

* chore: update lockfile

Co-authored-by: Matthew Phillips <matthew@skypack.dev>
Co-authored-by: Evan Boehs <evan@boehs.org>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Fred K. Schott <fkschott@gmail.com>
This commit is contained in:
Nate Moore 2022-03-09 15:44:26 -06:00 committed by GitHub
parent c139829b10
commit 72c2c86e9d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
130 changed files with 2439 additions and 2455 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes the static build to write to 404.html

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes use of private .env variables with the static build

View file

@ -0,0 +1,19 @@
---
'astro': minor
---
New default build strategy
This change marks the "static build" as the new default build strategy. If you are unfamiliar with this build strategy check out the [migration guide](https://docs.astro.build/en/migrate/#planned-deprecations) on how to change your code to be compatible with this new bulid strategy.
If you'd like to keep using the old build strategy, use the flag `--legacy-build` both in your `astro dev` and `astro build` scripts, for ex:
```json
{
"scripts": {
"build": "astro build --legacy-build"
}
}
```
Note that the legacy build *is* deprecated and will be removed in a future version. You should only use this flag until you have the time to migration your code.

View file

@ -0,0 +1,7 @@
---
'astro': minor
---
## Updated `<head>` and `<body>` behavior
Since `astro@0.21`, Astro placed certain restrictions on what files could use `<head>` or `<body>` tags. In `astro@0.24`, the restrictions have been lifted. Astro will be able to correctly handle `<head>` and `<body>` tags in _any_ component, not just those in `src/pages/` or `src/layouts/`.

57
.changeset/pre.json Normal file
View file

@ -0,0 +1,57 @@
{
"mode": "pre",
"tag": "next",
"initialVersions": {
"@example/blog": "0.0.1",
"@example/blog-multiple-authors": "0.0.1",
"@example/component": "0.0.1",
"@example/my-component-demo": "0.0.1",
"@example/my-component": "0.0.1",
"@example/docs": "0.0.1",
"@example/env-vars": "0.0.1",
"@example/fast-build": "0.0.1",
"@example/framework-alpine": "0.0.1",
"@example/framework-lit": "0.0.1",
"@example/framework-multiple": "0.0.1",
"@example/framework-preact": "0.0.1",
"@example/framework-react": "0.0.1",
"@example/framework-solid": "0.0.1",
"@example/framework-svelte": "0.0.1",
"@example/framework-vue": "0.0.1",
"@example/minimal": "0.0.1",
"@example/non-html-pages": "0.0.1",
"@example/portfolio": "0.0.1",
"@example/portfolio-svelte": "0.0.1",
"@example/ssr": "0.0.1",
"@example/starter": "0.0.1",
"@example/subpath": "0.0.1",
"@example/with-markdown": "0.0.1",
"@example/with-markdown-plugins": "0.0.2",
"@example/with-markdown-shiki": "0.0.1",
"@example/with-nanostores": "0.0.1",
"@example/with-tailwindcss": "0.0.1",
"@example/with-vite-plugin-pwa": "0.0.1",
"astro": "0.23.3",
"@astrojs/parser": "0.22.1",
"@astrojs/prism": "0.4.0",
"@astrojs/test-custom-element-renderer": "0.1.0",
"@astrojs/test-static-build-pkg": "0.0.3",
"create-astro": "0.7.1",
"@astrojs/markdown-remark": "0.6.2",
"@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.4.0",
"@astrojs/renderer-vue": "0.4.0",
"astro-scripts": "0.0.1",
"astro.build": "0.0.1",
"docs": "0.0.7"
},
"changesets": [
"fresh-ladybugs-think",
"modern-elephants-burn",
"new-pianos-boil",
"thick-ravens-chew"
]
}

View file

@ -0,0 +1,5 @@
---
"astro": patch
---
Replace `send` dependency with `sirv`

32
.npmrc
View file

@ -3,19 +3,19 @@ prefer-workspace-packages=true
link-workspace-packages=true link-workspace-packages=true
save-workspace-protocol=false # This prevents the examples to have the `workspace:` prefix save-workspace-protocol=false # This prevents the examples to have the `workspace:` prefix
use-node-version=14.19.0 shamefully-hoist=true
# TODO: We would like to move to individual opt-in hoisting, but Astro was not originally
# Rather than shamefully hoisting everything, just make problematic packages public # written with this in mind. In the future, it would be good to hoist individual packages only.
public-hoist-pattern[]=autoprefixer # public-hoist-pattern[]=autoprefixer
public-hoist-pattern[]=astro # public-hoist-pattern[]=astro
public-hoist-pattern[]=remark-* # public-hoist-pattern[]=remark-*
public-hoist-pattern[]=rehype-* # public-hoist-pattern[]=rehype-*
public-hoist-pattern[]=react # public-hoist-pattern[]=react
public-hoist-pattern[]=react-dom # public-hoist-pattern[]=react-dom
public-hoist-pattern[]=preact # public-hoist-pattern[]=preact
public-hoist-pattern[]=preact-render-to-string # public-hoist-pattern[]=preact-render-to-string
public-hoist-pattern[]=vue # public-hoist-pattern[]=vue
public-hoist-pattern[]=svelte # public-hoist-pattern[]=svelte
public-hoist-pattern[]=solid-js # public-hoist-pattern[]=solid-js
public-hoist-pattern[]=lit # public-hoist-pattern[]=lit
public-hoist-pattern[]=@webcomponents/template-shadowroot # public-hoist-pattern[]=@webcomponents/template-shadowroot

View file

View file

@ -10,7 +10,7 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"astro": "^0.23.7", "astro": "^0.24.0-next.0",
"sass": "^1.49.8" "sass": "^1.49.8"
} }
} }

View file

@ -9,7 +9,7 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7", "astro": "^0.24.0-next.0",
"@astrojs/renderer-preact": "^0.5.0" "@astrojs/renderer-preact": "^0.5.0"
} }
} }

View file

@ -9,7 +9,7 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7", "@example/my-component": "workspace:*",
"@example/my-component": "workspace:*" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -8,6 +8,6 @@
"serve": "astro --project-root demo preview" "serve": "astro --project-root demo preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -9,12 +9,16 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"dependencies": { "dependencies": {
"@algolia/client-search": "^4.12.0",
"@docsearch/css": "^3.0.0", "@docsearch/css": "^3.0.0",
"@docsearch/react": "^1.0.0-alpha.28" "@docsearch/react": "^3.0.0",
"@types/react": "^17.0.39",
"react": "^17.0.2",
"react-dom": "^17.0.2"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-react": "^0.5.0", "@astrojs/renderer-react": "^0.5.0",
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -57,6 +57,7 @@ export default function Search() {
initialScrollY={window.scrollY} initialScrollY={window.scrollY}
onClose={onClose} onClose={onClose}
indexName={(CONFIG as any).ALGOLIA.indexName} indexName={(CONFIG as any).ALGOLIA.indexName}
appId={(CONFIG as any).ALGOLIA.appId}
apiKey={(CONFIG as any).ALGOLIA.apiKey} apiKey={(CONFIG as any).ALGOLIA.apiKey}
transformItems={(items) => { transformItems={(items) => {
return items.map((item) => { return items.map((item) => {

View file

@ -26,6 +26,7 @@ export const KNOWN_LANGUAGES = {
// See "Algolia" section of the README for more information. // See "Algolia" section of the README for more information.
// export const ALGOLIA = { // export const ALGOLIA = {
// indexName: 'XXXXXXXXXX', // indexName: 'XXXXXXXXXX',
// appId: 'XXXXXXXXXX',
// apiKey: 'XXXXXXXXXX', // apiKey: 'XXXXXXXXXX',
// } // }

View file

@ -9,6 +9,6 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -12,10 +12,10 @@
"devDependencies": { "devDependencies": {
"@astrojs/renderer-vue": "^0.4.0", "@astrojs/renderer-vue": "^0.4.0",
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"astro": "^0.23.7",
"sass": "^1.49.8", "sass": "^1.49.8",
"astro": "^0.24.0-next.0",
"preact": "~10.6.5",
"unocss": "^0.15.5", "unocss": "^0.15.5",
"vite-imagetools": "^4.0.1", "vite-imagetools": "^4.0.1"
"@astrojs/renderer-vue": "^0.4.0"
} }
} }

View file

@ -9,6 +9,6 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,6 +10,6 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-lit": "^0.4.0", "@astrojs/renderer-lit": "^0.4.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -13,8 +13,8 @@
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"@astrojs/renderer-react": "^0.5.0", "@astrojs/renderer-react": "^0.5.0",
"@astrojs/renderer-solid": "^0.4.0", "@astrojs/renderer-solid": "^0.4.0",
"@astrojs/renderer-svelte": "^0.4.0", "@astrojs/renderer-svelte": "^0.5.1",
"@astrojs/renderer-vue": "^0.4.0", "@astrojs/renderer-vue": "^0.4.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,6 +10,6 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,6 +10,6 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-react": "^0.5.0", "@astrojs/renderer-react": "^0.5.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,6 +10,6 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-solid": "^0.4.0", "@astrojs/renderer-solid": "^0.4.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -9,7 +9,7 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-svelte": "^0.4.0", "@astrojs/renderer-svelte": "^0.5.1",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,6 +10,6 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-vue": "^0.4.0", "@astrojs/renderer-vue": "^0.4.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -9,6 +9,6 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -9,6 +9,6 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -9,7 +9,7 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-svelte": "^0.4.0", "@astrojs/renderer-svelte": "^0.5.1",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,6 +10,6 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -11,10 +11,9 @@
"server": "node server/server.mjs" "server": "node server/server.mjs"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-svelte": "^0.4.0", "@astrojs/renderer-svelte": "^0.5.1",
"astro": "^0.23.7", "astro": "^0.24.0-next.0",
"unocss": "^0.15.5", "unocss": "^0.15.5",
"vite-imagetools": "^4.0.1", "vite-imagetools": "^4.0.1"
"@astrojs/renderer-svelte": "^0.5.1"
} }
} }

View file

@ -9,6 +9,6 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -9,7 +9,7 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7", "@astrojs/renderer-react": "^0.5.0",
"@astrojs/renderer-react": "^0.5.0" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,7 +10,7 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/markdown-remark": "^0.6.1", "@astrojs/markdown-remark": "^0.6.1",
"astro": "^0.23.7", "astro": "^0.24.0-next.0",
"hast-util-select": "5.0.1", "hast-util-select": "5.0.1",
"rehype-autolink-headings": "^6.1.0", "rehype-autolink-headings": "^6.1.0",
"rehype-slug": "^5.0.0", "rehype-slug": "^5.0.0",

View file

@ -10,6 +10,6 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/markdown-remark": "^0.6.1", "@astrojs/markdown-remark": "^0.6.1",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -11,9 +11,9 @@
"devDependencies": { "devDependencies": {
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"@astrojs/renderer-react": "^0.5.0", "@astrojs/renderer-react": "^0.5.0",
"@astrojs/renderer-svelte": "^0.4.0", "@astrojs/renderer-svelte": "^0.5.1",
"@astrojs/renderer-vue": "^0.4.0", "@astrojs/renderer-vue": "^0.4.0",
"@astrojs/markdown-remark": "^0.6.1", "@astrojs/markdown-remark": "^0.6.1",
"astro": "^0.23.7" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -13,18 +13,18 @@
"@nanostores/react": "^0.1.5", "@nanostores/react": "^0.1.5",
"@nanostores/vue": "^0.4.1", "@nanostores/vue": "^0.4.1",
"nanostores": "^0.5.7", "nanostores": "^0.5.7",
"solid-nanostores": "0.0.6" "solid-nanostores": "0.0.6",
"preact": "^10.6.5",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"vue": "^3.2.0"
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-react": "^0.5.0", "@astrojs/renderer-react": "^0.5.0",
"@astrojs/renderer-svelte": "^0.4.0",
"@astrojs/renderer-vue": "^0.4.0", "@astrojs/renderer-vue": "^0.4.0",
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"@astrojs/renderer-solid": "^0.4.0", "@astrojs/renderer-solid": "^0.4.0",
"astro": "^0.23.7",
"@astrojs/renderer-preact": "^0.5.0",
"@astrojs/renderer-react": "^0.5.0",
"@astrojs/renderer-svelte": "^0.5.1", "@astrojs/renderer-svelte": "^0.5.1",
"@astrojs/renderer-vue": "^0.4.0" "astro": "^0.24.0-next.0"
} }
} }

View file

@ -10,9 +10,9 @@
}, },
"devDependencies": { "devDependencies": {
"@astrojs/renderer-preact": "^0.5.0", "@astrojs/renderer-preact": "^0.5.0",
"astro": "^0.23.7", "postcss": "^8.3.8",
"astro": "^0.24.0-next.0",
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
"tailwindcss": "^3.0.5", "tailwindcss": "^3.0.5"
"@astrojs/renderer-preact": "^0.5.0"
} }
} }

View file

@ -9,7 +9,7 @@
"preview": "astro preview" "preview": "astro preview"
}, },
"devDependencies": { "devDependencies": {
"astro": "^0.23.7", "astro": "^0.24.0-next.0",
"vite-plugin-pwa": "0.11.11", "vite-plugin-pwa": "0.11.11",
"workbox-window": "^6.5.0" "workbox-window": "^6.5.0"
} }

View file

@ -43,7 +43,8 @@
"peerDependencyRules": { "peerDependencyRules": {
"ignoreMissing": [ "ignoreMissing": [
"@babel/core", "@babel/core",
"@babel/plugin-transform-react-jsx" "@babel/plugin-transform-react-jsx",
"vite"
] ]
} }
}, },
@ -51,20 +52,20 @@
"@astrojs/webapi": "workspace:*" "@astrojs/webapi": "workspace:*"
}, },
"devDependencies": { "devDependencies": {
"@changesets/changelog-github": "^0.4.2", "@changesets/changelog-github": "^0.4.3",
"@changesets/cli": "^2.16.0", "@changesets/cli": "^2.21.1",
"@octokit/action": "^3.15.4", "@octokit/action": "^3.18.0",
"@typescript-eslint/eslint-plugin": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/parser": "^5.14.0",
"del": "^6.0.0", "del": "^6.0.0",
"esbuild": "0.13.7", "esbuild": "0.13.7",
"eslint": "^8.0.1", "eslint": "^8.10.0",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.0.0", "eslint-plugin-prettier": "^4.0.0",
"execa": "^6.0.0", "execa": "^6.1.0",
"prettier": "^2.4.1", "prettier": "^2.5.1",
"pretty-bytes": "^6.0.0", "pretty-bytes": "^6.0.0",
"tiny-glob": "^0.2.8", "tiny-glob": "^0.2.9",
"turbo": "^1.1.5", "turbo": "^1.1.5",
"typescript": "4.5.2" "typescript": "4.5.2"
} }

View file

@ -1,5 +1,35 @@
# astro # astro
## 0.24.0-next.0
### Minor Changes
- [#2705](https://github.com/withastro/astro/pull/2705) [`8ce63ee6`](https://github.com/withastro/astro/commit/8ce63ee658677ecabcb3068f00413b51e7db30ef) Thanks [@natemoo-re](https://github.com/natemoo-re)! - New default build strategy
This change marks the "static build" as the new default build strategy. If you are unfamiliar with this build strategy check out the [migration guide](https://docs.astro.build/en/migrate/#planned-deprecations) on how to change your code to be compatible with this new bulid strategy.
If you'd like to keep using the old build strategy, use the flag `--legacy-build` both in your `astro dev` and `astro build` scripts, for ex:
```json
{
"scripts": {
"build": "astro build --legacy-build"
}
}
```
Note that the legacy build _is_ deprecated and will be removed in a future version. You should only use this flag until you have the time to migration your code.
- **Updated `<head>` and `<body>` behavior**
Since `astro@0.21`, Astro placed certain restrictions on what files could use `<head>` or `<body>` tags. In `astro@0.24`, the restrictions have been lifted. Astro will be able to correctly handle `<head>` and `<body>` tags in _any_ component, not just those in `src/pages/` or `src/layouts/`.
### Patch Changes
- [#2705](https://github.com/withastro/astro/pull/2705) [`176d4082`](https://github.com/withastro/astro/commit/176d4082ca642e3d7b996529f1efed7048b4d04f) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Fixes use of private .env variables with the static build
* [#2705](https://github.com/withastro/astro/pull/2705) [`a483c044`](https://github.com/withastro/astro/commit/a483c0446ba222edf4258f4683cd918ea209b8f4) Thanks [@natemoo-re](https://github.com/natemoo-re)! - Replace `send` dependency with `sirv`
## 0.23.7 ## 0.23.7
### Patch Changes ### Patch Changes
@ -96,12 +126,12 @@
```typescript ```typescript
// src/pages/company.json.ts // src/pages/company.json.ts
export async function get() { export async function get() {
return { return {
body: JSON.stringify({ body: JSON.stringify({
name: 'Astro Technology Company', name: 'Astro Technology Company',
url: 'https://astro.build/', url: 'https://astro.build/',
}), }),
}; };
} }
``` ```
@ -263,12 +293,12 @@
```typescript ```typescript
// src/pages/company.json.ts // src/pages/company.json.ts
export async function get() { export async function get() {
return { return {
body: JSON.stringify({ body: JSON.stringify({
name: 'Astro Technology Company', name: 'Astro Technology Company',
url: 'https://astro.build/', url: 'https://astro.build/',
}), }),
}; };
} }
``` ```
@ -1623,10 +1653,10 @@ For convenience, you may now also move your `astro.config.js` file to a top-leve
```js ```js
export default { export default {
markdownOptions: { markdownOptions: {
remarkPlugins: ['remark-slug', ['remark-autolink-headings', { behavior: 'prepend' }]], remarkPlugins: ['remark-slug', ['remark-autolink-headings', { behavior: 'prepend' }]],
rehypePlugins: ['rehype-slug', ['rehype-autolink-headings', { behavior: 'prepend' }]], rehypePlugins: ['rehype-slug', ['rehype-autolink-headings', { behavior: 'prepend' }]],
}, },
}; };
``` ```
@ -1646,10 +1676,10 @@ For convenience, you may now also move your `astro.config.js` file to a top-leve
```js ```js
export default { export default {
name: '@matthewp/my-renderer', name: '@matthewp/my-renderer',
server: './server.js', server: './server.js',
client: './client.js', client: './client.js',
hydrationPolyfills: ['./my-polyfill.js'], hydrationPolyfills: ['./my-polyfill.js'],
}; };
``` ```

View file

@ -1,6 +1,6 @@
{ {
"name": "astro", "name": "astro",
"version": "0.23.7", "version": "0.24.0-next.0",
"description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.", "description": "Astro is a modern site builder with web best practices, performance, and DX front-of-mind.",
"type": "module", "type": "module",
"author": "withastro", "author": "withastro",
@ -54,11 +54,11 @@
"dev": "astro-scripts dev \"src/**/*.ts\"", "dev": "astro-scripts dev \"src/**/*.ts\"",
"postbuild": "astro-scripts copy \"src/**/*.astro\"", "postbuild": "astro-scripts copy \"src/**/*.astro\"",
"benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js", "benchmark": "node test/benchmark/dev.bench.js && node test/benchmark/build.bench.js",
"test": "mocha --parallel --timeout 15000 --ignore **/lit-element.test.js && mocha **/lit-element.test.js", "test": "mocha --parallel --timeout 20000 --ignore **/lit-element.test.js && mocha --timeout 20000 **/lit-element.test.js",
"test:match": "mocha --timeout 15000 -g" "test:match": "mocha --timeout 20000 -g"
}, },
"dependencies": { "dependencies": {
"@astrojs/compiler": "^0.11.4", "@astrojs/compiler": "^0.12.0-next.8",
"@astrojs/language-server": "^0.8.6", "@astrojs/language-server": "^0.8.6",
"@astrojs/markdown-remark": "^0.6.4", "@astrojs/markdown-remark": "^0.6.4",
"@astrojs/prism": "0.4.0", "@astrojs/prism": "0.4.0",
@ -95,7 +95,7 @@
"resolve": "^1.20.0", "resolve": "^1.20.0",
"rollup": "^2.64.0", "rollup": "^2.64.0",
"semver": "^7.3.5", "semver": "^7.3.5",
"send": "^0.17.1", "sirv": "^2.0.2",
"serialize-javascript": "^6.0.0", "serialize-javascript": "^6.0.0",
"shiki": "^0.10.0", "shiki": "^0.10.0",
"shorthash": "^0.0.2", "shorthash": "^0.0.2",

View file

@ -27,8 +27,10 @@ export interface CLIFlags {
hostname?: string; hostname?: string;
port?: number; port?: number;
config?: string; config?: string;
/** @deprecated */
experimentalStaticBuild?: boolean; experimentalStaticBuild?: boolean;
experimentalSsr?: boolean; experimentalSsr?: boolean;
legacyBuild?: boolean;
drafts?: boolean; drafts?: boolean;
} }
@ -266,12 +268,19 @@ export interface AstroUserConfig {
*/ */
drafts?: boolean; drafts?: boolean;
/** /**
* Experimental: Enables "static build mode" for faster builds. * Enables "legacy build mode" for compatibility with older Astro versions.
* Default: false * Default: false
*/ */
legacyBuild?: boolean;
/**
* @deprecated
* Experimental: Enables "static build mode" for faster builds.
* Default: true
*/
experimentalStaticBuild?: boolean; experimentalStaticBuild?: boolean;
/** /**
* Enable a build for SSR support. * Enable a build for SSR support.
* Default: false
*/ */
experimentalSsr?: boolean; experimentalSsr?: boolean;
}; };
@ -506,17 +515,6 @@ export type Params = Record<string, string | undefined>;
export type Props = Record<string, unknown>; export type Props = Record<string, unknown>;
export interface RenderPageOptions {
request: {
params?: Params;
url: URL;
canonicalURL: URL;
};
children: any[];
props: Props;
css?: string[];
}
type Body = string; type Body = string;
export interface EndpointOutput<Output extends Body = Body> { export interface EndpointOutput<Output extends Body = Body> {
@ -634,7 +632,7 @@ export interface SSRElement {
export interface SSRMetadata { export interface SSRMetadata {
renderers: Renderer[]; renderers: Renderer[];
pathname: string; pathname: string;
experimentalStaticBuild: boolean; legacyBuild: boolean;
} }
export interface SSRResult { export interface SSRResult {

View file

@ -46,7 +46,7 @@ export class App {
const scripts = createModuleScriptElementWithSrcSet(info.scripts, manifest.site); const scripts = createModuleScriptElementWithSrcSet(info.scripts, manifest.site);
return render({ return render({
experimentalStaticBuild: true, legacyBuild: false,
links, links,
logging: defaultLogOptions, logging: defaultLogOptions,
markdownRender: manifest.markdown.render, markdownRender: manifest.markdown.render,

View file

@ -110,7 +110,7 @@ class AstroBuilder {
timer.buildStart = performance.now(); timer.buildStart = performance.now();
// Use the new faster static based build. // Use the new faster static based build.
if (this.config.buildOptions.experimentalStaticBuild) { if (!this.config.buildOptions.legacyBuild) {
await staticBuild({ await staticBuild({
allPages, allPages,
astroConfig: this.config, astroConfig: this.config,

View file

@ -38,9 +38,10 @@ export interface StaticBuildOptions {
const MAX_CONCURRENT_RENDERS = 10; const MAX_CONCURRENT_RENDERS = 10;
const STATUS_CODE_PAGES = new Set(['/404', '/500']);
function addPageName(pathname: string, opts: StaticBuildOptions): void { function addPageName(pathname: string, opts: StaticBuildOptions): void {
const pathrepl = opts.astroConfig.buildOptions.pageUrlFormat === 'directory' ? '/index.html' : pathname === '/' ? 'index.html' : '.html'; opts.pageNames.push(pathname.replace(/\/?$/, '/').replace(/^\//, ''));
opts.pageNames.push(pathname.replace(/\/?$/, pathrepl).replace(/^\//, ''));
} }
// Gives back a facadeId that is relative to the root. // Gives back a facadeId that is relative to the root.
@ -131,6 +132,8 @@ export async function staticBuild(opts: StaticBuildOptions) {
const topLevelImports = new Set([ const topLevelImports = new Set([
// Any component that gets hydrated // Any component that gets hydrated
...metadata.hydratedComponentPaths(), ...metadata.hydratedComponentPaths(),
// Client-only components
...metadata.clientOnlyComponentPaths(),
// Any hydration directive like astro/client/idle.js // Any hydration directive like astro/client/idle.js
...metadata.hydrationDirectiveSpecifiers(), ...metadata.hydrationDirectiveSpecifiers(),
// The client path for each renderer // The client path for each renderer
@ -182,6 +185,7 @@ async function ssrBuild(opts: StaticBuildOptions, internals: BuildInternals, inp
logLevel: 'error', logLevel: 'error',
mode: 'production', mode: 'production',
build: { build: {
...viteConfig.build,
emptyOutDir: false, emptyOutDir: false,
manifest: ssr, manifest: ssr,
minify: false, minify: false,
@ -360,12 +364,12 @@ async function generatePath(pathname: string, opts: StaticBuildOptions, gopts: G
debug('build', `Generating: ${pathname}`); debug('build', `Generating: ${pathname}`);
const site = astroConfig.buildOptions.site; const site = astroConfig.buildOptions.site;
const links = createLinkStylesheetElementSet(linkIds, site); const links = createLinkStylesheetElementSet(linkIds.reverse(), site);
const scripts = createModuleScriptElementWithSrcSet(hoistedId ? [hoistedId] : [], site); const scripts = createModuleScriptElementWithSrcSet(hoistedId ? [hoistedId] : [], site);
try { try {
const html = await render({ const html = await render({
experimentalStaticBuild: true, legacyBuild: false,
links, links,
logging, logging,
markdownRender: astroConfig.markdownOptions.render, markdownRender: astroConfig.markdownOptions.render,
@ -454,8 +458,7 @@ async function generateManifest(result: RollupOutput, opts: StaticBuildOptions,
} }
function getOutRoot(astroConfig: AstroConfig): URL { function getOutRoot(astroConfig: AstroConfig): URL {
const rootPathname = appendForwardSlash(astroConfig.buildOptions.site ? new URL(astroConfig.buildOptions.site).pathname : '/'); return new URL('./', astroConfig.dist);
return new URL('.' + rootPathname, astroConfig.dist);
} }
function getServerRoot(astroConfig: AstroConfig): URL { function getServerRoot(astroConfig: AstroConfig): URL {
@ -479,10 +482,16 @@ function getOutFolder(astroConfig: AstroConfig, pathname: string, routeType: Rou
return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot); return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot);
case 'page': case 'page':
switch (astroConfig.buildOptions.pageUrlFormat) { switch (astroConfig.buildOptions.pageUrlFormat) {
case 'directory': case 'directory': {
if(STATUS_CODE_PAGES.has(pathname)) {
return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot);
}
return new URL('.' + appendForwardSlash(pathname), outRoot); return new URL('.' + appendForwardSlash(pathname), outRoot);
case 'file': }
case 'file': {
return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot); return new URL('.' + appendForwardSlash(npath.dirname(pathname)), outRoot);
}
} }
} }
} }
@ -493,10 +502,17 @@ function getOutFile(astroConfig: AstroConfig, outFolder: URL, pathname: string,
return new URL(npath.basename(pathname), outFolder); return new URL(npath.basename(pathname), outFolder);
case 'page': case 'page':
switch (astroConfig.buildOptions.pageUrlFormat) { switch (astroConfig.buildOptions.pageUrlFormat) {
case 'directory': case 'directory': {
if(STATUS_CODE_PAGES.has(pathname)) {
const baseName = npath.basename(pathname);
return new URL('./' + (baseName || 'index') + '.html', outFolder);
}
return new URL('./index.html', outFolder); return new URL('./index.html', outFolder);
case 'file': }
return new URL('./' + npath.basename(pathname) + '.html', outFolder); case 'file': {
const baseName = npath.basename(pathname);
return new URL('./' + (baseName || 'index') + '.html', outFolder);
}
} }
} }
} }

View file

@ -26,11 +26,6 @@ export const AstroConfigSchema = z.object({
.optional() .optional()
.default('./src/pages') .default('./src/pages')
.transform((val) => new URL(val)), .transform((val) => new URL(val)),
layouts: z
.string()
.optional()
.default('./src/layouts')
.transform((val) => new URL(val)),
public: z public: z
.string() .string()
.optional() .optional()
@ -60,7 +55,8 @@ export const AstroConfigSchema = z.object({
.union([z.literal('file'), z.literal('directory')]) .union([z.literal('file'), z.literal('directory')])
.optional() .optional()
.default('directory'), .default('directory'),
experimentalStaticBuild: z.boolean().optional().default(false), legacyBuild: z.boolean().optional().default(false),
experimentalStaticBuild: z.boolean().optional().default(true),
experimentalSsr: z.boolean().optional().default(false), experimentalSsr: z.boolean().optional().default(false),
drafts: z.boolean().optional().default(false), drafts: z.boolean().optional().default(false),
}) })
@ -98,10 +94,6 @@ export async function validateConfig(userConfig: any, root: string): Promise<Ast
.string() .string()
.default('./src/pages') .default('./src/pages')
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)), .transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
layouts: z
.string()
.default('./src/layouts')
.transform((val) => new URL(addTrailingSlash(val), fileProtocolRoot)),
public: z public: z
.string() .string()
.default('./public') .default('./public')
@ -121,6 +113,11 @@ function addTrailingSlash(str: string): string {
/** Convert the generic "yargs" flag object into our own, custom TypeScript object. */ /** Convert the generic "yargs" flag object into our own, custom TypeScript object. */
function resolveFlags(flags: Partial<Flags>): CLIFlags { function resolveFlags(flags: Partial<Flags>): CLIFlags {
if(flags.experimentalStaticBuild) {
// eslint-disable-next-line no-console
console.warn(`Passing --experimental-static-build is no longer necessary and is now the default. The flag will be removed in a future version of Astro.`)
}
return { return {
projectRoot: typeof flags.projectRoot === 'string' ? flags.projectRoot : undefined, projectRoot: typeof flags.projectRoot === 'string' ? flags.projectRoot : undefined,
site: typeof flags.site === 'string' ? flags.site : undefined, site: typeof flags.site === 'string' ? flags.site : undefined,
@ -128,7 +125,8 @@ function resolveFlags(flags: Partial<Flags>): CLIFlags {
port: typeof flags.port === 'number' ? flags.port : undefined, port: typeof flags.port === 'number' ? flags.port : undefined,
config: typeof flags.config === 'string' ? flags.config : undefined, config: typeof flags.config === 'string' ? flags.config : undefined,
hostname: typeof flags.hostname === 'string' ? flags.hostname : undefined, hostname: typeof flags.hostname === 'string' ? flags.hostname : undefined,
experimentalStaticBuild: typeof flags.experimentalStaticBuild === 'boolean' ? flags.experimentalStaticBuild : false, legacyBuild: typeof flags.legacyBuild === 'boolean' ? flags.legacyBuild : false,
experimentalStaticBuild: typeof flags.experimentalStaticBuild === 'boolean' ? flags.experimentalStaticBuild : true,
experimentalSsr: typeof flags.experimentalSsr === 'boolean' ? flags.experimentalSsr : false, experimentalSsr: typeof flags.experimentalSsr === 'boolean' ? flags.experimentalSsr : false,
drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false, drafts: typeof flags.drafts === 'boolean' ? flags.drafts : false,
}; };
@ -147,6 +145,7 @@ function mergeCLIFlags(astroConfig: AstroUserConfig, flags: CLIFlags) {
astroConfig.buildOptions.experimentalSsr = flags.experimentalSsr; astroConfig.buildOptions.experimentalSsr = flags.experimentalSsr;
if (flags.experimentalSsr) { if (flags.experimentalSsr) {
astroConfig.buildOptions.experimentalStaticBuild = true; astroConfig.buildOptions.experimentalStaticBuild = true;
astroConfig.buildOptions.legacyBuild = false;
} }
} }
if (typeof flags.drafts === 'boolean') astroConfig.buildOptions.drafts = flags.drafts; if (typeof flags.drafts === 'boolean') astroConfig.buildOptions.drafts = flags.drafts;

View file

@ -2,17 +2,15 @@ import type { AstroConfig } from '../../@types/astro';
import type { LogOptions } from '../logger'; import type { LogOptions } from '../logger';
import type { Stats } from 'fs'; import type { Stats } from 'fs';
import type { AddressInfo } from 'net'; import type { AddressInfo } from 'net';
import http from 'http'; import http from 'http';
import sirv from 'sirv';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import send from 'send';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import fs from 'fs';
import * as msg from '../messages.js'; import * as msg from '../messages.js';
import { error, info } from '../logger.js'; import { error, info } from '../logger.js';
import { subpathNotUsedTemplate, notFoundTemplate, default as template } from '../../template/4xx.js';
import { appendForwardSlash, trimSlashes } from '../path.js'; import { appendForwardSlash, trimSlashes } from '../path.js';
import { getLocalAddress } from '../dev/util.js'; import { getLocalAddress } from '../dev/util.js';
import { subpathNotUsedTemplate, notFoundTemplate } from '../../template/4xx.js';
interface PreviewOptions { interface PreviewOptions {
logging: LogOptions; logging: LogOptions;
@ -25,28 +23,19 @@ export interface PreviewServer {
stop(): Promise<void>; stop(): Promise<void>;
} }
const HAS_FILE_EXTENSION_REGEXP = /^.*\.[^\\]+$/;
/** The primary dev action */ /** The primary dev action */
export default async function preview(config: AstroConfig, { logging }: PreviewOptions): Promise<PreviewServer> { export default async function preview(config: AstroConfig, { logging }: PreviewOptions): Promise<PreviewServer> {
const startServerTime = performance.now(); const startServerTime = performance.now();
const pageUrlFormat = config.buildOptions.pageUrlFormat;
const trailingSlash = config.devOptions.trailingSlash;
const forceTrailingSlash = trailingSlash === 'always';
const blockTrailingSlash = trailingSlash === 'never';
/** Default file served from a directory. */
const defaultFile = 'index.html';
const defaultOrigin = 'http://localhost'; const defaultOrigin = 'http://localhost';
const trailingSlash = config.devOptions.trailingSlash
const sendOptions = {
extensions: pageUrlFormat === 'file' ? ['html'] : false,
index: false,
root: fileURLToPath(config.dist),
};
/** Base request URL. */ /** Base request URL. */
let baseURL = new URL(config.buildOptions.site || '/', defaultOrigin); let baseURL = new URL(config.buildOptions.site || '/', defaultOrigin);
const staticFileServer = sirv(fileURLToPath(config.dist), {
etag: true,
maxAge: 0,
})
// Create the preview server, send static files out of the `dist/` directory. // Create the preview server, send static files out of the `dist/` directory.
const server = http.createServer((req, res) => { const server = http.createServer((req, res) => {
const requestURL = new URL(req.url as string, defaultOrigin); const requestURL = new URL(req.url as string, defaultOrigin);
@ -64,55 +53,25 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO
const isRoot = pathname === '/'; const isRoot = pathname === '/';
const hasTrailingSlash = isRoot || pathname.endsWith('/'); const hasTrailingSlash = isRoot || pathname.endsWith('/');
let tryTrailingSlash = true; function sendError(message: string) {
let tryHtmlExtension = true;
let url: URL;
const onErr = (message: string) => {
res.statusCode = 404; res.statusCode = 404;
res.end(notFoundTemplate(pathname, message)); res.end(notFoundTemplate(pathname, message));
}; };
const onStat = (err: NodeJS.ErrnoException | null, stat: Stats) => { switch (true) {
switch (true) { case hasTrailingSlash && trailingSlash == 'never' && !isRoot:
// retry nonexistent paths without an html extension sendError('Not Found (devOptions.trailingSlash is set to "never")');
case err && tryHtmlExtension && hasTrailingSlash && !blockTrailingSlash: return;
case err && tryHtmlExtension && !hasTrailingSlash && !forceTrailingSlash && !pathname.endsWith('.html'): case !hasTrailingSlash && trailingSlash == 'always' && !isRoot && !HAS_FILE_EXTENSION_REGEXP.test(pathname):
tryHtmlExtension = false; sendError('Not Found (devOptions.trailingSlash is set to "always")');
return fs.stat((url = new URL(url.pathname + '.html', url)), onStat); return;
default: {
// 404 on nonexistent paths (that are yet handled) // HACK: rewrite req.url so that sirv finds the file
case err !== null: req.url = '/' + req.url?.replace(baseURL.pathname, '')
return onErr('Path not found'); staticFileServer(req, res, () => sendError('Not Found'));
return;
// 404 on directories when a trailing slash is present but blocked
case stat.isDirectory() && hasTrailingSlash && blockTrailingSlash && !isRoot:
return onErr('Prohibited trailing slash');
// 404 on directories when a trailing slash is missing but forced
case stat.isDirectory() && !hasTrailingSlash && forceTrailingSlash && !isRoot:
return onErr('Required trailing slash');
// retry on directories when a default file is missing but allowed (that are yet handled)
case stat.isDirectory() && tryTrailingSlash:
tryTrailingSlash = false;
return fs.stat((url = new URL(url.pathname + (url.pathname.endsWith('/') ? defaultFile : '/' + defaultFile), url)), onStat);
// 404 on existent directories (that are yet handled)
case stat.isDirectory():
return onErr('Path not found');
// handle existent paths
default:
send(req, fileURLToPath(url), {
extensions: false,
index: false,
}).pipe(res);
} }
}; }
fs.stat((url = new URL(trimSlashes(pathname), config.dist)), onStat);
}); });
let { hostname, port } = config.devOptions; let { hostname, port } = config.devOptions;
@ -165,7 +124,9 @@ export default async function preview(config: AstroConfig, { logging }: PreviewO
port, port,
server: httpServer!, server: httpServer!,
stop: async () => { stop: async () => {
httpServer.close(); await new Promise((resolve, reject) => {
httpServer.close((err) => err ? reject(err) : resolve(undefined));
});
}, },
}; };
} }

View file

@ -1,7 +1,7 @@
import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, Renderer, RouteData, SSRElement } from '../../@types/astro'; import type { ComponentInstance, EndpointHandler, MarkdownRenderOptions, Params, Props, Renderer, RouteData, SSRElement } from '../../@types/astro';
import type { LogOptions } from '../logger.js'; import type { LogOptions } from '../logger.js';
import { renderEndpoint, renderPage } from '../../runtime/server/index.js'; import { renderEndpoint, renderHead, renderToString } from '../../runtime/server/index.js';
import { getParams } from '../routing/index.js'; import { getParams } from '../routing/index.js';
import { createResult } from './result.js'; import { createResult } from './result.js';
import { findPathItemByKey, RouteCache, callGetStaticPaths } from './route-cache.js'; import { findPathItemByKey, RouteCache, callGetStaticPaths } from './route-cache.js';
@ -50,7 +50,7 @@ async function getParamsAndProps(opts: GetParamsAndPropsOptions): Promise<[Param
} }
interface RenderOptions { interface RenderOptions {
experimentalStaticBuild: boolean; legacyBuild: boolean;
logging: LogOptions; logging: LogOptions;
links: Set<SSRElement>; links: Set<SSRElement>;
markdownRender: MarkdownRenderOptions; markdownRender: MarkdownRenderOptions;
@ -66,7 +66,7 @@ interface RenderOptions {
} }
export async function render(opts: RenderOptions): Promise<string> { export async function render(opts: RenderOptions): Promise<string> {
const { experimentalStaticBuild, links, logging, origin, markdownRender, mod, pathname, scripts, renderers, resolve, route, routeCache, site } = opts; const { legacyBuild, links, logging, origin, markdownRender, mod, pathname, scripts, renderers, resolve, route, routeCache, site } = opts;
const [params, pageProps] = await getParamsAndProps({ const [params, pageProps] = await getParamsAndProps({
logging, logging,
@ -87,7 +87,7 @@ export async function render(opts: RenderOptions): Promise<string> {
if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`); if (!Component.isAstroComponentFactory) throw new Error(`Unable to SSR non-Astro component (${route?.component})`);
const result = createResult({ const result = createResult({
experimentalStaticBuild, legacyBuild,
links, links,
logging, logging,
markdownRender, markdownRender,
@ -100,10 +100,17 @@ export async function render(opts: RenderOptions): Promise<string> {
scripts, scripts,
}); });
let html = await renderPage(result, Component, pageProps, null); let html = await renderToString(result, Component, pageProps, null);
// handle final head injection if it hasn't happened already
if (html.indexOf("<!--astro:head:injected-->") == -1) {
html = await renderHead(result) + html;
}
// cleanup internal state flags
html = html.replace("<!--astro:head:injected-->", '');
// inject <!doctype html> if missing (TODO: is a more robust check needed for comments, etc.?) // inject <!doctype html> if missing (TODO: is a more robust check needed for comments, etc.?)
if (experimentalStaticBuild && !/<!doctype html/i.test(html)) { if (!legacyBuild && !/<!doctype html/i.test(html)) {
html = '<!DOCTYPE html>\n' + html; html = '<!DOCTYPE html>\n' + html;
} }

View file

@ -1,5 +1,5 @@
import type * as vite from 'vite'; import type * as vite from 'vite';
import type { AstroConfig, ComponentInstance, Renderer, RouteData, RuntimeMode } from '../../../@types/astro'; import type { AstroConfig, ComponentInstance, Renderer, RouteData, RuntimeMode, SSRElement } from '../../../@types/astro';
import { LogOptions } from '../../logger.js'; import { LogOptions } from '../../logger.js';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { getStylesForURL } from './css.js'; import { getStylesForURL } from './css.js';
@ -48,14 +48,15 @@ export async function preload({ astroConfig, filePath, viteServer }: SSROptions)
/** use Vite to SSR */ /** use Vite to SSR */
export async function render(renderers: Renderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<string> { export async function render(renderers: Renderer[], mod: ComponentInstance, ssrOpts: SSROptions): Promise<string> {
const { astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer } = ssrOpts; const { astroConfig, filePath, logging, mode, origin, pathname, route, routeCache, viteServer } = ssrOpts;
const legacy = astroConfig.buildOptions.legacyBuild;
// Add hoisted script tags // Add hoisted script tags
const scripts = createModuleScriptElementWithSrcSet( const scripts = createModuleScriptElementWithSrcSet(
astroConfig.buildOptions.experimentalStaticBuild && mod.hasOwnProperty('$$metadata') ? Array.from(mod.$$metadata.hoistedScriptPaths()) : [] !legacy && mod.hasOwnProperty('$$metadata') ? Array.from(mod.$$metadata.hoistedScriptPaths()) : []
); );
// Inject HMR scripts // Inject HMR scripts
if (mod.hasOwnProperty('$$metadata') && mode === 'development' && astroConfig.buildOptions.experimentalStaticBuild) { if (mod.hasOwnProperty('$$metadata') && mode === 'development' && !legacy) {
scripts.add({ scripts.add({
props: { type: 'module', src: '/@vite/client' }, props: { type: 'module', src: '/@vite/client' },
children: '', children: '',
@ -66,9 +67,31 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
}); });
} }
// Pass framework CSS in as link tags to be appended to the page.
let links = new Set<SSRElement>();
if(!legacy) {
[...getStylesForURL(filePath, viteServer)].forEach((href) => {
if (mode === 'development' && svelteStylesRE.test(href)) {
scripts.add({
props: { type: 'module', src: href },
children: '',
});
} else {
links.add({
props: {
rel: 'stylesheet',
href,
'data-astro-injected': true,
},
children: '',
});
}
});
}
let content = await coreRender({ let content = await coreRender({
experimentalStaticBuild: astroConfig.buildOptions.experimentalStaticBuild, legacyBuild: astroConfig.buildOptions.legacyBuild,
links: new Set(), links,
logging, logging,
markdownRender: astroConfig.markdownOptions.render, markdownRender: astroConfig.markdownOptions.render,
mod, mod,
@ -80,7 +103,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
// The legacy build needs these to remain unresolved so that vite HTML // The legacy build needs these to remain unresolved so that vite HTML
// Can do the resolution. Without this condition the build output will be // 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. // broken in the legacy build. This can be removed once the legacy build is removed.
if (astroConfig.buildOptions.experimentalStaticBuild) { if (!astroConfig.buildOptions.legacyBuild) {
const [, resolvedPath] = await viteServer.moduleGraph.resolveUrl(s); const [, resolvedPath] = await viteServer.moduleGraph.resolveUrl(s);
return resolvedPath; return resolvedPath;
} else { } else {
@ -101,7 +124,7 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
const tags: vite.HtmlTagDescriptor[] = []; const tags: vite.HtmlTagDescriptor[] = [];
// dev only: inject Astro HMR client // dev only: inject Astro HMR client
if (mode === 'development' && !astroConfig.buildOptions.experimentalStaticBuild) { if (mode === 'development' && legacy) {
tags.push({ tags.push({
tag: 'script', tag: 'script',
attrs: { type: 'module' }, attrs: { type: 'module' },
@ -113,31 +136,34 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
} }
// inject CSS // inject CSS
[...getStylesForURL(filePath, viteServer)].forEach((href) => { if(legacy) {
if (mode === 'development' && svelteStylesRE.test(href)) { [...getStylesForURL(filePath, viteServer)].forEach((href) => {
tags.push({ if (mode === 'development' && svelteStylesRE.test(href)) {
tag: 'script', tags.push({
attrs: { type: 'module', src: href }, tag: 'script',
injectTo: 'head', attrs: { type: 'module', src: href },
}); injectTo: 'head',
} else { });
tags.push({ } else {
tag: 'link', tags.push({
attrs: { tag: 'link',
rel: 'stylesheet', attrs: {
href, rel: 'stylesheet',
'data-astro-injected': true, href,
}, 'data-astro-injected': true,
injectTo: 'head', },
}); injectTo: 'head',
} });
}); }
});
}
// add injected tags // add injected tags
content = injectTags(content, tags); content = injectTags(content, tags);
// run transformIndexHtml() in dev to run Vite dev transformations // run transformIndexHtml() in dev to run Vite dev transformations
if (mode === 'development' && !astroConfig.buildOptions.experimentalStaticBuild) { if (mode === 'development' && astroConfig.buildOptions.legacyBuild) {
const relativeURL = filePath.href.replace(astroConfig.projectRoot.href, '/'); const relativeURL = filePath.href.replace(astroConfig.projectRoot.href, '/');
content = await viteServer.transformIndexHtml(relativeURL, content, pathname); content = await viteServer.transformIndexHtml(relativeURL, content, pathname);
} }

View file

@ -8,7 +8,7 @@ import { renderSlot } from '../../runtime/server/index.js';
import { warn, LogOptions } from '../logger.js'; import { warn, LogOptions } from '../logger.js';
export interface CreateResultArgs { export interface CreateResultArgs {
experimentalStaticBuild: boolean; legacyBuild: boolean;
logging: LogOptions; logging: LogOptions;
origin: string; origin: string;
markdownRender: MarkdownRenderOptions; markdownRender: MarkdownRenderOptions;
@ -22,7 +22,7 @@ export interface CreateResultArgs {
} }
export function createResult(args: CreateResultArgs): SSRResult { export function createResult(args: CreateResultArgs): SSRResult {
const { experimentalStaticBuild, origin, markdownRender, params, pathname, renderers, resolve, site: buildOptionsSite } = args; const { legacyBuild, origin, markdownRender, params, pathname, renderers, resolve, site: buildOptionsSite } = args;
// Create the result object that will be passed into the render function. // Create the result object that will be passed into the render function.
// This object starts here as an empty shell (not yet the result) but then // This object starts here as an empty shell (not yet the result) but then
@ -45,7 +45,7 @@ export function createResult(args: CreateResultArgs): SSRResult {
url, url,
}, },
resolve(path: string) { resolve(path: string) {
if (experimentalStaticBuild) { if (!legacyBuild) {
let extra = `This can be replaced with a dynamic import like so: await import("${path}")`; let extra = `This can be replaced with a dynamic import like so: await import("${path}")`;
if (isCSSRequest(path)) { if (isCSSRequest(path)) {
extra = `It looks like you are resolving styles. If you are adding a link tag, replace with this: extra = `It looks like you are resolving styles. If you are adding a link tag, replace with this:
@ -116,7 +116,7 @@ ${extra}`
_metadata: { _metadata: {
renderers, renderers,
pathname, pathname,
experimentalStaticBuild, legacyBuild
}, },
}; };

View file

@ -3,7 +3,6 @@ const STATUS_CODE_PAGE_REGEXP = /\/[0-9]{3}\/?$/;
/** Construct sitemap.xml given a set of URLs */ /** Construct sitemap.xml given a set of URLs */
export function generateSitemap(pages: string[]): string { export function generateSitemap(pages: string[]): string {
// TODO: find way to respect <link rel="canonical"> URLs here // TODO: find way to respect <link rel="canonical"> URLs here
// TODO: find way to exclude pages from sitemap // TODO: find way to exclude pages from sitemap
// copy just in case original copy is needed // copy just in case original copy is needed

View file

@ -216,7 +216,7 @@ export function createRouteManifest({ config, cwd }: { config: AstroConfig; cwd?
basename, basename,
ext, ext,
parts, parts,
file: slash(file), file: file.replace(/\\/g, '/'),
isDir, isDir,
isIndex, isIndex,
isPage, isPage,

View file

@ -71,7 +71,7 @@ export class AstroComponent {
const html = htmlParts[i]; const html = htmlParts[i];
const expression = expressions[i]; const expression = expressions[i];
yield _render(unescapeHTML(html)); yield unescapeHTML(html);
yield _render(expression); yield _render(expression);
} }
} }
@ -337,12 +337,14 @@ export function createAstro(filePathname: string, _site: string, projectRootStr:
}; };
} }
const toAttributeString = (value: any) => String(value).replace(/&/g, '&#38;').replace(/"/g, '&#34;'); const toAttributeString = (value: any, shouldEscape = true) => shouldEscape ?
String(value).replace(/&/g, '&#38;').replace(/"/g, '&#34;') :
value;
const STATIC_DIRECTIVES = new Set(['set:html', 'set:text']); const STATIC_DIRECTIVES = new Set(['set:html', 'set:text']);
// A helper used to turn expressions into attribute key/value // A helper used to turn expressions into attribute key/value
export function addAttribute(value: any, key: string) { export function addAttribute(value: any, key: string, shouldEscape = true) {
if (value == null) { if (value == null) {
return ''; return '';
} }
@ -372,15 +374,15 @@ Make sure to use the static attribute syntax (\`${key}={value}\`) instead of the
if (value === true && (key.startsWith('data-') || htmlBooleanAttributes.test(key))) { if (value === true && (key.startsWith('data-') || htmlBooleanAttributes.test(key))) {
return unescapeHTML(` ${key}`); return unescapeHTML(` ${key}`);
} else { } else {
return unescapeHTML(` ${key}="${toAttributeString(value)}"`); return unescapeHTML(` ${key}="${toAttributeString(value, shouldEscape)}"`);
} }
} }
// Adds support for `<Component {...value} /> // Adds support for `<Component {...value} />
export function spreadAttributes(values: Record<any, any>) { export function spreadAttributes(values: Record<any, any>, shouldEscape = true) {
let output = ''; let output = '';
for (const [key, value] of Object.entries(values)) { for (const [key, value] of Object.entries(values)) {
output += addAttribute(value, key); output += addAttribute(value, key, shouldEscape);
} }
return unescapeHTML(output); return unescapeHTML(output);
} }
@ -403,20 +405,6 @@ export function defineScriptVars(vars: Record<any, any>) {
return output; return output;
} }
// Calls a component and renders it into a string of HTML
export async function renderToString(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any) {
const Component = await componentFactory(result, props, children);
let template = await renderAstroComponent(Component);
return unescapeHTML(template);
}
// Filter out duplicate elements in our set
const uniqueElements = (item: any, index: number, all: any[]) => {
const props = JSON.stringify(item.props);
const children = item.children;
return index === all.findIndex((i) => JSON.stringify(i.props) === props && i.children == children);
};
// Renders an endpoint request to completion, returning the body. // Renders an endpoint request to completion, returning the body.
export async function renderEndpoint(mod: EndpointHandler, params: any) { export async function renderEndpoint(mod: EndpointHandler, params: any) {
const method = 'get'; const method = 'get';
@ -431,15 +419,34 @@ export async function renderEndpoint(mod: EndpointHandler, params: any) {
return body; return body;
} }
// Calls a component and renders it into a string of HTML
export async function renderToString(result: SSRResult, componentFactory: AstroComponentFactory, props: any, children: any) {
const Component = await componentFactory(result, props, children);
let template = await renderAstroComponent(Component);
// <!--astro:head--> injected by compiler
// Must be handled at the end of the rendering process
if (template.indexOf('<!--astro:head-->') > -1) {
template = template.replace('<!--astro:head-->', await renderHead(result));
}
return template;
}
// Filter out duplicate elements in our set
const uniqueElements = (item: any, index: number, all: any[]) => {
const props = JSON.stringify(item.props);
const children = item.children;
return index === all.findIndex((i) => JSON.stringify(i.props) === props && i.children == children);
};
// Renders a page to completion by first calling the factory callback, waiting for its result, and then appending // Renders a page to completion by first calling the factory callback, waiting for its result, and then appending
// styles and scripts into the head. // styles and scripts into the head.
export async function renderPage(result: SSRResult, Component: AstroComponentFactory, props: any, children: any) { export async function renderHead(result: SSRResult) {
const template = await renderToString(result, Component, props, children);
const styles = Array.from(result.styles) const styles = Array.from(result.styles)
.filter(uniqueElements) .filter(uniqueElements)
.map((style) => { .map((style) => {
const styleChildren = result._metadata.experimentalStaticBuild ? '' : style.children; const styleChildren = !result._metadata.legacyBuild ? '' : style.children;
return renderElement('style', { return renderElement('style', {
children: styleChildren, children: styleChildren,
props: { ...style.props, 'astro-style': true }, props: { ...style.props, 'astro-style': true },
@ -460,17 +467,10 @@ export async function renderPage(result: SSRResult, Component: AstroComponentFac
if (needsHydrationStyles) { if (needsHydrationStyles) {
styles.push(renderElement('style', { props: { 'astro-style': true }, children: 'astro-root, astro-fragment { display: contents; }' })); styles.push(renderElement('style', { props: { 'astro-style': true }, children: 'astro-root, astro-fragment { display: contents; }' }));
} }
const links = Array.from(result.links) const links = Array.from(result.links)
.filter(uniqueElements) .filter(uniqueElements)
.map((link) => renderElement('link', link)); .map((link) => renderElement('link', link, false));
return unescapeHTML(links.join('\n') + styles.join('\n') + scripts.join('\n') + '\n' + '<!--astro:head:injected-->');
// inject styles & scripts at end of <head>
let headPos = template.indexOf('</head>');
if (headPos === -1) {
return links.join('\n') + styles.join('\n') + scripts.join('\n') + template; // if no </head>, prepend styles & scripts
}
return template.substring(0, headPos) + links.join('\n') + styles.join('\n') + scripts.join('\n') + template.substring(headPos);
} }
export async function renderAstroComponent(component: InstanceType<typeof AstroComponent>) { export async function renderAstroComponent(component: InstanceType<typeof AstroComponent>) {
@ -513,7 +513,7 @@ function getHTMLElementName(constructor: typeof HTMLElement) {
return assignedName; return assignedName;
} }
function renderElement(name: string, { props: _props, children = '' }: SSRElement) { function renderElement(name: string, { props: _props, children = '' }: SSRElement, shouldEscape = true) {
// Do not print `hoist`, `lang`, `global` // Do not print `hoist`, `lang`, `global`
const { lang: _, 'data-astro-id': astroId, 'define:vars': defineVars, ...props } = _props; const { lang: _, 'data-astro-id': astroId, 'define:vars': defineVars, ...props } = _props;
if (defineVars) { if (defineVars) {
@ -530,5 +530,5 @@ function renderElement(name: string, { props: _props, children = '' }: SSRElemen
children = defineScriptVars(defineVars) + '\n' + children; children = defineScriptVars(defineVars) + '\n' + children;
} }
} }
return `<${name}${spreadAttributes(props)}>${children}</${name}>`; return `<${name}${spreadAttributes(props, shouldEscape)}>${children}</${name}>`;
} }

View file

@ -13,6 +13,7 @@ interface ComponentMetadata {
interface CreateMetadataOptions { interface CreateMetadataOptions {
modules: ModuleInfo[]; modules: ModuleInfo[];
hydratedComponents: any[]; hydratedComponents: any[];
clientOnlyComponents: any[];
hydrationDirectives: Set<string>; hydrationDirectives: Set<string>;
hoisted: any[]; hoisted: any[];
} }
@ -22,6 +23,7 @@ export class Metadata {
public modules: ModuleInfo[]; public modules: ModuleInfo[];
public hoisted: any[]; public hoisted: any[];
public hydratedComponents: any[]; public hydratedComponents: any[];
public clientOnlyComponents: any[];
public hydrationDirectives: Set<string>; public hydrationDirectives: Set<string>;
private metadataCache: Map<any, ComponentMetadata | null>; private metadataCache: Map<any, ComponentMetadata | null>;
@ -30,6 +32,7 @@ export class Metadata {
this.modules = opts.modules; this.modules = opts.modules;
this.hoisted = opts.hoisted; this.hoisted = opts.hoisted;
this.hydratedComponents = opts.hydratedComponents; this.hydratedComponents = opts.hydratedComponents;
this.clientOnlyComponents = opts.clientOnlyComponents;
this.hydrationDirectives = opts.hydrationDirectives; this.hydrationDirectives = opts.hydrationDirectives;
this.mockURL = new URL(filePathname, 'http://example.com'); this.mockURL = new URL(filePathname, 'http://example.com');
this.metadataCache = new Map<any, ComponentMetadata | null>(); this.metadataCache = new Map<any, ComponentMetadata | null>();
@ -66,6 +69,19 @@ export class Metadata {
} }
} }
*clientOnlyComponentPaths() {
const found = new Set<string>();
for (const metadata of this.deepMetadata()) {
for (const component of metadata.clientOnlyComponents) {
const path = metadata.resolvePath(component);
if (path && !found.has(path)) {
found.add(path);
yield path;
}
}
}
}
/** /**
* Gets all of the hydration specifiers used within this component. * Gets all of the hydration specifiers used within this component.
*/ */

View file

@ -33,11 +33,8 @@ function safelyReplaceImportPlaceholder(code: string) {
const configCache = new WeakMap<AstroConfig, CompilationCache>(); const configCache = new WeakMap<AstroConfig, CompilationCache>();
async function compile(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: { ssr: boolean }): Promise<CompileResult> { async function compile(config: AstroConfig, filename: string, source: string, viteTransform: TransformHook, opts: { ssr: boolean }): Promise<CompileResult> {
// pages and layouts should be transformed as full documents (implicit <head> <body> etc)
// everything else is treated as a fragment
const filenameURL = new URL(`file://${filename}`); const filenameURL = new URL(`file://${filename}`);
const normalizedID = fileURLToPath(filenameURL); const normalizedID = fileURLToPath(filenameURL);
const isPage = normalizedID.startsWith(fileURLToPath(config.pages)) || normalizedID.startsWith(fileURLToPath(config.layouts));
const pathname = filenameURL.pathname.substr(config.projectRoot.pathname.length - 1); const pathname = filenameURL.pathname.substr(config.projectRoot.pathname.length - 1);
let rawCSSDeps = new Set<string>(); let rawCSSDeps = new Set<string>();
@ -47,22 +44,21 @@ async function compile(config: AstroConfig, filename: string, source: string, vi
// use `sourcemap: "both"` so that sourcemap is included in the code // use `sourcemap: "both"` so that sourcemap is included in the code
// result passed to esbuild, but also available in the catch handler. // result passed to esbuild, but also available in the catch handler.
const transformResult = await transform(source, { const transformResult = await transform(source, {
as: isPage ? 'document' : 'fragment',
pathname, pathname,
projectRoot: config.projectRoot.toString(), projectRoot: config.projectRoot.toString(),
site: config.buildOptions.site, site: config.buildOptions.site,
sourcefile: filename, sourcefile: filename,
sourcemap: 'both', sourcemap: 'both',
internalURL: `/@fs${new URL('../runtime/server/index.js', import.meta.url).pathname}`, internalURL: `/@fs${new URL('../runtime/server/index.js', import.meta.url).pathname}`,
experimentalStaticExtraction: config.buildOptions.experimentalStaticBuild, experimentalStaticExtraction: !config.buildOptions.legacyBuild,
// TODO add experimental flag here // TODO add experimental flag here
preprocessStyle: async (value: string, attrs: Record<string, string>) => { preprocessStyle: async (value: string, attrs: Record<string, string>) => {
const lang = `.${attrs?.lang || 'css'}`.toLowerCase(); const lang = `.${attrs?.lang || 'css'}`.toLowerCase();
try { try {
// In the static build, grab any @import as CSS dependencies for HMR. // In the static build, grab any @import as CSS dependencies for HMR.
if (config.buildOptions.experimentalStaticBuild) { if (!config.buildOptions.legacyBuild) {
value = value.replace(/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi, (match, spec) => { value.replace(/(?:@import)\s(?:url\()?\s?["\'](.*?)["\']\s?\)?(?:[^;]*);?/gi, (match, spec) => {
rawCSSDeps.add(spec); rawCSSDeps.add(spec);
// If the language is CSS: prevent `@import` inlining to prevent scoping of imports. // If the language is CSS: prevent `@import` inlining to prevent scoping of imports.
// Otherwise: Sass, etc. need to see imports for variables, so leave in for their compiler to handle. // Otherwise: Sass, etc. need to see imports for variables, so leave in for their compiler to handle.

View file

@ -1,5 +1,6 @@
import type * as vite from 'vite'; import type * as vite from 'vite';
import type { AstroConfig } from '../@types/astro'; import type { AstroConfig } from '../@types/astro';
import type { TransformPluginContext } from 'rollup';
import MagicString from 'magic-string'; import MagicString from 'magic-string';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { loadEnv } from 'vite'; import { loadEnv } from 'vite';
@ -30,7 +31,7 @@ function getPrivateEnv(viteConfig: vite.ResolvedConfig, astroConfig: AstroConfig
if (privateKeys.length === 0) { if (privateKeys.length === 0) {
return null; return null;
} }
return Object.fromEntries(privateKeys.map((key) => [key, fullEnv[key]])); return Object.fromEntries(privateKeys.map((key) => [key, JSON.stringify(fullEnv[key])]));
} }
function referencesPrivateKey(source: string, privateEnv: Record<string, any>) { function referencesPrivateKey(source: string, privateEnv: Record<string, any>) {
@ -43,39 +44,56 @@ function referencesPrivateKey(source: string, privateEnv: Record<string, any>) {
export default function envVitePlugin({ config: astroConfig }: EnvPluginOptions): vite.PluginOption { export default function envVitePlugin({ config: astroConfig }: EnvPluginOptions): vite.PluginOption {
let privateEnv: Record<string, any> | null; let privateEnv: Record<string, any> | null;
let config: vite.ResolvedConfig; let config: vite.ResolvedConfig;
let replacements: Record<string, string>;
let pattern: RegExp | undefined;
return { return {
name: 'astro:vite-plugin-env', name: 'astro:vite-plugin-env',
enforce: 'pre', enforce: 'pre',
configResolved(resolvedConfig) { configResolved(resolvedConfig) {
config = resolvedConfig; config = resolvedConfig;
if (config.envPrefix) {
}
}, },
async transform(source, id, options) { async transform(source, id, options) {
const ssr = options?.ssr === true; const ssr = options?.ssr === true;
if (!ssr) return source;
if (!source.includes('import.meta')) return source; if(!ssr) {
if (!/\benv\b/.test(source)) return source; return source;
}
if(!source.includes('import.meta') || !/\benv\b/.test(source)) {
return source;
}
if (typeof privateEnv === 'undefined') { if (typeof privateEnv === 'undefined') {
privateEnv = getPrivateEnv(config, astroConfig); privateEnv = getPrivateEnv(config, astroConfig);
if(privateEnv) {
const entries = Object.entries(privateEnv).map(([key, value]) => ([`import.meta.env.${key}`, value]));
replacements = Object.fromEntries(entries);
pattern = new RegExp(
// Do not allow preceding '.', but do allow preceding '...' for spread operations
'(?<!(?<!\\.\\.)\\.)\\b(' +
Object.keys(replacements)
.map((str) => {
return str.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
})
.join('|') +
// prevent trailing assignments
')\\b(?!\\s*?=[^=])', 'g');
}
} }
if (!privateEnv) return source;
if (!privateEnv || !pattern) return source;
if (!referencesPrivateKey(source, privateEnv)) return source; if (!referencesPrivateKey(source, privateEnv)) return source;
// Find matches for *private* env and do our own replacement.
const s = new MagicString(source); const s = new MagicString(source);
// prettier-ignore let match: RegExpExecArray | null
s.prepend(`import.meta.env = new Proxy(import.meta.env, {` +
`get(target, prop, reciever) {` + while ((match = pattern.exec(source))) {
`const PRIVATE = ${JSON.stringify(privateEnv)};` + const start = match.index
`if (typeof PRIVATE[prop] !== 'undefined') {` + const end = start + match[0].length
`return PRIVATE[prop];` + const replacement = '' + replacements[match[1]]
`}` + s.overwrite(start, end, replacement)
`return Reflect.get(target, prop, reciever);` + }
`}` +
`});\n`);
return s.toString(); return s.toString();
}, },

View file

@ -15,12 +15,17 @@ describe('CSS', function () {
fixture = await loadFixture({ fixture = await loadFixture({
projectRoot: './fixtures/0-css/', projectRoot: './fixtures/0-css/',
renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'], renderers: ['@astrojs/renderer-react', '@astrojs/renderer-svelte', '@astrojs/renderer-vue'],
vite: {
build: {
assetsInlineLimit: 0
}
}
}); });
}); });
// test HTML and CSS contents for accuracy // test HTML and CSS contents for accuracy
describe('build', () => { describe('build', () => {
this.timeout(30000); // test needs a little more time in CI this.timeout(45000); // test needs a little more time in CI
let $; let $;
let bundledCSS; let bundledCSS;
@ -31,7 +36,7 @@ describe('CSS', function () {
// get bundled CSS (will be hashed, hence DOM query) // get bundled CSS (will be hashed, hence DOM query)
const html = await fixture.readFile('/index.html'); const html = await fixture.readFile('/index.html');
$ = cheerio.load(html); $ = cheerio.load(html);
const bundledCSSHREF = $('link[rel=stylesheet][href^=./assets/]').attr('href'); const bundledCSSHREF = $('link[rel=stylesheet][href^=/assets/]').attr('href');
bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/')); bundledCSS = await fixture.readFile(bundledCSSHREF.replace(/^\/?/, '/'));
}); });
@ -47,7 +52,8 @@ describe('CSS', function () {
expect(el2.attr('class')).to.equal(`visible ${scopedClass}`); expect(el2.attr('class')).to.equal(`visible ${scopedClass}`);
// 2. check CSS // 2. check CSS
expect(bundledCSS).to.include(`.blue.${scopedClass}{color:#b0e0e6}.color\\:blue.${scopedClass}{color:#b0e0e6}.visible.${scopedClass}{display:block}`); const expected = `.blue.${scopedClass}{color:#b0e0e6}.color\\\\:blue.${scopedClass}{color:#b0e0e6}.visible.${scopedClass}{display:block}`;
expect(bundledCSS).to.include(expected);
}); });
it('No <style> skips scoping', async () => { it('No <style> skips scoping', async () => {
@ -60,7 +66,8 @@ describe('CSS', function () {
}); });
it('Using hydrated components adds astro-root styles', async () => { it('Using hydrated components adds astro-root styles', async () => {
expect(bundledCSS).to.include('display:contents'); const inline = $('style').html();
expect(inline).to.include('display: contents');
}); });
it('<style lang="sass">', async () => { it('<style lang="sass">', async () => {
@ -217,7 +224,7 @@ describe('CSS', function () {
it('<style>', async () => { it('<style>', async () => {
const el = $('#svelte-css'); const el = $('#svelte-css');
const classes = el.attr('class').split(' '); const classes = el.attr('class').split(' ');
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); const scopedClass = classes.find((name) => name !== 'svelte-css' && /^svelte-[A-Za-z0-9-]+/.test(name));
// 1. check HTML // 1. check HTML
expect(el.attr('class')).to.include('svelte-css'); expect(el.attr('class')).to.include('svelte-css');
@ -229,7 +236,7 @@ describe('CSS', function () {
it('<style lang="sass">', async () => { it('<style lang="sass">', async () => {
const el = $('#svelte-sass'); const el = $('#svelte-sass');
const classes = el.attr('class').split(' '); const classes = el.attr('class').split(' ');
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); const scopedClass = classes.find((name) => name !== 'svelte-sass' && /^svelte-[A-Za-z0-9-]+/.test(name));
// 1. check HTML // 1. check HTML
expect(el.attr('class')).to.include('svelte-sass'); expect(el.attr('class')).to.include('svelte-sass');
@ -241,7 +248,7 @@ describe('CSS', function () {
it('<style lang="scss">', async () => { it('<style lang="scss">', async () => {
const el = $('#svelte-scss'); const el = $('#svelte-scss');
const classes = el.attr('class').split(' '); const classes = el.attr('class').split(' ');
const scopedClass = classes.find((name) => /^s-[A-Za-z0-9-]+/.test(name)); const scopedClass = classes.find((name) => name !== 'svelte-scss' && /^svelte-[A-Za-z0-9-]+/.test(name));
// 1. check HTML // 1. check HTML
expect(el.attr('class')).to.include('svelte-scss'); expect(el.attr('class')).to.include('svelte-scss');
@ -272,11 +279,6 @@ describe('CSS', function () {
expect((await fixture.fetch(href)).status).to.equal(200); expect((await fixture.fetch(href)).status).to.equal(200);
}); });
it('resolves CSS in src/', async () => {
const href = $('link[href$="linked.css"]').attr('href');
expect((await fixture.fetch(href)).status).to.equal(200);
});
// Skipped until upstream fix lands // Skipped until upstream fix lands
// Our fix: https://github.com/withastro/astro/pull/2106 // Our fix: https://github.com/withastro/astro/pull/2106
// OG Vite PR: https://github.com/vitejs/vite/pull/5940 // OG Vite PR: https://github.com/vitejs/vite/pull/5940

View file

@ -11,7 +11,14 @@ describe('Assets', () => {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-assets/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-assets/',
vite: {
build: {
assetsInlineLimit: 0
}
}
});
await fixture.build(); await fixture.build();
}); });
@ -19,7 +26,7 @@ describe('Assets', () => {
const html = await fixture.readFile('/index.html'); const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html); const $ = cheerio.load(html);
const imgPath = $('img').attr('src'); const imgPath = $('img').attr('src');
const data = await fixture.readFile('/' + imgPath); const data = await fixture.readFile( imgPath);
expect(!!data).to.equal(true); expect(!!data).to.equal(true);
}); });
@ -29,7 +36,7 @@ describe('Assets', () => {
const srcset = $('img').attr('srcset'); const srcset = $('img').attr('srcset');
const candidates = matchSrcset(srcset); const candidates = matchSrcset(srcset);
const match = candidates.find((a) => a.density === 2); const match = candidates.find((a) => a.density === 2);
const data = await fixture.readFile('/' + match.url); const data = await fixture.readFile(match.url);
expect(!!data).to.equal(true); expect(!!data).to.equal(true);
}); });
@ -39,14 +46,14 @@ describe('Assets', () => {
const srcset = $('img').attr('srcset'); const srcset = $('img').attr('srcset');
const candidates = matchSrcset(srcset); const candidates = matchSrcset(srcset);
const match = candidates.find((a) => a.density === 3); const match = candidates.find((a) => a.density === 3);
const data = await fixture.readFile('/' + match.url); const data = await fixture.readFile(match.url);
expect(!!data).to.equal(true); expect(!!data).to.equal(true);
}); });
it('built image from an import specifier', async () => { it('built image from an import specifier', async () => {
const html = await fixture.readFile('/index.html'); const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html); const $ = cheerio.load(html);
const src = '/' + $('#import-no-url').attr('src'); const src = $('#import-no-url').attr('src');
const data = await fixture.readFile(src); const data = await fixture.readFile(src);
expect(!!data).to.equal(true); expect(!!data).to.equal(true);
}); });
@ -54,7 +61,7 @@ describe('Assets', () => {
it('built image from an import specifier using ?url', async () => { it('built image from an import specifier using ?url', async () => {
const html = await fixture.readFile('/index.html'); const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html); const $ = cheerio.load(html);
const src = '/' + $('#import-url').attr('src'); const src = $('#import-url').attr('src');
const data = await fixture.readFile(src); const data = await fixture.readFile(src);
expect(!!data).to.equal(true); expect(!!data).to.equal(true);
}); });

View file

@ -7,7 +7,9 @@ describe('Astro basics', () => {
let previewServer; let previewServer;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-basic/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-basic/',
});
await fixture.build(); await fixture.build();
previewServer = await fixture.preview(); previewServer = await fixture.preview();
}); });

View file

@ -6,7 +6,9 @@ describe('Client only components', () => {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-client-only/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-client-only/',
});
await fixture.build(); await fixture.build();
}); });
@ -16,19 +18,10 @@ describe('Client only components', () => {
// test 1: <astro-root> is empty // test 1: <astro-root> is empty
expect($('astro-root').html()).to.equal(''); expect($('astro-root').html()).to.equal('');
const src = $('script').attr('src'); const $script = $('script');
const script = $script.html();
const script = await fixture.readFile(src);
// test 2: svelte renderer is on the page // test 2: svelte renderer is on the page
const exp = /import\("(.\/client.*)"\)/g; expect(/import\(".\/PersistentCounter.*/g.test(script)).to.be.ok;
let match, svelteRenderer;
while ((match = exp.exec(script))) {
svelteRenderer = match[1].replace(/^\./, '/assets/');
}
expect(svelteRenderer).to.be.ok;
// test 3: can load svelte renderer
const svelteClient = await fixture.readFile(svelteRenderer);
expect(svelteClient).to.be.ok;
}); });
}); });

View file

@ -1,56 +0,0 @@
/**
* UNCOMMENT: add support for functional components in frontmatter
import { expect } from 'chai';
import cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
let fixture;
before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-components/' });
await fixture.build();
});
// TODO: add support for functional components in frontmatter
describe('Components tests', () => {
it('Astro components are able to render framework components', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
// test 1: Renders Astro component
const $astro = $('#astro');
expect($astro.children()).to.have.lengthOf(3);
// test 2: Renders React component
const $react = $('#react');
expect($react).not.to.have.lengthOf(0);
// test 3: Renders Vue component
const $vue = $('#vue');
expect($vue).not.to.have.lengthOf(0);
// test 4: Renders Svelte component
const $svelte = $('#svelte');
expect($svelte).not.to.have.lengthOf(0);
});
it('Allows Components defined in frontmatter', async () => {
const html = await fixture.readFile('/frontmatter-component/index.html');
const $ = cheerio.load(html);
expect($('h1')).to.have.lengthOf(1);
});
it('Still throws an error for undefined components', async () => {
const result = await fixture.readFile('/undefined-component/index.html');
expect(result.status).to.equal(500);
});
it('Client attrs not added', async () => {
const html = await fixture.readFile('/client/index.html');
expect(html).not.to.include(`"client:load": true`);
});
});
*/
it.skip('is skipped', () => {});

View file

@ -6,7 +6,9 @@ describe('CSS Bundling (ESM import)', () => {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-css-bundling-import/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-css-bundling-import/',
});
await fixture.build(); await fixture.build();
}); });
@ -32,8 +34,7 @@ describe('CSS Bundling (ESM import)', () => {
expect(css.indexOf('p{color:green}')).to.be.greaterThan(css.indexOf('p{color:#00f}')); expect(css.indexOf('p{color:green}')).to.be.greaterThan(css.indexOf('p{color:#00f}'));
}); });
// TODO: re-enable this it('no empty CSS files', async () => {
it.skip('no empty CSS files', async () => {
for (const page of ['/page-1/index.html', '/page-2/index.html']) { for (const page of ['/page-1/index.html', '/page-2/index.html']) {
const html = await fixture.readFile(page); const html = await fixture.readFile(page);
const $ = cheerio.load(html); const $ = cheerio.load(html);

View file

@ -1,38 +0,0 @@
import { expect } from 'chai';
import cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
describe('nested layouts', () => {
let fixture;
before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-css-bundling-nested-layouts/' });
await fixture.build();
});
it('page-1 has all CSS', async () => {
const html = await fixture.readFile('/page-1/index.html');
const $ = cheerio.load(html);
const stylesheets = $('link[rel=stylesheet]')
.toArray()
.map((el) => el.attribs.href);
// page-one.[hash].css exists
expect(stylesheets.some((href) => /page-one\.\w+\.css/.test(href))).to.be.true;
});
it('page-2 has all CSS', async () => {
const html = await fixture.readFile('/page-2/index.html');
const $ = cheerio.load(html);
const stylesheets = $('link[rel=stylesheet]')
.toArray()
.map((el) => el.attribs.href);
// page-one.[hash].css exists
expect(stylesheets.some((href) => /page-one\.\w+\.css/.test(href))).to.be.true;
// page-2.[hash].css exists
expect(stylesheets.some((href) => /page-2\.\w+\.css/.test(href))).to.be.true;
});
});

View file

@ -5,11 +5,9 @@ import { loadFixture } from './test-utils.js';
// note: the hashes should be deterministic, but updating the file contents will change hashes // note: the hashes should be deterministic, but updating the file contents will change hashes
// be careful not to test that the HTML simply contains CSS, because it always will! filename and quanity matter here (bundling). // be careful not to test that the HTML simply contains CSS, because it always will! filename and quanity matter here (bundling).
const EXPECTED_CSS = { const EXPECTED_CSS = {
'/index.html': ['./assets/index', './assets/typography'], // dont match hashes, which change based on content '/index.html': ['/assets/index'], // dont match hashes, which change based on content
'/one/index.html': ['../assets/one'], '/one/index.html': ['/assets/one'],
'/two/index.html': ['../assets/two', '../assets/typography'], '/two/index.html': ['/assets/two'],
'/preload/index.html': ['../assets/preload'],
'/preload-merge/index.html': ['../assets/preload-merge'],
}; };
const UNEXPECTED_CSS = ['/src/components/nav.css', '../css/typography.css', '../css/colors.css', '../css/page-index.css', '../css/page-one.css', '../css/page-two.css']; const UNEXPECTED_CSS = ['/src/components/nav.css', '../css/typography.css', '../css/colors.css', '../css/page-index.css', '../css/page-one.css', '../css/page-two.css'];
@ -17,7 +15,9 @@ describe('CSS Bundling', function () {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-css-bundling/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-css-bundling/',
});
await fixture.build({ mode: 'production' }); await fixture.build({ mode: 'production' });
}); });
@ -43,21 +43,7 @@ describe('CSS Bundling', function () {
expect(link).to.have.lengthOf(0); expect(link).to.have.lengthOf(0);
} }
// test 3: preload tags was not removed and attributes was preserved // test 3: assert all bundled CSS was built and contains CSS
if (filepath === '/preload/index.html') {
const stylesheet = $('link[rel="stylesheet"][href^="../assets/preload"]');
const preload = $('link[rel="preload"][href^="../assets/preload"]');
expect(stylesheet[0].attribs.media).to.equal('print');
expect(preload).to.have.lengthOf(1); // Preload tag was removed
}
// test 4: preload tags was not removed and attributes was preserved
if (filepath === '/preload-merge/index.html') {
const preload = $('link[rel="preload"]');
expect(preload).to.have.lengthOf(1);
}
// test 5: assert all bundled CSS was built and contains CSS
for (const url of builtCSS.keys()) { for (const url of builtCSS.keys()) {
const css = await fixture.readFile(url); const css = await fixture.readFile(url);
expect(css).to.be.ok; expect(css).to.be.ok;

View file

@ -6,7 +6,9 @@ describe('Dynamic components', () => {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-dynamic/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-dynamic/',
});
await fixture.build(); await fixture.build();
}); });
@ -14,7 +16,7 @@ describe('Dynamic components', () => {
const html = await fixture.readFile('/index.html'); const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html); const $ = cheerio.load(html);
expect($('script').length).to.eq(1); expect($('script').length).to.eq(2);
}); });
it('Loads pages using client:media hydrator', async () => { it('Loads pages using client:media hydrator', async () => {
@ -23,11 +25,7 @@ describe('Dynamic components', () => {
const $ = cheerio.load(html); const $ = cheerio.load(html);
// test 1: static value rendered // test 1: static value rendered
let js = await fixture.readFile(new URL($('script').attr('src'), root).pathname); expect($('script').length).to.equal(2); // One for each
expect(js).to.include(`value:"(max-width: 700px)"`);
// test 2: dynamic value rendered
expect(js).to.include(`value:"(max-width: 600px)"`);
}); });
it('Loads pages using client:only hydrator', async () => { it('Loads pages using client:only hydrator', async () => {

View file

@ -5,7 +5,9 @@ describe('Environment Variables', () => {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-envs/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-envs/',
});
await fixture.build(); await fixture.build();
}); });
@ -22,7 +24,7 @@ describe('Environment Variables', () => {
}); });
it('includes public env in client-side JS', async () => { it('includes public env in client-side JS', async () => {
let dirs = await fixture.readdir('/assets'); let dirs = await fixture.readdir('/');
let found = false; let found = false;
// Look in all of the .js files to see if the public env is inlined. // Look in all of the .js files to see if the public env is inlined.
@ -31,7 +33,7 @@ describe('Environment Variables', () => {
await Promise.all( await Promise.all(
dirs.map(async (path) => { dirs.map(async (path) => {
if (path.endsWith('.js')) { if (path.endsWith('.js')) {
let js = await fixture.readFile(`/assets/${path}`); let js = await fixture.readFile(`/${path}`);
if (js.includes('BLUE_BAYOU')) { if (js.includes('BLUE_BAYOU')) {
found = true; found = true;
} }
@ -43,7 +45,7 @@ describe('Environment Variables', () => {
}); });
it('does not include private env in client-side JS', async () => { it('does not include private env in client-side JS', async () => {
let dirs = await fixture.readdir('/assets'); let dirs = await fixture.readdir('/');
let found = false; let found = false;
// Look in all of the .js files to see if the public env is inlined. // Look in all of the .js files to see if the public env is inlined.
@ -52,7 +54,7 @@ describe('Environment Variables', () => {
await Promise.all( await Promise.all(
dirs.map(async (path) => { dirs.map(async (path) => {
if (path.endsWith('.js')) { if (path.endsWith('.js')) {
let js = await fixture.readFile(`/assets/${path}`); let js = await fixture.readFile(`/${path}`);
if (js.includes('CLUB_33')) { if (js.includes('CLUB_33')) {
found = true; found = true;
} }

View file

@ -10,7 +10,7 @@ describe('Astro.*', () => {
projectRoot: './fixtures/astro-global/', projectRoot: './fixtures/astro-global/',
buildOptions: { buildOptions: {
site: 'https://mysite.dev/blog/', site: 'https://mysite.dev/blog/',
sitemap: false, sitemap: false
}, },
}); });
await fixture.build(); await fixture.build();
@ -48,13 +48,6 @@ describe('Astro.*', () => {
expect($('#site').attr('href')).to.equal('https://mysite.dev/blog/'); expect($('#site').attr('href')).to.equal('https://mysite.dev/blog/');
}); });
it('Astro.resolve built', async () => {
const html = await fixture.readFile('/resolve/index.html');
const $ = cheerio.load(html);
expect($('img').attr('src')).to.include('assets/penguin.ccd44411.png'); // Main src/images
expect($('#inner-child img').attr('src')).to.include('assets/penguin.b9ab122a.png');
});
it('Astro.fetchContent() returns the correct "url" property, including buildOptions.site subpath', async () => { it('Astro.fetchContent() returns the correct "url" property, including buildOptions.site subpath', async () => {
const html = await fixture.readFile('/posts/1/index.html'); const html = await fixture.readFile('/posts/1/index.html');
const $ = cheerio.load(html); const $ = cheerio.load(html);

View file

@ -7,7 +7,9 @@ describe('Partial HTML ', async () => {
let devServer; let devServer;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-partial-html/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-partial-html/',
});
devServer = await fixture.startDevServer(); devServer = await fixture.startDevServer();
}); });
@ -23,7 +25,12 @@ describe('Partial HTML ', async () => {
expect(html).to.match(/^<!DOCTYPE html/); expect(html).to.match(/^<!DOCTYPE html/);
// test 2: correct CSS present // test 2: correct CSS present
const css = $('style[astro-style]').html(); const link = $('link').attr('href');
const css = await fixture.fetch(link, {
headers: {
accept: 'text/css'
}
}).then(res => res.text());
expect(css).to.match(/\.astro-[^{]+{color:red;}/); expect(css).to.match(/\.astro-[^{]+{color:red;}/);
}); });

View file

@ -7,7 +7,14 @@ describe('Scripts (hoisted and not)', () => {
let fixture; let fixture;
before(async () => { before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/astro-scripts/' }); fixture = await loadFixture({
projectRoot: './fixtures/astro-scripts/',
vite: {
build: {
assetsInlineLimit: 0
}
}
});
await fixture.build(); await fixture.build();
}); });
@ -38,8 +45,8 @@ describe('Scripts (hoisted and not)', () => {
// test 2: attr removed // test 2: attr removed
expect($('script').attr('data-astro')).to.equal(undefined); expect($('script').attr('data-astro')).to.equal(undefined);
let entryURL = path.join('inline', $('script').attr('src')); const entryURL = $('script').attr('src');
let inlineEntryJS = await fixture.readFile(entryURL); const inlineEntryJS = await fixture.readFile(entryURL);
// test 3: the JS exists // test 3: the JS exists
expect(inlineEntryJS).to.be.ok; expect(inlineEntryJS).to.be.ok;
@ -53,7 +60,7 @@ describe('Scripts (hoisted and not)', () => {
expect($('script')).to.have.lengthOf(2); expect($('script')).to.have.lengthOf(2);
let el = $('script').get(1); let el = $('script').get(1);
let entryURL = path.join('external', $(el).attr('src')); let entryURL = $(el).attr('src');
let externalEntryJS = await fixture.readFile(entryURL); let externalEntryJS = await fixture.readFile(entryURL);
// test 2: the JS exists // test 2: the JS exists

View file

@ -1,27 +0,0 @@
import { expect } from 'chai';
import cheerio from 'cheerio';
import { loadFixture } from './test-utils.js';
describe('Node builtins', () => {
let fixture;
before(async () => {
fixture = await loadFixture({ projectRoot: './fixtures/builtins/' });
await fixture.build();
});
it('Can be used with the node: prefix', async () => {
const html = await fixture.readFile('/index.html');
const $ = cheerio.load(html);
expect($('#version').text()).to.equal('1.2.0');
expect($('#dep-version').text()).to.equal('0.1.0');
});
it('Can also be used with the non-prefixed version', async () => {
const html = await fixture.readFile('/bare/index.html');
const $ = cheerio.load(html);
expect($('h1').text()).to.equal('true');
});
});

View file

@ -1,51 +0,0 @@
import { expect } from 'chai';
import { cli } from './test-utils.js';
import { promises as fs } from 'fs';
import { fileURLToPath } from 'url';
describe('astro cli', () => {
it('astro', async () => {
const proc = await cli();
expect(proc.stdout).to.include('astro - Futuristic web development tool');
});
it('astro --version', async () => {
const pkgURL = new URL('../package.json', import.meta.url);
const pkgVersion = await fs.readFile(pkgURL, 'utf8').then((data) => JSON.parse(data).version);
const proc = await cli('--version');
expect(proc.stdout).to.equal(pkgVersion);
});
[undefined, '0.0.0.0', '127.0.0.1'].forEach((hostname) => {
it(`astro dev --hostname=${hostname}`, async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const hostnameArgs = hostname ? ['--hostname', hostname] : [];
const proc = cli('dev', '--project-root', fileURLToPath(projectRootURL), ...hostnameArgs);
let stdout = '';
for await (const chunk of proc.stdout) {
stdout += chunk;
if (chunk.includes('Local:')) break;
}
proc.kill();
expect(stdout).to.include('Local: http://localhost:3000');
expect(stdout).to.include(`Network: http://${hostname ?? '127.0.0.1'}:3000`);
});
});
it('astro build', async () => {
const projectRootURL = new URL('./fixtures/astro-basic/', import.meta.url);
const proc = await cli('build', '--project-root', fileURLToPath(projectRootURL));
expect(proc.stdout).to.include('Done');
});
});

View file

@ -47,8 +47,8 @@ describe('Custom Elements', () => {
expect($('my-element template[shadowroot=open]')).to.have.lengthOf(1); expect($('my-element template[shadowroot=open]')).to.have.lengthOf(1);
// Hydration // Hydration
// test 3: Component and polyfill scripts bundled together // test 3: Component and polyfill scripts bundled separately
expect($('script[type=module]')).to.have.lengthOf(1); expect($('script[type=module]')).to.have.lengthOf(2);
}); });
it('Polyfills are added even if not hydrating', async () => { it('Polyfills are added even if not hydrating', async () => {

View file

@ -19,7 +19,7 @@ import VueScoped from '../components/VueScoped.vue';
import VueScss from '../components/VueScss.vue'; import VueScss from '../components/VueScss.vue';
import ReactDynamic from '../components/ReactDynamic.jsx'; import ReactDynamic from '../components/ReactDynamic.jsx';
import importedUrl from '../styles/imported-url.css?url'; import '../styles/imported-url.css';
--- ---
<html> <html>
@ -36,10 +36,15 @@ import importedUrl from '../styles/imported-url.css?url';
} }
</style> </style>
<link rel="stylesheet" type="text/css" href="/global.css"> <link rel="stylesheet" type="text/css" href="/global.css">
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.css')}> <style>
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.scss')}> @import '../styles/linked.css';
<link rel="stylesheet" type="text/css" href={Astro.resolve('../styles/linked.sass')}> </style>
<link rel="stylesheet" type="text/css" href={importedUrl}> <style lang="scss">
@import '../styles/linked.scss';
</style>
<style lang="sass">
@import '../styles/linked.sass'
</style>
</head> </head>
<body> <body>
<div class="wrapper"> <div class="wrapper">

View file

@ -9,7 +9,7 @@ import p2Url from '../images/penguin2.jpg?url';
</style> </style>
<body> <body>
<h1>Icons</h1> <h1>Icons</h1>
<img src={Astro.resolve('../images/twitter.png')} srcset={`${Astro.resolve('../images/twitter.png')} 1x, ${Astro.resolve('../images/twitter@2x.png')} 2x, ${Astro.resolve('../images/twitter@3x.png')} 3x`} /> <img src={(await import('../images/twitter.png')).default} srcset={`${(await import('../images/twitter.png')).default} 1x, ${(await import('../images/twitter@2x.png')).default} 2x, ${(await import('../images/twitter@3x.png')).default} 3x`} />
<img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 600w, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 800w"> <img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 600w, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 800w">
<img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 1.5x, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 2x"> <img srcset="https://ik.imagekit.io/demo/tr:w-300,h-300/medium_cafe_B1iTdD0C.jpg, https://ik.imagekit.io/demo/tr:w-450,h-450/medium_cafe_B1iTdD0C.jpg 1.5x, https://ik.imagekit.io/demo/tr:w-600,h-600/medium_cafe_B1iTdD0C.jpg 2x">
<!-- <!--

View file

@ -1,11 +0,0 @@
---
import ReactComponent from './Component.jsx';
import VueComponent from './Component.vue';
import SvelteComponent from './Component.svelte';
---
<div id="astro">
<ReactComponent />
<VueComponent />
<SvelteComponent />
</div>

View file

@ -1,5 +0,0 @@
import { h } from 'preact';
export default function PreactComponent({ children }) {
return <div id="preact">{children}</div>;
}

View file

@ -1,3 +0,0 @@
<div id="svelte">
<slot />
</div>

View file

@ -1,9 +0,0 @@
<template>
<div id="vue">
<slot />
</div>
</template>
<script>
export default {}
</script>

View file

@ -1,6 +0,0 @@
---
export interface Props {
test: string;
}
---
<h1 id="direct-props-h1">{props.test}</h1>

View file

@ -1,9 +0,0 @@
---
import SvelteComponent from '../components/Component.svelte';
---
<html>
<head><title>Components</title></head>
<body>
<SvelteComponent client:load />
</body>
</html>

View file

@ -1,6 +0,0 @@
---
const { level = 1 } = Astro.props;
const Element = `h${level}`;
---
<Element>Hello world!</Element>

View file

@ -1,9 +0,0 @@
---
import AstroComponent from '../components/Component.astro';
---
<html>
<head><title>Components</title></head>
<body>
<AstroComponent />
</body>
</html>

View file

@ -1,4 +0,0 @@
---
import PropsComponent from "../components/Props-Component.astro"
---
<PropsComponent test="test string"/>

View file

@ -1,11 +0,0 @@
---
function FnComponent() {
const lame = 'ugh';
return <h2>Hey</h2>
}
const Defined = 'h1'
---
<FnComponent />
<Defined>Hello world!</Defined>
<Undefined />

View file

@ -1,27 +0,0 @@
---
import "../styles/site.css"
const {title} = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
<style>
</style>
</head>
<body>
<ul>
<li><a href="/page-1">Page 1</a></li>
<li><a href="/page-2">Page 2</a></li>
<!-- <li><a href="/page-2-reduced-layout">Page 2 reduced layout</a></li> -->
</ul>
<slot></slot>
</body>
</html>

View file

@ -1,12 +0,0 @@
---
import BaseLayout from "./BaseLayout.astro"
import "../styles/page-one.css"
const {title} = Astro.props;
---
<BaseLayout title={title}>
<main id="page">
<slot></slot>
</main>
</BaseLayout>

View file

@ -1,15 +0,0 @@
---
import PageLayout from "../layouts/PageLayout.astro"
const date = new Date();
---
<PageLayout title="Page 1">
<h1>Page 1</h1>
<p>This page has styling in dev-server. But the built page has no styling. </p>
<p>Check <code>dist/page-1/index.html</code>. There are no stylesheets imported.</p>
<p>Additionally, there is an empty js file in the <code>dist/assets</code> folder. Thankfully the file is not required by any page.</p>
<p>Execute the build <code>npm run build</code> and preview it <code>npx http-server dist/</code> at <a href="https://github-qoihup--8080.local.webcontainer.io/page-1/">https://github-qoihup--8080.local.webcontainer.io/page-1/</a></p>
<p>Date: {date}</p>
</PageLayout>

View file

@ -1,9 +0,0 @@
---
import PageLayout from "../layouts/PageLayout.astro"
import "../styles/page-two.css"
---
<PageLayout title="Page 2">
<h1>Page 2</h1>
<p>Nothing to see here. Check <a href="/page-1">Page 1</a></p>
</PageLayout>

View file

@ -1,3 +0,0 @@
p {
color: blue;
}

View file

@ -1,3 +0,0 @@
p {
color: green;
}

View file

@ -1,7 +0,0 @@
p {
color: red;
}
h1 {
outline: 1px solid red;
}

View file

@ -4,9 +4,11 @@ import Nav from '../components/Nav.astro';
<html> <html>
<head> <head>
<link rel="stylesheet" href={Astro.resolve('../css/typography.css')}> <style>
<link rel="stylesheet" href={Astro.resolve('../css/colors.css')}> @import '../css/typography.css';
<link rel="stylesheet" href={Astro.resolve('../css/page-index.css')}> @import '../css/colors.css';
@import '../css/page-index.css';
</style>
</head> </head>
<body> <body>
<Nav /> <Nav />

View file

@ -4,8 +4,10 @@ import Nav from '../components/Nav.astro';
<html> <html>
<head> <head>
<link rel="stylesheet" href={Astro.resolve('../css/typography.css')} /> <style>
<link rel="stylesheet" href={Astro.resolve('../css/page-one.css')} /> @import '../css/typography.css';
@import '../css/page-one.css';
</style>
</head> </head>
<body> <body>
<Nav /> <Nav />

View file

@ -1,17 +0,0 @@
---
import Nav from '../components/Nav.astro';
---
<html>
<head>
<link rel="preload" as="style" href={Astro.resolve('../css/page-preload-merge.css')} />
<link rel="preload" as="style" href={Astro.resolve('../css/page-preload-merge-2.css')} />
<link rel="stylesheet" href={Astro.resolve('../css/page-preload-merge.css')} />
<link rel="stylesheet" href={Astro.resolve('../css/page-preload-merge-2.css')} />
</head>
<body>
<Nav />
<h1>Preload merge page</h1>
</body>
</html>

View file

@ -1,14 +0,0 @@
---
import Nav from '../components/Nav.astro';
---
<html>
<head>
<link rel="preload" as="style" href={Astro.resolve('../css/page-preload.css')} />
<link rel="stylesheet" href={Astro.resolve('../css/page-preload.css')} media="print" onload="this.media='all'" />
</head>
<body>
<Nav />
<h1>Preload page</h1>
</body>
</html>

View file

@ -4,9 +4,11 @@ import Nav from '../components/Nav.astro';
<html> <html>
<head> <head>
<link rel="stylesheet" href={Astro.resolve('../css/typography.css')} /> <style>
<link rel="stylesheet" href={Astro.resolve('../css/colors.css')} /> @import '../css/typography.css';
<link rel="stylesheet" href={Astro.resolve('../css/page-two.css')} /> @import '../css/colors.css';
@import '../css/page-two.css';
</style>
</head> </head>
<body> <body>
<Nav /> <Nav />

View file

@ -1,5 +0,0 @@
---
const penguinUrl = Astro.resolve('../images/penguin.png');
---
<img src={penguinUrl} />
<img src="../images/penguin.png" />

View file

@ -1,6 +0,0 @@
---
const penguinUrl = Astro.resolve('./images/penguin.png');
---
<div id="inner-child">
<img src={penguinUrl} />
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

View file

@ -1,14 +0,0 @@
---
import Child from '../components/ChildResolve.astro';
import InnerChild from '../components/nested/InnerChild.astro';
---
<html>
<head>
<title>Testing</title>
</head>
<body>
<Child />
<InnerChild />
</body>
</html>

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