diff --git a/.changeset/breezy-frogs-learn.md b/.changeset/breezy-frogs-learn.md
new file mode 100644
index 000000000..b3f2f86b9
--- /dev/null
+++ b/.changeset/breezy-frogs-learn.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/cloudflare': minor
+---
+
+More efficient \_routes.json
diff --git a/.changeset/dry-pandas-flash.md b/.changeset/dry-pandas-flash.md
deleted file mode 100644
index fb18de65d..000000000
--- a/.changeset/dry-pandas-flash.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'create-astro': patch
----
-
-Add support for more Starlight templates
diff --git a/.changeset/funny-glasses-bathe.md b/.changeset/funny-glasses-bathe.md
deleted file mode 100644
index 28db2f746..000000000
--- a/.changeset/funny-glasses-bathe.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Fixed `EndpointOutput` types with `{ encoding: 'binary' }`
diff --git a/.changeset/great-icons-turn.md b/.changeset/great-icons-turn.md
deleted file mode 100644
index c3d937f91..000000000
--- a/.changeset/great-icons-turn.md
+++ /dev/null
@@ -1,5 +0,0 @@
----
-'astro': patch
----
-
-Fix quadratic quote escaping in nested data in island props
diff --git a/.changeset/wild-jobs-tan.md b/.changeset/wild-jobs-tan.md
new file mode 100644
index 000000000..8372c01b1
--- /dev/null
+++ b/.changeset/wild-jobs-tan.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Move hoisted script analysis optimization behind the `experimental.optimizeHoistedScript` option
diff --git a/examples/basics/package.json b/examples/basics/package.json
index 5c0958016..1fd8bec2c 100644
--- a/examples/basics/package.json
+++ b/examples/basics/package.json
@@ -11,6 +11,6 @@
"astro": "astro"
},
"dependencies": {
- "astro": "^2.10.3"
+ "astro": "^2.10.4"
}
}
diff --git a/examples/blog/src/content/blog/markdown-style-guide.md b/examples/blog/src/content/blog/markdown-style-guide.md
index 58a03467d..666559d2d 100644
--- a/examples/blog/src/content/blog/markdown-style-guide.md
+++ b/examples/blog/src/content/blog/markdown-style-guide.md
@@ -31,18 +31,44 @@ Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sap
## Images
-
+#### Syntax
+
+```markdown
+
+```
+
+#### Output
+
+
## Blockquotes
The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations.
-#### Blockquote without attribution
+### Blockquote without attribution
+
+#### Syntax
+
+```markdown
+> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
+> **Note** that you can use _Markdown syntax_ within a blockquote.
+```
+
+#### Output
> Tiam, ad mint andaepu dandae nostion secatur sequo quae.
> **Note** that you can use _Markdown syntax_ within a blockquote.
-#### Blockquote with attribution
+### Blockquote with attribution
+
+#### Syntax
+
+```markdown
+> Don't communicate by sharing memory, share memory by communicating.
+> — Rob Pike[^1]
+```
+
+#### Output
> Don't communicate by sharing memory, share memory by communicating.
> — Rob Pike[^1]
@@ -51,12 +77,43 @@ The blockquote element represents content that is quoted from another source, op
## Tables
+#### Syntax
+
+```markdown
+| Italics | Bold | Code |
+| --------- | -------- | ------ |
+| _italics_ | **bold** | `code` |
+```
+
+#### Output
+
| Italics | Bold | Code |
| --------- | -------- | ------ |
| _italics_ | **bold** | `code` |
## Code Blocks
+#### Syntax
+
+we can use 3 backticks ``` in new line and write snippet and close with 3 backticks on new line and to highlight language specific syntac, write one word of language name after first 3 backticks, for eg. html, javascript, css, markdown, typescript, txt, bash
+
+````markdown
+```html
+
+
+
+
+ Example HTML5 Document
+
+
+ Test
+
+
+```
+````
+
+Output
+
```html
@@ -72,19 +129,53 @@ The blockquote element represents content that is quoted from another source, op
## List Types
-#### Ordered List
+### Ordered List
+
+#### Syntax
+
+```markdown
+1. First item
+2. Second item
+3. Third item
+```
+
+#### Output
1. First item
2. Second item
3. Third item
-#### Unordered List
+### Unordered List
+
+#### Syntax
+
+```markdown
+- List item
+- Another item
+- And another item
+```
+
+#### Output
- List item
- Another item
- And another item
-#### Nested list
+### Nested list
+
+#### Syntax
+
+```markdown
+- Fruit
+ - Apple
+ - Orange
+ - Banana
+- Dairy
+ - Milk
+ - Cheese
+```
+
+#### Output
- Fruit
- Apple
@@ -96,6 +187,22 @@ The blockquote element represents content that is quoted from another source, op
## Other Elements — abbr, sub, sup, kbd, mark
+#### Syntax
+
+```markdown
+GIF is a bitmap image format.
+
+H2O
+
+Xn + Yn = Zn
+
+Press CTRL+ALT+Delete to end the session.
+
+Most salamanders are nocturnal, and hunt for insects, worms, and other small creatures.
+```
+
+#### Output
+
GIF is a bitmap image format.
H2O
diff --git a/packages/astro/CHANGELOG.md b/packages/astro/CHANGELOG.md
index 1ed5c949a..62fa23502 100644
--- a/packages/astro/CHANGELOG.md
+++ b/packages/astro/CHANGELOG.md
@@ -219,6 +219,16 @@
- @astrojs/internal-helpers@0.2.0-beta.0
- @astrojs/markdown-remark@3.0.0-beta.0
+## 2.10.4
+
+### Patch Changes
+
+- [#8003](https://github.com/withastro/astro/pull/8003) [`16161afb2`](https://github.com/withastro/astro/commit/16161afb2b3a04ca7605fcd16de06efe3fabdef2) Thanks [@JuanM04](https://github.com/JuanM04)! - Fixed `EndpointOutput` types with `{ encoding: 'binary' }`
+
+- [#7995](https://github.com/withastro/astro/pull/7995) [`79376f842`](https://github.com/withastro/astro/commit/79376f842d25edfe4dc2948548e99b59e1c4d24f) Thanks [@belluzj](https://github.com/belluzj)! - Fix quadratic quote escaping in nested data in island props
+
+- [#8007](https://github.com/withastro/astro/pull/8007) [`58b121d42`](https://github.com/withastro/astro/commit/58b121d42a9f58a5a992f0c378b036f37e9715fc) Thanks [@paperdave](https://github.com/paperdave)! - Support Bun by adjusting how `@babel/plugin-transform-react-jsx` is imported.
+
## 2.10.3
### Patch Changes
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index f7b07dbfe..4867728b1 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1271,6 +1271,28 @@ export interface AstroUserConfig {
* ```
*/
viewTransitions?: boolean;
+
+ /**
+ * @docs
+ * @name experimental.optimizeHoistedScript
+ * @type {boolean}
+ * @default `false`
+ * @version 2.10.4
+ * @description
+ * Prevents unused components' scripts from being included in a page unexpectedly.
+ * The optimization is best-effort and may inversely miss including the used scripts. Make sure to double-check your built pages
+ * before publishing.
+ * Enable hoisted script analysis optimization by adding the experimental flag:
+ *
+ * ```js
+ * {
+ * experimental: {
+ * optimizeHoistedScript: true,
+ * },
+ * }
+ * ```
+ */
+ optimizeHoistedScript?: boolean;
};
// Legacy options to be removed
diff --git a/packages/astro/src/core/build/plugins/index.ts b/packages/astro/src/core/build/plugins/index.ts
index 3a44824d6..decfefd04 100644
--- a/packages/astro/src/core/build/plugins/index.ts
+++ b/packages/astro/src/core/build/plugins/index.ts
@@ -16,7 +16,7 @@ import { pluginSSR, pluginSSRSplit } from './plugin-ssr.js';
export function registerAllPlugins({ internals, options, register }: AstroBuildPluginContainer) {
register(pluginComponentEntry(internals));
register(pluginAliasResolve(internals));
- register(pluginAnalyzer(internals));
+ register(pluginAnalyzer(options, internals));
register(pluginInternals(internals));
register(pluginRenderers(options));
register(pluginMiddleware(options, internals));
diff --git a/packages/astro/src/core/build/plugins/plugin-analyzer.ts b/packages/astro/src/core/build/plugins/plugin-analyzer.ts
index b62de1a2b..bb632eb49 100644
--- a/packages/astro/src/core/build/plugins/plugin-analyzer.ts
+++ b/packages/astro/src/core/build/plugins/plugin-analyzer.ts
@@ -6,11 +6,11 @@ import type { BuildInternals } from '../internal.js';
import type { AstroBuildPlugin } from '../plugin.js';
import type { ExportDefaultDeclaration, ExportNamedDeclaration, ImportDeclaration } from 'estree';
-import { walk } from 'estree-walker';
import { PROPAGATED_ASSET_FLAG } from '../../../content/consts.js';
import { prependForwardSlash } from '../../../core/path.js';
import { getTopLevelPages, moduleIsTopLevelPage, walkParentInfos } from '../graph.js';
import { getPageDataByViteID, trackClientOnlyPageDatas } from '../internal.js';
+import type { StaticBuildOptions } from '../types.js';
function isPropagatedAsset(id: string) {
try {
@@ -31,29 +31,28 @@ async function doesParentImportChild(
): Promise<'no' | 'dynamic' | string[]> {
if (!childInfo || !parentInfo.ast || !childExportNames) return 'no';
+ // If we're dynamically importing the child, return `dynamic` directly to opt-out of optimization
if (childExportNames === 'dynamic' || parentInfo.dynamicallyImportedIds?.includes(childInfo.id)) {
return 'dynamic';
}
const imports: Array = [];
const exports: Array = [];
- walk(parentInfo.ast as ESTreeNode, {
- enter(node) {
- if (node.type === 'ImportDeclaration') {
- imports.push(node as ImportDeclaration);
- } else if (
- node.type === 'ExportDefaultDeclaration' ||
- node.type === 'ExportNamedDeclaration'
- ) {
- exports.push(node as ExportNamedDeclaration | ExportDefaultDeclaration);
- }
- },
- });
- // All of the aliases the current component is imported as
+ for (const node of (parentInfo.ast as any).body) {
+ if (node.type === 'ImportDeclaration') {
+ imports.push(node);
+ } else if (node.type === 'ExportDefaultDeclaration' || node.type === 'ExportNamedDeclaration') {
+ exports.push(node);
+ }
+ }
+
+ // All local import names that could be importing the child component
const importNames: string[] = [];
// All of the aliases the child component is exported as
const exportNames: string[] = [];
+ // Iterate each import, find it they import the child component, if so, check if they
+ // import the child component name specifically. We can verify this with `childExportNames`.
for (const node of imports) {
const resolved = await this.resolve(node.source.value as string, parentInfo.id);
if (!resolved || resolved.id !== childInfo.id) continue;
@@ -68,14 +67,17 @@ async function doesParentImportChild(
}
}
}
+
+ // Iterate each export, find it they re-export the child component, and push the exported name to `exportNames`
for (const node of exports) {
if (node.type === 'ExportDefaultDeclaration') {
if (node.declaration.type === 'Identifier' && importNames.includes(node.declaration.name)) {
exportNames.push('default');
- // return
}
} else {
- // handle `export { x } from 'something';`, where the export and import are in the same node
+ // Handle:
+ // export { Component } from './Component.astro'
+ // export { Component as AliasedComponent } from './Component.astro'
if (node.source) {
const resolved = await this.resolve(node.source.value as string, parentInfo.id);
if (!resolved || resolved.id !== childInfo.id) continue;
@@ -86,6 +88,9 @@ async function doesParentImportChild(
}
}
}
+ // Handle:
+ // export const AliasedComponent = Component
+ // export const AliasedComponent = Component, let foo = 'bar'
if (node.declaration) {
if (node.declaration.type !== 'VariableDeclaration') continue;
for (const declarator of node.declaration.declarations) {
@@ -96,6 +101,9 @@ async function doesParentImportChild(
}
}
}
+ // Handle:
+ // export { Component }
+ // export { Component as AliasedComponent }
for (const specifier of node.specifiers) {
if (importNames.includes(specifier.local.name)) {
exportNames.push(specifier.exported.name);
@@ -116,7 +124,10 @@ async function doesParentImportChild(
return exportNames;
}
-export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
+export function vitePluginAnalyzer(
+ options: StaticBuildOptions,
+ internals: BuildInternals
+): VitePlugin {
function hoistedScriptScanner() {
const uniqueHoistedIds = new Map();
const pageScripts = new Map<
@@ -140,6 +151,7 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
}
if (hoistedScripts.size) {
+ // These variables are only used for hoisted script analysis optimization
const depthsToChildren = new Map();
const depthsToExportNames = new Map();
// The component export from the original component file will always be default.
@@ -148,25 +160,28 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
for (const [parentInfo, depth] of walkParentInfos(from, this, function until(importer) {
return isPropagatedAsset(importer);
})) {
- depthsToChildren.set(depth, parentInfo);
- // If at any point
- if (depth > 0) {
- // Check if the component is actually imported:
- const childInfo = depthsToChildren.get(depth - 1);
- const childExportNames = depthsToExportNames.get(depth - 1);
+ // If hoisted script analysis optimization is enabled, try to analyse and bail early if possible
+ if (options.settings.config.experimental.optimizeHoistedScript) {
+ depthsToChildren.set(depth, parentInfo);
+ // If at any point
+ if (depth > 0) {
+ // Check if the component is actually imported:
+ const childInfo = depthsToChildren.get(depth - 1);
+ const childExportNames = depthsToExportNames.get(depth - 1);
- const doesImport = await doesParentImportChild.call(
- this,
- parentInfo,
- childInfo,
- childExportNames
- );
+ const doesImport = await doesParentImportChild.call(
+ this,
+ parentInfo,
+ childInfo,
+ childExportNames
+ );
- if (doesImport === 'no') {
- // Break the search if the parent doesn't import the child.
- continue;
+ if (doesImport === 'no') {
+ // Break the search if the parent doesn't import the child.
+ continue;
+ }
+ depthsToExportNames.set(depth, doesImport);
}
- depthsToExportNames.set(depth, doesImport);
}
if (isPropagatedAsset(parentInfo.id)) {
@@ -311,13 +326,16 @@ export function vitePluginAnalyzer(internals: BuildInternals): VitePlugin {
};
}
-export function pluginAnalyzer(internals: BuildInternals): AstroBuildPlugin {
+export function pluginAnalyzer(
+ options: StaticBuildOptions,
+ internals: BuildInternals
+): AstroBuildPlugin {
return {
build: 'ssr',
hooks: {
'build:before': () => {
return {
- vitePlugin: vitePluginAnalyzer(internals),
+ vitePlugin: vitePluginAnalyzer(options, internals),
};
},
},
diff --git a/packages/astro/src/core/config/schema.ts b/packages/astro/src/core/config/schema.ts
index 64dda0f93..59bd76b53 100644
--- a/packages/astro/src/core/config/schema.ts
+++ b/packages/astro/src/core/config/schema.ts
@@ -46,6 +46,7 @@ const ASTRO_CONFIG_DEFAULTS = {
experimental: {
assets: false,
viewTransitions: false,
+ optimizeHoistedScript: false,
},
} satisfies AstroUserConfig & { server: { open: boolean } };
@@ -245,6 +246,10 @@ export const AstroConfigSchema = z.object({
.boolean()
.optional()
.default(ASTRO_CONFIG_DEFAULTS.experimental.viewTransitions),
+ optimizeHoistedScript: z
+ .boolean()
+ .optional()
+ .default(ASTRO_CONFIG_DEFAULTS.experimental.optimizeHoistedScript),
})
.passthrough()
.refine(
diff --git a/packages/astro/src/jsx/renderer.ts b/packages/astro/src/jsx/renderer.ts
index 78ac1a0b2..39d7f5adb 100644
--- a/packages/astro/src/jsx/renderer.ts
+++ b/packages/astro/src/jsx/renderer.ts
@@ -3,10 +3,9 @@ const renderer = {
serverEntrypoint: 'astro/jsx/server.js',
jsxImportSource: 'astro',
jsxTransformOptions: async () => {
- const {
- default: { default: jsx },
- // @ts-expect-error
- } = await import('@babel/plugin-transform-react-jsx');
+ // @ts-expect-error types not found
+ const plugin = await import('@babel/plugin-transform-react-jsx');
+ const jsx = plugin.default?.default ?? plugin.default;
const { default: astroJSX } = await import('./babel.js');
return {
plugins: [
diff --git a/packages/astro/test/hoisted-imports.test.js b/packages/astro/test/hoisted-imports.test.js
index 973d7103e..c5ce98e05 100644
--- a/packages/astro/test/hoisted-imports.test.js
+++ b/packages/astro/test/hoisted-imports.test.js
@@ -8,6 +8,9 @@ describe('Hoisted Imports', () => {
before(async () => {
fixture = await loadFixture({
root: './fixtures/hoisted-imports/',
+ experimental: {
+ optimizeHoistedScript: true,
+ },
});
});
diff --git a/packages/create-astro/CHANGELOG.md b/packages/create-astro/CHANGELOG.md
index 558ae689a..76642c962 100644
--- a/packages/create-astro/CHANGELOG.md
+++ b/packages/create-astro/CHANGELOG.md
@@ -6,6 +6,12 @@
- [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023.
+## 3.1.12
+
+### Patch Changes
+
+- [#7993](https://github.com/withastro/astro/pull/7993) [`315d58f27`](https://github.com/withastro/astro/commit/315d58f27b022c9d4285cf13f445ed18c26c327e) Thanks [@delucis](https://github.com/delucis)! - Add support for more Starlight templates
+
## 3.1.11
### Patch Changes
diff --git a/packages/integrations/cloudflare/README.md b/packages/integrations/cloudflare/README.md
index 7f4292d97..45f8e01ba 100644
--- a/packages/integrations/cloudflare/README.md
+++ b/packages/integrations/cloudflare/README.md
@@ -106,7 +106,10 @@ Cloudflare has support for adding custom [headers](https://developers.cloudflare
### Custom `_routes.json`
-By default, `@astrojs/cloudflare` will generate a `_routes.json` file that lists all files from your `dist/` folder and redirects from the `_redirects` file in the `exclude` array. This will enable Cloudflare to serve files and process static redirects without a function invocation. Creating a custom `_routes.json` will override this automatic optimization and, if not configured manually, cause function invocations that will count against the request limits of your Cloudflare plan.
+By default, `@astrojs/cloudflare` will generate a `_routes.json` file with `include` and `exclude` rules based on your applications's dynamic and static routes.
+This will enable Cloudflare to serve files and process static redirects without a function invocation. Creating a custom `_routes.json` will override this automatic optimization and, if not configured manually, cause function invocations that will count against the request limits of your Cloudflare plan.
+
+See [Cloudflare's documentation](https://developers.cloudflare.com/pages/platform/functions/routing/#create-a-_routesjson-file) for more details.
## Troubleshooting
diff --git a/packages/integrations/cloudflare/src/index.ts b/packages/integrations/cloudflare/src/index.ts
index a3bb76fbb..46d87a3eb 100644
--- a/packages/integrations/cloudflare/src/index.ts
+++ b/packages/integrations/cloudflare/src/index.ts
@@ -59,6 +59,11 @@ const SHIM = `globalThis.process = {
const SERVER_BUILD_FOLDER = '/$server_build/';
+/**
+ * These route types are candiates for being part of the `_routes.json` `include` array.
+ */
+const potentialFunctionRouteTypes = ['endpoint', 'page'];
+
export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
@@ -253,6 +258,32 @@ export default function createIntegration(args?: Options): AstroIntegration {
// cloudflare to handle static files and support _redirects configuration
// (without calling the function)
if (!routesExists) {
+ const functionEndpoints = routes
+ // Certain route types, when their prerender option is set to false, a run on the server as function invocations
+ .filter((route) => potentialFunctionRouteTypes.includes(route.type) && !route.prerender)
+ .map((route) => {
+ const includePattern =
+ '/' +
+ route.segments
+ .flat()
+ .map((segment) => (segment.dynamic ? '*' : segment.content))
+ .join('/');
+
+ const regexp = new RegExp(
+ '^\\/' +
+ route.segments
+ .flat()
+ .map((segment) => (segment.dynamic ? '(.*)' : segment.content))
+ .join('\\/') +
+ '$'
+ );
+
+ return {
+ includePattern,
+ regexp,
+ };
+ });
+
const staticPathList: Array = (
await glob(`${fileURLToPath(_buildConfig.client)}/**/*`, {
cwd: fileURLToPath(_config.outDir),
@@ -260,7 +291,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
})
)
.filter((file: string) => cloudflareSpecialFiles.indexOf(file) < 0)
- .map((file: string) => `/${file}`);
+ .map((file: string) => `/${file.replace(/\\/g, '/')}`);
for (let page of pages) {
let pagePath = prependForwardSlash(page.pathname);
@@ -323,13 +354,41 @@ export default function createIntegration(args?: Options): AstroIntegration {
);
}
+ staticPathList.push(...routes.filter((r) => r.type === 'redirect').map((r) => r.route));
+
+ // In order to product the shortest list of patterns, we first try to
+ // include all function endpoints, and then exclude all static paths
+ let include = deduplicatePatterns(
+ functionEndpoints.map((endpoint) => endpoint.includePattern)
+ );
+ let exclude = deduplicatePatterns(
+ staticPathList.filter((file: string) =>
+ functionEndpoints.some((endpoint) => endpoint.regexp.test(file))
+ )
+ );
+
+ // Cloudflare requires at least one include pattern:
+ // https://developers.cloudflare.com/pages/platform/functions/routing/#limits
+ // So we add a pattern that we immediately exclude again
+ if (include.length === 0) {
+ include = ['/'];
+ exclude = ['/'];
+ }
+
+ // If using only an exclude list would produce a shorter list of patterns,
+ // we use that instead
+ if (include.length + exclude.length > staticPathList.length) {
+ include = ['/*'];
+ exclude = deduplicatePatterns(staticPathList);
+ }
+
await fs.promises.writeFile(
new URL('./_routes.json', _config.outDir),
JSON.stringify(
{
version: 1,
- include: ['/*'],
- exclude: staticPathList,
+ include,
+ exclude,
},
null,
2
@@ -344,3 +403,28 @@ export default function createIntegration(args?: Options): AstroIntegration {
function prependForwardSlash(path: string) {
return path[0] === '/' ? path : '/' + path;
}
+
+/**
+ * Remove duplicates and redundant patterns from an `include` or `exclude` list.
+ * Otherwise Cloudflare will throw an error on deployment. Plus, it saves more entries.
+ * E.g. `['/foo/*', '/foo/*', '/foo/bar'] => ['/foo/*']`
+ * @param patterns a list of `include` or `exclude` patterns
+ * @returns a deduplicated list of patterns
+ */
+function deduplicatePatterns(patterns: string[]) {
+ const openPatterns: RegExp[] = [];
+
+ return [...new Set(patterns)]
+ .sort((a, b) => a.length - b.length)
+ .filter((pattern) => {
+ if (openPatterns.some((p) => p.test(pattern))) {
+ return false;
+ }
+
+ if (pattern.endsWith('*')) {
+ openPatterns.push(new RegExp(`^${pattern.replace(/(\*\/)*\*$/g, '.*')}`));
+ }
+
+ return true;
+ });
+}
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/astro.config.mjs b/packages/integrations/cloudflare/test/fixtures/routesJson/astro.config.mjs
new file mode 100644
index 000000000..66b50c098
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/astro.config.mjs
@@ -0,0 +1,11 @@
+import { defineConfig } from 'astro/config';
+import cloudflare from '@astrojs/cloudflare';
+
+export default defineConfig({
+ adapter: cloudflare({ mode: 'directory' }),
+ output: 'hybrid',
+ redirects: {
+ '/a/redirect': '/',
+ },
+ srcDir: process.env.SRC
+});
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/package.json b/packages/integrations/cloudflare/test/fixtures/routesJson/package.json
new file mode 100644
index 000000000..4ff746f02
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/package.json
@@ -0,0 +1,9 @@
+{
+ "name": "@test/astro-cloudflare-routes-json",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "@astrojs/cloudflare": "workspace:*",
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/dynamicOnly/pages/another.astro b/packages/integrations/cloudflare/test/fixtures/routesJson/src/dynamicOnly/pages/another.astro
new file mode 100644
index 000000000..9a2306b86
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/dynamicOnly/pages/another.astro
@@ -0,0 +1,5 @@
+---
+export const prerender=false;
+---
+
+ok
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/dynamicOnly/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/routesJson/src/dynamicOnly/pages/index.astro
new file mode 100644
index 000000000..9a2306b86
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/dynamicOnly/pages/index.astro
@@ -0,0 +1,5 @@
+---
+export const prerender=false;
+---
+
+ok
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/[...rest].astro b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/[...rest].astro
new file mode 100644
index 000000000..9a2306b86
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/[...rest].astro
@@ -0,0 +1,5 @@
+---
+export const prerender=false;
+---
+
+ok
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/[id].astro b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/[id].astro
new file mode 100644
index 000000000..9a2306b86
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/[id].astro
@@ -0,0 +1,5 @@
+---
+export const prerender=false;
+---
+
+ok
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/endpoint.ts b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/endpoint.ts
new file mode 100644
index 000000000..d43d0cd2a
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/endpoint.ts
@@ -0,0 +1 @@
+export const prerender = false;
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/index.astro b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/index.astro
new file mode 100644
index 000000000..9766475a4
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/a/index.astro
@@ -0,0 +1 @@
+ok
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/b/index.html b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/b/index.html
new file mode 100644
index 000000000..9766475a4
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/pages/b/index.html
@@ -0,0 +1 @@
+ok
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/public/public.txt b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/public/public.txt
new file mode 100644
index 000000000..9766475a4
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/mixed/public/public.txt
@@ -0,0 +1 @@
+ok
diff --git a/packages/integrations/cloudflare/test/fixtures/routesJson/src/staticOnly/pages/index.astro b/packages/integrations/cloudflare/test/fixtures/routesJson/src/staticOnly/pages/index.astro
new file mode 100644
index 000000000..9766475a4
--- /dev/null
+++ b/packages/integrations/cloudflare/test/fixtures/routesJson/src/staticOnly/pages/index.astro
@@ -0,0 +1 @@
+ok
diff --git a/packages/integrations/cloudflare/test/prerender.test.js b/packages/integrations/cloudflare/test/prerender.test.js
index 847bd950a..fe0721f27 100644
--- a/packages/integrations/cloudflare/test/prerender.test.js
+++ b/packages/integrations/cloudflare/test/prerender.test.js
@@ -18,13 +18,14 @@ describe('Prerendering', () => {
fixture.clean();
});
- it('includes prerendered routes in the routes.json config', async () => {
- const foundRoutes = JSON.parse(await fixture.readFile('/_routes.json')).exclude.map((r) =>
- r.replace(/\\/g, '/')
- );
- const expectedExcludedRoutes = ['/_worker.js', '/one/index.html', '/one/'];
+ it('includes non prerendered routes in the routes.json config', async () => {
+ const foundRoutes = JSON.parse(await fixture.readFile('/_routes.json'));
- expect(foundRoutes.every((element) => expectedExcludedRoutes.includes(element))).to.be.true;
+ expect(foundRoutes).to.deep.equal({
+ version: 1,
+ include: ['/'],
+ exclude: [],
+ });
});
});
@@ -45,12 +46,13 @@ describe('Hybrid rendering', () => {
delete process.env.PRERENDER;
});
- it('includes prerendered routes in the routes.json config', async () => {
- const foundRoutes = JSON.parse(await fixture.readFile('/_routes.json')).exclude.map((r) =>
- r.replace(/\\/g, '/')
- );
- const expectedExcludedRoutes = ['/_worker.js', '/index.html', '/'];
+ it('includes non prerendered routes in the routes.json config', async () => {
+ const foundRoutes = JSON.parse(await fixture.readFile('/_routes.json'));
- expect(foundRoutes.every((element) => expectedExcludedRoutes.includes(element))).to.be.true;
+ expect(foundRoutes).to.deep.equal({
+ version: 1,
+ include: ['/one'],
+ exclude: [],
+ });
});
});
diff --git a/packages/integrations/cloudflare/test/routesJson.js b/packages/integrations/cloudflare/test/routesJson.js
new file mode 100644
index 000000000..927e4c38e
--- /dev/null
+++ b/packages/integrations/cloudflare/test/routesJson.js
@@ -0,0 +1,78 @@
+import { expect } from 'chai';
+import { loadFixture } from './test-utils.js';
+
+/** @type {import('./test-utils.js').Fixture} */
+describe('_routes.json generation', () => {
+ after(() => {
+ delete process.env.SRC;
+ });
+
+ describe('of both functions and static files', () => {
+ let fixture;
+
+ before(async () => {
+ process.env.SRC = './src/mixed';
+ fixture = await loadFixture({
+ root: './fixtures/routesJson/',
+ });
+ await fixture.build();
+ });
+
+ it('creates `include` for functions and `exclude` for static files where needed', async () => {
+ const _routesJson = await fixture.readFile('/_routes.json');
+ const routes = JSON.parse(_routesJson);
+
+ expect(routes).to.deep.equal({
+ version: 1,
+ include: ['/a/*'],
+ exclude: ['/a/', '/a/redirect', '/a/index.html'],
+ });
+ });
+ });
+
+ describe('of only functions', () => {
+ let fixture;
+
+ before(async () => {
+ process.env.SRC = './src/dynamicOnly';
+ fixture = await loadFixture({
+ root: './fixtures/routesJson/',
+ });
+ await fixture.build();
+ });
+
+ it('creates a wildcard `include` and `exclude` only for the redirect', async () => {
+ const _routesJson = await fixture.readFile('/_routes.json');
+ const routes = JSON.parse(_routesJson);
+
+ expect(routes).to.deep.equal({
+ version: 1,
+ include: ['/*'],
+ exclude: ['/a/redirect'],
+ });
+ });
+ });
+
+ describe('of only static files', () => {
+ let fixture;
+
+ before(async () => {
+ process.env.SRC = './src/staticOnly';
+ fixture = await loadFixture({
+ root: './fixtures/routesJson/',
+ });
+ await fixture.build();
+ });
+
+ it('create only one `include` and `exclude` that are supposed to match nothing', async () => {
+ const _routesJson = await fixture.readFile('/_routes.json');
+ const routes = JSON.parse(_routesJson);
+
+ expect(routes).to.deep.equal({
+ version: 1,
+ include: ['/'],
+ exclude: ['/'],
+ });
+ });
+ });
+});
diff --git a/packages/integrations/preact/CHANGELOG.md b/packages/integrations/preact/CHANGELOG.md
index e24312338..184d1914e 100644
--- a/packages/integrations/preact/CHANGELOG.md
+++ b/packages/integrations/preact/CHANGELOG.md
@@ -6,6 +6,12 @@
- [`1eae2e3f7`](https://github.com/withastro/astro/commit/1eae2e3f7d693c9dfe91c8ccfbe606d32bf2fb81) Thanks [@Princesseuh](https://github.com/Princesseuh)! - Remove support for Node 16. The lowest supported version by Astro and all integrations is now v18.14.1. As a reminder, Node 16 will be deprecated on the 11th September 2023.
+## 2.2.2
+
+### Patch Changes
+
+- [#8007](https://github.com/withastro/astro/pull/8007) [`58b121d42`](https://github.com/withastro/astro/commit/58b121d42a9f58a5a992f0c378b036f37e9715fc) Thanks [@paperdave](https://github.com/paperdave)! - Support Bun by adjusting how `@babel/plugin-transform-react-jsx` is imported.
+
## 2.2.1
### Patch Changes
diff --git a/packages/integrations/preact/src/index.ts b/packages/integrations/preact/src/index.ts
index 4f4b0ee79..98a2dd205 100644
--- a/packages/integrations/preact/src/index.ts
+++ b/packages/integrations/preact/src/index.ts
@@ -7,10 +7,9 @@ function getRenderer(development: boolean): AstroRenderer {
serverEntrypoint: '@astrojs/preact/server.js',
jsxImportSource: 'preact',
jsxTransformOptions: async () => {
- const {
- default: { default: jsx },
- // @ts-expect-error types not found
- } = await import('@babel/plugin-transform-react-jsx');
+ // @ts-expect-error types not found
+ const plugin = await import('@babel/plugin-transform-react-jsx');
+ const jsx = plugin.default?.default ?? plugin.default;
return {
plugins: [jsx({}, { runtime: 'automatic', importSource: 'preact' })],
};
@@ -25,10 +24,9 @@ function getCompatRenderer(development: boolean): AstroRenderer {
serverEntrypoint: '@astrojs/preact/server.js',
jsxImportSource: 'react',
jsxTransformOptions: async () => {
- const {
- default: { default: jsx },
- // @ts-expect-error types not found
- } = await import('@babel/plugin-transform-react-jsx');
+ // @ts-expect-error types not found
+ const plugin = await import('@babel/plugin-transform-react-jsx');
+ const jsx = plugin.default?.default ?? plugin.default;
return {
plugins: [
jsx({}, { runtime: 'automatic', importSource: 'preact/compat' }),