mirror of
https://github.com/withastro/astro.git
synced 2024-12-30 22:03:56 -05:00
Merge branch 'main' into feat/get-action-path
This commit is contained in:
commit
2470ef6bd1
12 changed files with 165 additions and 16 deletions
5
.changeset/neat-pumas-accept.md
Normal file
5
.changeset/neat-pumas-accept.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"astro": patch
|
||||
---
|
||||
|
||||
Adds type support for the `closedby` attribute for `<dialog>` elements
|
5
.changeset/selfish-paws-play.md
Normal file
5
.changeset/selfish-paws-play.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes a bug where Astro couldn't correctly parse `params` and `props` when receiving i18n fallback URLs
|
5
.changeset/tame-bags-remember.md
Normal file
5
.changeset/tame-bags-remember.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixes a bug that caused errors in dev when editing sites with large numbers of MDX pages
|
5
.changeset/twelve-donuts-hide.md
Normal file
5
.changeset/twelve-donuts-hide.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
"Added `inert` to htmlBooleanAttributes"
|
1
packages/astro/astro-jsx.d.ts
vendored
1
packages/astro/astro-jsx.d.ts
vendored
|
@ -679,6 +679,7 @@ declare namespace astroHTML.JSX {
|
|||
|
||||
interface DialogHTMLAttributes extends HTMLAttributes {
|
||||
open?: boolean | string | undefined | null;
|
||||
closedby?: 'none' | 'closerequest' | 'any' | undefined | null;
|
||||
}
|
||||
|
||||
interface EmbedHTMLAttributes extends HTMLAttributes {
|
||||
|
|
|
@ -9,6 +9,8 @@ import { contentModuleToId } from './utils.js';
|
|||
|
||||
const SAVE_DEBOUNCE_MS = 500;
|
||||
|
||||
const MAX_DEPTH = 10;
|
||||
|
||||
/**
|
||||
* Extends the DataStore with the ability to change entries and write them to disk.
|
||||
* This is kept as a separate class to avoid needing node builtins at runtime, when read-only access is all that is needed.
|
||||
|
@ -86,7 +88,7 @@ export class MutableDataStore extends ImmutableDataStore {
|
|||
|
||||
if (this.#assetImports.size === 0) {
|
||||
try {
|
||||
await fs.writeFile(filePath, 'export default new Map();');
|
||||
await this.#writeFileAtomic(filePath, 'export default new Map();');
|
||||
} catch (err) {
|
||||
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
|
||||
}
|
||||
|
@ -110,7 +112,7 @@ ${imports.join('\n')}
|
|||
export default new Map([${exports.join(', ')}]);
|
||||
`;
|
||||
try {
|
||||
await fs.writeFile(filePath, code);
|
||||
await this.#writeFileAtomic(filePath, code);
|
||||
} catch (err) {
|
||||
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
|
||||
}
|
||||
|
@ -122,7 +124,7 @@ export default new Map([${exports.join(', ')}]);
|
|||
|
||||
if (this.#moduleImports.size === 0) {
|
||||
try {
|
||||
await fs.writeFile(filePath, 'export default new Map();');
|
||||
await this.#writeFileAtomic(filePath, 'export default new Map();');
|
||||
} catch (err) {
|
||||
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
|
||||
}
|
||||
|
@ -143,7 +145,7 @@ export default new Map([${exports.join(', ')}]);
|
|||
export default new Map([\n${lines.join(',\n')}]);
|
||||
`;
|
||||
try {
|
||||
await fs.writeFile(filePath, code);
|
||||
await this.#writeFileAtomic(filePath, code);
|
||||
} catch (err) {
|
||||
throw new AstroError(AstroErrorData.UnknownFilesystemError, { cause: err });
|
||||
}
|
||||
|
@ -190,6 +192,42 @@ export default new Map([\n${lines.join(',\n')}]);
|
|||
}
|
||||
}
|
||||
|
||||
#writing = new Set<string>();
|
||||
#pending = new Set<string>();
|
||||
|
||||
async #writeFileAtomic(filePath: PathLike, data: string, depth = 0) {
|
||||
if (depth > MAX_DEPTH) {
|
||||
// If we hit the max depth, we skip a write to prevent the stack from growing too large
|
||||
// In theory this means we may miss the latest data, but in practice this will only happen when the file is being written to very frequently
|
||||
// so it will be saved on the next write. This is unlikely to ever happen in practice, as the writes are debounced. It requires lots of writes to very large files.
|
||||
return;
|
||||
}
|
||||
const fileKey = filePath.toString();
|
||||
// If we are already writing this file, instead of writing now, flag it as pending and write it when we're done.
|
||||
if (this.#writing.has(fileKey)) {
|
||||
this.#pending.add(fileKey);
|
||||
return;
|
||||
}
|
||||
// Prevent concurrent writes to this file by flagging it as being written
|
||||
this.#writing.add(fileKey);
|
||||
|
||||
const tempFile = filePath instanceof URL ? new URL(`${filePath.href}.tmp`) : `${filePath}.tmp`;
|
||||
try {
|
||||
// Write it to a temporary file first and then move it to prevent partial reads.
|
||||
await fs.writeFile(tempFile, data);
|
||||
await fs.rename(tempFile, filePath);
|
||||
} finally {
|
||||
// We're done writing. Unflag the file and check if there are any pending writes for this file.
|
||||
this.#writing.delete(fileKey);
|
||||
// If there are pending writes, we need to write again to ensure we flush the latest data.
|
||||
if (this.#pending.has(fileKey)) {
|
||||
this.#pending.delete(fileKey);
|
||||
// Call ourself recursively to write the file again
|
||||
await this.#writeFileAtomic(filePath, data, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scopedStore(collectionName: string): DataStore {
|
||||
return {
|
||||
get: <TData extends Record<string, unknown> = Record<string, unknown>>(key: string) =>
|
||||
|
@ -298,7 +336,7 @@ export default new Map([\n${lines.join(',\n')}]);
|
|||
return;
|
||||
}
|
||||
try {
|
||||
await fs.writeFile(filePath, this.toString());
|
||||
await this.#writeFileAtomic(filePath, this.toString());
|
||||
this.#file = filePath;
|
||||
this.#dirty = false;
|
||||
} catch (err) {
|
||||
|
|
|
@ -47,7 +47,8 @@ export async function getProps(opts: GetParamsAndPropsOptions): Promise<Props> {
|
|||
base,
|
||||
});
|
||||
|
||||
// The pathname used here comes from the server, which already encored.
|
||||
if (!staticPaths.length) return {};
|
||||
// The pathname used here comes from the server, which already encoded.
|
||||
// Since we decided to not mess up with encoding anymore, we need to decode them back so the parameters can match
|
||||
// the ones expected from the users
|
||||
const params = getParams(route, decodeURI(pathname));
|
||||
|
@ -77,7 +78,11 @@ export function getParams(route: RouteData, pathname: string): Params {
|
|||
if (!route.params.length) return {};
|
||||
// The RegExp pattern expects a decoded string, but the pathname is encoded
|
||||
// when the URL contains non-English characters.
|
||||
const paramsMatch = route.pattern.exec(pathname);
|
||||
const paramsMatch =
|
||||
route.pattern.exec(pathname) ||
|
||||
route.fallbackRoutes
|
||||
.map((fallbackRoute) => fallbackRoute.pattern.exec(pathname))
|
||||
.find((x) => x);
|
||||
if (!paramsMatch) return {};
|
||||
const params: Params = {};
|
||||
route.params.forEach((key, i) => {
|
||||
|
|
|
@ -7,7 +7,7 @@ import { HTMLString, markHTMLString } from '../escape.js';
|
|||
export const voidElementNames =
|
||||
/^(area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr)$/i;
|
||||
const htmlBooleanAttributes =
|
||||
/^(?:allowfullscreen|async|autofocus|autoplay|checked|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|selected|itemscope)$/i;
|
||||
/^(?:allowfullscreen|async|autofocus|autoplay|checked|controls|default|defer|disabled|disablepictureinpicture|disableremoteplayback|formnovalidate|hidden|inert|loop|nomodule|novalidate|open|playsinline|readonly|required|reversed|scoped|seamless|selected|itemscope)$/i;
|
||||
|
||||
const AMPERSAND_REGEX = /&/g;
|
||||
const DOUBLE_QUOTE_REGEX = /"/g;
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
---
|
||||
// for SSR
|
||||
const blogs = {
|
||||
1: { content: "Hello world" },
|
||||
2: { content: "Eat Something" },
|
||||
3: { content: "How are you?" },
|
||||
}
|
||||
const id = Astro.params?.id;
|
||||
const ssrContent = id && blogs[id]?.content;
|
||||
|
||||
// for SSG
|
||||
export function getStaticPaths() {
|
||||
return [
|
||||
{params: {id: '1'}, props: { content: "Hello world" }},
|
||||
{params: {id: '2'}, props: { content: "Eat Something" }},
|
||||
{params: {id: '3'}, props: { content: "How are you?" }},
|
||||
];
|
||||
]
|
||||
}
|
||||
const { content } = Astro.props;
|
||||
const { content } = Astro.props
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
{content}
|
||||
{content || ssrContent}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
---
|
||||
const blogs = {
|
||||
1: { content: "Hola mundo" },
|
||||
2: { content: "Eat Something" },
|
||||
3: { content: "How are you?" },
|
||||
}
|
||||
const id = Astro.params?.id;
|
||||
const ssrContent = id && blogs[id]?.content;
|
||||
|
||||
export function getStaticPaths() {
|
||||
return [
|
||||
{params: {id: '1'}, props: { content: "Hola mundo" }},
|
||||
|
@ -13,6 +21,6 @@ const { content } = Astro.props;
|
|||
<title>Astro</title>
|
||||
</head>
|
||||
<body>
|
||||
{content}
|
||||
{content || ssrContent}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -2000,12 +2000,14 @@ describe('Fallback rewrite dev server', () => {
|
|||
root: './fixtures/i18n-routing-fallback/',
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
locales: ['en', 'fr', 'es', 'it', 'pt'],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
},
|
||||
fallback: {
|
||||
fr: 'en',
|
||||
it: 'en',
|
||||
es: 'pt',
|
||||
},
|
||||
fallbackType: 'rewrite',
|
||||
},
|
||||
|
@ -2021,6 +2023,27 @@ describe('Fallback rewrite dev server', () => {
|
|||
assert.match(html, /Hello/);
|
||||
// assert.fail()
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with path parameters correctly (fr)', async () => {
|
||||
let response = await fixture.fetch('/fr/blog/1');
|
||||
assert.equal(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.match(text, /Hello world/);
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with path parameters correctly (es)', async () => {
|
||||
let response = await fixture.fetch('/es/blog/1');
|
||||
assert.equal(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.match(text, /Hola mundo/);
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with query parameters correctly (it)', async () => {
|
||||
let response = await fixture.fetch('/it/blog/1');
|
||||
assert.equal(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.match(text, /Hello world/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fallback rewrite SSG', () => {
|
||||
|
@ -2032,13 +2055,15 @@ describe('Fallback rewrite SSG', () => {
|
|||
root: './fixtures/i18n-routing-fallback/',
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
locales: ['en', 'fr', 'es', 'it', 'pt'],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
fallbackType: 'rewrite',
|
||||
},
|
||||
fallback: {
|
||||
fr: 'en',
|
||||
it: 'en',
|
||||
es: 'pt',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -2051,6 +2076,21 @@ describe('Fallback rewrite SSG', () => {
|
|||
assert.match(html, /Hello/);
|
||||
// assert.fail()
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with path parameters correctly (fr)', async () => {
|
||||
const html = await fixture.readFile('/fr/blog/1/index.html');
|
||||
assert.match(html, /Hello world/);
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with path parameters correctly (es)', async () => {
|
||||
const html = await fixture.readFile('/es/blog/1/index.html');
|
||||
assert.match(html, /Hola mundo/);
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with query parameters correctly (it)', async () => {
|
||||
const html = await fixture.readFile('/it/blog/1/index.html');
|
||||
assert.match(html, /Hello world/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Fallback rewrite SSR', () => {
|
||||
|
@ -2066,13 +2106,15 @@ describe('Fallback rewrite SSR', () => {
|
|||
adapter: testAdapter(),
|
||||
i18n: {
|
||||
defaultLocale: 'en',
|
||||
locales: ['en', 'fr'],
|
||||
locales: ['en', 'fr', 'es', 'it', 'pt'],
|
||||
routing: {
|
||||
prefixDefaultLocale: false,
|
||||
fallbackType: 'rewrite',
|
||||
},
|
||||
fallback: {
|
||||
fr: 'en',
|
||||
it: 'en',
|
||||
es: 'pt',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -2087,4 +2129,28 @@ describe('Fallback rewrite SSR', () => {
|
|||
const html = await response.text();
|
||||
assert.match(html, /Hello/);
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with path parameters correctly (fr)', async () => {
|
||||
let request = new Request('http://example.com/new-site/fr/blog/1');
|
||||
let response = await app.render(request);
|
||||
assert.equal(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.match(text, /Hello world/);
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with path parameters correctly (es)', async () => {
|
||||
let request = new Request('http://example.com/new-site/es/blog/1');
|
||||
let response = await app.render(request);
|
||||
assert.equal(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.match(text, /Hola mundo/);
|
||||
});
|
||||
|
||||
it('should render fallback locale paths with query parameters correctly (it)', async () => {
|
||||
let request = new Request('http://example.com/new-site/it/blog/1');
|
||||
let response = await app.render(request);
|
||||
assert.equal(response.status, 200);
|
||||
const text = await response.text();
|
||||
assert.match(text, /Hello world/);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import path from 'node:path';
|
||||
import {fileURLToPath} from "node:url";
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [path.join(__dirname, 'src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}')],
|
||||
content: [path.join(path.dirname(fileURLToPath(import.meta.url)), 'src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}')],
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue