fontdue-js 3.0.0-alpha7 → 3.0.0-alpha9
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 +9 -0
- package/README.md +84 -1
- package/dist/__generated__/orderTrackingUpdateOrderTrackingMutation.graphql.js +1 -8
- package/dist/__tests__/networkFetch.test.js +66 -0
- package/dist/__tests__/nextAdapter.test.js +447 -0
- package/dist/components/Cart/CartOrder.js +9 -1
- package/dist/components/Cart/orderTracking.js +8 -15
- package/dist/components/FontStyle/index.d.ts +2 -0
- package/dist/components/FontStyle/index.js +4 -2
- package/dist/components/TypeTester/index.js +3 -1
- package/dist/components/TypeTester/useTypeTesterStyler.d.ts +3 -1
- package/dist/components/TypeTester/useTypeTesterStyler.js +70 -20
- package/dist/components/elements/StoreModalUnifiedCheckout.js +8 -0
- package/dist/components/useFontStyle.d.ts +7 -0
- package/dist/components/useFontStyle.js +2 -1
- package/dist/next/config.d.ts +41 -0
- package/dist/next/config.js +193 -0
- package/dist/next/image-loader.d.ts +7 -0
- package/dist/next/image-loader.js +39 -0
- package/dist/next/index.d.ts +2 -0
- package/dist/next/index.js +10 -0
- package/dist/next/revalidate.d.ts +1 -0
- package/dist/next/revalidate.js +37 -0
- package/dist/next/tenant.d.ts +26 -0
- package/dist/next/tenant.js +169 -0
- package/dist/relay/environment.js +11 -1
- package/dist/relay/serverConfig.d.ts +6 -0
- package/dist/vite.js +2 -0
- package/package.json +5 -2
- package/types/next-cache.d.ts +6 -0
- package/types/next-navigation.d.ts +6 -0
- package/vitest.config.ts +4 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// Tenant/mode resolution for Next.js apps. An app using this adapter runs in
|
|
2
|
+
// one of two modes:
|
|
3
|
+
//
|
|
4
|
+
// - Single-tenant (default): NEXT_PUBLIC_FONTDUE_URL points at one Fontdue
|
|
5
|
+
// site and every request renders that site.
|
|
6
|
+
//
|
|
7
|
+
// - Multi-tenant (FONTDUE_MULTI_TENANT=1): the tenant is derived per request
|
|
8
|
+
// from the (forwarded) Host header, and one deployment serves every
|
|
9
|
+
// tenant. The rewrites installed by withFontdue (see ./config) turn each
|
|
10
|
+
// request into the /[domain]/... route tree so pages are rendered and
|
|
11
|
+
// cached per domain.
|
|
12
|
+
//
|
|
13
|
+
// In multi-tenant mode, GraphQL is fetched from FONTDUE_ORIGIN (the internal
|
|
14
|
+
// Fontdue server, e.g. http://localhost:4000 when running next to it) with
|
|
15
|
+
// the tenant's domain forwarded via X-Forwarded-Host, authenticated by the
|
|
16
|
+
// FONTDUE_PROXY_SECRET shared secret (the Fontdue server refuses to trust
|
|
17
|
+
// X-Forwarded-Host without it). Without FONTDUE_ORIGIN it falls back to
|
|
18
|
+
// fetching the tenant's public URL directly, which is useful for local
|
|
19
|
+
// development against live sites.
|
|
20
|
+
//
|
|
21
|
+
// The fontdue-js components embedded in pages fetch the same way: their
|
|
22
|
+
// server-side preloads read the per-render config set by
|
|
23
|
+
// configureFontdueRender, and in the browser they fetch the relative
|
|
24
|
+
// /graphql on the page's own origin — so multi-tenant mode needs no
|
|
25
|
+
// NEXT_PUBLIC_FONTDUE_URL at all.
|
|
26
|
+
|
|
27
|
+
import { notFound } from 'next/navigation';
|
|
28
|
+
import { setFontdueServerConfig, getFontdueServerConfig } from '../relay/serverConfig.js';
|
|
29
|
+
export const isMultiTenant = process.env.FONTDUE_MULTI_TENANT === '1';
|
|
30
|
+
const singleTenantUrl = process.env.NEXT_PUBLIC_FONTDUE_URL;
|
|
31
|
+
const internalOrigin = process.env.FONTDUE_ORIGIN;
|
|
32
|
+
const proxySecret = process.env.FONTDUE_PROXY_SECRET;
|
|
33
|
+
|
|
34
|
+
// Hostnames only: letters/digits/hyphens/dots, no path or port. Anything else
|
|
35
|
+
// is rejected before it can reach the GraphQL fetch or the filesystem cache.
|
|
36
|
+
const DOMAIN_RE = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
|
37
|
+
export function isValidDomain(domain) {
|
|
38
|
+
return domain.length <= 253 && DOMAIN_RE.test(domain);
|
|
39
|
+
}
|
|
40
|
+
// Where to fetch GraphQL for a given tenant domain, plus any headers needed
|
|
41
|
+
// for the Fontdue server to resolve that tenant.
|
|
42
|
+
export function fontdueEndpoint(domain) {
|
|
43
|
+
const tags = ['graphql', `graphql:${domain}`];
|
|
44
|
+
if (!isMultiTenant) {
|
|
45
|
+
return {
|
|
46
|
+
domain,
|
|
47
|
+
origin: requireSingleTenantUrl(),
|
|
48
|
+
headers: {},
|
|
49
|
+
tags
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (internalOrigin) {
|
|
53
|
+
return {
|
|
54
|
+
domain,
|
|
55
|
+
origin: internalOrigin,
|
|
56
|
+
headers: {
|
|
57
|
+
'x-forwarded-host': domain,
|
|
58
|
+
...(proxySecret ? {
|
|
59
|
+
'x-fontdue-proxy-secret': proxySecret
|
|
60
|
+
} : {})
|
|
61
|
+
},
|
|
62
|
+
tags
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
return {
|
|
66
|
+
domain,
|
|
67
|
+
origin: `https://${domain}`,
|
|
68
|
+
headers: {},
|
|
69
|
+
tags
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
function requireSingleTenantUrl() {
|
|
73
|
+
if (!singleTenantUrl) {
|
|
74
|
+
throw new Error('fontdue-js/next: set NEXT_PUBLIC_FONTDUE_URL (single-tenant) or FONTDUE_MULTI_TENANT=1 (multi-tenant).');
|
|
75
|
+
}
|
|
76
|
+
return singleTenantUrl;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Per-render config for fontdue-js's own server-side fetches: same endpoint
|
|
80
|
+
// and headers as the app's fetches, plus the per-domain cache tag so the
|
|
81
|
+
// revalidate handler purges embed data (theme config, type testers, store)
|
|
82
|
+
// along with the pages. ('graphql' itself is added by the network layer.)
|
|
83
|
+
export function fontdueServerConfig(domain) {
|
|
84
|
+
const {
|
|
85
|
+
origin,
|
|
86
|
+
headers
|
|
87
|
+
} = fontdueEndpoint(domain);
|
|
88
|
+
return {
|
|
89
|
+
url: origin,
|
|
90
|
+
headers,
|
|
91
|
+
cacheTags: [`graphql:${domain}`],
|
|
92
|
+
domain
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// The one call a data helper needs at the top of every page render:
|
|
97
|
+
// validates the request-derived domain (null means "treat as 404"), then
|
|
98
|
+
// points fontdue-js's server-side fetches at the tenant's endpoint for the
|
|
99
|
+
// rest of this render pass.
|
|
100
|
+
//
|
|
101
|
+
// Call it in a data helper every page goes through, not only in the root
|
|
102
|
+
// layout: an App Router soft navigation re-renders just the page segment,
|
|
103
|
+
// and the per-render config store starts empty on every pass.
|
|
104
|
+
export function configureFontdueRender(domain) {
|
|
105
|
+
if (!isValidDomain(domain)) return null;
|
|
106
|
+
setFontdueServerConfig(fontdueServerConfig(domain));
|
|
107
|
+
return fontdueEndpoint(domain);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// The props every page, layout and generateMetadata receives. Any params
|
|
111
|
+
// object is structurally compatible; only `domain` is read.
|
|
112
|
+
|
|
113
|
+
// The one line at the top of every page, layout and generateMetadata body
|
|
114
|
+
// in the [domain] route tree:
|
|
115
|
+
//
|
|
116
|
+
// await prepareFontdueRender(props);
|
|
117
|
+
//
|
|
118
|
+
// Reads the request's site from the [domain] route param, 404s anything
|
|
119
|
+
// that isn't a plain hostname (stray paths can reach the catch-all route
|
|
120
|
+
// with their first segment as the "domain"), and points every Fontdue fetch
|
|
121
|
+
// in the rest of this render pass — the app's fetches via
|
|
122
|
+
// currentFontdueEndpoint and fontdue-js's embedded-component preloads — at
|
|
123
|
+
// that site.
|
|
124
|
+
//
|
|
125
|
+
// It must run per entry point, not only in the layout: a soft navigation
|
|
126
|
+
// re-renders just the page segment, and the per-render store starts empty
|
|
127
|
+
// on every pass. Forgetting it is loud, not subtle: currentFontdueEndpoint
|
|
128
|
+
// throws in multi-tenant mode when no render config was set.
|
|
129
|
+
//
|
|
130
|
+
// Route handlers are not React renders — the render-scoped store doesn't
|
|
131
|
+
// exist there, so use the returned endpoint explicitly instead of relying
|
|
132
|
+
// on currentFontdueEndpoint.
|
|
133
|
+
export async function prepareFontdueRender(props) {
|
|
134
|
+
const {
|
|
135
|
+
domain
|
|
136
|
+
} = await props.params;
|
|
137
|
+
const endpoint = typeof domain === 'string' ? configureFontdueRender(domain) : null;
|
|
138
|
+
if (!endpoint) notFound();
|
|
139
|
+
return endpoint;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Endpoint for the app's own GraphQL fetches in this render pass: whatever
|
|
143
|
+
// prepareFontdueRender configured, or — in a single-tenant app without the
|
|
144
|
+
// [domain] tree, where prepareFontdueRender is never called — the
|
|
145
|
+
// NEXT_PUBLIC_FONTDUE_URL site. Throwing rather than guessing in
|
|
146
|
+
// multi-tenant mode turns a forgotten prepareFontdueRender into an
|
|
147
|
+
// unmissable error instead of a silent wrong-site fetch on soft
|
|
148
|
+
// navigations.
|
|
149
|
+
export function currentFontdueEndpoint() {
|
|
150
|
+
var _getFontdueServerConf;
|
|
151
|
+
const domain = (_getFontdueServerConf = getFontdueServerConfig()) === null || _getFontdueServerConf === void 0 ? void 0 : _getFontdueServerConf.domain;
|
|
152
|
+
if (domain) return fontdueEndpoint(domain);
|
|
153
|
+
if (isMultiTenant) {
|
|
154
|
+
throw new Error('fontdue-js/next: no render config set — call prepareFontdueRender(props) at the top of every page, layout and generateMetadata that fetches.');
|
|
155
|
+
}
|
|
156
|
+
return fontdueEndpoint(new URL(requireSingleTenantUrl()).host);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Re-export from routes in the [domain] tree:
|
|
160
|
+
//
|
|
161
|
+
// export { generateStaticParams } from 'fontdue-js/next';
|
|
162
|
+
//
|
|
163
|
+
// Domains aren't known at build time, so nothing is prerendered — but
|
|
164
|
+
// providing generateStaticParams is what opts a dynamic route into
|
|
165
|
+
// static-on-demand rendering (generated on first request, then cached until
|
|
166
|
+
// revalidated) instead of fully dynamic rendering.
|
|
167
|
+
export async function generateStaticParams() {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
@@ -4,7 +4,7 @@ import { getFontdueServerConfig } from './serverConfig.js';
|
|
|
4
4
|
|
|
5
5
|
// `__FONTDUE_JS_VERSION__` is replaced by an inline babel plugin
|
|
6
6
|
// (defineVersionPlugin in .babelrc.cjs) with the literal package.json#version.
|
|
7
|
-
const version = "3.0.0-
|
|
7
|
+
const version = "3.0.0-alpha9";
|
|
8
8
|
const IS_SERVER = typeof window === typeof undefined;
|
|
9
9
|
|
|
10
10
|
// Read env from either process.env (Node/Next.js) or import.meta.env (Vite/Astro).
|
|
@@ -63,6 +63,16 @@ export function createNetworkFetch(options) {
|
|
|
63
63
|
query: request.text,
|
|
64
64
|
variables
|
|
65
65
|
}),
|
|
66
|
+
// Server-side fetches are all site content (per-session data is
|
|
67
|
+
// fetched client-side only), so opt them into Next's data cache —
|
|
68
|
+
// without this, Next 15 treats them as no-store and silently makes
|
|
69
|
+
// every page fully dynamic. The tags below let the revalidate
|
|
70
|
+
// handler purge them when content changes. Inert outside Next:
|
|
71
|
+
// Node's fetch accepts and ignores this cache mode, and the
|
|
72
|
+
// browser path doesn't set it.
|
|
73
|
+
...(IS_SERVER ? {
|
|
74
|
+
cache: 'force-cache'
|
|
75
|
+
} : {}),
|
|
66
76
|
// @ts-ignore
|
|
67
77
|
next: {
|
|
68
78
|
tags: ['graphql', ...((serverConfig === null || serverConfig === void 0 ? void 0 : serverConfig.cacheTags) ?? []), `operation:${request.name}`]
|
|
@@ -5,6 +5,12 @@ export interface FontdueServerConfig {
|
|
|
5
5
|
headers?: Record<string, string>;
|
|
6
6
|
/** Extra Next.js fetch cache tags applied to every server-side GraphQL fetch. */
|
|
7
7
|
cacheTags?: string[];
|
|
8
|
+
/**
|
|
9
|
+
* The site domain this render is for, when known. Set by
|
|
10
|
+
* fontdue-js/next's prepareFontdueRender so render-scoped helpers
|
|
11
|
+
* (currentFontdueEndpoint) can recover it.
|
|
12
|
+
*/
|
|
13
|
+
domain?: string;
|
|
8
14
|
}
|
|
9
15
|
export declare function setFontdueServerConfig(config: FontdueServerConfig): void;
|
|
10
16
|
export declare function getFontdueServerConfig(): FontdueServerConfig | undefined;
|
package/dist/vite.js
CHANGED
|
@@ -79,7 +79,9 @@ function getFontdueJsSubpaths() {
|
|
|
79
79
|
for (const key of Object.keys(pkg.exports ?? {})) {
|
|
80
80
|
if (!key.startsWith('./')) continue;
|
|
81
81
|
if (key.endsWith('.css')) continue;
|
|
82
|
+
// Build-config and server-only entrypoints never reach a browser bundle.
|
|
82
83
|
if (key === './vite') continue;
|
|
84
|
+
if (key === './next' || key.startsWith('./next/')) continue;
|
|
83
85
|
out.push(`fontdue-js/${key.slice(2)}`);
|
|
84
86
|
}
|
|
85
87
|
return out;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fontdue-js",
|
|
3
|
-
"version": "3.0.0-
|
|
3
|
+
"version": "3.0.0-alpha9",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "npm run relay && run-p build-js build-css build-ts",
|
|
@@ -82,7 +82,10 @@
|
|
|
82
82
|
},
|
|
83
83
|
"exports": {
|
|
84
84
|
".": "./dist/index.js",
|
|
85
|
-
"./
|
|
85
|
+
"./next": "./dist/next/index.js",
|
|
86
|
+
"./next/config": "./dist/next/config.js",
|
|
87
|
+
"./next/revalidate": "./dist/next/revalidate.js",
|
|
88
|
+
"./next/image-loader": "./dist/next/image-loader.js",
|
|
86
89
|
"./fontdue.css": "./dist/fontdue.css",
|
|
87
90
|
"./BuyButton": {
|
|
88
91
|
"react-server": "./dist/components/BuyButton/index.server.js",
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Minimal declaration so src/next/revalidate.ts type-checks without `next`
|
|
2
|
+
// installed (it's the host app's dependency; this package only ever runs the
|
|
3
|
+
// import inside a Next.js server).
|
|
4
|
+
declare module 'next/cache' {
|
|
5
|
+
export function revalidateTag(tag: string): void;
|
|
6
|
+
}
|
package/vitest.config.ts
CHANGED
|
@@ -2,6 +2,10 @@ import { defineConfig } from 'vitest/config';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
|
+
// Stand-in for the babel define plugin that inlines the package version.
|
|
6
|
+
define: {
|
|
7
|
+
__FONTDUE_JS_VERSION__: JSON.stringify('0.0.0-test'),
|
|
8
|
+
},
|
|
5
9
|
test: {
|
|
6
10
|
alias: {
|
|
7
11
|
'__generated__': path.resolve(__dirname, 'src/__generated__'),
|