webstudio 0.0.0-588fe22 → 0.0.0-73cd6ea

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.
Files changed (59) hide show
  1. package/README.md +1 -7
  2. package/lib/cli.js +5131 -2582
  3. package/package.json +37 -34
  4. package/templates/cloudflare/package.json +2 -2
  5. package/templates/cloudflare/vite.config.ts +8 -0
  6. package/templates/defaults/app/root.tsx +6 -2
  7. package/templates/defaults/app/route-templates/html.tsx +12 -13
  8. package/templates/defaults/app/route-templates/xml.tsx +3 -5
  9. package/templates/defaults/package.json +14 -14
  10. package/templates/defaults/vite.config.ts +8 -0
  11. package/templates/{react-router-docker → react-router}/app/root.tsx +6 -2
  12. package/templates/{react-router-docker → react-router}/app/route-templates/html.tsx +12 -13
  13. package/templates/{react-router-docker → react-router}/app/route-templates/xml.tsx +3 -1
  14. package/templates/react-router/package.json +34 -0
  15. package/templates/react-router/vite.config.ts +14 -0
  16. package/templates/react-router-cloudflare/app/constants.mjs +21 -0
  17. package/templates/react-router-cloudflare/app/entry.server.tsx +43 -0
  18. package/templates/react-router-cloudflare/package.json +12 -0
  19. package/templates/react-router-cloudflare/react-router.config.ts +7 -0
  20. package/templates/react-router-cloudflare/tsconfig.json +23 -0
  21. package/templates/react-router-cloudflare/vite.config.ts +15 -0
  22. package/templates/react-router-cloudflare/workers/app.ts +26 -0
  23. package/templates/react-router-cloudflare/wrangler.jsonc +6 -0
  24. package/templates/react-router-docker/app/constants.mjs +14 -1
  25. package/templates/react-router-docker/package.json +5 -33
  26. package/templates/{netlify-edge-functions → react-router-netlify}/app/constants.mjs +1 -1
  27. package/templates/react-router-netlify/netlify.toml +6 -0
  28. package/templates/react-router-netlify/package.json +10 -0
  29. package/templates/react-router-netlify/vite.config.ts +15 -0
  30. package/templates/{vercel → react-router-vercel}/app/constants.mjs +1 -1
  31. package/templates/react-router-vercel/package.json +9 -0
  32. package/templates/react-router-vercel/react-router.config.ts +6 -0
  33. package/templates/{vercel → react-router-vercel}/vercel.json +1 -0
  34. package/templates/saas-helpers/package.json +4 -1
  35. package/templates/saas-helpers/vite.config.ts +48 -0
  36. package/templates/ssg/app/route-templates/html/+Head.tsx +14 -6
  37. package/templates/ssg/app/route-templates/html/+Page.tsx +3 -1
  38. package/templates/ssg/package.json +10 -10
  39. package/templates/ssg/renderer/+onRenderHtml.tsx +13 -5
  40. package/templates/ssg/vite.config.ts +8 -0
  41. package/templates/netlify-edge-functions/app/entry.server.tsx +0 -21
  42. package/templates/netlify-edge-functions/netlify.toml +0 -16
  43. package/templates/netlify-edge-functions/package.json +0 -9
  44. package/templates/netlify-edge-functions/vite.config.ts +0 -18
  45. package/templates/netlify-functions/app/constants.mjs +0 -29
  46. package/templates/netlify-functions/app/entry.server.tsx +0 -1
  47. package/templates/netlify-functions/netlify.toml +0 -16
  48. package/templates/netlify-functions/package.json +0 -9
  49. package/templates/netlify-functions/vite.config.ts +0 -18
  50. package/templates/react-router-docker/vite.config.ts +0 -6
  51. package/templates/vercel/.vercelignore +0 -8
  52. package/templates/vercel/package.json +0 -1
  53. /package/templates/{react-router-docker → react-router}/app/extension.ts +0 -0
  54. /package/templates/{react-router-docker → react-router}/app/route-templates/default-sitemap.tsx +0 -0
  55. /package/templates/{react-router-docker → react-router}/app/route-templates/redirect.tsx +0 -0
  56. /package/templates/{react-router-docker → react-router}/app/routes/[robots.txt].tsx +0 -0
  57. /package/templates/{react-router-docker → react-router}/app/routes.ts +0 -0
  58. /package/templates/{react-router-docker → react-router}/public/favicon.ico +0 -0
  59. /package/templates/{react-router-docker → react-router}/tsconfig.json +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "webstudio",
3
- "version": "0.0.0-588fe22",
3
+ "version": "0.0.0-73cd6ea",
4
4
  "description": "Webstudio CLI",
5
5
  "author": "Webstudio <github@webstudio.is>",
6
6
  "homepage": "https://webstudio.is",
@@ -26,58 +26,61 @@
26
26
  "node": ">=20.12"
27
27
  },
28
28
  "dependencies": {
29
- "@clack/prompts": "^0.9.1",
29
+ "@clack/prompts": "^0.10.0",
30
30
  "@emotion/hash": "^0.9.2",
31
- "acorn": "^8.14.0",
31
+ "acorn": "^8.14.1",
32
32
  "acorn-walk": "^8.3.4",
33
33
  "change-case": "^5.4.4",
34
34
  "deepmerge": "^4.3.1",
35
35
  "env-paths": "^3.0.0",
36
- "nanoid": "^5.0.9",
36
+ "nanoid": "^5.1.5",
37
37
  "p-limit": "^6.2.0",
38
- "parse5": "7.2.1",
38
+ "parse5": "7.3.0",
39
39
  "picocolors": "^1.1.1",
40
40
  "reserved-identifiers": "^1.0.0",
41
41
  "tinyexec": "^0.3.2",
42
42
  "yargs": "^17.7.2",
43
- "zod": "^3.22.4"
43
+ "zod": "^3.24.2"
44
44
  },
45
45
  "devDependencies": {
46
- "@netlify/remix-adapter": "^2.5.1",
47
- "@netlify/remix-edge-adapter": "3.4.2",
48
- "@react-router/dev": "^7.1.3",
49
- "@react-router/fs-routes": "^7.1.3",
50
- "@remix-run/cloudflare": "^2.15.2",
51
- "@remix-run/cloudflare-pages": "^2.15.2",
52
- "@remix-run/dev": "^2.15.2",
53
- "@remix-run/node": "^2.15.2",
54
- "@remix-run/react": "^2.15.2",
55
- "@remix-run/server-runtime": "^2.15.2",
46
+ "@cloudflare/vite-plugin": "^1.1.0",
47
+ "@netlify/vite-plugin-react-router": "^1.0.1",
48
+ "@react-router/dev": "^7.5.3",
49
+ "@react-router/fs-routes": "^7.5.3",
50
+ "@remix-run/cloudflare": "^2.16.5",
51
+ "@remix-run/cloudflare-pages": "^2.16.5",
52
+ "@remix-run/dev": "^2.16.5",
53
+ "@remix-run/node": "^2.16.5",
54
+ "@remix-run/react": "^2.16.5",
55
+ "@remix-run/server-runtime": "^2.16.5",
56
56
  "@types/react": "^18.2.70",
57
57
  "@types/react-dom": "^18.2.25",
58
58
  "@types/yargs": "^17.0.33",
59
- "@vitejs/plugin-react": "^4.3.4",
60
- "h3": "^1.14.0",
61
- "ipx": "^3.0.1",
62
- "prettier": "3.4.2",
59
+ "@vercel/react-router": "^1.1.0",
60
+ "@vitejs/plugin-react": "^4.4.1",
61
+ "h3": "^1.15.1",
62
+ "ipx": "^3.0.3",
63
+ "isbot": "^5.1.25",
64
+ "prettier": "3.5.3",
63
65
  "react": "18.3.0-canary-14898b6a9-20240318",
64
66
  "react-dom": "18.3.0-canary-14898b6a9-20240318",
65
- "react-router": "^7.1.3",
67
+ "react-router": "^7.5.3",
66
68
  "ts-expect": "^1.3.0",
67
- "vike": "^0.4.219",
68
- "vite": "^5.4.11",
69
- "vitest": "^3.0.2",
69
+ "vike": "^0.4.229",
70
+ "vite": "^6.3.4",
71
+ "vitest": "^3.1.2",
70
72
  "wrangler": "^3.63.2",
71
- "@webstudio-is/http-client": "0.0.0-588fe22",
72
- "@webstudio-is/react-sdk": "0.0.0-588fe22",
73
- "@webstudio-is/sdk": "0.0.0-588fe22",
74
- "@webstudio-is/image": "0.0.0-588fe22",
75
- "@webstudio-is/sdk-components-animation": "0.0.0-588fe22",
76
- "@webstudio-is/sdk-components-react": "0.0.0-588fe22",
77
- "@webstudio-is/sdk-components-react-radix": "0.0.0-588fe22",
78
- "@webstudio-is/sdk-components-react-remix": "0.0.0-588fe22",
79
- "@webstudio-is/sdk-components-react-router": "0.0.0-588fe22",
80
- "@webstudio-is/tsconfig": "1.0.7"
73
+ "@webstudio-is/css-engine": "0.0.0-73cd6ea",
74
+ "@webstudio-is/http-client": "0.0.0-73cd6ea",
75
+ "@webstudio-is/image": "0.0.0-73cd6ea",
76
+ "@webstudio-is/sdk": "0.0.0-73cd6ea",
77
+ "@webstudio-is/react-sdk": "0.0.0-73cd6ea",
78
+ "@webstudio-is/sdk-components-animation": "0.0.0-73cd6ea",
79
+ "@webstudio-is/sdk-components-react-router": "0.0.0-73cd6ea",
80
+ "@webstudio-is/sdk-components-react-remix": "0.0.0-73cd6ea",
81
+ "@webstudio-is/sdk-components-react": "0.0.0-73cd6ea",
82
+ "@webstudio-is/tsconfig": "1.0.7",
83
+ "@webstudio-is/sdk-components-react-radix": "0.0.0-73cd6ea"
81
84
  },
82
85
  "scripts": {
83
86
  "typecheck": "tsc",
@@ -12,8 +12,8 @@
12
12
  "build-cf-types": "wrangler types"
13
13
  },
14
14
  "dependencies": {
15
- "@remix-run/cloudflare": "2.15.2",
16
- "@remix-run/cloudflare-pages": "2.15.2"
15
+ "@remix-run/cloudflare": "2.16.5",
16
+ "@remix-run/cloudflare-pages": "2.16.5"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@cloudflare/workers-types": "^4.20240620.0",
@@ -18,4 +18,12 @@ export default defineConfig(({ mode }) => ({
18
18
  },
19
19
  }),
20
20
  ].filter(Boolean),
21
+ resolve: {
22
+ conditions: ["browser", "development|production"],
23
+ },
24
+ ssr: {
25
+ resolve: {
26
+ conditions: ["node", "development|production"],
27
+ },
28
+ },
21
29
  }));
@@ -3,7 +3,7 @@
3
3
  import { Links, Meta, Outlet, useMatches } from "@remix-run/react";
4
4
  // @todo think about how to make __generated__ typeable
5
5
  // @ts-ignore
6
- import { CustomCode } from "./__generated__/_index";
6
+ import { CustomCode, projectId, lastPublished } from "./__generated__/_index";
7
7
 
8
8
  const Root = () => {
9
9
  // Get language from matches
@@ -19,7 +19,11 @@ const Root = () => {
19
19
  const lang = lastMatchWithLanguage?.data?.pageMeta?.language ?? "en";
20
20
 
21
21
  return (
22
- <html lang={lang}>
22
+ <html
23
+ lang={lang}
24
+ data-ws-project={projectId}
25
+ data-ws-last-published={lastPublished}
26
+ >
23
27
  <head>
24
28
  <meta charSet="utf-8" />
25
29
  <meta name="viewport" content="width=device-width,initial-scale=1" />
@@ -23,20 +23,21 @@ import {
23
23
  PageSettingsCanonicalLink,
24
24
  } from "@webstudio-is/react-sdk/runtime";
25
25
  import {
26
+ projectId,
26
27
  Page,
27
28
  siteName,
28
29
  favIconAsset,
29
30
  pageFontAssets,
30
31
  pageBackgroundImageAssets,
32
+ breakpoints,
31
33
  } from "__CLIENT__";
32
34
  import {
33
35
  getResources,
34
36
  getPageMeta,
35
37
  getRemixParams,
36
- projectId,
37
38
  contactEmail,
38
39
  } from "__SERVER__";
39
- import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
40
+ import * as constants from "__CONSTANTS__";
40
41
  import css from "__CSS__?url";
41
42
  import { sitemap } from "__SITEMAP__";
42
43
 
@@ -150,8 +151,8 @@ export const links: LinksFunction = () => {
150
151
  if (favIconAsset) {
151
152
  result.push({
152
153
  rel: "icon",
153
- href: imageLoader({
154
- src: `${assetBaseUrl}${favIconAsset.name}`,
154
+ href: constants.imageLoader({
155
+ src: `${constants.assetBaseUrl}${favIconAsset}`,
155
156
  // width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search
156
157
  width: 144,
157
158
  height: 144,
@@ -166,7 +167,7 @@ export const links: LinksFunction = () => {
166
167
  for (const asset of pageFontAssets) {
167
168
  result.push({
168
169
  rel: "preload",
169
- href: `${assetBaseUrl}${asset.name}`,
170
+ href: `${constants.assetBaseUrl}${asset}`,
170
171
  as: "font",
171
172
  crossOrigin: "anonymous",
172
173
  });
@@ -175,7 +176,7 @@ export const links: LinksFunction = () => {
175
176
  for (const backgroundImageAsset of pageBackgroundImageAssets) {
176
177
  result.push({
177
178
  rel: "preload",
178
- href: `${assetBaseUrl}${backgroundImageAsset.name}`,
179
+ href: `${constants.assetBaseUrl}${backgroundImageAsset}`,
179
180
  as: "image",
180
181
  });
181
182
  }
@@ -232,10 +233,6 @@ export const action = async ({
232
233
  formData.delete(formBotFieldName);
233
234
 
234
235
  if (resource) {
235
- resource.headers.push({
236
- name: "Content-Type",
237
- value: "application/json",
238
- });
239
236
  resource.body = Object.fromEntries(formData);
240
237
  } else {
241
238
  if (contactEmail === undefined) {
@@ -274,9 +271,10 @@ const Outlet = () => {
274
271
  return (
275
272
  <ReactSdkContext.Provider
276
273
  value={{
277
- imageLoader,
278
- assetBaseUrl,
274
+ ...constants,
279
275
  resources,
276
+ breakpoints,
277
+ onError: console.error,
280
278
  }}
281
279
  >
282
280
  {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
@@ -286,7 +284,8 @@ const Outlet = () => {
286
284
  pageMeta={pageMeta}
287
285
  host={host}
288
286
  siteName={siteName}
289
- imageLoader={imageLoader}
287
+ imageLoader={constants.imageLoader}
288
+ assetBaseUrl={constants.assetBaseUrl}
290
289
  />
291
290
  <PageSettingsTitle>{pageMeta.title}</PageSettingsTitle>
292
291
  <PageSettingsCanonicalLink href={url} />
@@ -5,7 +5,7 @@ import {
5
5
  ReactSdkContext,
6
6
  xmlNodeTagSuffix,
7
7
  } from "@webstudio-is/react-sdk/runtime";
8
- import { Page } from "__CLIENT__";
8
+ import { Page, breakpoints } from "__CLIENT__";
9
9
  import { getPageMeta, getRemixParams, getResources } from "__SERVER__";
10
10
  import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
11
11
  import { sitemap } from "__SITEMAP__";
@@ -65,16 +65,14 @@ export const loader = async (arg: LoaderFunctionArgs) => {
65
65
  imageLoader,
66
66
  assetBaseUrl,
67
67
  resources,
68
+ breakpoints,
69
+ onError: console.error,
68
70
  }}
69
71
  >
70
72
  <Page system={system} />
71
73
  </ReactSdkContext.Provider>
72
74
  );
73
75
 
74
- // Xml is wrapped with <svg> to prevent React from hoisting elements like <title>, <meta>, and <link> out of their intended scope during rendering.
75
- // More details: https://github.com/facebook/react/blob/7c8e5e7ab8bb63de911637892392c5efd8ce1d0f/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js#L3083
76
- text = text.replace(/^<svg>/g, "").replace(/<\/svg>$/g, "");
77
-
78
76
  // React has issues rendering certain elements, such as errors when a <link> element has children.
79
77
  // To render XML, we wrap it with an <svg> tag and add a suffix to avoid React's default behavior on these elements.
80
78
  text = text.replaceAll(xmlNodeTagSuffix, "");
@@ -8,26 +8,26 @@
8
8
  "typecheck": "tsc"
9
9
  },
10
10
  "dependencies": {
11
- "@remix-run/node": "2.15.2",
12
- "@remix-run/react": "2.15.2",
13
- "@remix-run/server-runtime": "2.15.2",
14
- "@webstudio-is/image": "0.0.0-588fe22",
15
- "@webstudio-is/react-sdk": "0.0.0-588fe22",
16
- "@webstudio-is/sdk": "0.0.0-588fe22",
17
- "@webstudio-is/sdk-components-react": "0.0.0-588fe22",
18
- "@webstudio-is/sdk-components-animation": "0.0.0-588fe22",
19
- "@webstudio-is/sdk-components-react-radix": "0.0.0-588fe22",
20
- "@webstudio-is/sdk-components-react-remix": "0.0.0-588fe22",
21
- "isbot": "^5.1.21",
11
+ "@remix-run/node": "2.16.5",
12
+ "@remix-run/react": "2.16.5",
13
+ "@remix-run/server-runtime": "2.16.5",
14
+ "@webstudio-is/image": "0.0.0-73cd6ea",
15
+ "@webstudio-is/react-sdk": "0.0.0-73cd6ea",
16
+ "@webstudio-is/sdk": "0.0.0-73cd6ea",
17
+ "@webstudio-is/sdk-components-react": "0.0.0-73cd6ea",
18
+ "@webstudio-is/sdk-components-animation": "0.0.0-73cd6ea",
19
+ "@webstudio-is/sdk-components-react-radix": "0.0.0-73cd6ea",
20
+ "@webstudio-is/sdk-components-react-remix": "0.0.0-73cd6ea",
21
+ "isbot": "^5.1.25",
22
22
  "react": "18.3.0-canary-14898b6a9-20240318",
23
23
  "react-dom": "18.3.0-canary-14898b6a9-20240318"
24
24
  },
25
25
  "devDependencies": {
26
- "@remix-run/dev": "2.15.2",
26
+ "@remix-run/dev": "2.16.5",
27
27
  "@types/react": "^18.2.70",
28
28
  "@types/react-dom": "^18.2.25",
29
- "typescript": "5.7.3",
30
- "vite": "^5.4.11"
29
+ "typescript": "5.8.2",
30
+ "vite": "^6.3.4"
31
31
  },
32
32
  "engines": {
33
33
  "node": ">=20.0.0"
@@ -13,4 +13,12 @@ export default defineConfig({
13
13
  },
14
14
  }),
15
15
  ],
16
+ resolve: {
17
+ conditions: ["browser", "development|production"],
18
+ },
19
+ ssr: {
20
+ resolve: {
21
+ conditions: ["node", "development|production"],
22
+ },
23
+ },
16
24
  });
@@ -3,7 +3,7 @@
3
3
  import { Links, Meta, Outlet, useMatches } from "react-router";
4
4
  // @todo think about how to make __generated__ typeable
5
5
  // @ts-ignore
6
- import { CustomCode } from "./__generated__/_index";
6
+ import { CustomCode, projectId, lastPublished } from "./__generated__/_index";
7
7
 
8
8
  const Root = () => {
9
9
  // Get language from matches
@@ -19,7 +19,11 @@ const Root = () => {
19
19
  const lang = lastMatchWithLanguage?.data?.pageMeta?.language ?? "en";
20
20
 
21
21
  return (
22
- <html lang={lang}>
22
+ <html
23
+ lang={lang}
24
+ data-ws-project={projectId}
25
+ data-ws-last-published={lastPublished}
26
+ >
23
27
  <head>
24
28
  <meta charSet="utf-8" />
25
29
  <meta name="viewport" content="width=device-width,initial-scale=1" />
@@ -22,20 +22,21 @@ import {
22
22
  PageSettingsTitle,
23
23
  } from "@webstudio-is/react-sdk/runtime";
24
24
  import {
25
+ projectId,
25
26
  Page,
26
27
  siteName,
27
28
  favIconAsset,
28
29
  pageFontAssets,
29
30
  pageBackgroundImageAssets,
31
+ breakpoints,
30
32
  } from "__CLIENT__";
31
33
  import {
32
34
  getResources,
33
35
  getPageMeta,
34
36
  getRemixParams,
35
- projectId,
36
37
  contactEmail,
37
38
  } from "__SERVER__";
38
- import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
39
+ import * as constants from "__CONSTANTS__";
39
40
  import css from "__CSS__?url";
40
41
  import { sitemap } from "__SITEMAP__";
41
42
 
@@ -149,8 +150,8 @@ export const links: LinksFunction = () => {
149
150
  if (favIconAsset) {
150
151
  result.push({
151
152
  rel: "icon",
152
- href: imageLoader({
153
- src: `${assetBaseUrl}${favIconAsset.name}`,
153
+ href: constants.imageLoader({
154
+ src: `${constants.assetBaseUrl}${favIconAsset}`,
154
155
  // width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search
155
156
  width: 144,
156
157
  height: 144,
@@ -165,7 +166,7 @@ export const links: LinksFunction = () => {
165
166
  for (const asset of pageFontAssets) {
166
167
  result.push({
167
168
  rel: "preload",
168
- href: `${assetBaseUrl}${asset.name}`,
169
+ href: `${constants.assetBaseUrl}${asset}`,
169
170
  as: "font",
170
171
  crossOrigin: "anonymous",
171
172
  });
@@ -174,7 +175,7 @@ export const links: LinksFunction = () => {
174
175
  for (const backgroundImageAsset of pageBackgroundImageAssets) {
175
176
  result.push({
176
177
  rel: "preload",
177
- href: `${assetBaseUrl}${backgroundImageAsset.name}`,
178
+ href: `${constants.assetBaseUrl}${backgroundImageAsset}`,
178
179
  as: "image",
179
180
  });
180
181
  }
@@ -231,10 +232,6 @@ export const action = async ({
231
232
  formData.delete(formBotFieldName);
232
233
 
233
234
  if (resource) {
234
- resource.headers.push({
235
- name: "Content-Type",
236
- value: "application/json",
237
- });
238
235
  resource.body = Object.fromEntries(formData);
239
236
  } else {
240
237
  if (contactEmail === undefined) {
@@ -273,9 +270,10 @@ const Outlet = () => {
273
270
  return (
274
271
  <ReactSdkContext.Provider
275
272
  value={{
276
- imageLoader,
277
- assetBaseUrl,
273
+ ...constants,
278
274
  resources,
275
+ breakpoints,
276
+ onError: console.error,
279
277
  }}
280
278
  >
281
279
  {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
@@ -285,7 +283,8 @@ const Outlet = () => {
285
283
  pageMeta={pageMeta}
286
284
  host={host}
287
285
  siteName={siteName}
288
- imageLoader={imageLoader}
286
+ imageLoader={constants.imageLoader}
287
+ assetBaseUrl={constants.assetBaseUrl}
289
288
  />
290
289
  <PageSettingsTitle>{pageMeta.title}</PageSettingsTitle>
291
290
  </ReactSdkContext.Provider>
@@ -5,7 +5,7 @@ import {
5
5
  ReactSdkContext,
6
6
  xmlNodeTagSuffix,
7
7
  } from "@webstudio-is/react-sdk/runtime";
8
- import { Page } from "__CLIENT__";
8
+ import { Page, breakpoints } from "__CLIENT__";
9
9
  import { getPageMeta, getRemixParams, getResources } from "__SERVER__";
10
10
  import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
11
11
  import { sitemap } from "__SITEMAP__";
@@ -65,6 +65,8 @@ export const loader = async (arg: LoaderFunctionArgs) => {
65
65
  imageLoader,
66
66
  assetBaseUrl,
67
67
  resources,
68
+ breakpoints,
69
+ onError: console.error,
68
70
  }}
69
71
  >
70
72
  <Page system={system} />
@@ -0,0 +1,34 @@
1
+ {
2
+ "type": "module",
3
+ "private": true,
4
+ "sideEffects": false,
5
+ "scripts": {
6
+ "build": "react-router build",
7
+ "dev": "react-router dev",
8
+ "typecheck": "tsc"
9
+ },
10
+ "dependencies": {
11
+ "@react-router/dev": "^7.5.3",
12
+ "@react-router/fs-routes": "^7.5.3",
13
+ "@webstudio-is/image": "0.0.0-73cd6ea",
14
+ "@webstudio-is/react-sdk": "0.0.0-73cd6ea",
15
+ "@webstudio-is/sdk": "0.0.0-73cd6ea",
16
+ "@webstudio-is/sdk-components-animation": "0.0.0-73cd6ea",
17
+ "@webstudio-is/sdk-components-react-radix": "0.0.0-73cd6ea",
18
+ "@webstudio-is/sdk-components-react-router": "0.0.0-73cd6ea",
19
+ "@webstudio-is/sdk-components-react": "0.0.0-73cd6ea",
20
+ "isbot": "^5.1.25",
21
+ "react": "18.3.0-canary-14898b6a9-20240318",
22
+ "react-dom": "18.3.0-canary-14898b6a9-20240318",
23
+ "react-router": "^7.5.3",
24
+ "vite": "^6.3.4"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^18.2.70",
28
+ "@types/react-dom": "^18.2.25",
29
+ "typescript": "5.8.2"
30
+ },
31
+ "engines": {
32
+ "node": ">=20.0.0"
33
+ }
34
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "vite";
2
+ import { reactRouter } from "@react-router/dev/vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [reactRouter()],
6
+ resolve: {
7
+ conditions: ["browser", "development|production"],
8
+ },
9
+ ssr: {
10
+ resolve: {
11
+ conditions: ["node", "development|production"],
12
+ },
13
+ },
14
+ });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * We use mjs extension as constants in this file is shared with the build script
3
+ * and we use `node --eval` to extract the constants.
4
+ */
5
+ export const assetBaseUrl = "/assets/";
6
+
7
+ /**
8
+ * @type {import("@webstudio-is/image").ImageLoader}
9
+ */
10
+ export const imageLoader = (props) => {
11
+ if (import.meta.env.DEV) {
12
+ return props.src;
13
+ }
14
+
15
+ if (props.format === "raw") {
16
+ return props.src;
17
+ }
18
+
19
+ // @todo https://developers.cloudflare.com/images/transform-images/transform-via-url/
20
+ return props.src;
21
+ };
@@ -0,0 +1,43 @@
1
+ import type { AppLoadContext, EntryContext } from "react-router";
2
+ import { ServerRouter } from "react-router";
3
+ import { isbot } from "isbot";
4
+ import { renderToReadableStream } from "react-dom/server";
5
+
6
+ export default async function handleRequest(
7
+ request: Request,
8
+ responseStatusCode: number,
9
+ responseHeaders: Headers,
10
+ routerContext: EntryContext,
11
+ _loadContext: AppLoadContext
12
+ ) {
13
+ let shellRendered = false;
14
+ const userAgent = request.headers.get("user-agent");
15
+
16
+ const body = await renderToReadableStream(
17
+ <ServerRouter context={routerContext} url={request.url} />,
18
+ {
19
+ onError(error: unknown) {
20
+ responseStatusCode = 500;
21
+ // Log streaming rendering errors from inside the shell. Don't log
22
+ // errors encountered during initial shell rendering since they'll
23
+ // reject and get logged in handleDocumentRequest.
24
+ if (shellRendered) {
25
+ console.error(error);
26
+ }
27
+ },
28
+ }
29
+ );
30
+ shellRendered = true;
31
+
32
+ // Ensure requests from bots and SPA Mode renders wait for all content to load before responding
33
+ // https://react.dev/reference/react-dom/server/renderToPipeableStream#waiting-for-all-content-to-load-for-crawlers-and-static-generation
34
+ if ((userAgent && isbot(userAgent)) || routerContext.isSpaMode) {
35
+ await body.allReady;
36
+ }
37
+
38
+ responseHeaders.set("Content-Type", "text/html");
39
+ return new Response(body, {
40
+ headers: responseHeaders,
41
+ status: responseStatusCode,
42
+ });
43
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "scripts": {
3
+ "typecheck": "wrangler types && tsc",
4
+ "typegen": "wrangler types",
5
+ "preview": "react-router build && vite preview",
6
+ "deploy": "react-router build && wrangler deploy"
7
+ },
8
+ "dependencies": {
9
+ "@cloudflare/vite-plugin": "^1.1.0",
10
+ "wrangler": "^4.14.1"
11
+ }
12
+ }
@@ -0,0 +1,7 @@
1
+ import type { Config } from "@react-router/dev/config";
2
+
3
+ export default {
4
+ future: {
5
+ unstable_viteEnvironmentApi: true,
6
+ },
7
+ } satisfies Config;
@@ -0,0 +1,23 @@
1
+ {
2
+ "include": ["**/*.ts", "**/*.tsx", "**/*.mjs"],
3
+ "compilerOptions": {
4
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
5
+ "types": [
6
+ "./worker-configuration.d.ts",
7
+ "vite/client",
8
+ "@webstudio-is/react-sdk/placeholder"
9
+ ],
10
+ "isolatedModules": true,
11
+ "esModuleInterop": true,
12
+ "jsx": "react-jsx",
13
+ "module": "ESNext",
14
+ "moduleResolution": "bundler",
15
+ "target": "ES2022",
16
+ "strict": true,
17
+ "allowJs": true,
18
+ "forceConsistentCasingInFileNames": true,
19
+ "noEmit": true,
20
+ "skipLibCheck": true,
21
+ "customConditions": ["webstudio"]
22
+ }
23
+ }
@@ -0,0 +1,15 @@
1
+ import { defineConfig } from "vite";
2
+ import { cloudflare } from "@cloudflare/vite-plugin";
3
+ import { reactRouter } from "@react-router/dev/vite";
4
+
5
+ export default defineConfig({
6
+ plugins: [cloudflare({ viteEnvironment: { name: "ssr" } }), reactRouter()],
7
+ resolve: {
8
+ conditions: ["browser", "development|production"],
9
+ },
10
+ ssr: {
11
+ resolve: {
12
+ conditions: ["node", "development|production"],
13
+ },
14
+ },
15
+ });
@@ -0,0 +1,26 @@
1
+ import { createRequestHandler } from "react-router";
2
+
3
+ declare module "react-router" {
4
+ export interface AppLoadContext {
5
+ cloudflare: {
6
+ env: Env;
7
+ ctx: ExecutionContext;
8
+ };
9
+ }
10
+ }
11
+
12
+ const requestHandler = createRequestHandler(
13
+ // @ts-ignore
14
+ () => import("virtual:react-router/server-build"),
15
+ import.meta.env.MODE
16
+ );
17
+
18
+ export default {
19
+ async fetch(request, env, ctx) {
20
+ return requestHandler(request, {
21
+ EXCLUDE_FROM_SEARCH: false,
22
+ getDefaultActionResource: undefined,
23
+ cloudflare: { env, ctx },
24
+ });
25
+ },
26
+ } satisfies ExportedHandler<Env>;