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 -![This is a placeholder image description](/blog-placeholder-1.jpg) +#### Syntax + +```markdown +![Alt text](./full/or/relative/path/of/image) +``` + +#### Output + +![blog placeholder](../../../public/blog-placeholder-about.jpg) ## 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' }),