@quilted/quilt 0.0.0-fix-react-types-20220320044002
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/build/cjs/App.cjs +37 -0
- package/build/cjs/email.cjs +32 -0
- package/build/cjs/env.cjs +2 -0
- package/build/cjs/global.cjs +5 -0
- package/build/cjs/html.cjs +112 -0
- package/build/cjs/http-handlers/index.cjs +14 -0
- package/build/cjs/http-handlers/node.cjs +14 -0
- package/build/cjs/http.cjs +108 -0
- package/build/cjs/index.cjs +166 -0
- package/build/cjs/matchers.cjs +5 -0
- package/build/cjs/polyfills/base.cjs +4 -0
- package/build/cjs/polyfills/fetch.cjs +4 -0
- package/build/cjs/polyfills/noop.cjs +4 -0
- package/build/cjs/react/index.cjs +22 -0
- package/build/cjs/react/jsx-runtime.cjs +14 -0
- package/build/cjs/react/server.cjs +14 -0
- package/build/cjs/react/test-utils.cjs +14 -0
- package/build/cjs/server/ServerContext.cjs +22 -0
- package/build/cjs/server/http-handler.cjs +87 -0
- package/build/cjs/server/index.cjs +89 -0
- package/build/cjs/server/render.cjs +44 -0
- package/build/cjs/static/StaticContext.cjs +22 -0
- package/build/cjs/static/index.cjs +302 -0
- package/build/cjs/static/render.cjs +44 -0
- package/build/cjs/testing.cjs +42 -0
- package/build/cjs/utilities/react.cjs +14 -0
- package/build/esm/App.mjs +33 -0
- package/build/esm/email.mjs +1 -0
- package/build/esm/env.mjs +1 -0
- package/build/esm/global.mjs +3 -0
- package/build/esm/html.mjs +1 -0
- package/build/esm/http-handlers/index.mjs +1 -0
- package/build/esm/http-handlers/node.mjs +1 -0
- package/build/esm/http.mjs +1 -0
- package/build/esm/index.mjs +10 -0
- package/build/esm/matchers.mjs +2 -0
- package/build/esm/polyfills/base.mjs +1 -0
- package/build/esm/polyfills/fetch.mjs +1 -0
- package/build/esm/polyfills/noop.mjs +1 -0
- package/build/esm/react/index.mjs +2 -0
- package/build/esm/react/jsx-runtime.mjs +1 -0
- package/build/esm/react/server.mjs +1 -0
- package/build/esm/react/test-utils.mjs +1 -0
- package/build/esm/server/ServerContext.mjs +18 -0
- package/build/esm/server/http-handler.mjs +81 -0
- package/build/esm/server/index.mjs +9 -0
- package/build/esm/server/render.mjs +40 -0
- package/build/esm/static/StaticContext.mjs +18 -0
- package/build/esm/static/index.mjs +280 -0
- package/build/esm/static/render.mjs +40 -0
- package/build/esm/testing.mjs +3 -0
- package/build/esm/utilities/react.mjs +10 -0
- package/build/esnext/App.esnext +33 -0
- package/build/esnext/email.esnext +1 -0
- package/build/esnext/env.esnext +1 -0
- package/build/esnext/global.esnext +3 -0
- package/build/esnext/html.esnext +1 -0
- package/build/esnext/http-handlers/index.esnext +1 -0
- package/build/esnext/http-handlers/node.esnext +1 -0
- package/build/esnext/http.esnext +1 -0
- package/build/esnext/index.esnext +10 -0
- package/build/esnext/matchers.esnext +2 -0
- package/build/esnext/polyfills/base.esnext +1 -0
- package/build/esnext/polyfills/fetch.esnext +1 -0
- package/build/esnext/polyfills/noop.esnext +1 -0
- package/build/esnext/react/index.esnext +2 -0
- package/build/esnext/react/jsx-runtime.esnext +1 -0
- package/build/esnext/react/server.esnext +1 -0
- package/build/esnext/react/test-utils.esnext +1 -0
- package/build/esnext/server/ServerContext.esnext +18 -0
- package/build/esnext/server/http-handler.esnext +81 -0
- package/build/esnext/server/index.esnext +9 -0
- package/build/esnext/server/render.esnext +40 -0
- package/build/esnext/static/StaticContext.esnext +18 -0
- package/build/esnext/static/index.esnext +280 -0
- package/build/esnext/static/render.esnext +40 -0
- package/build/esnext/testing.esnext +3 -0
- package/build/esnext/utilities/react.esnext +10 -0
- package/build/tsconfig.tsbuildinfo +1 -0
- package/build/typescript/App.d.ts +15 -0
- package/build/typescript/App.d.ts.map +1 -0
- package/build/typescript/email.d.ts +3 -0
- package/build/typescript/email.d.ts.map +1 -0
- package/build/typescript/env.d.ts +5 -0
- package/build/typescript/env.d.ts.map +1 -0
- package/build/typescript/global.d.ts +2 -0
- package/build/typescript/global.d.ts.map +1 -0
- package/build/typescript/html.d.ts +3 -0
- package/build/typescript/html.d.ts.map +1 -0
- package/build/typescript/http-handlers/index.d.ts +2 -0
- package/build/typescript/http-handlers/index.d.ts.map +1 -0
- package/build/typescript/http-handlers/node.d.ts +2 -0
- package/build/typescript/http-handlers/node.d.ts.map +1 -0
- package/build/typescript/http.d.ts +2 -0
- package/build/typescript/http.d.ts.map +1 -0
- package/build/typescript/index.d.ts +18 -0
- package/build/typescript/index.d.ts.map +1 -0
- package/build/typescript/magic/app.d.ts +4 -0
- package/build/typescript/magic/app.d.ts.map +1 -0
- package/build/typescript/magic/asset-loader.d.ts +6 -0
- package/build/typescript/magic/asset-loader.d.ts.map +1 -0
- package/build/typescript/magic/http-handler.d.ts +4 -0
- package/build/typescript/magic/http-handler.d.ts.map +1 -0
- package/build/typescript/matchers.d.ts +3 -0
- package/build/typescript/matchers.d.ts.map +1 -0
- package/build/typescript/polyfills/base.d.ts +2 -0
- package/build/typescript/polyfills/base.d.ts.map +1 -0
- package/build/typescript/polyfills/crypto.d.ts +2 -0
- package/build/typescript/polyfills/crypto.d.ts.map +1 -0
- package/build/typescript/polyfills/fetch.d.ts +2 -0
- package/build/typescript/polyfills/fetch.d.ts.map +1 -0
- package/build/typescript/polyfills/noop.d.ts +2 -0
- package/build/typescript/polyfills/noop.d.ts.map +1 -0
- package/build/typescript/react/index.d.ts +3 -0
- package/build/typescript/react/index.d.ts.map +1 -0
- package/build/typescript/react/jsx-runtime.d.ts +2 -0
- package/build/typescript/react/jsx-runtime.d.ts.map +1 -0
- package/build/typescript/react/server.d.ts +2 -0
- package/build/typescript/react/server.d.ts.map +1 -0
- package/build/typescript/react/test-utils.d.ts +2 -0
- package/build/typescript/react/test-utils.d.ts.map +1 -0
- package/build/typescript/server/ServerContext.d.ts +13 -0
- package/build/typescript/server/ServerContext.d.ts.map +1 -0
- package/build/typescript/server/http-handler.d.ts +15 -0
- package/build/typescript/server/http-handler.d.ts.map +1 -0
- package/build/typescript/server/index.d.ts +13 -0
- package/build/typescript/server/index.d.ts.map +1 -0
- package/build/typescript/server/render.d.ts +17 -0
- package/build/typescript/server/render.d.ts.map +1 -0
- package/build/typescript/static/StaticContext.d.ts +13 -0
- package/build/typescript/static/StaticContext.d.ts.map +1 -0
- package/build/typescript/static/index.d.ts +22 -0
- package/build/typescript/static/index.d.ts.map +1 -0
- package/build/typescript/static/render.d.ts +17 -0
- package/build/typescript/static/render.d.ts.map +1 -0
- package/build/typescript/testing.d.ts +4 -0
- package/build/typescript/testing.d.ts.map +1 -0
- package/build/typescript/utilities/react.d.ts +3 -0
- package/build/typescript/utilities/react.d.ts.map +1 -0
- package/package.json +255 -0
- package/sewing-kit.config.ts +37 -0
- package/src/App.tsx +53 -0
- package/src/email.ts +9 -0
- package/src/env.ts +5 -0
- package/src/global.ts +3 -0
- package/src/html.ts +29 -0
- package/src/http-handlers/index.ts +1 -0
- package/src/http-handlers/node.ts +1 -0
- package/src/http.ts +27 -0
- package/src/index.ts +83 -0
- package/src/magic/app.ts +5 -0
- package/src/magic/asset-loader.ts +5 -0
- package/src/magic/http-handler.ts +5 -0
- package/src/matchers.ts +2 -0
- package/src/polyfills/README.md +3 -0
- package/src/polyfills/base.ts +1 -0
- package/src/polyfills/crypto.ts +1 -0
- package/src/polyfills/fetch.ts +1 -0
- package/src/polyfills/noop.ts +1 -0
- package/src/react/index.ts +4 -0
- package/src/react/jsx-runtime.ts +2 -0
- package/src/react/server.ts +2 -0
- package/src/react/test-utils.ts +2 -0
- package/src/server/ServerContext.tsx +42 -0
- package/src/server/http-handler.tsx +101 -0
- package/src/server/index.ts +40 -0
- package/src/server/render.tsx +43 -0
- package/src/static/StaticContext.tsx +42 -0
- package/src/static/index.tsx +307 -0
- package/src/static/render.tsx +43 -0
- package/src/testing.ts +7 -0
- package/src/utilities/react.tsx +13 -0
- package/tsconfig.json +29 -0
package/src/matchers.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
# `@quilted/quilt/polyfills/*`
|
|
2
|
+
|
|
3
|
+
This directory re-exports the polyfills provided by `@quilted/polyfills`. This is done because most consumers will have a dependency only on `@quilted/quilt`, not `@quilted/polyfills`, but we need to insert imports for polyfills into “app code”. Strict package managers, like `pnpm`, will then install `@quilted/polyfills` in such a way that imports of that package fail to resolve. To fix this, we re-export all the entry points that can be inserted in app code from `@quilted/quilt/polyfills/*`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@quilted/polyfills/base';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@quilted/polyfills/crypto';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@quilted/polyfills/fetch';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import '@quilted/polyfills/noop';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type {PropsWithChildren} from 'react';
|
|
2
|
+
|
|
3
|
+
import {InitialUrlContext} from '@quilted/react-router';
|
|
4
|
+
import {HtmlContext} from '@quilted/react-html/server';
|
|
5
|
+
import type {HtmlManager} from '@quilted/react-html/server';
|
|
6
|
+
import {AsyncAssetContext} from '@quilted/react-async/server';
|
|
7
|
+
import type {AsyncAssetManager} from '@quilted/react-async/server';
|
|
8
|
+
import {HttpServerContext} from '@quilted/react-http/server';
|
|
9
|
+
import type {HttpManager} from '@quilted/react-http/server';
|
|
10
|
+
|
|
11
|
+
import {maybeWrapContext} from '../utilities/react';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
url?: string | URL;
|
|
15
|
+
html?: HtmlManager;
|
|
16
|
+
http?: HttpManager;
|
|
17
|
+
asyncAssets?: AsyncAssetManager;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ServerContext({
|
|
21
|
+
url,
|
|
22
|
+
html,
|
|
23
|
+
http,
|
|
24
|
+
asyncAssets,
|
|
25
|
+
children,
|
|
26
|
+
}: PropsWithChildren<Props>) {
|
|
27
|
+
const normalizedUrl = typeof url === 'string' ? new URL(url) : url;
|
|
28
|
+
|
|
29
|
+
return maybeWrapContext(
|
|
30
|
+
AsyncAssetContext,
|
|
31
|
+
asyncAssets,
|
|
32
|
+
maybeWrapContext(
|
|
33
|
+
HttpServerContext,
|
|
34
|
+
http,
|
|
35
|
+
maybeWrapContext(
|
|
36
|
+
HtmlContext,
|
|
37
|
+
html,
|
|
38
|
+
maybeWrapContext(InitialUrlContext, normalizedUrl, children),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type {ComponentType, ReactElement} from 'react';
|
|
2
|
+
|
|
3
|
+
import type {AssetLoader} from '@quilted/async/server';
|
|
4
|
+
import {render as renderToString, Html} from '@quilted/react-html/server';
|
|
5
|
+
import type {Options as ExtractOptions} from '@quilted/react-server-render/server';
|
|
6
|
+
|
|
7
|
+
import {createHttpHandler, html, redirect} from '@quilted/http-handlers';
|
|
8
|
+
import type {
|
|
9
|
+
Request,
|
|
10
|
+
HttpHandler,
|
|
11
|
+
RequestHandler,
|
|
12
|
+
} from '@quilted/http-handlers';
|
|
13
|
+
|
|
14
|
+
import {renderApp} from './render';
|
|
15
|
+
|
|
16
|
+
export interface Options<Props = Record<string, never>> extends ExtractOptions {
|
|
17
|
+
assets?: AssetLoader<unknown>;
|
|
18
|
+
handler?: HttpHandler;
|
|
19
|
+
renderProps?(options: {request: Request}): Props;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function createServerRenderingHttpHandler<Props>(
|
|
23
|
+
App: ComponentType<Props>,
|
|
24
|
+
{handler = createHttpHandler(), ...options}: Options<Props>,
|
|
25
|
+
) {
|
|
26
|
+
handler.get(createServerRenderingRequestHandler(App, options));
|
|
27
|
+
return handler;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createServerRenderingRequestHandler<Props>(
|
|
31
|
+
App: ComponentType<Props>,
|
|
32
|
+
{renderProps, ...options}: Omit<Options<Props>, 'handler'> = {},
|
|
33
|
+
): RequestHandler {
|
|
34
|
+
return (request) => {
|
|
35
|
+
return renderToResponse(
|
|
36
|
+
<App {...(renderProps?.({request}) as any)} />,
|
|
37
|
+
request,
|
|
38
|
+
options,
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function renderToResponse<Props>(
|
|
44
|
+
element: ReactElement<Props>,
|
|
45
|
+
request: Request,
|
|
46
|
+
{assets, ...options}: Omit<Options<Props>, 'handler' | 'renderProps'> = {},
|
|
47
|
+
) {
|
|
48
|
+
const accepts = request.headers.get('Accept');
|
|
49
|
+
|
|
50
|
+
if (accepts != null && !accepts.includes('text/html')) return;
|
|
51
|
+
|
|
52
|
+
const {
|
|
53
|
+
html: htmlManager,
|
|
54
|
+
http,
|
|
55
|
+
markup,
|
|
56
|
+
asyncAssets,
|
|
57
|
+
} = await renderApp(element, {
|
|
58
|
+
...options,
|
|
59
|
+
url: request.url,
|
|
60
|
+
headers: request.headers,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const {headers, statusCode = 200, redirectUrl} = http.state;
|
|
64
|
+
|
|
65
|
+
if (redirectUrl) {
|
|
66
|
+
return redirect(redirectUrl, {
|
|
67
|
+
status: statusCode as 301,
|
|
68
|
+
headers,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const usedAssets = asyncAssets.used({timing: 'load'});
|
|
73
|
+
const assetOptions = {userAgent: request.headers.get('User-Agent')};
|
|
74
|
+
|
|
75
|
+
const [styles, scripts, preload] = assets
|
|
76
|
+
? await Promise.all([
|
|
77
|
+
assets.styles({async: usedAssets, options: assetOptions}),
|
|
78
|
+
assets.scripts({async: usedAssets, options: assetOptions}),
|
|
79
|
+
assets.asyncAssets(asyncAssets.used({timing: 'preload'}), {
|
|
80
|
+
options: assetOptions,
|
|
81
|
+
}),
|
|
82
|
+
])
|
|
83
|
+
: [];
|
|
84
|
+
|
|
85
|
+
return html(
|
|
86
|
+
renderToString(
|
|
87
|
+
<Html
|
|
88
|
+
manager={htmlManager}
|
|
89
|
+
styles={styles}
|
|
90
|
+
scripts={scripts}
|
|
91
|
+
preloadAssets={preload}
|
|
92
|
+
>
|
|
93
|
+
{markup}
|
|
94
|
+
</Html>,
|
|
95
|
+
),
|
|
96
|
+
{
|
|
97
|
+
headers,
|
|
98
|
+
status: statusCode,
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export {
|
|
2
|
+
render,
|
|
3
|
+
Html,
|
|
4
|
+
Serialize,
|
|
5
|
+
HtmlManager,
|
|
6
|
+
HtmlContext,
|
|
7
|
+
SERVER_ACTION_ID as HTML_SERVER_ACTION_ID,
|
|
8
|
+
} from '@quilted/react-html/server';
|
|
9
|
+
export {
|
|
10
|
+
ServerAction,
|
|
11
|
+
useServerAction,
|
|
12
|
+
ServerRenderManager,
|
|
13
|
+
ServerRenderContext,
|
|
14
|
+
extract,
|
|
15
|
+
} from '@quilted/react-server-render/server';
|
|
16
|
+
export {createAssetLoader} from '@quilted/async/server';
|
|
17
|
+
export type {
|
|
18
|
+
Asset,
|
|
19
|
+
AssetLoader,
|
|
20
|
+
AssetSelector,
|
|
21
|
+
AssetManifest,
|
|
22
|
+
AssetManifestEntry,
|
|
23
|
+
} from '@quilted/async/server';
|
|
24
|
+
export {
|
|
25
|
+
AsyncAssetContext,
|
|
26
|
+
AsyncAssetManager,
|
|
27
|
+
SERVER_ACTION_ID as ASYNC_ASSETS_SERVER_ACTION_ID,
|
|
28
|
+
} from '@quilted/react-async/server';
|
|
29
|
+
export type {ServerRenderPass} from '@quilted/react-server-render/server';
|
|
30
|
+
export {renderEmail} from '@quilted/react-email/server';
|
|
31
|
+
export type {HttpState} from '@quilted/react-http/server';
|
|
32
|
+
export {createHttpHandler} from '@quilted/http-handlers';
|
|
33
|
+
|
|
34
|
+
export {renderApp} from './render';
|
|
35
|
+
export {ServerContext} from './ServerContext';
|
|
36
|
+
export {
|
|
37
|
+
createServerRenderingRequestHandler,
|
|
38
|
+
createServerRenderingHttpHandler,
|
|
39
|
+
renderToResponse,
|
|
40
|
+
} from './http-handler';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {ReactElement} from 'react';
|
|
2
|
+
|
|
3
|
+
import {extract} from '@quilted/react-server-render/server';
|
|
4
|
+
import type {Options as ExtractOptions} from '@quilted/react-server-render/server';
|
|
5
|
+
import {HtmlManager} from '@quilted/react-html/server';
|
|
6
|
+
import {HttpManager} from '@quilted/react-http/server';
|
|
7
|
+
import {AsyncAssetManager} from '@quilted/react-async/server';
|
|
8
|
+
|
|
9
|
+
import {ServerContext} from './ServerContext';
|
|
10
|
+
|
|
11
|
+
interface Options extends ExtractOptions {
|
|
12
|
+
url?: string | URL;
|
|
13
|
+
headers?: NonNullable<
|
|
14
|
+
ConstructorParameters<typeof HttpManager>[0]
|
|
15
|
+
>['headers'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function renderApp(
|
|
19
|
+
app: ReactElement<any>,
|
|
20
|
+
{decorate, url, headers, ...rest}: Options = {},
|
|
21
|
+
) {
|
|
22
|
+
const html = new HtmlManager();
|
|
23
|
+
const asyncAssets = new AsyncAssetManager();
|
|
24
|
+
const http = new HttpManager({headers});
|
|
25
|
+
|
|
26
|
+
const markup = await extract(app, {
|
|
27
|
+
decorate(app) {
|
|
28
|
+
return (
|
|
29
|
+
<ServerContext
|
|
30
|
+
asyncAssets={asyncAssets}
|
|
31
|
+
http={http}
|
|
32
|
+
html={html}
|
|
33
|
+
url={url}
|
|
34
|
+
>
|
|
35
|
+
{decorate?.(app) ?? app}
|
|
36
|
+
</ServerContext>
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
...rest,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {markup, http, html, asyncAssets};
|
|
43
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type {PropsWithChildren} from 'react';
|
|
2
|
+
|
|
3
|
+
import {InitialUrlContext} from '@quilted/react-router';
|
|
4
|
+
import {HtmlContext} from '@quilted/react-html/server';
|
|
5
|
+
import type {HtmlManager} from '@quilted/react-html/server';
|
|
6
|
+
import {AsyncAssetContext} from '@quilted/react-async/server';
|
|
7
|
+
import type {AsyncAssetManager} from '@quilted/react-async/server';
|
|
8
|
+
import {HttpServerContext} from '@quilted/react-http/server';
|
|
9
|
+
import type {HttpManager} from '@quilted/react-http/server';
|
|
10
|
+
|
|
11
|
+
import {maybeWrapContext} from '../utilities/react';
|
|
12
|
+
|
|
13
|
+
interface Props {
|
|
14
|
+
url?: string | URL;
|
|
15
|
+
html?: HtmlManager;
|
|
16
|
+
http?: HttpManager;
|
|
17
|
+
asyncAssets?: AsyncAssetManager;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function StaticContext({
|
|
21
|
+
url,
|
|
22
|
+
html,
|
|
23
|
+
http,
|
|
24
|
+
asyncAssets,
|
|
25
|
+
children,
|
|
26
|
+
}: PropsWithChildren<Props>) {
|
|
27
|
+
const normalizedUrl = typeof url === 'string' ? new URL(url) : url;
|
|
28
|
+
|
|
29
|
+
return maybeWrapContext(
|
|
30
|
+
AsyncAssetContext,
|
|
31
|
+
asyncAssets,
|
|
32
|
+
maybeWrapContext(
|
|
33
|
+
HttpServerContext,
|
|
34
|
+
http,
|
|
35
|
+
maybeWrapContext(
|
|
36
|
+
HtmlContext,
|
|
37
|
+
html,
|
|
38
|
+
maybeWrapContext(InitialUrlContext, normalizedUrl, children),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type {ComponentType} from 'react';
|
|
2
|
+
|
|
3
|
+
import type {AssetLoader} from '@quilted/async/server';
|
|
4
|
+
import {render, Html} from '@quilted/react-html/server';
|
|
5
|
+
import type {RouteDefinition} from '@quilted/react-router';
|
|
6
|
+
import {
|
|
7
|
+
StaticRenderer,
|
|
8
|
+
StaticRendererContext,
|
|
9
|
+
} from '@quilted/react-router/static';
|
|
10
|
+
import type {HttpState} from '@quilted/react-http/server';
|
|
11
|
+
|
|
12
|
+
import {renderApp} from './render';
|
|
13
|
+
|
|
14
|
+
export interface RenderDetails {
|
|
15
|
+
readonly route: string;
|
|
16
|
+
readonly hasChildren: boolean;
|
|
17
|
+
readonly fallback: boolean;
|
|
18
|
+
readonly content: string;
|
|
19
|
+
readonly http: HttpState;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface RenderableRoute {
|
|
23
|
+
route: string;
|
|
24
|
+
fallback: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Options {
|
|
28
|
+
routes: string[];
|
|
29
|
+
assets: AssetLoader<{modules: boolean}>;
|
|
30
|
+
crawl?: boolean;
|
|
31
|
+
baseUrl?: string;
|
|
32
|
+
prettify?: boolean;
|
|
33
|
+
onRender(details: RenderDetails): void | Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const BASE_URL = 'http://localhost:3000';
|
|
37
|
+
|
|
38
|
+
export async function renderStatic(
|
|
39
|
+
App: ComponentType<any>,
|
|
40
|
+
{
|
|
41
|
+
assets,
|
|
42
|
+
routes: startingRoutes,
|
|
43
|
+
onRender,
|
|
44
|
+
crawl = true,
|
|
45
|
+
baseUrl = BASE_URL,
|
|
46
|
+
prettify = true,
|
|
47
|
+
}: Options,
|
|
48
|
+
) {
|
|
49
|
+
const routesToHandle = startingRoutes.map<RenderableRoute>((route) => ({
|
|
50
|
+
route: removePostfixSlash(new URL(route, baseUrl).pathname),
|
|
51
|
+
fallback: false,
|
|
52
|
+
}));
|
|
53
|
+
const seenRoutes = [...routesToHandle];
|
|
54
|
+
const seenRouteIds = new Set<string>(seenRoutes.map(({route}) => route));
|
|
55
|
+
|
|
56
|
+
let renderableRoute: RenderableRoute | undefined;
|
|
57
|
+
|
|
58
|
+
while ((renderableRoute = routesToHandle.shift())) {
|
|
59
|
+
const {route, fallback} = renderableRoute;
|
|
60
|
+
|
|
61
|
+
const url = new URL(route, baseUrl);
|
|
62
|
+
|
|
63
|
+
const {html, http, routes} = await renderUrl(url, {fallback});
|
|
64
|
+
|
|
65
|
+
if (crawl) {
|
|
66
|
+
for (const {
|
|
67
|
+
routes: routeDefinitions,
|
|
68
|
+
fallback = false,
|
|
69
|
+
consumedPath,
|
|
70
|
+
prefix,
|
|
71
|
+
} of routes) {
|
|
72
|
+
const basePathname = joinPath(prefix, consumedPath);
|
|
73
|
+
const baseId =
|
|
74
|
+
basePathname === '/'
|
|
75
|
+
? basePathname
|
|
76
|
+
: `__QUILT_BASE_${basePathname}__`;
|
|
77
|
+
|
|
78
|
+
for (const routeDefinition of routeDefinitions) {
|
|
79
|
+
await recordRouteDefinition(routeDefinition, {
|
|
80
|
+
baseId,
|
|
81
|
+
basePathname,
|
|
82
|
+
addFallbacks: fallback,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (
|
|
87
|
+
fallback &&
|
|
88
|
+
routeDefinitions[routeDefinitions.length - 1]?.match != null
|
|
89
|
+
) {
|
|
90
|
+
await recordRouteDefinition(
|
|
91
|
+
{},
|
|
92
|
+
{
|
|
93
|
+
baseId,
|
|
94
|
+
basePathname,
|
|
95
|
+
addFallbacks: fallback,
|
|
96
|
+
},
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await onRender({
|
|
103
|
+
route,
|
|
104
|
+
content: html,
|
|
105
|
+
http,
|
|
106
|
+
fallback,
|
|
107
|
+
hasChildren:
|
|
108
|
+
!fallback &&
|
|
109
|
+
seenRoutes.some((otherRoute) =>
|
|
110
|
+
otherRoute.route.startsWith(`${route}/`),
|
|
111
|
+
),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function recordRouteDefinition(
|
|
116
|
+
{match, children, renderStatic}: RouteDefinition,
|
|
117
|
+
{
|
|
118
|
+
basePathname,
|
|
119
|
+
baseId,
|
|
120
|
+
addFallbacks,
|
|
121
|
+
}: {basePathname: string; baseId: string; addFallbacks: boolean},
|
|
122
|
+
) {
|
|
123
|
+
if (renderStatic === false) return;
|
|
124
|
+
|
|
125
|
+
let routeId: string;
|
|
126
|
+
const hasChildren = children && children.length > 0;
|
|
127
|
+
const hasManualMatches =
|
|
128
|
+
typeof renderStatic === 'function' && typeof match !== 'string';
|
|
129
|
+
|
|
130
|
+
const matchedRoutes: {id: string; route: string; fallback: boolean}[] = [];
|
|
131
|
+
|
|
132
|
+
if (typeof match === 'string') {
|
|
133
|
+
routeId = joinPath(baseId, match);
|
|
134
|
+
matchedRoutes.push({
|
|
135
|
+
id: routeId,
|
|
136
|
+
route: joinPath(basePathname, match),
|
|
137
|
+
fallback: false,
|
|
138
|
+
});
|
|
139
|
+
} else if (typeof match === 'function') {
|
|
140
|
+
routeId = joinPath(
|
|
141
|
+
baseId,
|
|
142
|
+
`__QUILT_FUNCTION_ROUTE_${match.toString()}__`,
|
|
143
|
+
);
|
|
144
|
+
} else if (match instanceof RegExp) {
|
|
145
|
+
routeId = joinPath(`__QUILT_REGEX_ROUTE_${match.source}__`);
|
|
146
|
+
} else {
|
|
147
|
+
routeId = joinPath(baseId, '__QUILT_FALLBACK_ROUTE__');
|
|
148
|
+
|
|
149
|
+
if (!hasManualMatches) {
|
|
150
|
+
matchedRoutes.push({id: routeId, route: basePathname, fallback: true});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (seenRouteIds.has(routeId)) return;
|
|
155
|
+
seenRouteIds.add(routeId);
|
|
156
|
+
|
|
157
|
+
if (typeof renderStatic === 'function' && typeof match !== 'string') {
|
|
158
|
+
const matchedRouteParts = await renderStatic();
|
|
159
|
+
|
|
160
|
+
for (const routePart of matchedRouteParts) {
|
|
161
|
+
const id = joinPath(baseId, `__QUILT_MATCH_${routePart}__`);
|
|
162
|
+
seenRouteIds.add(id);
|
|
163
|
+
|
|
164
|
+
matchedRoutes.push({
|
|
165
|
+
id,
|
|
166
|
+
route: joinPath(basePathname, routePart),
|
|
167
|
+
fallback: false,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (hasChildren) {
|
|
173
|
+
for (const {id, route} of matchedRoutes) {
|
|
174
|
+
for (const child of children!) {
|
|
175
|
+
await recordRouteDefinition(child, {
|
|
176
|
+
addFallbacks,
|
|
177
|
+
basePathname: route,
|
|
178
|
+
baseId: id,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (addFallbacks && children![children!.length - 1]?.match != null) {
|
|
183
|
+
await recordRouteDefinition(
|
|
184
|
+
{},
|
|
185
|
+
{
|
|
186
|
+
addFallbacks,
|
|
187
|
+
basePathname: route,
|
|
188
|
+
baseId: id,
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for (const {route, fallback} of matchedRoutes) {
|
|
198
|
+
const renderableRoute = {route, fallback};
|
|
199
|
+
seenRoutes.push(renderableRoute);
|
|
200
|
+
routesToHandle.push(renderableRoute);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function renderUrl(url: URL, {fallback = false} = {}) {
|
|
205
|
+
const routeRecorder = new StaticRenderer({
|
|
206
|
+
forceFallback: fallback ? url.pathname : undefined,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
const {
|
|
210
|
+
http,
|
|
211
|
+
html: htmlManager,
|
|
212
|
+
markup,
|
|
213
|
+
asyncAssets,
|
|
214
|
+
} = await renderApp(<App />, {
|
|
215
|
+
url,
|
|
216
|
+
decorate(app) {
|
|
217
|
+
return (
|
|
218
|
+
<StaticRendererContext.Provider value={routeRecorder}>
|
|
219
|
+
{app}
|
|
220
|
+
</StaticRendererContext.Provider>
|
|
221
|
+
);
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
const usedAssets = asyncAssets.used({timing: 'load'});
|
|
226
|
+
|
|
227
|
+
const [
|
|
228
|
+
moduleStyles,
|
|
229
|
+
moduleScripts,
|
|
230
|
+
modulePreload,
|
|
231
|
+
nomoduleStyles,
|
|
232
|
+
nomoduleScripts,
|
|
233
|
+
] = await Promise.all([
|
|
234
|
+
assets.styles({async: usedAssets, options: {modules: true}}),
|
|
235
|
+
assets.scripts({async: usedAssets, options: {modules: true}}),
|
|
236
|
+
assets.asyncAssets(asyncAssets.used({timing: 'preload'}), {
|
|
237
|
+
options: {modules: true},
|
|
238
|
+
}),
|
|
239
|
+
assets.styles({async: usedAssets, options: {modules: false}}),
|
|
240
|
+
assets.scripts({async: usedAssets, options: {modules: false}}),
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
// We don’t want to load styles from both bundles, so we only use module styles,
|
|
244
|
+
// since modules are intended to be the default and CSS (usually) doesn’t
|
|
245
|
+
// have features that meaningfully break older user agents.
|
|
246
|
+
const styles = moduleStyles.length > 0 ? moduleStyles : nomoduleStyles;
|
|
247
|
+
|
|
248
|
+
// If there are nomodule scripts, we can’t really do preloading, because we can’t
|
|
249
|
+
// prevent the nomodule scripts from being preloaded in module browsers. If there
|
|
250
|
+
// are only module scripts, we can preload those.
|
|
251
|
+
const preload = nomoduleScripts.length > 0 ? [] : modulePreload;
|
|
252
|
+
|
|
253
|
+
const scripts = [
|
|
254
|
+
...moduleScripts,
|
|
255
|
+
...nomoduleScripts.map((script) => ({...script, nomodule: true})),
|
|
256
|
+
];
|
|
257
|
+
|
|
258
|
+
const minifiedHtml = render(
|
|
259
|
+
<Html
|
|
260
|
+
manager={htmlManager}
|
|
261
|
+
styles={styles}
|
|
262
|
+
scripts={scripts}
|
|
263
|
+
preloadAssets={preload}
|
|
264
|
+
>
|
|
265
|
+
{markup}
|
|
266
|
+
</Html>,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const html = prettify ? await prettifyHtml(minifiedHtml) : minifiedHtml;
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
html,
|
|
273
|
+
http: http.state,
|
|
274
|
+
routes: routeRecorder.state,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async function prettifyHtml(html: string) {
|
|
280
|
+
try {
|
|
281
|
+
const {default: prettier} = await import('prettier');
|
|
282
|
+
return prettier.format(html, {parser: 'html'});
|
|
283
|
+
} catch (error) {
|
|
284
|
+
return html;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function joinPath(...parts: (string | undefined | null | false)[]) {
|
|
289
|
+
let path = '/';
|
|
290
|
+
|
|
291
|
+
for (const part of parts) {
|
|
292
|
+
if (typeof part !== 'string') continue;
|
|
293
|
+
const normalizedPart = part.startsWith('/') ? part.slice(1) : part;
|
|
294
|
+
if (normalizedPart.length === 0) continue;
|
|
295
|
+
if (path !== '/') path += '/';
|
|
296
|
+
path += normalizedPart;
|
|
297
|
+
path = removePostfixSlash(path);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return path;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function removePostfixSlash(value: string) {
|
|
304
|
+
return value.endsWith('/') && value !== '/'
|
|
305
|
+
? value.slice(0, value.length - 1)
|
|
306
|
+
: value;
|
|
307
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type {ReactElement} from 'react';
|
|
2
|
+
|
|
3
|
+
import {extract} from '@quilted/react-server-render/server';
|
|
4
|
+
import type {Options as ExtractOptions} from '@quilted/react-server-render/server';
|
|
5
|
+
import {HtmlManager} from '@quilted/react-html/server';
|
|
6
|
+
import {HttpManager} from '@quilted/react-http/server';
|
|
7
|
+
import {AsyncAssetManager} from '@quilted/react-async/server';
|
|
8
|
+
|
|
9
|
+
import {StaticContext} from './StaticContext';
|
|
10
|
+
|
|
11
|
+
interface Options extends ExtractOptions {
|
|
12
|
+
url?: string | URL;
|
|
13
|
+
headers?: NonNullable<
|
|
14
|
+
ConstructorParameters<typeof HttpManager>[0]
|
|
15
|
+
>['headers'];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function renderApp(
|
|
19
|
+
app: ReactElement<any>,
|
|
20
|
+
{decorate, url, headers, ...rest}: Options = {},
|
|
21
|
+
) {
|
|
22
|
+
const html = new HtmlManager();
|
|
23
|
+
const asyncAssets = new AsyncAssetManager();
|
|
24
|
+
const http = new HttpManager({headers});
|
|
25
|
+
|
|
26
|
+
const markup = await extract(app, {
|
|
27
|
+
decorate(app) {
|
|
28
|
+
return (
|
|
29
|
+
<StaticContext
|
|
30
|
+
asyncAssets={asyncAssets}
|
|
31
|
+
html={html}
|
|
32
|
+
http={http}
|
|
33
|
+
url={url}
|
|
34
|
+
>
|
|
35
|
+
{decorate?.(app) ?? app}
|
|
36
|
+
</StaticContext>
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
...rest,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return {markup, http, html, asyncAssets};
|
|
43
|
+
}
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type {Context as ReactContext, ReactNode, ReactElement} from 'react';
|
|
2
|
+
|
|
3
|
+
export function maybeWrapContext<T>(
|
|
4
|
+
Context: ReactContext<T>,
|
|
5
|
+
value: T | null | undefined,
|
|
6
|
+
children: ReactNode,
|
|
7
|
+
): ReactElement {
|
|
8
|
+
return value ? (
|
|
9
|
+
<Context.Provider value={value}>{children}</Context.Provider>
|
|
10
|
+
) : (
|
|
11
|
+
(children as any)
|
|
12
|
+
);
|
|
13
|
+
}
|