satto 1.0.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/LICENSE +21 -0
- package/package.json +31 -0
- package/readme.md +197 -0
- package/src/bin/satto.js +203 -0
- package/src/lib/index.js +119 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Satto
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "satto",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A minimal server-rendered web framework for Node.js",
|
|
5
|
+
"main": "src/lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"satto": "./src/bin/satto.js"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/Satto-js/satto.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"satto",
|
|
15
|
+
"web-framework",
|
|
16
|
+
"ssr",
|
|
17
|
+
"nodejs"
|
|
18
|
+
],
|
|
19
|
+
"author": "joaopedroleonel",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"type": "commonjs",
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/Satto-js/satto/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/Satto-js/satto#readme",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"ejs": "^3.1.10",
|
|
28
|
+
"esbuild": "^0.27.1",
|
|
29
|
+
"express": "^5.2.1"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# Satto
|
|
2
|
+
|
|
3
|
+
**Satto** is a lightweight web framework for building server-rendered applications with a **file-based structure**, **simple routing**, and **fast builds** powered by **Node.js**, **Express**, and **esbuild**.
|
|
4
|
+
|
|
5
|
+
It is designed to be **minimal**, **opinionated**, and **easy to reason about**, focusing on productivity without unnecessary abstraction.
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
* File-based page structure
|
|
10
|
+
* Simple and explicit routing
|
|
11
|
+
* Server-Side Rendering (SSR)
|
|
12
|
+
* Page-scoped CSS and JavaScript
|
|
13
|
+
* Fast production builds with esbuild
|
|
14
|
+
* Development mode with file watching
|
|
15
|
+
* Minimal templating syntax for SSR
|
|
16
|
+
* Zero configuration by default
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Install globally to use the CLI:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g satto
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or install locally in a project:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install satto
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Creating a New Project
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
satto init my-app
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This command creates a new project with the following structure:
|
|
39
|
+
|
|
40
|
+
```txt
|
|
41
|
+
my-app/
|
|
42
|
+
├── src/
|
|
43
|
+
│ ├── app/
|
|
44
|
+
│ │ └── home/
|
|
45
|
+
│ │ ├── home.html
|
|
46
|
+
│ │ ├── home.css
|
|
47
|
+
│ │ └── home.js
|
|
48
|
+
│ ├── static/
|
|
49
|
+
│ │ └── styles.css
|
|
50
|
+
│ ├── index.html
|
|
51
|
+
│ └── server.js
|
|
52
|
+
└── package.json
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Development Server
|
|
56
|
+
|
|
57
|
+
Start the development server with file watching enabled:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm run dev
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
or
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
satto run dev
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
The application will be available at:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
http://localhost:3000
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Production Build
|
|
76
|
+
|
|
77
|
+
Create an optimized production build:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
npm run build
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
or
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
satto run build
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This generates the following output:
|
|
90
|
+
|
|
91
|
+
```txt
|
|
92
|
+
dist/
|
|
93
|
+
├── app/
|
|
94
|
+
├── static/
|
|
95
|
+
├── index.html
|
|
96
|
+
└── server.js
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
All assets are minified and ready for deployment.
|
|
100
|
+
|
|
101
|
+
## Creating Routes
|
|
102
|
+
|
|
103
|
+
Generate a new route using the CLI:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
satto route blog
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
This creates:
|
|
110
|
+
|
|
111
|
+
```txt
|
|
112
|
+
src/app/blog/
|
|
113
|
+
├── blog.html
|
|
114
|
+
├── blog.css
|
|
115
|
+
└── blog.js
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
And automatically updates `src/server.js`:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
const routes = [
|
|
122
|
+
{ path: "/", page: "home" },
|
|
123
|
+
{ path: "/blog", page: "blog" },
|
|
124
|
+
];
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Page Structure
|
|
128
|
+
|
|
129
|
+
Each route corresponds to a folder inside `src/app/` and may contain:
|
|
130
|
+
|
|
131
|
+
* `page.html` – Page markup
|
|
132
|
+
* `page.css` – Page-specific styles
|
|
133
|
+
* `page.js` – Page-specific scripts
|
|
134
|
+
|
|
135
|
+
The page content is automatically injected into `<routes></routes>` inside `index.html`.
|
|
136
|
+
|
|
137
|
+
## Server-Side Rendering (SSR)
|
|
138
|
+
|
|
139
|
+
Satto provides a minimal SSR syntax for rendering data on the server.
|
|
140
|
+
|
|
141
|
+
### Example
|
|
142
|
+
|
|
143
|
+
```html
|
|
144
|
+
<ssr url="https://jsonplaceholder.typicode.com/posts" response="posts">
|
|
145
|
+
<section>
|
|
146
|
+
<for condition="let post in posts">
|
|
147
|
+
<div>
|
|
148
|
+
<h1>{{ post.title }}</h1>
|
|
149
|
+
</div>
|
|
150
|
+
</for>
|
|
151
|
+
</section>
|
|
152
|
+
</ssr>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Supported Directives
|
|
156
|
+
|
|
157
|
+
* `{{ variable }}`
|
|
158
|
+
* `<for condition="let item in array">`
|
|
159
|
+
* `<if condition="expression">`
|
|
160
|
+
* Attribute binding: `[src]="image.url"`
|
|
161
|
+
|
|
162
|
+
## Cache Busting
|
|
163
|
+
|
|
164
|
+
Satto automatically appends a version query string to assets:
|
|
165
|
+
|
|
166
|
+
```txt
|
|
167
|
+
styles.css?v=timestamp
|
|
168
|
+
page.js?v=timestamp
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
This ensures browsers always load the latest version.
|
|
172
|
+
|
|
173
|
+
## API Reference
|
|
174
|
+
|
|
175
|
+
### `createServer`
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
const createServer = require("satto");
|
|
179
|
+
|
|
180
|
+
const routes = [
|
|
181
|
+
{ path: "/", page: "home" },
|
|
182
|
+
{ path: "/blog", page: "blog" },
|
|
183
|
+
];
|
|
184
|
+
|
|
185
|
+
createServer(__dirname, routes, 3000);
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Parameters:**
|
|
189
|
+
|
|
190
|
+
* `root` – Project root directory
|
|
191
|
+
* `routes` – Array of route definitions
|
|
192
|
+
* `port` – Server port (default: 3000)
|
|
193
|
+
|
|
194
|
+
## Requirements
|
|
195
|
+
|
|
196
|
+
* Node.js 18 or later
|
|
197
|
+
* npm
|
package/src/bin/satto.js
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { execSync } = require("child_process");
|
|
6
|
+
const esbuild = require("esbuild");
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
|
|
10
|
+
const banner = [
|
|
11
|
+
" ____ _ _ ",
|
|
12
|
+
" / ___| __ _| |_| |_ ___ ",
|
|
13
|
+
" \\___ \\ / _` | __| __/ _ \\ ",
|
|
14
|
+
" ___) | (_| | |_| || (_) |",
|
|
15
|
+
" |____/ \\__,_|\\__|\\__\\___/ ",
|
|
16
|
+
" "
|
|
17
|
+
].join("\n");
|
|
18
|
+
|
|
19
|
+
console.log(banner + "\n");
|
|
20
|
+
|
|
21
|
+
function write(filePath, content) {
|
|
22
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
23
|
+
fs.writeFileSync(filePath, content);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (args[0] === "init") {
|
|
27
|
+
const appName = args[1];
|
|
28
|
+
|
|
29
|
+
if (!appName) {
|
|
30
|
+
console.error("Error: You must provide an app name.");
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log(`Creating project '${appName}'...`);
|
|
35
|
+
|
|
36
|
+
const root = path.join(process.cwd(), appName);
|
|
37
|
+
|
|
38
|
+
const template = {
|
|
39
|
+
[`${root}/src/app/home/home.html`]: `<h1>Home</h1>`,
|
|
40
|
+
[`${root}/src/app/home/home.css`]: `h1 {color: green}`,
|
|
41
|
+
[`${root}/src/app/home/home.js`]: `console.log("home loaded");`,
|
|
42
|
+
[`${root}/src/static/styles.css`]: ``,
|
|
43
|
+
[`${root}/src/index.html`]: `<!DOCTYPE html>
|
|
44
|
+
<html lang="en">
|
|
45
|
+
<head>
|
|
46
|
+
<meta charset="UTF-8">
|
|
47
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
48
|
+
<link rel="stylesheet" href="/styles.css">
|
|
49
|
+
<title>${appName}</title>
|
|
50
|
+
</head>
|
|
51
|
+
<body>
|
|
52
|
+
<routes></routes>
|
|
53
|
+
</body>
|
|
54
|
+
</html>`,
|
|
55
|
+
[`${root}/src/server.js`]: `const createServer = require("satto");
|
|
56
|
+
|
|
57
|
+
const routes = [
|
|
58
|
+
{ path: "/", page: "home" },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
createServer(__dirname, routes);
|
|
62
|
+
`,
|
|
63
|
+
[`${root}/package.json`]: `{
|
|
64
|
+
"name": "${appName}",
|
|
65
|
+
"version": "1.0.0",
|
|
66
|
+
"type": "commonjs",
|
|
67
|
+
"scripts": {
|
|
68
|
+
"dev": "satto run dev",
|
|
69
|
+
"build": "satto run build"
|
|
70
|
+
}
|
|
71
|
+
}`
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
for (const file in template) {
|
|
75
|
+
write(file, template[file]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
execSync(
|
|
79
|
+
`npm i satto`,
|
|
80
|
+
{
|
|
81
|
+
stdio: "inherit",
|
|
82
|
+
cwd: root
|
|
83
|
+
}
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
console.log("Project created successfully!");
|
|
87
|
+
process.exit(0);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (args[0] === "route") {
|
|
91
|
+
const routeName = args[1];
|
|
92
|
+
|
|
93
|
+
if (!routeName) {
|
|
94
|
+
console.error("Error: You must provide a route name.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const root = process.cwd();
|
|
99
|
+
|
|
100
|
+
console.log(`Creating route '${routeName}'...`);
|
|
101
|
+
|
|
102
|
+
write(`${root}/src/app/${routeName}/${routeName}.html`, `<h1>${routeName}</h1>`);
|
|
103
|
+
write(`${root}/src/app/${routeName}/${routeName}.css`, ``);
|
|
104
|
+
write(`${root}/src/app/${routeName}/${routeName}.js`, ``);
|
|
105
|
+
|
|
106
|
+
const serverPath = `${root}/src/server.js`;
|
|
107
|
+
|
|
108
|
+
if (!fs.existsSync(serverPath)) {
|
|
109
|
+
console.error("Error: server.js not found. Run this command inside a project created using 'satto init'.");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let serverContent = fs.readFileSync(serverPath, "utf8");
|
|
114
|
+
|
|
115
|
+
const routeEntry = ` { path: "/${routeName}", page: "${routeName}" },`;
|
|
116
|
+
|
|
117
|
+
serverContent = serverContent.replace(
|
|
118
|
+
/const routes\s*=\s*\[/,
|
|
119
|
+
`const routes = [\n${routeEntry}`
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
fs.writeFileSync(serverPath, serverContent);
|
|
123
|
+
|
|
124
|
+
console.log(`Route '${routeName}' created.`);
|
|
125
|
+
process.exit(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (args[0] === "run" && args[1] === "dev") {
|
|
129
|
+
console.log("Development server is starting...");
|
|
130
|
+
console.clear();
|
|
131
|
+
|
|
132
|
+
execSync(
|
|
133
|
+
"node --watch --watch-path=./src ./src/server.js",
|
|
134
|
+
{
|
|
135
|
+
stdio: "inherit",
|
|
136
|
+
cwd: process.cwd()
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function copyRecursive(src, dest) {
|
|
144
|
+
if (!fs.existsSync(src)) return;
|
|
145
|
+
|
|
146
|
+
const stats = fs.lstatSync(src);
|
|
147
|
+
|
|
148
|
+
if (stats.isFile()) {
|
|
149
|
+
if(src.includes("css") || src.includes("js")) {
|
|
150
|
+
esbuild.buildSync({
|
|
151
|
+
entryPoints: [src],
|
|
152
|
+
outfile: dest,
|
|
153
|
+
minify: true,
|
|
154
|
+
bundle: false
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
fs.copyFileSync(src, dest);
|
|
158
|
+
}
|
|
159
|
+
} else if (stats.isDirectory()) {
|
|
160
|
+
if (!fs.existsSync(dest)) {
|
|
161
|
+
fs.mkdirSync(dest);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const entries = fs.readdirSync(src);
|
|
165
|
+
|
|
166
|
+
for (const entry of entries) {
|
|
167
|
+
const srcPath = path.join(src, entry);
|
|
168
|
+
const destPath = path.join(dest, entry);
|
|
169
|
+
copyRecursive(srcPath, destPath);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (args[0] === "run" && args[1] === "build") {
|
|
175
|
+
console.log("Build is starting...");
|
|
176
|
+
|
|
177
|
+
const root = process.cwd();
|
|
178
|
+
if (fs.existsSync(`${root}/dist`)) fs.rmSync(`${root}/dist`, { recursive: true, force: true });
|
|
179
|
+
|
|
180
|
+
esbuild.buildSync({
|
|
181
|
+
entryPoints: ["src/server.js"],
|
|
182
|
+
bundle: true,
|
|
183
|
+
platform: "node",
|
|
184
|
+
outfile: "dist/server.js",
|
|
185
|
+
format: "cjs",
|
|
186
|
+
minify: true,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
copyRecursive(`${root}/src/index.html`, `${root}/dist/index.html`);
|
|
190
|
+
copyRecursive(`${root}/src/app`, `${root}/dist/app`);
|
|
191
|
+
copyRecursive(`${root}/src/static`, `${root}/dist/static`);
|
|
192
|
+
|
|
193
|
+
console.log("Build completed successfully!");
|
|
194
|
+
process.exit(0);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
console.log("Unknown command.");
|
|
198
|
+
console.log("Usage:");
|
|
199
|
+
console.log(" satto init <app_name> # Initialize a new Satto application");
|
|
200
|
+
console.log(" satto route <route_name> # Create a new route in the application");
|
|
201
|
+
console.log(" satto run dev # Run the application in development mode");
|
|
202
|
+
console.log(" satto run build # Build the application for production");
|
|
203
|
+
process.exit(1);
|
package/src/lib/index.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const ejs = require("ejs");
|
|
5
|
+
|
|
6
|
+
function hasContent(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
return fs.statSync(filePath).size > 0;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function applyParams(template, params) {
|
|
15
|
+
return template.replace(/{{\s*params\.(\w+)\s*}}/g, (_, key) => {
|
|
16
|
+
return params[key] ?? "";
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function renderPage(html, params) {
|
|
21
|
+
const match = html.match(/<ssr\s+url="([^"]+)"\s+.*response="([^"]+)"[^>]*>/);
|
|
22
|
+
|
|
23
|
+
if(match) {
|
|
24
|
+
const url = applyParams(match?.[1], params);
|
|
25
|
+
const resVar = match?.[2];
|
|
26
|
+
let data = {};
|
|
27
|
+
|
|
28
|
+
if (url) {
|
|
29
|
+
const res = await fetch(url);
|
|
30
|
+
data = await res.json();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
html = html.replace(/<ssr[^>]*>([\s\S]*?)<\/ssr>/g, (_, content) => {
|
|
34
|
+
return content.replace(
|
|
35
|
+
/<ssr[^>]*>|<\/ssr>|\{\{\s*([\w.]+)\s*\}\}|<for\s+condition="let\s+(\w+)\s+in\s+([\w.]+)"\s*>|<\/for>|<if\s+condition="(.+?)"\s*>|<\/if>|\[(\w+)\]="([^"]+)"/g,
|
|
36
|
+
(match, expr, item, arr, cond, attr, val) => {
|
|
37
|
+
if (match.startsWith("<ssr")) return "";
|
|
38
|
+
if (match === "</ssr>") return "";
|
|
39
|
+
if (expr) return `<%= ${expr} %>`;
|
|
40
|
+
if (item && arr) return `<% ${arr}.forEach(${item} => { %>`;
|
|
41
|
+
if (match === "</for>") return "<% }) %>";
|
|
42
|
+
if (cond) return `<% if (${cond}) { %>`;
|
|
43
|
+
if (match === "</if>") return "<% } %>";
|
|
44
|
+
if (attr && val) return `${attr}="<%= ${val} %>"`;
|
|
45
|
+
return match;
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return ejs.render(html, {params, [resVar]: data});
|
|
51
|
+
} else {
|
|
52
|
+
return html;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function createServer(__dirname, routes = [], port = 3000) {
|
|
57
|
+
const versionApp = Date.now();
|
|
58
|
+
const app = express();
|
|
59
|
+
|
|
60
|
+
app.set("views", path.join(__dirname, "app"));
|
|
61
|
+
app.engine("html", ejs.renderFile);
|
|
62
|
+
app.set("view engine", "html");
|
|
63
|
+
|
|
64
|
+
app.use(express.static(path.join(__dirname, "app")));
|
|
65
|
+
app.use(express.static(path.join(__dirname, "static")));
|
|
66
|
+
|
|
67
|
+
routes.forEach((route) => {
|
|
68
|
+
app.get(route.path, (req, res) => {
|
|
69
|
+
const page = route.page;
|
|
70
|
+
const htmlPath = path.join(__dirname, "app", page, page + ".html");
|
|
71
|
+
app.use(express.static(path.join(__dirname, "app", page)));
|
|
72
|
+
|
|
73
|
+
fs.readFile(htmlPath, "utf8", async (err, html) => {
|
|
74
|
+
if (err) {
|
|
75
|
+
return res.status(404).send();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const params = { ...req.params };
|
|
79
|
+
const index = fs.readFileSync(__dirname + "/index.html", "utf8");
|
|
80
|
+
|
|
81
|
+
const CssPath = path.join(__dirname, "app", page, page + ".css");
|
|
82
|
+
const JsPath = path.join(__dirname, "app", page, page + ".js");
|
|
83
|
+
|
|
84
|
+
if(hasContent(JsPath)) {
|
|
85
|
+
html += `\n<script src="./${page}.js?v=${versionApp}"></script>`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
html = index.replace("<routes></routes>", html);
|
|
89
|
+
html = html.replace(`<link rel="stylesheet" href="/styles.css">`, `<link rel="stylesheet" href="/styles.css?v=${versionApp}">`);
|
|
90
|
+
|
|
91
|
+
if(hasContent(CssPath)) {
|
|
92
|
+
html = html.replace(
|
|
93
|
+
"</head>",
|
|
94
|
+
`<link rel="stylesheet" href="./${page}.css?v=${versionApp}">
|
|
95
|
+
</head>`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
res.send(await renderPage(html, params));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
app.listen(port, () => {
|
|
105
|
+
const banner = [
|
|
106
|
+
" ____ _ _ ",
|
|
107
|
+
" / ___| __ _| |_| |_ ___ ",
|
|
108
|
+
" \\___ \\ / _` | __| __/ _ \\ ",
|
|
109
|
+
" ___) | (_| | |_| || (_) |",
|
|
110
|
+
" |____/ \\__,_|\\__|\\__\\___/ ",
|
|
111
|
+
" "
|
|
112
|
+
].join("\n");
|
|
113
|
+
|
|
114
|
+
console.log(banner + "\n");
|
|
115
|
+
console.log(`Server running on http://localhost:${port}`);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = createServer;
|