arcanajs 2.1.6 → 2.2.0
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/README.md +169 -2
- package/framework/cli/index.js +110 -17
- package/framework/cli/webpack.config.js +69 -42
- package/framework/lib/client/index.js +22 -6
- package/framework/lib/index.d.ts +2 -0
- package/framework/lib/index.js +42 -17
- package/framework/lib/server/ArcanaJSMiddleware.js +17 -10
- package/framework/lib/server/ArcanaJSServer.js +45 -29
- package/framework/lib/server/ControllerBinder.js +4 -1
- package/framework/lib/server/CsrfMiddleware.js +10 -3
- package/framework/lib/server/DynamicRouter.js +5 -1
- package/framework/lib/server/ResponseHandlerMiddleware.js +5 -1
- package/framework/lib/server/Router.js +15 -7
- package/framework/lib/server.d.ts +9 -0
- package/framework/lib/server.js +40 -6
- package/framework/lib/shared/components/Body.js +7 -3
- package/framework/lib/shared/components/Head.js +47 -10
- package/framework/lib/shared/components/Link.js +9 -5
- package/framework/lib/shared/components/NavLink.js +10 -6
- package/framework/lib/shared/components/Page.js +9 -5
- package/framework/lib/shared/context/HeadContext.js +5 -2
- package/framework/lib/shared/context/PageContext.js +5 -2
- package/framework/lib/shared/context/RouterContext.js +9 -5
- package/framework/lib/shared/core/ArcanaJSApp.js +18 -14
- package/framework/lib/shared/hooks/useDynamicComponents.js +8 -4
- package/framework/lib/shared/hooks/useHead.d.ts +1 -1
- package/framework/lib/shared/hooks/useHead.js +7 -3
- package/framework/lib/shared/hooks/useLocation.js +7 -3
- package/framework/lib/shared/hooks/usePage.js +7 -3
- package/framework/lib/shared/hooks/useParams.js +8 -4
- package/framework/lib/shared/hooks/useQuery.js +7 -3
- package/framework/lib/shared/hooks/useRouter.d.ts +1 -1
- package/framework/lib/shared/hooks/useRouter.js +8 -4
- package/framework/lib/shared/utils/createSingletonContext.js +6 -3
- package/framework/lib/shared/views/ErrorPage.d.ts +7 -0
- package/framework/lib/shared/views/ErrorPage.js +11 -0
- package/framework/lib/shared/views/NotFoundPage.d.ts +5 -0
- package/framework/lib/shared/views/NotFoundPage.js +12 -0
- package/framework/templates/ErrorPage.tsx +137 -0
- package/framework/templates/HomePage.tsx +325 -0
- package/framework/templates/NotFoundPage.tsx +109 -0
- package/framework/templates/arcanajs.png +0 -0
- package/framework/templates/arcanajs.svg +12 -0
- package/framework/templates/client-index.tsx +7 -0
- package/framework/templates/favicon.ico +0 -0
- package/framework/templates/globals.css +198 -0
- package/framework/templates/package.json +15 -0
- package/framework/templates/postcss.config.js +6 -0
- package/framework/templates/server-controller-home.ts +7 -0
- package/framework/templates/server-index.ts +10 -0
- package/framework/templates/server-routes-web.ts +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ArcanaJS Framework
|
|
2
2
|
|
|
3
|
-
ArcanaJS is a modern React framework for building server-side rendered (SSR) applications with ease. It combines the power of Express, React, and
|
|
3
|
+
ArcanaJS is a modern React framework for building server-side rendered (SSR) applications with ease. It combines the power of Express, React, TypeScript, and Tailwind CSS v4 to provide a seamless development experience.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
@@ -8,9 +8,176 @@ ArcanaJS is a modern React framework for building server-side rendered (SSR) app
|
|
|
8
8
|
- **TypeScript Support:** Built with TypeScript for type safety and better developer experience.
|
|
9
9
|
- **File-based Routing:** Intuitive routing based on your file structure.
|
|
10
10
|
- **Hot Module Replacement (HMR):** Fast development cycle with instant updates.
|
|
11
|
-
- **Tailwind CSS:** Integrated support for
|
|
11
|
+
- **Tailwind CSS v4:** Integrated support for the latest Tailwind CSS with CSS-first configuration.
|
|
12
|
+
- **Zero Configuration:** Get started quickly with sensible defaults.
|
|
12
13
|
|
|
14
|
+
## Quick Start
|
|
13
15
|
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install arcanajs
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### Initialize a New Project
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx arcanajs init
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
This creates the following structure:
|
|
29
|
+
```
|
|
30
|
+
your-project/
|
|
31
|
+
├── src/
|
|
32
|
+
│ ├── client/
|
|
33
|
+
│ │ └── index.tsx # Client-side entry point
|
|
34
|
+
│ ├── server/
|
|
35
|
+
│ │ └── index.ts # Server-side entry point
|
|
36
|
+
│ ├── views/
|
|
37
|
+
│ │ └── Home.tsx # Your first page component
|
|
38
|
+
│ └── globals.css # Tailwind CSS styles and theme
|
|
39
|
+
├── postcss.config.js # PostCSS configuration
|
|
40
|
+
└── package.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Development
|
|
44
|
+
|
|
45
|
+
Start the development server:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm run dev
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Visit `http://localhost:3000` to see your application.
|
|
52
|
+
|
|
53
|
+
### Build for Production
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm run build
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Start Production Server
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm run start
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Tailwind CSS v4 Integration
|
|
66
|
+
|
|
67
|
+
ArcanaJS comes with Tailwind CSS v4 pre-configured. Unlike previous versions, Tailwind v4 uses CSS-first configuration.
|
|
68
|
+
|
|
69
|
+
### Theme Customization
|
|
70
|
+
|
|
71
|
+
Edit `src/globals.css` to customize your theme:
|
|
72
|
+
|
|
73
|
+
```css
|
|
74
|
+
@import "tailwindcss";
|
|
75
|
+
|
|
76
|
+
@theme {
|
|
77
|
+
--color-primary: #3b82f6;
|
|
78
|
+
--font-family-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
|
|
79
|
+
--spacing-custom: 2.5rem;
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Using Tailwind Classes
|
|
84
|
+
|
|
85
|
+
Use Tailwind utility classes directly in your components:
|
|
86
|
+
|
|
87
|
+
```tsx
|
|
88
|
+
export default function MyComponent() {
|
|
89
|
+
return (
|
|
90
|
+
<div className="bg-primary text-white p-custom rounded-lg">
|
|
91
|
+
<h1 className="text-2xl font-bold">Hello Tailwind v4!</h1>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Custom Components
|
|
98
|
+
|
|
99
|
+
Define reusable component styles in your `globals.css`:
|
|
100
|
+
|
|
101
|
+
```css
|
|
102
|
+
.btn {
|
|
103
|
+
@apply px-4 py-2 rounded font-medium transition-colors;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.btn-primary {
|
|
107
|
+
@apply btn bg-blue-600 text-white hover:bg-blue-700;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Views and Routing
|
|
112
|
+
|
|
113
|
+
Create pages by adding components to the `src/views/` directory:
|
|
114
|
+
|
|
115
|
+
```tsx
|
|
116
|
+
// src/views/About.tsx
|
|
117
|
+
import { Page, Head, Body } from 'arcanajs';
|
|
118
|
+
|
|
119
|
+
export default function About() {
|
|
120
|
+
return (
|
|
121
|
+
<Page>
|
|
122
|
+
<Head>
|
|
123
|
+
<title>About Us</title>
|
|
124
|
+
</Head>
|
|
125
|
+
<Body>
|
|
126
|
+
<div className="container mx-auto px-4 py-8">
|
|
127
|
+
<h1 className="text-4xl font-bold text-gray-900">About Us</h1>
|
|
128
|
+
</div>
|
|
129
|
+
</Body>
|
|
130
|
+
</Page>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The page will automatically be available at `/About`.
|
|
136
|
+
|
|
137
|
+
## Server Configuration
|
|
138
|
+
|
|
139
|
+
Customize your server in `src/server/index.ts`:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import express from 'express';
|
|
143
|
+
import { createArcanaServer } from 'arcanajs/server';
|
|
144
|
+
|
|
145
|
+
const app = express();
|
|
146
|
+
const server = createArcanaServer(app, {
|
|
147
|
+
// Custom configuration options
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const PORT = process.env.PORT || 3000;
|
|
151
|
+
server.listen(PORT, () => {
|
|
152
|
+
console.log(`🚀 ArcanaJS server running on http://localhost:${PORT}`);
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Available Commands
|
|
157
|
+
|
|
158
|
+
- `arcanajs init` - Initialize a new ArcanaJS project
|
|
159
|
+
- `arcanajs dev` - Start development server with hot reload
|
|
160
|
+
- `arcanajs build` - Build for production
|
|
161
|
+
- `arcanajs start` - Start production server
|
|
162
|
+
|
|
163
|
+
## Components
|
|
164
|
+
|
|
165
|
+
ArcanaJS provides several built-in components:
|
|
166
|
+
|
|
167
|
+
- `<Page>` - Wrapper component for pages
|
|
168
|
+
- `<Head>` - Document head management
|
|
169
|
+
- `<Body>` - Body content wrapper
|
|
170
|
+
- `<Link>` - Client-side navigation
|
|
171
|
+
- `<NavLink>` - Navigation link with active state
|
|
172
|
+
|
|
173
|
+
## Hooks
|
|
174
|
+
|
|
175
|
+
- `useRouter()` - Access router functionality
|
|
176
|
+
- `useLocation()` - Get current location
|
|
177
|
+
- `useParams()` - Get URL parameters
|
|
178
|
+
- `useQuery()` - Get query parameters
|
|
179
|
+
- `usePage()` - Access page context
|
|
180
|
+
- `useHead()` - Manage document head
|
|
14
181
|
|
|
15
182
|
## License
|
|
16
183
|
|
package/framework/cli/index.js
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const child_process_1 = require("child_process");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const webpack_1 = __importDefault(require("webpack"));
|
|
10
|
+
const webpack_config_1 = require("./webpack.config");
|
|
5
11
|
const args = process.argv.slice(2);
|
|
6
12
|
const command = args[0];
|
|
7
13
|
if (!command) {
|
|
8
|
-
console.error("Please specify a command: dev, build, start");
|
|
14
|
+
console.error("Please specify a command: init, dev, build, start");
|
|
9
15
|
process.exit(1);
|
|
10
16
|
}
|
|
11
17
|
const runCompiler = (compiler) => {
|
|
@@ -29,8 +35,8 @@ const startDevServer = () => {
|
|
|
29
35
|
if (serverProcess) {
|
|
30
36
|
serverProcess.kill();
|
|
31
37
|
}
|
|
32
|
-
const serverPath =
|
|
33
|
-
serverProcess = spawn("node", [serverPath], { stdio: "inherit" });
|
|
38
|
+
const serverPath = path_1.default.resolve(process.cwd(), "dist/server.js");
|
|
39
|
+
serverProcess = (0, child_process_1.spawn)("node", [serverPath], { stdio: "inherit" });
|
|
34
40
|
serverProcess.on("close", (code) => {
|
|
35
41
|
if (code !== 0 && code !== null) {
|
|
36
42
|
console.error(`Dev server exited with code ${code}`);
|
|
@@ -52,11 +58,11 @@ const watchCompiler = (compiler, onBuildComplete) => {
|
|
|
52
58
|
const build = async () => {
|
|
53
59
|
process.env.NODE_ENV = "production";
|
|
54
60
|
console.log("Building for production...");
|
|
55
|
-
const clientConfig = createClientConfig();
|
|
56
|
-
const serverConfig = createServerConfig();
|
|
61
|
+
const clientConfig = (0, webpack_config_1.createClientConfig)();
|
|
62
|
+
const serverConfig = (0, webpack_config_1.createServerConfig)();
|
|
57
63
|
try {
|
|
58
|
-
await runCompiler(
|
|
59
|
-
await runCompiler(
|
|
64
|
+
await runCompiler((0, webpack_1.default)(clientConfig));
|
|
65
|
+
await runCompiler((0, webpack_1.default)(serverConfig));
|
|
60
66
|
console.log("Build complete.");
|
|
61
67
|
}
|
|
62
68
|
catch (error) {
|
|
@@ -67,26 +73,113 @@ const build = async () => {
|
|
|
67
73
|
const dev = async () => {
|
|
68
74
|
process.env.NODE_ENV = "development";
|
|
69
75
|
console.log("Starting development server...");
|
|
70
|
-
const clientConfig = createClientConfig();
|
|
71
|
-
const serverConfig = createServerConfig();
|
|
76
|
+
const clientConfig = (0, webpack_config_1.createClientConfig)();
|
|
77
|
+
const serverConfig = (0, webpack_config_1.createServerConfig)();
|
|
72
78
|
// Watch client
|
|
73
|
-
watchCompiler(
|
|
79
|
+
watchCompiler((0, webpack_1.default)(clientConfig));
|
|
74
80
|
// Watch server and restart on build
|
|
75
|
-
watchCompiler(
|
|
81
|
+
watchCompiler((0, webpack_1.default)(serverConfig), () => {
|
|
76
82
|
console.log("Server build complete. Restarting server...");
|
|
77
83
|
startDevServer();
|
|
78
84
|
});
|
|
79
85
|
};
|
|
86
|
+
const init = () => {
|
|
87
|
+
console.log("Initializing ArcanaJS project with Tailwind CSS...");
|
|
88
|
+
const cwd = process.cwd();
|
|
89
|
+
const templatesDir = path_1.default.resolve(__dirname, "../templates");
|
|
90
|
+
// Create necessary directories
|
|
91
|
+
const publicDir = path_1.default.resolve(cwd, "public");
|
|
92
|
+
const srcDir = path_1.default.resolve(cwd, "src");
|
|
93
|
+
const clientDir = path_1.default.resolve(cwd, "src/client");
|
|
94
|
+
const serverDir = path_1.default.resolve(cwd, "src/server");
|
|
95
|
+
const routesDir = path_1.default.resolve(cwd, "src/server/routes");
|
|
96
|
+
const controllersDir = path_1.default.resolve(cwd, "src/server/controllers");
|
|
97
|
+
const viewsDir = path_1.default.resolve(cwd, "src/views");
|
|
98
|
+
[
|
|
99
|
+
publicDir,
|
|
100
|
+
srcDir,
|
|
101
|
+
clientDir,
|
|
102
|
+
serverDir,
|
|
103
|
+
routesDir,
|
|
104
|
+
controllersDir,
|
|
105
|
+
viewsDir,
|
|
106
|
+
].forEach((dir) => {
|
|
107
|
+
if (!fs_1.default.existsSync(dir)) {
|
|
108
|
+
fs_1.default.mkdirSync(dir, { recursive: true });
|
|
109
|
+
console.log(`Created directory: ${path_1.default.relative(cwd, dir)}`);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// Copy configuration files
|
|
113
|
+
const configFiles = [
|
|
114
|
+
{ src: "package.json", dest: "package.json" },
|
|
115
|
+
{ src: "postcss.config.js", dest: "postcss.config.js" },
|
|
116
|
+
{ src: "globals.css", dest: "src/client/globals.css" },
|
|
117
|
+
{ src: "client-index.tsx", dest: "src/client/index.tsx" },
|
|
118
|
+
{ src: "server-index.ts", dest: "src/server/index.ts" },
|
|
119
|
+
{ src: "server-routes-web.ts", dest: "src/server/routes/web.ts" },
|
|
120
|
+
{
|
|
121
|
+
src: "server-controller-home.ts",
|
|
122
|
+
dest: "src/server/controllers/HomeController.ts",
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
src: "HomePage.tsx",
|
|
126
|
+
dest: "src/views/HomePage.tsx",
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
src: "arcanajs.png",
|
|
130
|
+
dest: "public/arcanajs.png",
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
src: "arcanajs.svg",
|
|
134
|
+
dest: "public/arcanajs.svg",
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
src: "favicon.ico",
|
|
138
|
+
dest: "public/favicon.ico",
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
configFiles.forEach(({ src, dest }) => {
|
|
142
|
+
const srcPath = path_1.default.resolve(templatesDir, src);
|
|
143
|
+
const destPath = path_1.default.resolve(cwd, dest);
|
|
144
|
+
if (!fs_1.default.existsSync(destPath)) {
|
|
145
|
+
fs_1.default.copyFileSync(srcPath, destPath);
|
|
146
|
+
console.log(`Created: ${dest}`);
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
console.log(`Skipped: ${dest} (already exists)`);
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
// Create default error pages
|
|
153
|
+
const errorPages = ["NotFoundPage.tsx", "ErrorPage.tsx"];
|
|
154
|
+
errorPages.forEach((page) => {
|
|
155
|
+
const viewPath = path_1.default.resolve(cwd, `src/views/${page}`);
|
|
156
|
+
const templatePath = path_1.default.resolve(templatesDir, page);
|
|
157
|
+
if (!fs_1.default.existsSync(viewPath) && fs_1.default.existsSync(templatePath)) {
|
|
158
|
+
fs_1.default.copyFileSync(templatePath, viewPath);
|
|
159
|
+
console.log(`Created: src/views/${page}`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
console.log("\n✅ ArcanaJS project initialized successfully!");
|
|
163
|
+
console.log("\nNext steps:");
|
|
164
|
+
console.log("1. Run 'npm run dev' to start development");
|
|
165
|
+
console.log("2. Visit http://localhost:3000 to see your app");
|
|
166
|
+
console.log("3. Edit src/views/HomePage.tsx to customize your homepage");
|
|
167
|
+
console.log("4. Customize your theme in src/client/globals.css");
|
|
168
|
+
console.log("5. Add your Tailwind classes and enjoy!");
|
|
169
|
+
};
|
|
80
170
|
const start = () => {
|
|
81
171
|
process.env.NODE_ENV = "production";
|
|
82
|
-
const serverPath =
|
|
172
|
+
const serverPath = path_1.default.resolve(process.cwd(), "dist/server.js");
|
|
83
173
|
console.log(`Starting server at ${serverPath}...`);
|
|
84
|
-
const child = spawn("node", [serverPath], { stdio: "inherit" });
|
|
174
|
+
const child = (0, child_process_1.spawn)("node", [serverPath], { stdio: "inherit" });
|
|
85
175
|
child.on("close", (code) => {
|
|
86
176
|
process.exit(code || 0);
|
|
87
177
|
});
|
|
88
178
|
};
|
|
89
179
|
switch (command) {
|
|
180
|
+
case "init":
|
|
181
|
+
init();
|
|
182
|
+
break;
|
|
90
183
|
case "build":
|
|
91
184
|
build();
|
|
92
185
|
break;
|
|
@@ -1,42 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createServerConfig = exports.createClientConfig = void 0;
|
|
7
|
+
const clean_webpack_plugin_1 = require("clean-webpack-plugin");
|
|
8
|
+
const html_webpack_plugin_1 = __importDefault(require("html-webpack-plugin"));
|
|
9
|
+
const mini_css_extract_plugin_1 = __importDefault(require("mini-css-extract-plugin"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const webpack_node_externals_1 = __importDefault(require("webpack-node-externals"));
|
|
6
12
|
const cwd = process.cwd();
|
|
7
13
|
// Helper to resolve loaders from the framework's node_modules
|
|
8
14
|
const resolveLoader = (loader) => require.resolve(loader);
|
|
9
|
-
|
|
15
|
+
const fs_1 = __importDefault(require("fs"));
|
|
10
16
|
// Helper to find entry file with supported extensions
|
|
11
17
|
const findEntry = (searchPaths) => {
|
|
12
18
|
const extensions = [".ts", ".tsx", ".js", ".jsx"];
|
|
13
19
|
for (const basePath of searchPaths) {
|
|
14
20
|
for (const ext of extensions) {
|
|
15
|
-
const fullPath =
|
|
16
|
-
if (
|
|
21
|
+
const fullPath = path_1.default.resolve(cwd, basePath + ext);
|
|
22
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
17
23
|
return fullPath;
|
|
18
24
|
}
|
|
19
25
|
// Also check for index files in directories
|
|
20
|
-
const indexPath =
|
|
21
|
-
if (
|
|
26
|
+
const indexPath = path_1.default.resolve(cwd, basePath, "index" + ext);
|
|
27
|
+
if (fs_1.default.existsSync(indexPath)) {
|
|
22
28
|
return indexPath;
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
}
|
|
26
32
|
// Fallback to example if not found (for internal framework dev) or throw error
|
|
27
33
|
// For now, we'll try the example paths as a last resort before failing
|
|
28
|
-
const exampleClient =
|
|
29
|
-
const exampleServer =
|
|
34
|
+
const exampleClient = path_1.default.resolve(cwd, "src/example/client/index.tsx");
|
|
35
|
+
const exampleServer = path_1.default.resolve(cwd, "src/example/server/index.ts");
|
|
30
36
|
if (searchPaths.some((p) => p.includes("client")) &&
|
|
31
|
-
|
|
37
|
+
fs_1.default.existsSync(exampleClient))
|
|
32
38
|
return exampleClient;
|
|
33
39
|
if (searchPaths.some((p) => p.includes("server")) &&
|
|
34
|
-
|
|
40
|
+
fs_1.default.existsSync(exampleServer))
|
|
35
41
|
return exampleServer;
|
|
36
42
|
throw new Error(`Could not find entry point. Searched in: ${searchPaths.join(", ")}`);
|
|
37
43
|
};
|
|
38
|
-
|
|
44
|
+
const getViewsLoaderPath = () => {
|
|
45
|
+
const viewsDir = path_1.default.resolve(cwd, "src/views");
|
|
46
|
+
const hasViews = fs_1.default.existsSync(viewsDir);
|
|
47
|
+
const viewsLoaderPath = path_1.default.resolve(__dirname, "../../node_modules/.cache/arcanajs/views-loader.js");
|
|
48
|
+
// Ensure cache directory exists
|
|
49
|
+
const cacheDir = path_1.default.dirname(viewsLoaderPath);
|
|
50
|
+
if (!fs_1.default.existsSync(cacheDir)) {
|
|
51
|
+
fs_1.default.mkdirSync(cacheDir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
// Generate the loader file
|
|
54
|
+
const loaderContent = hasViews
|
|
55
|
+
? `module.exports = require.context('${viewsDir}', true, /\\.(tsx|jsx)$/);`
|
|
56
|
+
: `module.exports = null;`;
|
|
57
|
+
fs_1.default.writeFileSync(viewsLoaderPath, loaderContent);
|
|
58
|
+
return viewsLoaderPath;
|
|
59
|
+
};
|
|
60
|
+
const createClientConfig = () => {
|
|
39
61
|
const isProduction = process.env.NODE_ENV === "production";
|
|
62
|
+
const viewsLoaderPath = getViewsLoaderPath();
|
|
40
63
|
const clientEntry = findEntry([
|
|
41
64
|
"src/client",
|
|
42
65
|
"src/client/index",
|
|
@@ -50,7 +73,7 @@ export const createClientConfig = () => {
|
|
|
50
73
|
client: clientEntry,
|
|
51
74
|
},
|
|
52
75
|
output: {
|
|
53
|
-
path:
|
|
76
|
+
path: path_1.default.resolve(cwd, "dist/public"),
|
|
54
77
|
filename: isProduction
|
|
55
78
|
? "[name].[contenthash].bundle.js"
|
|
56
79
|
: "[name].bundle.js",
|
|
@@ -59,9 +82,12 @@ export const createClientConfig = () => {
|
|
|
59
82
|
},
|
|
60
83
|
resolve: {
|
|
61
84
|
extensions: [".ts", ".tsx", ".js", ".jsx"],
|
|
85
|
+
alias: {
|
|
86
|
+
"arcana-views": viewsLoaderPath,
|
|
87
|
+
},
|
|
62
88
|
},
|
|
63
89
|
resolveLoader: {
|
|
64
|
-
modules: ["node_modules",
|
|
90
|
+
modules: ["node_modules", path_1.default.resolve(__dirname, "../../node_modules")],
|
|
65
91
|
},
|
|
66
92
|
module: {
|
|
67
93
|
rules: [
|
|
@@ -86,10 +112,22 @@ export const createClientConfig = () => {
|
|
|
86
112
|
test: /\.css$/,
|
|
87
113
|
use: [
|
|
88
114
|
isProduction
|
|
89
|
-
?
|
|
115
|
+
? mini_css_extract_plugin_1.default.loader
|
|
90
116
|
: resolveLoader("style-loader"),
|
|
91
|
-
|
|
92
|
-
|
|
117
|
+
{
|
|
118
|
+
loader: resolveLoader("css-loader"),
|
|
119
|
+
options: {
|
|
120
|
+
importLoaders: 1,
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
loader: resolveLoader("postcss-loader"),
|
|
125
|
+
options: {
|
|
126
|
+
postcssOptions: {
|
|
127
|
+
config: path_1.default.resolve(cwd, "postcss.config.js"),
|
|
128
|
+
},
|
|
129
|
+
},
|
|
130
|
+
},
|
|
93
131
|
],
|
|
94
132
|
},
|
|
95
133
|
{
|
|
@@ -99,9 +137,9 @@ export const createClientConfig = () => {
|
|
|
99
137
|
],
|
|
100
138
|
},
|
|
101
139
|
plugins: [
|
|
102
|
-
new CleanWebpackPlugin(),
|
|
103
|
-
new
|
|
104
|
-
template:
|
|
140
|
+
new clean_webpack_plugin_1.CleanWebpackPlugin(),
|
|
141
|
+
new html_webpack_plugin_1.default({
|
|
142
|
+
template: path_1.default.resolve(__dirname, "../lib/server/default-index.html"),
|
|
105
143
|
filename: "index.html",
|
|
106
144
|
inject: "body",
|
|
107
145
|
minify: isProduction
|
|
@@ -119,7 +157,7 @@ export const createClientConfig = () => {
|
|
|
119
157
|
}
|
|
120
158
|
: false,
|
|
121
159
|
}),
|
|
122
|
-
new
|
|
160
|
+
new mini_css_extract_plugin_1.default({
|
|
123
161
|
filename: isProduction ? "[name].[contenthash].css" : "[name].css",
|
|
124
162
|
}),
|
|
125
163
|
],
|
|
@@ -138,37 +176,25 @@ export const createClientConfig = () => {
|
|
|
138
176
|
devtool: isProduction ? "source-map" : "eval-source-map",
|
|
139
177
|
};
|
|
140
178
|
};
|
|
141
|
-
|
|
179
|
+
exports.createClientConfig = createClientConfig;
|
|
180
|
+
const createServerConfig = () => {
|
|
142
181
|
const isProduction = process.env.NODE_ENV === "production";
|
|
143
182
|
const serverEntry = findEntry([
|
|
144
183
|
"src/server",
|
|
145
184
|
"src/server/index",
|
|
146
185
|
"src/server/main",
|
|
147
186
|
]);
|
|
148
|
-
|
|
149
|
-
const viewsDir = path.resolve(cwd, "src/views");
|
|
150
|
-
const hasViews = fs.existsSync(viewsDir);
|
|
151
|
-
const viewsLoaderPath = path.resolve(__dirname, "../../node_modules/.cache/arcanajs/views-loader.js");
|
|
152
|
-
// Ensure cache directory exists
|
|
153
|
-
const cacheDir = path.dirname(viewsLoaderPath);
|
|
154
|
-
if (!fs.existsSync(cacheDir)) {
|
|
155
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
156
|
-
}
|
|
157
|
-
// Generate the loader file
|
|
158
|
-
const loaderContent = hasViews
|
|
159
|
-
? `module.exports = require.context('${viewsDir}', true, /\\.(tsx|jsx)$/);`
|
|
160
|
-
: `module.exports = null;`;
|
|
161
|
-
fs.writeFileSync(viewsLoaderPath, loaderContent);
|
|
187
|
+
const viewsLoaderPath = getViewsLoaderPath();
|
|
162
188
|
return {
|
|
163
189
|
mode: isProduction ? "production" : "development",
|
|
164
190
|
target: "node",
|
|
165
191
|
entry: serverEntry,
|
|
166
192
|
output: {
|
|
167
|
-
path:
|
|
193
|
+
path: path_1.default.resolve(cwd, "dist"),
|
|
168
194
|
filename: "server.js",
|
|
169
195
|
},
|
|
170
196
|
externals: [
|
|
171
|
-
|
|
197
|
+
(0, webpack_node_externals_1.default)({
|
|
172
198
|
allowlist: [/^arcanajs/],
|
|
173
199
|
}),
|
|
174
200
|
],
|
|
@@ -179,7 +205,7 @@ export const createServerConfig = () => {
|
|
|
179
205
|
},
|
|
180
206
|
},
|
|
181
207
|
resolveLoader: {
|
|
182
|
-
modules: ["node_modules",
|
|
208
|
+
modules: ["node_modules", path_1.default.resolve(__dirname, "../../node_modules")],
|
|
183
209
|
},
|
|
184
210
|
module: {
|
|
185
211
|
rules: [
|
|
@@ -216,3 +242,4 @@ export const createServerConfig = () => {
|
|
|
216
242
|
// devtool: isProduction ? "source-map" : "eval-source-map",
|
|
217
243
|
};
|
|
218
244
|
};
|
|
245
|
+
exports.createServerConfig = createServerConfig;
|
|
@@ -1,8 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.hydrateArcanaJS = void 0;
|
|
7
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
8
|
+
const client_1 = require("react-dom/client");
|
|
9
|
+
const HeadContext_1 = require("../shared/context/HeadContext");
|
|
10
|
+
const ArcanaJSApp_1 = require("../shared/core/ArcanaJSApp");
|
|
11
|
+
const ErrorPage_1 = __importDefault(require("../shared/views/ErrorPage"));
|
|
12
|
+
const NotFoundPage_1 = __importDefault(require("../shared/views/NotFoundPage"));
|
|
13
|
+
const hydrateArcanaJS = (viewsOrContext, layout) => {
|
|
6
14
|
let views = {};
|
|
7
15
|
if (viewsOrContext.keys && typeof viewsOrContext.keys === "function") {
|
|
8
16
|
viewsOrContext.keys().forEach((key) => {
|
|
@@ -13,6 +21,13 @@ export const hydrateArcanaJS = (viewsOrContext, layout) => {
|
|
|
13
21
|
else {
|
|
14
22
|
views = viewsOrContext;
|
|
15
23
|
}
|
|
24
|
+
// Add default error views if not present
|
|
25
|
+
if (!views["NotFoundPage"]) {
|
|
26
|
+
views["NotFoundPage"] = NotFoundPage_1.default;
|
|
27
|
+
}
|
|
28
|
+
if (!views["ErrorPage"]) {
|
|
29
|
+
views["ErrorPage"] = ErrorPage_1.default;
|
|
30
|
+
}
|
|
16
31
|
const container = document.getElementById("root");
|
|
17
32
|
const dataScript = document.getElementById("__ArcanaJS_DATA__");
|
|
18
33
|
// Client-side HeadManager (noop for push, as Head handles client updates via useEffect)
|
|
@@ -23,10 +38,11 @@ export const hydrateArcanaJS = (viewsOrContext, layout) => {
|
|
|
23
38
|
if (container && dataScript) {
|
|
24
39
|
try {
|
|
25
40
|
const { page, data, params, csrfToken } = JSON.parse(dataScript.textContent || "{}");
|
|
26
|
-
hydrateRoot(container,
|
|
41
|
+
(0, client_1.hydrateRoot)(container, (0, jsx_runtime_1.jsx)(HeadContext_1.HeadContext.Provider, { value: headManager, children: (0, jsx_runtime_1.jsx)(ArcanaJSApp_1.ArcanaJSApp, { initialPage: page, initialData: data, initialParams: params, csrfToken: csrfToken, views: views, layout: layout }) }));
|
|
27
42
|
}
|
|
28
43
|
catch (e) {
|
|
29
44
|
console.error("Failed to parse initial data", e);
|
|
30
45
|
}
|
|
31
46
|
}
|
|
32
47
|
};
|
|
48
|
+
exports.hydrateArcanaJS = hydrateArcanaJS;
|
package/framework/lib/index.d.ts
CHANGED
|
@@ -15,3 +15,5 @@ export * from "./shared/hooks/usePage";
|
|
|
15
15
|
export * from "./shared/hooks/useParams";
|
|
16
16
|
export * from "./shared/hooks/useQuery";
|
|
17
17
|
export * from "./shared/hooks/useRouter";
|
|
18
|
+
export { default as NotFoundPage } from "./shared/views/NotFoundPage";
|
|
19
|
+
export { default as ErrorPage } from "./shared/views/ErrorPage";
|