rasengan 1.0.0-beta.1

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 (118) hide show
  1. package/LICENSE +21 -0
  2. package/lib/cli/dirname.d.ts +2 -0
  3. package/lib/cli/dirname.js +6 -0
  4. package/lib/cli/dirname.js.map +1 -0
  5. package/lib/cli/index.d.ts +2 -0
  6. package/lib/cli/index.js +101 -0
  7. package/lib/cli/index.js.map +1 -0
  8. package/lib/config/index.d.ts +39 -0
  9. package/lib/config/index.js +57 -0
  10. package/lib/config/index.js.map +1 -0
  11. package/lib/config/type.d.ts +68 -0
  12. package/lib/config/type.js +2 -0
  13. package/lib/config/type.js.map +1 -0
  14. package/lib/core/components/index.d.ts +26 -0
  15. package/lib/core/components/index.js +72 -0
  16. package/lib/core/components/index.js.map +1 -0
  17. package/lib/core/index.d.ts +2 -0
  18. package/lib/core/index.js +2 -0
  19. package/lib/core/index.js.map +1 -0
  20. package/lib/core/interfaces.d.ts +76 -0
  21. package/lib/core/interfaces.js +91 -0
  22. package/lib/core/interfaces.js.map +1 -0
  23. package/lib/core/types.d.ts +45 -0
  24. package/lib/core/types.js +2 -0
  25. package/lib/core/types.js.map +1 -0
  26. package/lib/decorators/index.d.ts +2 -0
  27. package/lib/decorators/index.js +3 -0
  28. package/lib/decorators/index.js.map +1 -0
  29. package/lib/decorators/route.d.ts +7 -0
  30. package/lib/decorators/route.js +25 -0
  31. package/lib/decorators/route.js.map +1 -0
  32. package/lib/decorators/router.d.ts +7 -0
  33. package/lib/decorators/router.js +24 -0
  34. package/lib/decorators/router.js.map +1 -0
  35. package/lib/decorators/types.d.ts +47 -0
  36. package/lib/decorators/types.js +2 -0
  37. package/lib/decorators/types.js.map +1 -0
  38. package/lib/entries/entry-client.d.ts +1 -0
  39. package/lib/entries/entry-client.js +15 -0
  40. package/lib/entries/entry-client.js.map +1 -0
  41. package/lib/entries/entry-server.d.ts +6 -0
  42. package/lib/entries/entry-server.js +20 -0
  43. package/lib/entries/entry-server.js.map +1 -0
  44. package/lib/hooks/index.d.ts +0 -0
  45. package/lib/hooks/index.js +1 -0
  46. package/lib/hooks/index.js.map +1 -0
  47. package/lib/index.d.ts +6 -0
  48. package/lib/index.js +10 -0
  49. package/lib/index.js.map +1 -0
  50. package/lib/routing/components/index.d.ts +32 -0
  51. package/lib/routing/components/index.js +69 -0
  52. package/lib/routing/components/index.js.map +1 -0
  53. package/lib/routing/index.d.ts +3 -0
  54. package/lib/routing/index.js +4 -0
  55. package/lib/routing/index.js.map +1 -0
  56. package/lib/routing/interfaces.d.ts +67 -0
  57. package/lib/routing/interfaces.js +88 -0
  58. package/lib/routing/interfaces.js.map +1 -0
  59. package/lib/routing/types.d.ts +4 -0
  60. package/lib/routing/types.js +2 -0
  61. package/lib/routing/types.js.map +1 -0
  62. package/lib/routing/utils/index.d.ts +40 -0
  63. package/lib/routing/utils/index.js +256 -0
  64. package/lib/routing/utils/index.js.map +1 -0
  65. package/lib/server/functions/vercel/api/index.d.ts +2 -0
  66. package/lib/server/functions/vercel/api/index.js +91 -0
  67. package/lib/server/functions/vercel/api/index.js.map +1 -0
  68. package/lib/server/functions/vercel/vercel.json +12 -0
  69. package/lib/server/utils/createFetchRequest.d.ts +5 -0
  70. package/lib/server/utils/createFetchRequest.js +34 -0
  71. package/lib/server/utils/createFetchRequest.js.map +1 -0
  72. package/lib/server/utils/getIp.d.ts +1 -0
  73. package/lib/server/utils/getIp.js +30 -0
  74. package/lib/server/utils/getIp.js.map +1 -0
  75. package/lib/server/utils/handleError.d.ts +2 -0
  76. package/lib/server/utils/handleError.js +28 -0
  77. package/lib/server/utils/handleError.js.map +1 -0
  78. package/lib/server/utils/index.d.ts +5 -0
  79. package/lib/server/utils/index.js +8 -0
  80. package/lib/server/utils/index.js.map +1 -0
  81. package/lib/server/utils/log.d.ts +6 -0
  82. package/lib/server/utils/log.js +69 -0
  83. package/lib/server/utils/log.js.map +1 -0
  84. package/package.json +75 -0
  85. package/server.js +229 -0
  86. package/src/cli/dirname.ts +7 -0
  87. package/src/cli/index.ts +134 -0
  88. package/src/config/index.ts +67 -0
  89. package/src/config/type.ts +76 -0
  90. package/src/core/components/index.tsx +111 -0
  91. package/src/core/index.ts +14 -0
  92. package/src/core/interfaces.tsx +129 -0
  93. package/src/core/types.ts +43 -0
  94. package/src/decorators/index.ts +2 -0
  95. package/src/decorators/route.ts +32 -0
  96. package/src/decorators/router.ts +30 -0
  97. package/src/decorators/types.ts +54 -0
  98. package/src/entries/entry-client.tsx +33 -0
  99. package/src/entries/entry-server.tsx +50 -0
  100. package/src/hooks/index.ts +0 -0
  101. package/src/index.ts +11 -0
  102. package/src/routing/components/index.tsx +125 -0
  103. package/src/routing/index.ts +23 -0
  104. package/src/routing/interfaces.ts +105 -0
  105. package/src/routing/types.ts +3 -0
  106. package/src/routing/utils/index.tsx +342 -0
  107. package/src/server/functions/vercel/api/index.ts +122 -0
  108. package/src/server/functions/vercel/vercel.json +12 -0
  109. package/src/server/utils/createFetchRequest.ts +40 -0
  110. package/src/server/utils/getIp.ts +37 -0
  111. package/src/server/utils/handleError.ts +36 -0
  112. package/src/server/utils/index.ts +15 -0
  113. package/src/server/utils/log.ts +115 -0
  114. package/src/vite-env.d.ts +1 -0
  115. package/tsconfig.json +30 -0
  116. package/tsconfig.lib.json +41 -0
  117. package/tsconfig.node.json +11 -0
  118. package/vite.config.ts +45 -0
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "rasengan",
3
+ "private": false,
4
+ "version": "1.0.0-beta.1",
5
+ "type": "module",
6
+ "main": "lib/index.js",
7
+ "bin": {
8
+ "rasengan": "./lib/cli/index.js"
9
+ },
10
+ "author": {
11
+ "name": "dilane3",
12
+ "email": "komboudilane125@gmail.com",
13
+ "url": "https://dilane3.com",
14
+ "twitter": "https://twitter.com/dilanekombou",
15
+ "github": "https://github.com/dilane3"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "https://github.com/rasengan/rasengan.git",
20
+ "issues": "https://github.com/rasengan/rasengan/issues"
21
+ },
22
+ "scripts": {
23
+ "dev": "node server",
24
+ "build": "npm run build:client && npm run build:server",
25
+ "build:client": "vite build --ssrManifest --outDir dist/client",
26
+ "build:server": "vite build --ssr node_modules/rasengan/lib/entries/entry-server.js --outDir dist/server",
27
+ "build:lib": "tsc -p tsconfig.lib.json",
28
+ "preview": "cross-env NODE_ENV=production node server",
29
+ "deploy": "npm publish --access public",
30
+ "pack": "npm pack --pack-destination ./../packages/rasengan/"
31
+ },
32
+ "dependencies": {
33
+ "@vercel/node": "^3.0.14",
34
+ "@vitejs/plugin-react": "^4.2.1",
35
+ "chalk": "^5.3.0",
36
+ "commander": "^11.1.0",
37
+ "compression": "^1.7.4",
38
+ "cross-spawn": "^7.0.3",
39
+ "execa": "^8.0.1",
40
+ "express": "^4.18.2",
41
+ "inquirer": "^9.2.12",
42
+ "keypress": "^0.2.1",
43
+ "node-fetch": "^3.3.2",
44
+ "ora": "^7.0.1",
45
+ "react-helmet": "^6.1.0",
46
+ "react-helmet-async": "^2.0.4",
47
+ "react-router-dom": "^6.20.1",
48
+ "sirv": "^2.0.3",
49
+ "vite": "^5.0.7",
50
+ "vite-plugin-css": "^1.0.4"
51
+ },
52
+ "peerDependencies": {
53
+ "react": "^18.2.0",
54
+ "react-dom": "^18.2.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/compression": "^1.7.4",
58
+ "@types/cross-spawn": "^6.0.4",
59
+ "@types/express": "^4.17.19",
60
+ "@types/node": "^20.8.6",
61
+ "@types/react": "^18.2.28",
62
+ "@types/react-dom": "^18.2.13",
63
+ "@types/react-helmet": "^6.1.8",
64
+ "cross-env": "^7.0.3",
65
+ "typescript": "^5.2.2"
66
+ },
67
+ "resolutions": {
68
+ "@vitejs/plugin-react": "^4.2.1"
69
+ },
70
+ "license": "MIT",
71
+ "engines": {
72
+ "node": ">=16.0.0",
73
+ "npm": ">=7.0.0"
74
+ }
75
+ }
package/server.js ADDED
@@ -0,0 +1,229 @@
1
+ import fs from "node:fs/promises";
2
+ import express from "express";
3
+ import { dirname, join } from "path";
4
+ import { fileURLToPath } from "url";
5
+ import {
6
+ createStaticHandler,
7
+ createStaticRouter,
8
+ } from "react-router-dom/server.js";
9
+ import chalk from "chalk";
10
+ import inquirer from "inquirer";
11
+
12
+ // Get config
13
+ // @ts-ignore
14
+ import config from "./../../rasengan.config.js";
15
+
16
+ // Load utils
17
+ import {
18
+ createFetchRequest,
19
+ logServerInfo,
20
+ fix404,
21
+ } from "./lib/server/utils/index.js";
22
+
23
+ /**
24
+ * This function is responsible for creating a server for the development environment.
25
+ * @param {boolean} isProduction - Whether the server is in production mode or not.
26
+ * @param {number} port - The port to run the server on.
27
+ * @param {string} base - The base path for the server.
28
+ */
29
+ async function createServer({
30
+ isProduction,
31
+ port,
32
+ base = "/",
33
+ enableSearchingPort = false,
34
+ }) {
35
+ // Get directory name
36
+ const __dirname = dirname(fileURLToPath(import.meta.url));
37
+
38
+ // Get app path
39
+ const appPath = join(__dirname, "./../../");
40
+
41
+ // Cached production assets
42
+ const templateHtml = isProduction
43
+ ? await fs.readFile(join(appPath, "dist/client/index.html"), "utf-8")
44
+ : "";
45
+ const ssrManifest = isProduction
46
+ ? await fs.readFile(
47
+ join(appPath, "dist/client/.vite/ssr-manifest.json"),
48
+ "utf-8"
49
+ )
50
+ : undefined;
51
+
52
+ // Create http server
53
+ const app = express();
54
+
55
+ // Add Vite or respective production middlewares
56
+ let vite;
57
+ if (!isProduction) {
58
+ const { createServer } = await import("vite");
59
+ vite = await createServer({
60
+ server: { middlewareMode: true },
61
+ appType: "custom",
62
+ base,
63
+ });
64
+ app.use(vite.middlewares);
65
+ } else {
66
+ const compression = (await import("compression")).default;
67
+ const sirv = (await import("sirv")).default;
68
+ app.use(compression());
69
+ app.use(base, sirv(join(appPath, "dist/client"), { extensions: [] }));
70
+ }
71
+
72
+ // Serve HTML
73
+ app.use("*", async (req, res) => {
74
+ try {
75
+ // ! 404 Fix related to some files not being found
76
+ fix404(req.originalUrl, res, appPath);
77
+
78
+ // ! Service Worker Fix
79
+
80
+ const url = req.originalUrl.replace(base, "");
81
+
82
+ let template;
83
+ let render;
84
+ let staticRoutes;
85
+ if (!isProduction) {
86
+ // Always read fresh template in development
87
+ template = await fs.readFile(join(appPath, "index.html"), "utf-8");
88
+ template = await vite.transformIndexHtml(url, template);
89
+
90
+ const entry = await vite.ssrLoadModule(
91
+ join(appPath, "node_modules/rasengan/lib/entries/entry-server.js")
92
+ );
93
+
94
+ render = entry.render;
95
+ staticRoutes = entry.staticRoutes;
96
+ } else {
97
+ template = templateHtml;
98
+ const entry = await import(
99
+ join(appPath, "dist/server/entry-server.js")
100
+ );
101
+ render = entry.render;
102
+ staticRoutes = entry.staticRoutes;
103
+ }
104
+
105
+ // Create static handler
106
+ let handler = createStaticHandler(staticRoutes);
107
+
108
+ // Create fetch request for static routing
109
+ let fetchRequest = createFetchRequest(req, req.get("host"));
110
+ let context = await handler.query(fetchRequest);
111
+
112
+ // Handle redirects
113
+ const status = context.status;
114
+
115
+ if (status === 302) {
116
+ const redirect = context.headers.get("Location");
117
+
118
+ return res.redirect(redirect);
119
+ }
120
+
121
+ // Helmet context
122
+ const helmetContext = {};
123
+
124
+ // Create static router
125
+ let router = createStaticRouter(handler.dataRoutes, context);
126
+
127
+ // const rendered = await render(url, ssrManifest);
128
+ const rendered = await render(router, context, helmetContext);
129
+
130
+ // Get metadata
131
+ const helmet = helmetContext.helmet;
132
+
133
+ const head = `
134
+ ${helmet.title.toString()}
135
+ ${helmet.meta.toString()}
136
+ `;
137
+
138
+ let html = template
139
+ .replace(`<!--app-head-->`, head ?? "")
140
+ .replace(`<!--app-html-->`, rendered.html ?? "");
141
+
142
+ res
143
+ .status(200)
144
+ .set({
145
+ "Content-Type": "text/html",
146
+ "Cache-Control": "max-age=31536000",
147
+ })
148
+ .end(html);
149
+ } catch (e) {
150
+ vite?.ssrFixStacktrace(e);
151
+ // console.log(e.stack);
152
+ res.status(500).end(e.stack);
153
+ }
154
+ });
155
+
156
+ // Start http server
157
+ const server = app.listen(port, () => {
158
+ setTimeout(() => {
159
+ logServerInfo(port, isProduction);
160
+ }, 100);
161
+ });
162
+
163
+ // Handle errors
164
+ server.on("error", async (err) => {
165
+ const multiplicationSymbol = "\u00D7";
166
+
167
+ // Handle PORT in use error
168
+ if (err.code === "EADDRINUSE") {
169
+ const newPort = port + 1;
170
+
171
+ console.error(
172
+ chalk.red(
173
+ `${chalk.bold.red(
174
+ multiplicationSymbol
175
+ )} Port ${port} is already in use. \n\n`
176
+ )
177
+ );
178
+
179
+ // Check if user wants to use a different port
180
+ if (!enableSearchingPort) {
181
+ // Ask user if they want to use a different port
182
+ const { useDifferentPort } = await inquirer.prompt([
183
+ {
184
+ type: "confirm",
185
+ name: "useDifferentPort",
186
+ message: `Do you want to use a different port?`,
187
+ },
188
+ ]);
189
+
190
+ if (!useDifferentPort) {
191
+ console.log(chalk.blue("Closing server... \n\n"));
192
+ process.exit(0);
193
+ }
194
+
195
+ console.log(chalk.blue(`Trying port ${newPort}... \n\n`));
196
+
197
+ await createServer({
198
+ isProduction,
199
+ port: newPort,
200
+ base,
201
+ enableSearchingPort: true,
202
+ });
203
+ } else {
204
+ console.log(chalk.blue(`Trying port ${newPort}... \n\n`));
205
+
206
+ await createServer({
207
+ isProduction,
208
+ port: newPort,
209
+ base,
210
+ enableSearchingPort,
211
+ });
212
+ }
213
+ }
214
+ });
215
+ }
216
+
217
+ // Constants
218
+ const isProduction = process.env.NODE_ENV === "production";
219
+ const port = !isProduction
220
+ ? (process.env.PORT && Number(process.env.PORT)) ||
221
+ config.server?.development?.port ||
222
+ 5320
223
+ : process.env.PORT || 4320;
224
+ const base = process.env.BASE || "/";
225
+
226
+ // Launch server
227
+ (async function launchServer() {
228
+ await createServer({ isProduction, port, base });
229
+ })();
@@ -0,0 +1,7 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { dirname } from 'path';
3
+
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+
7
+ export default __dirname;
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import chalk from "chalk";
3
+ import { Command } from "commander";
4
+ import __dirname from "./dirname.js";
5
+ import { execa } from "execa";
6
+
7
+ // Config
8
+
9
+ // @ts-ignore
10
+ import config from "../../../../rasengan.config.js";
11
+
12
+ const program = new Command();
13
+
14
+ program
15
+ .name(chalk.blue("rasengan"))
16
+ .version("1.0.0", "-v, --version", "Output the current version number");
17
+
18
+ // Handle the dev command
19
+ program
20
+ .command("dev")
21
+ .option("-p <port>")
22
+ .description("Start development server")
23
+ .action(async ({ p: port }: { p: number }) => {
24
+ const convertedPort = Number(port);
25
+
26
+ // Checking port
27
+ if (
28
+ port &&
29
+ (isNaN(convertedPort) || convertedPort < 0 || convertedPort > 65535)
30
+ ) {
31
+ console.log("");
32
+ console.log(
33
+ chalk.red("Please provide a valid port number between 0-65535")
34
+ );
35
+ console.log("");
36
+ process.exit(1);
37
+ }
38
+
39
+ execa("node", ["node_modules/rasengan/server"], {
40
+ stdio: "inherit",
41
+ env: {
42
+ ...process.env,
43
+ PORT: convertedPort ? convertedPort.toString() : undefined,
44
+ },
45
+ });
46
+ });
47
+
48
+ // Handle the build command
49
+ program
50
+ .command("build")
51
+ .description("Build the project")
52
+ .action(() => {
53
+ // const childProcess = exec("npm --prefix node_modules/rasengan run build");
54
+ execa("npm", ["run", "build"], {
55
+ cwd: "node_modules/rasengan",
56
+ stdio: "inherit", // Pipe child process output to the parent process
57
+ });
58
+ });
59
+
60
+ // Handle the prebuild command
61
+ program
62
+ .command("prebuild")
63
+ .description("Prebuild the project")
64
+ .action(() => {
65
+ // Displaying the message
66
+ console.log("");
67
+ console.log(chalk.blue("Prebuilding your project..."));
68
+ console.log("");
69
+
70
+ // Checking the config file in order to know about hosting strategy
71
+ const { server } = config;
72
+
73
+ const hostingStrategy = server?.production?.hosting ?? "custom";
74
+
75
+ if (hostingStrategy === "vercel") {
76
+ // Displaying the message
77
+ console.log(
78
+ `Your project is configured to be hosted on ${chalk.bold.blue(
79
+ hostingStrategy
80
+ )}\n`
81
+ );
82
+
83
+ // Copying the api folder to the root directory
84
+ execa(
85
+ "cp",
86
+ ["-r", "node_modules/rasengan/lib/server/functions/vercel/api", "."],
87
+ {
88
+ stdio: "inherit",
89
+ }
90
+ );
91
+
92
+ // Copying the vercel.json file to the root directory
93
+ execa(
94
+ "cp",
95
+ ["node_modules/rasengan/src/server/functions/vercel/vercel.json", "."],
96
+ {
97
+ stdio: "inherit",
98
+ }
99
+ );
100
+
101
+ // Removing index.d.ts and index.js.map files from the api folder
102
+ execa("rm", ["api/index.d.ts", "api/index.js.map"], {
103
+ stdio: "inherit",
104
+ });
105
+ } else if (hostingStrategy === "netlify") {
106
+ // Copying the netlify.toml file to the root directory
107
+ // execa(
108
+ // "cp",
109
+ // ["node_modules/rasengan/src/server/functions/netlify/netlify.toml", "."],
110
+ // {
111
+ // stdio: "inherit",
112
+ // }
113
+ // );
114
+ }
115
+ });
116
+
117
+ // Handle the start command
118
+ program
119
+ .command("start")
120
+ .description("Start the project in production mode")
121
+ .action(async () => {
122
+ const childProcess = execa("npm", ["run", "preview"], {
123
+ cwd: "node_modules/rasengan",
124
+ stdio: "inherit", // Pipe child process output to the parent process
125
+ });
126
+
127
+ childProcess.on("close", (code) => {
128
+ if (code === 0) {
129
+ process.stdout.write("Project started Succesfully");
130
+ }
131
+ });
132
+ });
133
+
134
+ program.parse(process.argv);
@@ -0,0 +1,67 @@
1
+ import { type AppConfig } from "./type.js";
2
+
3
+ /**
4
+ * Function to define the config for the app
5
+ * It will be used by vite.config.ts and other files in other to configure the app
6
+ * @param {AppConfig} loadedConfig
7
+ */
8
+ export const defineConfig = (loadedConfig: AppConfig) => {
9
+ const { reactStrictMode, server, vite } = loadedConfig;
10
+
11
+ try {
12
+ const config = {
13
+ reactStrictMode: reactStrictMode === undefined ? true : reactStrictMode,
14
+ server,
15
+ vite: {
16
+ plugins: vite?.plugins || [],
17
+
18
+ optimizeDeps: {
19
+ exclude: [
20
+ "node:http",
21
+ "node-fetch",
22
+ ...(vite?.optimizeDeps?.exclude || []),
23
+ ],
24
+ },
25
+
26
+ css: {
27
+ postcss: vite?.css?.postcss || undefined,
28
+ },
29
+
30
+ build: {
31
+ external: vite?.build?.external || [],
32
+ },
33
+
34
+ appType: "custom",
35
+ },
36
+ // More config options...
37
+ };
38
+
39
+ return config;
40
+ } catch (error) {
41
+ console.error(error);
42
+ return {
43
+ reactStrictMode: true,
44
+ };
45
+ }
46
+ };
47
+
48
+ /**
49
+ * Function to adapt the path for dev and prod
50
+ * @param {string | Array<string>} paths
51
+ */
52
+ export const adaptPath = (paths: string | Array<string>) => {
53
+ // Check if we are in dev mode or prod
54
+ const isProduction = process.env.NODE_ENV === "production";
55
+ const prefix = isProduction ? "./../../" : "";
56
+
57
+ // Chech if the path is an array
58
+ const isArray = Array.isArray(paths);
59
+
60
+ // If the path is an array
61
+ if (isArray) {
62
+ return paths.map((path) => `${prefix}${path}`);
63
+ }
64
+
65
+ // If the path is a string
66
+ return `${prefix}${paths}`;
67
+ }
@@ -0,0 +1,76 @@
1
+ export type AppConfig = {
2
+ /**
3
+ * Enable strict mode
4
+ * @default true
5
+ */
6
+ reactStrictMode?: boolean;
7
+
8
+ /**
9
+ * Configure server both in development and production
10
+ */
11
+ server?: {
12
+ /**
13
+ * Configure server in development
14
+ */
15
+ development?: {
16
+ /**
17
+ * Port to listen on
18
+ * @default 3000
19
+ */
20
+ port?: number;
21
+ };
22
+
23
+ /**
24
+ * Configure server in production
25
+ */
26
+ production?: {
27
+ /**
28
+ * Set the hosting strategy
29
+ * @default "custom"
30
+ */
31
+ hosting?: HostingStrategy;
32
+ };
33
+ };
34
+
35
+ /**
36
+ * Configure Vite
37
+ */
38
+ vite?: {
39
+ /**
40
+ * Configure Vite plugins
41
+ */
42
+ plugins?: any[];
43
+
44
+ /**
45
+ * Optimize dependencies
46
+ */
47
+ optimizeDeps?: {
48
+ exclude?: string[];
49
+ };
50
+
51
+ /**
52
+ * Configure css options
53
+ */
54
+ css?: {
55
+ postcss?: {
56
+ plugins?: any[];
57
+ };
58
+ };
59
+
60
+ /**
61
+ * Configure build options
62
+ */
63
+ build?: {
64
+ /**
65
+ * Configure external dependencies
66
+ */
67
+ external?: string[];
68
+ }
69
+ };
70
+ // More config options...
71
+ };
72
+
73
+ /**
74
+ * Hosting strategy
75
+ */
76
+ export type HostingStrategy = "vercel" | "netlify" | "heroku" | "custom";
@@ -0,0 +1,111 @@
1
+ import * as React from "react";
2
+ import { ComponentProps, PageToRenderProps } from "../types.js";
3
+ import { getRouter } from "../../routing/utils/index.js";
4
+ import * as pkg from "react-helmet-async";
5
+ import { useRouteError } from "react-router-dom";
6
+
7
+ // @ts-ignore
8
+ const { Helmet } = pkg.default || pkg;
9
+
10
+ /**
11
+ * App component that represent the entry point of the application
12
+ */
13
+ export const Component = ({ router: AppRouter }: ComponentProps) => {
14
+ const Router = getRouter(AppRouter);
15
+
16
+ return <Router />;
17
+ };
18
+
19
+ /**
20
+ * Page component that defines title and description to a page
21
+ */
22
+ export const PageToRender = ({ page, data }: PageToRenderProps) => {
23
+ // Get the page component
24
+ const Page = page.render;
25
+
26
+ // Get the page props
27
+ const props = data.props || {};
28
+
29
+ return (
30
+ <React.Fragment>
31
+ <Helmet>
32
+ <title>{page.title}</title>
33
+ <meta name="description" content={page.description} />
34
+ </Helmet>
35
+
36
+ <Page {...props} />
37
+ </React.Fragment>
38
+ );
39
+ };
40
+
41
+ /**
42
+ * Error fallback component that will be displayed if an error occurs
43
+ */
44
+ export class ErrorBoundary extends React.Component {
45
+ state = { hasError: false, error: null, info: null };
46
+
47
+ componentDidCatch(error: any, info: any) {
48
+ this.setState({ hasError: true, error, info });
49
+ }
50
+
51
+ render() {
52
+ const { error, info } = this.state;
53
+
54
+ if (this.state.hasError) {
55
+ return <ErrorFallbackComponent error={error} info={info} />;
56
+ }
57
+
58
+ // @ts-ignore
59
+ return <RouteErrorBoundary>{this.props.children}</RouteErrorBoundary>;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Error fallback component that will be displayed if an error occurs during a routing
65
+ */
66
+ export const RouteErrorBoundary = ({ children }: any) => {
67
+ const error = useRouteError();
68
+
69
+ console.log({ error });
70
+
71
+ if (error) {
72
+ return <ErrorFallbackComponent error={error} info={null} />;
73
+ }
74
+
75
+ return children;
76
+ };
77
+
78
+ /**
79
+ * Error fallback component that will be displayed if an error occurs
80
+ */
81
+ const ErrorFallbackComponent = ({ error, info }: any) => {
82
+ console.log({ error, info });
83
+ return (
84
+ <div
85
+ style={{
86
+ width: "calc(100% - 80px)",
87
+ height: "calc(100vh - 80px)",
88
+ padding: "40px",
89
+ backgroundColor: "#fff",
90
+ }}
91
+ >
92
+ <div>
93
+ <h1 style={{ fontSize: "2rem" }}>Something went wrong</h1>
94
+ <p>{error.toString()}</p>
95
+
96
+ <div
97
+ style={{
98
+ width: "100%",
99
+ height: "auto",
100
+ borderRadius: 10,
101
+ padding: "20px",
102
+ marginTop: "10px",
103
+ backgroundColor: "#f0f0f0",
104
+ }}
105
+ >
106
+ <p>{info.componentStack}</p>
107
+ </div>
108
+ </div>
109
+ </div>
110
+ );
111
+ };