arcanajs 2.1.6 → 2.2.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.
- package/README.md +45 -3
- package/framework/cli/index.js +127 -25
- package/framework/cli/templates.d.ts +6 -0
- package/framework/cli/templates.js +41 -0
- package/framework/cli/webpack.config.js +87 -44
- package/framework/lib/client/index.js +23 -7
- package/framework/lib/index.d.ts +2 -2
- package/framework/lib/index.js +40 -17
- package/framework/lib/server/ArcanaJSMiddleware.d.ts +9 -1
- package/framework/lib/server/ArcanaJSMiddleware.js +35 -13
- package/framework/lib/server/ArcanaJSServer.d.ts +2 -0
- package/framework/lib/server/ArcanaJSServer.js +77 -59
- package/framework/lib/server/ControllerBinder.js +4 -1
- package/framework/lib/server/CsrfMiddleware.js +10 -3
- package/framework/lib/server/DynamicRouter.d.ts +1 -1
- package/framework/lib/server/DynamicRouter.js +5 -1
- package/framework/lib/server/ResponseHandlerMiddleware.js +5 -1
- package/framework/lib/server/Router.d.ts +1 -1
- package/framework/lib/server/Router.js +16 -8
- package/framework/lib/server.d.ts +10 -0
- package/framework/lib/server.js +41 -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/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 +10 -0
- package/framework/templates/ErrorPage.tsx +137 -0
- package/framework/templates/HomePage.tsx +344 -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 +30 -25
- package/framework/lib/shared/hooks/useDynamicComponents.d.ts +0 -1
- package/framework/lib/shared/hooks/useDynamicComponents.js +0 -16
package/README.md
CHANGED
|
@@ -1,16 +1,58 @@
|
|
|
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
|
|
|
7
7
|
- **Server-Side Rendering (SSR):** Fast initial load times and SEO-friendly pages.
|
|
8
8
|
- **TypeScript Support:** Built with TypeScript for type safety and better developer experience.
|
|
9
|
-
- **File-
|
|
9
|
+
- **File-Server-Based Routing:** Intuitive routing based on your server routes and 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
|
+
### Create a new folder
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
mkdir my-app
|
|
20
|
+
cd my-app
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Initialize a New Project
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx arcanajs init
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Development
|
|
30
|
+
|
|
31
|
+
Install dependencies:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Start the development server:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm run dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Visit `http://localhost:3000` to see your application.
|
|
44
|
+
|
|
45
|
+
### Build for Production
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm run build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Start Production Server
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm start
|
|
55
|
+
```
|
|
14
56
|
|
|
15
57
|
## License
|
|
16
58
|
|
package/framework/cli/index.js
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
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 templates_1 = require("./templates");
|
|
11
|
+
const webpack_config_1 = require("./webpack.config");
|
|
5
12
|
const args = process.argv.slice(2);
|
|
6
13
|
const command = args[0];
|
|
7
14
|
if (!command) {
|
|
8
|
-
console.error("Please specify a command: dev, build, start");
|
|
15
|
+
console.error("Please specify a command: init, dev, build, start");
|
|
9
16
|
process.exit(1);
|
|
10
17
|
}
|
|
11
18
|
const runCompiler = (compiler) => {
|
|
@@ -25,16 +32,28 @@ const runCompiler = (compiler) => {
|
|
|
25
32
|
});
|
|
26
33
|
};
|
|
27
34
|
let serverProcess = null;
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
serverProcess = spawn("node", [serverPath], { stdio: "inherit" });
|
|
34
|
-
serverProcess.on("close", (code) => {
|
|
35
|
-
if (code !== 0 && code !== null) {
|
|
36
|
-
console.error(`Dev server exited with code ${code}`);
|
|
35
|
+
const ws_1 = require("ws");
|
|
36
|
+
const startDevServer = (hmrPort) => {
|
|
37
|
+
return new Promise((resolve) => {
|
|
38
|
+
if (serverProcess) {
|
|
39
|
+
serverProcess.kill();
|
|
37
40
|
}
|
|
41
|
+
const serverPath = path_1.default.resolve(process.cwd(), "dist/server.js");
|
|
42
|
+
serverProcess = (0, child_process_1.spawn)("node", [serverPath], {
|
|
43
|
+
stdio: ["inherit", "pipe", "inherit"],
|
|
44
|
+
env: { ...process.env, ARCANA_HMR_PORT: hmrPort.toString() },
|
|
45
|
+
});
|
|
46
|
+
serverProcess.stdout?.on("data", (data) => {
|
|
47
|
+
process.stdout.write(data);
|
|
48
|
+
if (data.toString().includes("Server is running")) {
|
|
49
|
+
resolve();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
serverProcess.on("close", (code) => {
|
|
53
|
+
if (code !== 0 && code !== null) {
|
|
54
|
+
console.error(`Dev server exited with code ${code}`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
38
57
|
});
|
|
39
58
|
};
|
|
40
59
|
const watchCompiler = (compiler, onBuildComplete) => {
|
|
@@ -52,11 +71,11 @@ const watchCompiler = (compiler, onBuildComplete) => {
|
|
|
52
71
|
const build = async () => {
|
|
53
72
|
process.env.NODE_ENV = "production";
|
|
54
73
|
console.log("Building for production...");
|
|
55
|
-
const clientConfig = createClientConfig();
|
|
56
|
-
const serverConfig = createServerConfig();
|
|
74
|
+
const clientConfig = (0, webpack_config_1.createClientConfig)();
|
|
75
|
+
const serverConfig = (0, webpack_config_1.createServerConfig)();
|
|
57
76
|
try {
|
|
58
|
-
await runCompiler(
|
|
59
|
-
await runCompiler(
|
|
77
|
+
await runCompiler((0, webpack_1.default)(clientConfig));
|
|
78
|
+
await runCompiler((0, webpack_1.default)(serverConfig));
|
|
60
79
|
console.log("Build complete.");
|
|
61
80
|
}
|
|
62
81
|
catch (error) {
|
|
@@ -67,26 +86,109 @@ const build = async () => {
|
|
|
67
86
|
const dev = async () => {
|
|
68
87
|
process.env.NODE_ENV = "development";
|
|
69
88
|
console.log("Starting development server...");
|
|
70
|
-
|
|
71
|
-
const
|
|
89
|
+
// Start HMR WebSocket Server
|
|
90
|
+
const HMR_PORT = 3001;
|
|
91
|
+
const wss = new ws_1.WebSocketServer({ port: HMR_PORT });
|
|
92
|
+
console.log(`HMR Server running on port ${HMR_PORT}`);
|
|
93
|
+
const broadcastReload = () => {
|
|
94
|
+
wss.clients.forEach((client) => {
|
|
95
|
+
if (client.readyState === 1) {
|
|
96
|
+
client.send(JSON.stringify({ type: "reload" }));
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
const clientConfig = (0, webpack_config_1.createClientConfig)();
|
|
101
|
+
const serverConfig = (0, webpack_config_1.createServerConfig)();
|
|
102
|
+
let isServerBuilding = false;
|
|
103
|
+
let pendingReload = false;
|
|
104
|
+
const serverCompiler = (0, webpack_1.default)(serverConfig);
|
|
105
|
+
serverCompiler.hooks.invalid.tap("ArcanaJS", () => {
|
|
106
|
+
isServerBuilding = true;
|
|
107
|
+
});
|
|
72
108
|
// Watch client
|
|
73
|
-
watchCompiler(
|
|
109
|
+
watchCompiler((0, webpack_1.default)(clientConfig), () => {
|
|
110
|
+
console.log("Client build complete.");
|
|
111
|
+
if (isServerBuilding) {
|
|
112
|
+
console.log("Server is building. Waiting to reload...");
|
|
113
|
+
pendingReload = true;
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
console.log("Reloading browsers...");
|
|
117
|
+
broadcastReload();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
74
120
|
// Watch server and restart on build
|
|
75
|
-
watchCompiler(
|
|
121
|
+
watchCompiler(serverCompiler, async () => {
|
|
76
122
|
console.log("Server build complete. Restarting server...");
|
|
77
|
-
startDevServer();
|
|
123
|
+
await startDevServer(HMR_PORT);
|
|
124
|
+
isServerBuilding = false;
|
|
125
|
+
if (pendingReload) {
|
|
126
|
+
console.log("Pending reload found. Reloading browsers...");
|
|
127
|
+
broadcastReload();
|
|
128
|
+
pendingReload = false;
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
const init = () => {
|
|
133
|
+
console.log("Initializing ArcanaJS project with Tailwind CSS...");
|
|
134
|
+
const cwd = process.cwd();
|
|
135
|
+
const templatesDir = path_1.default.resolve(__dirname, "../templates");
|
|
136
|
+
// Create necessary directories
|
|
137
|
+
templates_1.requiredDirs.forEach((dir) => {
|
|
138
|
+
const fullPath = path_1.default.resolve(cwd, dir);
|
|
139
|
+
if (!fs_1.default.existsSync(fullPath)) {
|
|
140
|
+
fs_1.default.mkdirSync(fullPath, { recursive: true });
|
|
141
|
+
console.log(`Created directory: ${dir}`);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
// Copy configuration files
|
|
145
|
+
templates_1.configFiles.forEach(({ src, dest }) => {
|
|
146
|
+
const srcPath = path_1.default.resolve(templatesDir, src);
|
|
147
|
+
const destPath = path_1.default.resolve(cwd, dest);
|
|
148
|
+
if (!fs_1.default.existsSync(destPath)) {
|
|
149
|
+
if (fs_1.default.existsSync(srcPath)) {
|
|
150
|
+
fs_1.default.copyFileSync(srcPath, destPath);
|
|
151
|
+
console.log(`Created: ${dest}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
console.warn(`Template not found: ${src}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
console.log(`Skipped: ${dest} (already exists)`);
|
|
159
|
+
}
|
|
78
160
|
});
|
|
161
|
+
// Create default error pages
|
|
162
|
+
templates_1.errorPages.forEach((page) => {
|
|
163
|
+
const viewPath = path_1.default.resolve(cwd, `src/views/${page}`);
|
|
164
|
+
const templatePath = path_1.default.resolve(templatesDir, page);
|
|
165
|
+
if (!fs_1.default.existsSync(viewPath) && fs_1.default.existsSync(templatePath)) {
|
|
166
|
+
fs_1.default.copyFileSync(templatePath, viewPath);
|
|
167
|
+
console.log(`Created: src/views/${page}`);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
console.log("\n✅ ArcanaJS project initialized successfully!");
|
|
171
|
+
console.log("\nNext steps:");
|
|
172
|
+
console.log("1. Run 'npm install' to install dependencies");
|
|
173
|
+
console.log("2. Run 'npm run dev' to start development");
|
|
174
|
+
console.log("3. Visit http://localhost:3000 to see your app");
|
|
175
|
+
console.log("4. Edit src/views/HomePage.tsx to customize your homepage");
|
|
176
|
+
console.log("5. Customize your theme in src/client/globals.css");
|
|
177
|
+
console.log("6. Add your Tailwind classes and enjoy!");
|
|
79
178
|
};
|
|
80
179
|
const start = () => {
|
|
81
180
|
process.env.NODE_ENV = "production";
|
|
82
|
-
const serverPath =
|
|
181
|
+
const serverPath = path_1.default.resolve(process.cwd(), "dist/server.js");
|
|
83
182
|
console.log(`Starting server at ${serverPath}...`);
|
|
84
|
-
const child = spawn("node", [serverPath], { stdio: "inherit" });
|
|
183
|
+
const child = (0, child_process_1.spawn)("node", [serverPath], { stdio: "inherit" });
|
|
85
184
|
child.on("close", (code) => {
|
|
86
185
|
process.exit(code || 0);
|
|
87
186
|
});
|
|
88
187
|
};
|
|
89
188
|
switch (command) {
|
|
189
|
+
case "init":
|
|
190
|
+
init();
|
|
191
|
+
break;
|
|
90
192
|
case "build":
|
|
91
193
|
build();
|
|
92
194
|
break;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.requiredDirs = exports.errorPages = exports.configFiles = void 0;
|
|
4
|
+
exports.configFiles = [
|
|
5
|
+
{ src: "package.json", dest: "package.json" },
|
|
6
|
+
{ src: "postcss.config.js", dest: "postcss.config.js" },
|
|
7
|
+
{ src: "globals.css", dest: "src/client/globals.css" },
|
|
8
|
+
{ src: "client-index.tsx", dest: "src/client/index.tsx" },
|
|
9
|
+
{ src: "server-index.ts", dest: "src/server/index.ts" },
|
|
10
|
+
{ src: "server-routes-web.ts", dest: "src/server/routes/web.ts" },
|
|
11
|
+
{
|
|
12
|
+
src: "server-controller-home.ts",
|
|
13
|
+
dest: "src/server/controllers/HomeController.ts",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
src: "HomePage.tsx",
|
|
17
|
+
dest: "src/views/HomePage.tsx",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
src: "arcanajs.png",
|
|
21
|
+
dest: "public/arcanajs.png",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
src: "arcanajs.svg",
|
|
25
|
+
dest: "public/arcanajs.svg",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
src: "favicon.ico",
|
|
29
|
+
dest: "public/favicon.ico",
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
exports.errorPages = ["NotFoundPage.tsx", "ErrorPage.tsx"];
|
|
33
|
+
exports.requiredDirs = [
|
|
34
|
+
"public",
|
|
35
|
+
"src",
|
|
36
|
+
"src/client",
|
|
37
|
+
"src/server",
|
|
38
|
+
"src/server/routes",
|
|
39
|
+
"src/server/controllers",
|
|
40
|
+
"src/views",
|
|
41
|
+
];
|
|
@@ -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
|
],
|
|
@@ -127,48 +165,52 @@ export const createClientConfig = () => {
|
|
|
127
165
|
splitChunks: {
|
|
128
166
|
chunks: "all",
|
|
129
167
|
cacheGroups: {
|
|
130
|
-
|
|
168
|
+
defaultVendors: {
|
|
131
169
|
test: /[\\/]node_modules[\\/]/,
|
|
132
|
-
|
|
170
|
+
priority: -10,
|
|
171
|
+
reuseExistingChunk: true,
|
|
172
|
+
},
|
|
173
|
+
default: {
|
|
174
|
+
minChunks: 2,
|
|
175
|
+
priority: -20,
|
|
176
|
+
reuseExistingChunk: true,
|
|
177
|
+
},
|
|
178
|
+
react: {
|
|
179
|
+
test: /[\\/]node_modules[\\/](react|react-dom|scheduler)[\\/]/,
|
|
180
|
+
name: "react-vendor",
|
|
133
181
|
chunks: "all",
|
|
182
|
+
priority: 10,
|
|
134
183
|
},
|
|
135
184
|
},
|
|
136
185
|
},
|
|
137
186
|
},
|
|
187
|
+
performance: {
|
|
188
|
+
maxEntrypointSize: 512000,
|
|
189
|
+
maxAssetSize: 512000,
|
|
190
|
+
hints: isProduction ? "warning" : false,
|
|
191
|
+
},
|
|
138
192
|
devtool: isProduction ? "source-map" : "eval-source-map",
|
|
139
193
|
};
|
|
140
194
|
};
|
|
141
|
-
|
|
195
|
+
exports.createClientConfig = createClientConfig;
|
|
196
|
+
const createServerConfig = () => {
|
|
142
197
|
const isProduction = process.env.NODE_ENV === "production";
|
|
143
198
|
const serverEntry = findEntry([
|
|
144
199
|
"src/server",
|
|
145
200
|
"src/server/index",
|
|
146
201
|
"src/server/main",
|
|
147
202
|
]);
|
|
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);
|
|
203
|
+
const viewsLoaderPath = getViewsLoaderPath();
|
|
162
204
|
return {
|
|
163
205
|
mode: isProduction ? "production" : "development",
|
|
164
206
|
target: "node",
|
|
165
207
|
entry: serverEntry,
|
|
166
208
|
output: {
|
|
167
|
-
path:
|
|
209
|
+
path: path_1.default.resolve(cwd, "dist"),
|
|
168
210
|
filename: "server.js",
|
|
169
211
|
},
|
|
170
212
|
externals: [
|
|
171
|
-
|
|
213
|
+
(0, webpack_node_externals_1.default)({
|
|
172
214
|
allowlist: [/^arcanajs/],
|
|
173
215
|
}),
|
|
174
216
|
],
|
|
@@ -179,7 +221,7 @@ export const createServerConfig = () => {
|
|
|
179
221
|
},
|
|
180
222
|
},
|
|
181
223
|
resolveLoader: {
|
|
182
|
-
modules: ["node_modules",
|
|
224
|
+
modules: ["node_modules", path_1.default.resolve(__dirname, "../../node_modules")],
|
|
183
225
|
},
|
|
184
226
|
module: {
|
|
185
227
|
rules: [
|
|
@@ -216,3 +258,4 @@ export const createServerConfig = () => {
|
|
|
216
258
|
// devtool: isProduction ? "source-map" : "eval-source-map",
|
|
217
259
|
};
|
|
218
260
|
};
|
|
261
|
+
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,8 +21,15 @@ 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
|
-
const dataScript = document.getElementById("
|
|
32
|
+
const dataScript = document.getElementById("__ARCANAJS_DATA__");
|
|
18
33
|
// Client-side HeadManager (noop for push, as Head handles client updates via useEffect)
|
|
19
34
|
const headManager = {
|
|
20
35
|
tags: [],
|
|
@@ -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
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
export * from "./client";
|
|
2
1
|
export * from "./shared/components/Body";
|
|
3
2
|
export * from "./shared/components/Head";
|
|
4
3
|
export * from "./shared/components/Link";
|
|
@@ -8,10 +7,11 @@ export * from "./shared/context/HeadContext";
|
|
|
8
7
|
export * from "./shared/context/PageContext";
|
|
9
8
|
export * from "./shared/context/RouterContext";
|
|
10
9
|
export * from "./shared/core/ArcanaJSApp";
|
|
11
|
-
export * from "./shared/hooks/useDynamicComponents";
|
|
12
10
|
export * from "./shared/hooks/useHead";
|
|
13
11
|
export * from "./shared/hooks/useLocation";
|
|
14
12
|
export * from "./shared/hooks/usePage";
|
|
15
13
|
export * from "./shared/hooks/useParams";
|
|
16
14
|
export * from "./shared/hooks/useQuery";
|
|
17
15
|
export * from "./shared/hooks/useRouter";
|
|
16
|
+
export { default as ErrorPage } from "./shared/views/ErrorPage";
|
|
17
|
+
export { default as NotFoundPage } from "./shared/views/NotFoundPage";
|