mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Merge remote-tracking branch 'origin/main' into next
This commit is contained in:
commit
670ef109db
12 changed files with 79 additions and 49 deletions
5
.changeset/dry-pandas-flash.md
Normal file
5
.changeset/dry-pandas-flash.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'create-astro': patch
|
||||
---
|
||||
|
||||
Add support for more Starlight templates
|
5
.changeset/funny-glasses-bathe.md
Normal file
5
.changeset/funny-glasses-bathe.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fixed `EndpointOutput` types with `{ encoding: 'binary' }`
|
5
.changeset/great-icons-turn.md
Normal file
5
.changeset/great-icons-turn.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix quadratic quote escaping in nested data in island props
|
|
@ -1870,10 +1870,15 @@ export interface APIContext<Props extends Record<string, any> = Record<string, a
|
|||
locals: App.Locals;
|
||||
}
|
||||
|
||||
export interface EndpointOutput {
|
||||
body: Body;
|
||||
encoding?: BufferEncoding;
|
||||
}
|
||||
export type EndpointOutput =
|
||||
| {
|
||||
body: Body;
|
||||
encoding?: Exclude<BufferEncoding, 'binary'>;
|
||||
}
|
||||
| {
|
||||
body: Uint8Array;
|
||||
encoding: 'binary';
|
||||
};
|
||||
|
||||
export type APIRoute<Props extends Record<string, any> = Record<string, any>> = (
|
||||
context: APIContext<Props>
|
||||
|
|
|
@ -194,7 +194,6 @@ export class App {
|
|||
}
|
||||
return response.response;
|
||||
} else {
|
||||
const body = response.body;
|
||||
const headers = new Headers();
|
||||
const mimeType = mime.getType(url.pathname);
|
||||
if (mimeType) {
|
||||
|
@ -202,7 +201,8 @@ export class App {
|
|||
} else {
|
||||
headers.set('Content-Type', 'text/plain;charset=utf-8');
|
||||
}
|
||||
const bytes = this.#encoder.encode(body);
|
||||
const bytes =
|
||||
response.encoding !== 'binary' ? this.#encoder.encode(response.body) : response.body;
|
||||
headers.set('Content-Length', bytes.byteLength.toString());
|
||||
|
||||
const newResponse = new Response(bytes, {
|
||||
|
|
|
@ -18,12 +18,10 @@ const clientAddressSymbol = Symbol.for('astro.clientAddress');
|
|||
const clientLocalsSymbol = Symbol.for('astro.locals');
|
||||
|
||||
export type EndpointCallResult =
|
||||
| {
|
||||
| (EndpointOutput & {
|
||||
type: 'simple';
|
||||
body: string;
|
||||
encoding?: BufferEncoding;
|
||||
cookies: AstroCookies;
|
||||
}
|
||||
})
|
||||
| {
|
||||
type: 'response';
|
||||
response: Response;
|
||||
|
@ -153,9 +151,8 @@ export async function callEndpoint<MiddlewareResult = Response | EndpointOutput>
|
|||
}
|
||||
|
||||
return {
|
||||
...response,
|
||||
type: 'simple',
|
||||
body: response.body,
|
||||
encoding: response.encoding,
|
||||
cookies: context.cookies,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -18,25 +18,32 @@ declare const Astro: {
|
|||
}
|
||||
|
||||
const propTypes: PropTypeSelector = {
|
||||
0: (value) => value,
|
||||
1: (value) => JSON.parse(value, reviver),
|
||||
0: (value) => reviveObject(value),
|
||||
1: (value) => reviveArray(value),
|
||||
2: (value) => new RegExp(value),
|
||||
3: (value) => new Date(value),
|
||||
4: (value) => new Map(JSON.parse(value, reviver)),
|
||||
5: (value) => new Set(JSON.parse(value, reviver)),
|
||||
4: (value) => new Map(reviveArray(value)),
|
||||
5: (value) => new Set(reviveArray(value)),
|
||||
6: (value) => BigInt(value),
|
||||
7: (value) => new URL(value),
|
||||
8: (value) => new Uint8Array(JSON.parse(value)),
|
||||
9: (value) => new Uint16Array(JSON.parse(value)),
|
||||
10: (value) => new Uint32Array(JSON.parse(value)),
|
||||
8: (value) => new Uint8Array(value),
|
||||
9: (value) => new Uint16Array(value),
|
||||
10: (value) => new Uint32Array(value),
|
||||
};
|
||||
|
||||
const reviver = (propKey: string, raw: string): any => {
|
||||
if (propKey === '' || !Array.isArray(raw)) return raw;
|
||||
// Not using JSON.parse reviver because it's bottom-up but we want top-down
|
||||
const reviveTuple = (raw: any): any => {
|
||||
const [type, value] = raw;
|
||||
return type in propTypes ? propTypes[type](value) : undefined;
|
||||
};
|
||||
|
||||
const reviveArray = (raw: any): any => (raw as Array<any>).map(reviveTuple);
|
||||
|
||||
const reviveObject = (raw: any): any => {
|
||||
if (typeof raw !== 'object' || raw === null) return raw;
|
||||
return Object.fromEntries(Object.entries(raw).map(([key, value]) => [key, reviveTuple(value)]));
|
||||
};
|
||||
|
||||
if (!customElements.get('astro-island')) {
|
||||
customElements.define(
|
||||
'astro-island',
|
||||
|
@ -132,7 +139,7 @@ declare const Astro: {
|
|||
|
||||
try {
|
||||
props = this.hasAttribute('props')
|
||||
? JSON.parse(this.getAttribute('props')!, reviver)
|
||||
? reviveObject(JSON.parse(this.getAttribute('props')!))
|
||||
: {};
|
||||
} catch (e) {
|
||||
let componentName: string = this.getAttribute('component-url') || '<unknown>';
|
||||
|
|
|
@ -4,7 +4,7 @@ type ValueOf<T> = T[keyof T];
|
|||
|
||||
const PROP_TYPE = {
|
||||
Value: 0,
|
||||
JSON: 1,
|
||||
JSON: 1, // Actually means Array
|
||||
RegExp: 2,
|
||||
Date: 3,
|
||||
Map: 4,
|
||||
|
@ -68,16 +68,10 @@ function convertToSerializedForm(
|
|||
return [PROP_TYPE.RegExp, (value as RegExp).source];
|
||||
}
|
||||
case '[object Map]': {
|
||||
return [
|
||||
PROP_TYPE.Map,
|
||||
JSON.stringify(serializeArray(Array.from(value as Map<any, any>), metadata, parents)),
|
||||
];
|
||||
return [PROP_TYPE.Map, serializeArray(Array.from(value as Map<any, any>), metadata, parents)];
|
||||
}
|
||||
case '[object Set]': {
|
||||
return [
|
||||
PROP_TYPE.Set,
|
||||
JSON.stringify(serializeArray(Array.from(value as Set<any>), metadata, parents)),
|
||||
];
|
||||
return [PROP_TYPE.Set, serializeArray(Array.from(value as Set<any>), metadata, parents)];
|
||||
}
|
||||
case '[object BigInt]': {
|
||||
return [PROP_TYPE.BigInt, (value as bigint).toString()];
|
||||
|
@ -86,16 +80,16 @@ function convertToSerializedForm(
|
|||
return [PROP_TYPE.URL, (value as URL).toString()];
|
||||
}
|
||||
case '[object Array]': {
|
||||
return [PROP_TYPE.JSON, JSON.stringify(serializeArray(value, metadata, parents))];
|
||||
return [PROP_TYPE.JSON, serializeArray(value, metadata, parents)];
|
||||
}
|
||||
case '[object Uint8Array]': {
|
||||
return [PROP_TYPE.Uint8Array, JSON.stringify(Array.from(value as Uint8Array))];
|
||||
return [PROP_TYPE.Uint8Array, Array.from(value as Uint8Array)];
|
||||
}
|
||||
case '[object Uint16Array]': {
|
||||
return [PROP_TYPE.Uint16Array, JSON.stringify(Array.from(value as Uint16Array))];
|
||||
return [PROP_TYPE.Uint16Array, Array.from(value as Uint16Array)];
|
||||
}
|
||||
case '[object Uint32Array]': {
|
||||
return [PROP_TYPE.Uint32Array, JSON.stringify(Array.from(value as Uint32Array))];
|
||||
return [PROP_TYPE.Uint32Array, Array.from(value as Uint32Array)];
|
||||
}
|
||||
default: {
|
||||
if (value !== null && typeof value === 'object') {
|
||||
|
|
|
@ -246,12 +246,15 @@ export async function handleRoute({
|
|||
if (computedMimeType) {
|
||||
contentType = computedMimeType;
|
||||
}
|
||||
const response = new Response(Buffer.from(result.body, result.encoding), {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': `${contentType};charset=utf-8`,
|
||||
},
|
||||
});
|
||||
const response = new Response(
|
||||
result.encoding !== 'binary' ? Buffer.from(result.body, result.encoding) : result.body,
|
||||
{
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': `${contentType};charset=utf-8`,
|
||||
},
|
||||
}
|
||||
);
|
||||
attachToResponse(response, result.cookies);
|
||||
await writeWebResponse(incomingResponse, response);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,13 @@ describe('serialize', () => {
|
|||
});
|
||||
it('serializes an array', () => {
|
||||
const input = { a: [0] };
|
||||
const output = `{"a":[1,"[[0,0]]"]}`;
|
||||
const output = `{"a":[1,[[0,0]]]}`;
|
||||
expect(serializeProps(input)).to.equal(output);
|
||||
});
|
||||
it('can serialize deeply nested data without quadratic quote escaping', () => {
|
||||
const input = { a: [{ b: [{ c: [{ d: [{ e: [{ f: [{ g: ['leaf'] }] }] }] }] }] }] };
|
||||
const output =
|
||||
'{"a":[1,[[0,{"b":[1,[[0,{"c":[1,[[0,{"d":[1,[[0,{"e":[1,[[0,{"f":[1,[[0,{"g":[1,[[0,"leaf"]]]}]]]}]]]}]]]}]]]}]]]}]]]}';
|
||||
expect(serializeProps(input)).to.equal(output);
|
||||
});
|
||||
it('serializes a regular expression', () => {
|
||||
|
@ -49,12 +55,12 @@ describe('serialize', () => {
|
|||
});
|
||||
it('serializes a Map', () => {
|
||||
const input = { a: new Map([[0, 1]]) };
|
||||
const output = `{"a":[4,"[[1,\\"[[0,0],[0,1]]\\"]]"]}`;
|
||||
const output = `{"a":[4,[[1,[[0,0],[0,1]]]]]}`;
|
||||
expect(serializeProps(input)).to.equal(output);
|
||||
});
|
||||
it('serializes a Set', () => {
|
||||
const input = { a: new Set([0, 1, 2, 3]) };
|
||||
const output = `{"a":[5,"[[0,0],[0,1],[0,2],[0,3]]"]}`;
|
||||
const output = `{"a":[5,[[0,0],[0,1],[0,2],[0,3]]]}`;
|
||||
expect(serializeProps(input)).to.equal(output);
|
||||
});
|
||||
it('serializes a BigInt', () => {
|
||||
|
@ -69,17 +75,17 @@ describe('serialize', () => {
|
|||
});
|
||||
it('serializes a Uint8Array', () => {
|
||||
const input = { a: new Uint8Array([1, 2, 3]) };
|
||||
const output = `{"a":[8,"[1,2,3]"]}`;
|
||||
const output = `{"a":[8,[1,2,3]]}`;
|
||||
expect(serializeProps(input)).to.equal(output);
|
||||
});
|
||||
it('serializes a Uint16Array', () => {
|
||||
const input = { a: new Uint16Array([1, 2, 3]) };
|
||||
const output = `{"a":[9,"[1,2,3]"]}`;
|
||||
const output = `{"a":[9,[1,2,3]]}`;
|
||||
expect(serializeProps(input)).to.equal(output);
|
||||
});
|
||||
it('serializes a Uint32Array', () => {
|
||||
const input = { a: new Uint32Array([1, 2, 3]) };
|
||||
const output = `{"a":[10,"[1,2,3]"]}`;
|
||||
const output = `{"a":[10,[1,2,3]]}`;
|
||||
expect(serializeProps(input)).to.equal(output);
|
||||
});
|
||||
it('cannot serialize a cyclic reference', () => {
|
||||
|
|
|
@ -67,9 +67,12 @@ const FILES_TO_UPDATE = {
|
|||
};
|
||||
|
||||
function getTemplateTarget(tmpl: string, ref = 'latest') {
|
||||
if (tmpl.startsWith('starlight')) {
|
||||
const [, starter = 'basics'] = tmpl.split('/');
|
||||
return `withastro/starlight/examples/${starter}`;
|
||||
}
|
||||
const isThirdParty = tmpl.includes('/');
|
||||
if (isThirdParty) return tmpl;
|
||||
if (tmpl === 'starlight') return `withastro/starlight/examples/basics`;
|
||||
return `github:withastro/astro/examples/${tmpl}#${ref}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -278,7 +278,7 @@ export default defineMarkdocConfig({
|
|||
|
||||
### Use client-side UI components
|
||||
|
||||
Tags and nodes are restricted to `.astro` files. To embed client-side UI components in Markdoc, [use a wrapper `.astro` component that renders a framework component](/en/core-concepts/framework-components/#nesting-framework-components) with your desired `client:` directive.
|
||||
Tags and nodes are restricted to `.astro` files. To embed client-side UI components in Markdoc, [use a wrapper `.astro` component that renders a framework component](https://docs.astro.build/en/core-concepts/framework-components/#nesting-framework-components) with your desired `client:` directive.
|
||||
|
||||
This example wraps a React `Aside.tsx` component with a `ClientAside.astro` component:
|
||||
|
||||
|
|
Loading…
Reference in a new issue