webstudio 0.0.0-588fe22 → 0.0.0-7cb4145

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 (58) hide show
  1. package/README.md +1 -7
  2. package/lib/cli.js +5139 -2563
  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 +8 -8
  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 +8 -8
  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/package.json +5 -33
  25. package/templates/{netlify-edge-functions → react-router-netlify}/app/constants.mjs +1 -1
  26. package/templates/react-router-netlify/netlify.toml +6 -0
  27. package/templates/react-router-netlify/package.json +10 -0
  28. package/templates/react-router-netlify/vite.config.ts +15 -0
  29. package/templates/{vercel → react-router-vercel}/app/constants.mjs +1 -1
  30. package/templates/react-router-vercel/package.json +9 -0
  31. package/templates/react-router-vercel/react-router.config.ts +6 -0
  32. package/templates/{vercel → react-router-vercel}/vercel.json +1 -0
  33. package/templates/saas-helpers/package.json +4 -1
  34. package/templates/saas-helpers/vite.config.ts +48 -0
  35. package/templates/ssg/app/route-templates/html/+Head.tsx +14 -6
  36. package/templates/ssg/app/route-templates/html/+Page.tsx +3 -1
  37. package/templates/ssg/package.json +10 -10
  38. package/templates/ssg/renderer/+onRenderHtml.tsx +13 -5
  39. package/templates/ssg/vite.config.ts +8 -0
  40. package/templates/netlify-edge-functions/app/entry.server.tsx +0 -21
  41. package/templates/netlify-edge-functions/netlify.toml +0 -16
  42. package/templates/netlify-edge-functions/package.json +0 -9
  43. package/templates/netlify-edge-functions/vite.config.ts +0 -18
  44. package/templates/netlify-functions/app/constants.mjs +0 -29
  45. package/templates/netlify-functions/app/entry.server.tsx +0 -1
  46. package/templates/netlify-functions/netlify.toml +0 -16
  47. package/templates/netlify-functions/package.json +0 -9
  48. package/templates/netlify-functions/vite.config.ts +0 -18
  49. package/templates/react-router-docker/vite.config.ts +0 -6
  50. package/templates/vercel/.vercelignore +0 -8
  51. package/templates/vercel/package.json +0 -1
  52. /package/templates/{react-router-docker → react-router}/app/extension.ts +0 -0
  53. /package/templates/{react-router-docker → react-router}/app/route-templates/default-sitemap.tsx +0 -0
  54. /package/templates/{react-router-docker → react-router}/app/route-templates/redirect.tsx +0 -0
  55. /package/templates/{react-router-docker → react-router}/app/routes/[robots.txt].tsx +0 -0
  56. /package/templates/{react-router-docker → react-router}/app/routes.ts +0 -0
  57. /package/templates/{react-router-docker → react-router}/public/favicon.ico +0 -0
  58. /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-7cb4145",
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/http-client": "0.0.0-7cb4145",
74
+ "@webstudio-is/image": "0.0.0-7cb4145",
75
+ "@webstudio-is/css-engine": "0.0.0-7cb4145",
76
+ "@webstudio-is/sdk": "0.0.0-7cb4145",
77
+ "@webstudio-is/react-sdk": "0.0.0-7cb4145",
78
+ "@webstudio-is/sdk-components-animation": "0.0.0-7cb4145",
79
+ "@webstudio-is/sdk-components-react-radix": "0.0.0-7cb4145",
80
+ "@webstudio-is/sdk-components-react-remix": "0.0.0-7cb4145",
81
+ "@webstudio-is/tsconfig": "1.0.7",
82
+ "@webstudio-is/sdk-components-react-router": "0.0.0-7cb4145",
83
+ "@webstudio-is/sdk-components-react": "0.0.0-7cb4145"
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,17 +23,18 @@ 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
40
  import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
@@ -151,7 +152,7 @@ export const links: LinksFunction = () => {
151
152
  result.push({
152
153
  rel: "icon",
153
154
  href: imageLoader({
154
- src: `${assetBaseUrl}${favIconAsset.name}`,
155
+ src: `${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: `${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: `${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) {
@@ -277,6 +274,8 @@ const Outlet = () => {
277
274
  imageLoader,
278
275
  assetBaseUrl,
279
276
  resources,
277
+ breakpoints,
278
+ onError: console.error,
280
279
  }}
281
280
  >
282
281
  {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
@@ -287,6 +286,7 @@ const Outlet = () => {
287
286
  host={host}
288
287
  siteName={siteName}
289
288
  imageLoader={imageLoader}
289
+ assetBaseUrl={assetBaseUrl}
290
290
  />
291
291
  <PageSettingsTitle>{pageMeta.title}</PageSettingsTitle>
292
292
  <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-7cb4145",
15
+ "@webstudio-is/react-sdk": "0.0.0-7cb4145",
16
+ "@webstudio-is/sdk": "0.0.0-7cb4145",
17
+ "@webstudio-is/sdk-components-react": "0.0.0-7cb4145",
18
+ "@webstudio-is/sdk-components-animation": "0.0.0-7cb4145",
19
+ "@webstudio-is/sdk-components-react-radix": "0.0.0-7cb4145",
20
+ "@webstudio-is/sdk-components-react-remix": "0.0.0-7cb4145",
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,17 +22,18 @@ 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
39
  import { assetBaseUrl, imageLoader } from "__CONSTANTS__";
@@ -150,7 +151,7 @@ export const links: LinksFunction = () => {
150
151
  result.push({
151
152
  rel: "icon",
152
153
  href: imageLoader({
153
- src: `${assetBaseUrl}${favIconAsset.name}`,
154
+ src: `${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: `${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: `${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) {
@@ -276,6 +273,8 @@ const Outlet = () => {
276
273
  imageLoader,
277
274
  assetBaseUrl,
278
275
  resources,
276
+ breakpoints,
277
+ onError: console.error,
279
278
  }}
280
279
  >
281
280
  {/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
@@ -286,6 +285,7 @@ const Outlet = () => {
286
285
  host={host}
287
286
  siteName={siteName}
288
287
  imageLoader={imageLoader}
288
+ assetBaseUrl={assetBaseUrl}
289
289
  />
290
290
  <PageSettingsTitle>{pageMeta.title}</PageSettingsTitle>
291
291
  </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-7cb4145",
14
+ "@webstudio-is/react-sdk": "0.0.0-7cb4145",
15
+ "@webstudio-is/sdk": "0.0.0-7cb4145",
16
+ "@webstudio-is/sdk-components-animation": "0.0.0-7cb4145",
17
+ "@webstudio-is/sdk-components-react-radix": "0.0.0-7cb4145",
18
+ "@webstudio-is/sdk-components-react-router": "0.0.0-7cb4145",
19
+ "@webstudio-is/sdk-components-react": "0.0.0-7cb4145",
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>;
@@ -0,0 +1,6 @@
1
+ {
2
+ "$schema": "node_modules/wrangler/config-schema.json",
3
+ "name": "webstudio-app",
4
+ "compatibility_date": "2025-04-28",
5
+ "main": "./workers/app.ts"
6
+ }