create-smaoog 0.0.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/LICENSE +21 -0
- package/README.md +44 -0
- package/package.json +39 -0
- package/src/index.js +62 -0
- package/src/preview.js +81 -0
- package/src/scaffold.js +178 -0
- package/template/gitignore +4 -0
- package/template/public/favicon.ico +0 -0
- package/template/src/app.css +1 -0
- package/template/src/client-entry.ts +3 -0
- package/template/src/document.tsx +21 -0
- package/template/src/routes/index.tsx +31 -0
- package/template/tsconfig.json +13 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aggelos Gesoulis
|
|
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/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# create-smaoog
|
|
2
|
+
|
|
3
|
+
Create a new [smaoog](https://www.npmjs.com/package/smaoog) app ready to go.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm create smaoog my-app
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
or equivalently:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx create-smaoog my-app
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Then:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
cd my-app
|
|
21
|
+
npm install
|
|
22
|
+
npm run dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## TypeScript and JavaScript
|
|
26
|
+
|
|
27
|
+
A TypeScript app is generated by default. For a JavaScript app, pass `--js`:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npm create smaoog my-app -- --js
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## What you get
|
|
34
|
+
|
|
35
|
+
- a sample file route in `src/routes/`
|
|
36
|
+
- a user-owned `src/document.tsx`
|
|
37
|
+
- a hydration entry at `src/client-entry.ts`
|
|
38
|
+
- Tailwind CSS wired up via `src/app.css`
|
|
39
|
+
- `public/favicon.ico`
|
|
40
|
+
- `dev` / `build` / `start` scripts
|
|
41
|
+
|
|
42
|
+
## License
|
|
43
|
+
|
|
44
|
+
[MIT](./LICENSE)
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-smaoog",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Scaffold a new smaoog app.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "Aggelos Gesoulis",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"smaoog",
|
|
10
|
+
"create",
|
|
11
|
+
"scaffold",
|
|
12
|
+
"starter",
|
|
13
|
+
"react",
|
|
14
|
+
"ssr"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/anges244/smaoog/tree/main/packages/create-smaoog#readme",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/anges244/smaoog.git",
|
|
20
|
+
"directory": "packages/create-smaoog"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/anges244/smaoog/issues"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20"
|
|
27
|
+
},
|
|
28
|
+
"bin": {
|
|
29
|
+
"create-smaoog": "./src/index.js"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"src",
|
|
33
|
+
"template",
|
|
34
|
+
"!src/**/*.test.js"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"preview": "node src/preview.js"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readdirSync } from "node:fs";
|
|
3
|
+
import { basename, resolve } from "node:path";
|
|
4
|
+
import { scaffold } from "./scaffold.js";
|
|
5
|
+
|
|
6
|
+
// Parse argv into { appName, js, help }. The first non-flag argument is the app
|
|
7
|
+
// name; `--js` selects the JavaScript template (`--ts` is the default).
|
|
8
|
+
function parseArgs(argv) {
|
|
9
|
+
let appName;
|
|
10
|
+
let js = false;
|
|
11
|
+
let help = false;
|
|
12
|
+
for (const arg of argv) {
|
|
13
|
+
if (arg === "--js") js = true;
|
|
14
|
+
else if (arg === "--ts") js = false;
|
|
15
|
+
else if (arg === "--help" || arg === "-h") help = true;
|
|
16
|
+
else if (!arg.startsWith("-") && appName === undefined) appName = arg;
|
|
17
|
+
}
|
|
18
|
+
return { appName, js, help };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const USAGE = `Usage: create-smaoog <app-name> [--js]
|
|
22
|
+
|
|
23
|
+
Creates a new smaoog app in ./<app-name>.
|
|
24
|
+
|
|
25
|
+
--js Generate a JavaScript app (TypeScript is the default)
|
|
26
|
+
--help Show this message`;
|
|
27
|
+
|
|
28
|
+
// A new app must land in an empty (or absent) directory so nothing is clobbered.
|
|
29
|
+
function isEmptyDir(dir) {
|
|
30
|
+
if (!existsSync(dir)) return true;
|
|
31
|
+
return readdirSync(dir).length === 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
const { appName, js, help } = parseArgs(process.argv.slice(2));
|
|
36
|
+
|
|
37
|
+
if (help || !appName) {
|
|
38
|
+
console.log(USAGE);
|
|
39
|
+
process.exit(help ? 0 : 1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const targetDir = resolve(process.cwd(), appName);
|
|
43
|
+
if (!isEmptyDir(targetDir)) {
|
|
44
|
+
console.error(`Cannot create app: "${appName}" already exists and is not empty.`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The package name is the destination folder name (the arg may be a path).
|
|
49
|
+
await scaffold({ targetDir, appName: basename(targetDir), js });
|
|
50
|
+
|
|
51
|
+
const lang = js ? "JavaScript" : "TypeScript";
|
|
52
|
+
console.log(`\nCreated a ${lang} smaoog app in ${targetDir}\n`);
|
|
53
|
+
console.log("Next steps:\n");
|
|
54
|
+
console.log(` cd ${appName}`);
|
|
55
|
+
console.log(" npm install");
|
|
56
|
+
console.log(" npm run dev\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
main().catch((err) => {
|
|
60
|
+
console.error(err);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
package/src/preview.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Local preview of the create-smaoog template.
|
|
3
|
+
//
|
|
4
|
+
// Scaffolds the template into `examples/preview` (a workspace member, so the
|
|
5
|
+
// generated `smaoog` dependency is rewritten from "latest" to "workspace:*" and
|
|
6
|
+
// resolves to this repo's framework source instead of a published release),
|
|
7
|
+
// installs, and starts the dev server. This is the fastest way to see what a
|
|
8
|
+
// freshly scaffolded app looks like while iterating on the template or the
|
|
9
|
+
// framework. The preview directory is gitignored and recreated on each run.
|
|
10
|
+
//
|
|
11
|
+
// Usage:
|
|
12
|
+
// node src/preview.js [--js] [--no-dev]
|
|
13
|
+
//
|
|
14
|
+
// --js Preview the JavaScript template (TypeScript is the default)
|
|
15
|
+
// --no-dev Scaffold and install only; skip starting the dev server
|
|
16
|
+
import { spawnSync } from "node:child_process";
|
|
17
|
+
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { scaffold } from "./scaffold.js";
|
|
21
|
+
|
|
22
|
+
const REPO_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
|
|
23
|
+
const PREVIEW_DIR = join(REPO_ROOT, "examples", "preview");
|
|
24
|
+
|
|
25
|
+
function parseArgs(argv) {
|
|
26
|
+
return {
|
|
27
|
+
js: argv.includes("--js"),
|
|
28
|
+
dev: !argv.includes("--no-dev"),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function run(command, args, opts = {}) {
|
|
33
|
+
const result = spawnSync(command, args, { stdio: "inherit", cwd: REPO_ROOT, ...opts });
|
|
34
|
+
if (result.error) throw result.error;
|
|
35
|
+
if (result.status !== 0) {
|
|
36
|
+
throw new Error(`\`${command} ${args.join(" ")}\` exited with code ${result.status}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
const { js, dev } = parseArgs(process.argv.slice(2));
|
|
42
|
+
const lang = js ? "JavaScript" : "TypeScript";
|
|
43
|
+
|
|
44
|
+
// Start from a clean directory so a previous run's files never linger.
|
|
45
|
+
await rm(PREVIEW_DIR, { recursive: true, force: true });
|
|
46
|
+
await scaffold({ targetDir: PREVIEW_DIR, appName: "preview", js });
|
|
47
|
+
|
|
48
|
+
// Point the generated app at this repo's framework source rather than the
|
|
49
|
+
// published "latest" release, so the preview reflects local changes.
|
|
50
|
+
const pkgPath = join(PREVIEW_DIR, "package.json");
|
|
51
|
+
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
52
|
+
pkg.name = "@smaoog-examples/preview";
|
|
53
|
+
pkg.dependencies.smaoog = "workspace:*";
|
|
54
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
55
|
+
|
|
56
|
+
console.log(`\nScaffolded the ${lang} template into examples/preview\n`);
|
|
57
|
+
|
|
58
|
+
// Install at the workspace root so pnpm links the local `smaoog` package.
|
|
59
|
+
// Installing adds an importer entry for the (gitignored) preview app to the
|
|
60
|
+
// committed lockfile; snapshot and restore it so the working tree stays clean.
|
|
61
|
+
// node_modules is already linked at this point, so the dev server is
|
|
62
|
+
// unaffected by restoring the on-disk lockfile.
|
|
63
|
+
const lockPath = join(REPO_ROOT, "pnpm-lock.yaml");
|
|
64
|
+
const lockBefore = await readFile(lockPath, "utf8").catch(() => null);
|
|
65
|
+
run("pnpm", ["install"]);
|
|
66
|
+
if (lockBefore !== null) await writeFile(lockPath, lockBefore);
|
|
67
|
+
|
|
68
|
+
if (!dev) {
|
|
69
|
+
console.log("\nSkipping dev server (--no-dev). Start it with:\n");
|
|
70
|
+
console.log(" pnpm --filter @smaoog-examples/preview dev\n");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log("\nStarting the preview dev server...\n");
|
|
75
|
+
run("pnpm", ["--filter", "@smaoog-examples/preview", "dev"]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
main().catch((err) => {
|
|
79
|
+
console.error(err?.message ?? err);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
});
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
const TEMPLATE_DIR = fileURLToPath(new URL("../template", import.meta.url));
|
|
6
|
+
|
|
7
|
+
// Pinned versions for the generated app. Kept here as the single source of truth
|
|
8
|
+
// for what a new smaoog app depends on. `smaoog` itself tracks the "latest"
|
|
9
|
+
// dist-tag so a freshly scaffolded app always pulls the newest framework release
|
|
10
|
+
// during the alpha, without create-smaoog needing a version bump per release.
|
|
11
|
+
const VERSIONS = {
|
|
12
|
+
react: "^19.2.0",
|
|
13
|
+
"react-dom": "^19.2.0",
|
|
14
|
+
smaoog: "latest",
|
|
15
|
+
"@tailwindcss/vite": "^4.3.1",
|
|
16
|
+
tailwindcss: "^4.3.1",
|
|
17
|
+
// Kept in sync with the repo's typescript devDependency, so the version a
|
|
18
|
+
// generated app pins is the same major the scaffold typecheck test validates
|
|
19
|
+
// smaoog's declarations against.
|
|
20
|
+
typescript: "^6.0.3",
|
|
21
|
+
"@types/react": "^19.2.0",
|
|
22
|
+
"@types/react-dom": "^19.2.0",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Files that are binary and must be copied byte-for-byte (no text transform).
|
|
26
|
+
const BINARY = new Set([".ico", ".png", ".jpg", ".jpeg", ".webp", ".woff", ".woff2"]);
|
|
27
|
+
|
|
28
|
+
async function walk(dir) {
|
|
29
|
+
const out = [];
|
|
30
|
+
for (const entry of await readdir(dir, { withFileTypes: true })) {
|
|
31
|
+
const full = join(dir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) out.push(...(await walk(full)));
|
|
33
|
+
else out.push(full);
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isBinary(path) {
|
|
39
|
+
const dot = path.lastIndexOf(".");
|
|
40
|
+
return dot !== -1 && BINARY.has(path.slice(dot));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// The single TypeScript template is the source of truth; the JavaScript app is
|
|
44
|
+
// derived from it (extensions renamed, no tsconfig). Deriving JS rewrites the few
|
|
45
|
+
// extension-bearing references and erases the small amount of type-only syntax the
|
|
46
|
+
// template carries (see toJsContent).
|
|
47
|
+
function jsRename(rel) {
|
|
48
|
+
if (rel.endsWith(".tsx")) return rel.slice(0, -4) + ".jsx";
|
|
49
|
+
if (rel.endsWith(".ts")) return rel.slice(0, -3) + ".js";
|
|
50
|
+
return rel;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function toJsContent(content) {
|
|
54
|
+
// The Document references the client entry by its real filename, which becomes
|
|
55
|
+
// .js in a JavaScript app. Type-only imports and the small amount of template
|
|
56
|
+
// annotation syntax are removed so the JS variant is still derived from the TS
|
|
57
|
+
// template without maintaining a second source tree. The template confines its
|
|
58
|
+
// types to these strippable forms on purpose, so a zero-dependency text pass
|
|
59
|
+
// (no TypeScript/esbuild) is enough to derive valid JavaScript.
|
|
60
|
+
return content
|
|
61
|
+
.replaceAll("client-entry.ts", "client-entry.js")
|
|
62
|
+
.replace(/^import\s+type\s+[^;]+;\n/gm, "")
|
|
63
|
+
.replace(/^type\s+\w+\s*=[^\n]*;\n/gm, "")
|
|
64
|
+
.replaceAll(": DocumentProps", "")
|
|
65
|
+
.replace(/: SmaoogRequest\b/g, "")
|
|
66
|
+
.replace(/: SmaoogResponse<[^>]*>/g, "")
|
|
67
|
+
.replaceAll(": Props", "");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Turn a directory name into a valid npm package name: lowercase, only
|
|
71
|
+
// url-safe characters, no leading dot/dash/underscore. Falls back to "app" if
|
|
72
|
+
// nothing usable remains (e.g. a name that was all punctuation).
|
|
73
|
+
function toPackageName(name) {
|
|
74
|
+
const slug = String(name)
|
|
75
|
+
.trim()
|
|
76
|
+
.toLowerCase()
|
|
77
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
78
|
+
.replace(/^[._-]+/, "")
|
|
79
|
+
.slice(0, 214)
|
|
80
|
+
.replace(/[._-]+$/, "");
|
|
81
|
+
return slug || "app";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function packageJson(appName, js) {
|
|
85
|
+
const pick = (...names) => Object.fromEntries(names.map((n) => [n, VERSIONS[n]]));
|
|
86
|
+
return (
|
|
87
|
+
JSON.stringify(
|
|
88
|
+
{
|
|
89
|
+
name: toPackageName(appName),
|
|
90
|
+
version: "0.0.1",
|
|
91
|
+
private: true,
|
|
92
|
+
type: "module",
|
|
93
|
+
scripts: { dev: "smaoog dev", build: "smaoog build", start: "smaoog start" },
|
|
94
|
+
dependencies: pick("react", "react-dom", "smaoog"),
|
|
95
|
+
devDependencies: js
|
|
96
|
+
? pick("@tailwindcss/vite", "tailwindcss")
|
|
97
|
+
: pick(
|
|
98
|
+
"@tailwindcss/vite",
|
|
99
|
+
"@types/react",
|
|
100
|
+
"@types/react-dom",
|
|
101
|
+
"tailwindcss",
|
|
102
|
+
"typescript",
|
|
103
|
+
),
|
|
104
|
+
},
|
|
105
|
+
null,
|
|
106
|
+
2,
|
|
107
|
+
) + "\n"
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function readme(appName) {
|
|
112
|
+
return [
|
|
113
|
+
`# ${appName}`,
|
|
114
|
+
"",
|
|
115
|
+
"A new app built with [smaoog](https://github.com/anges244/smaoog).",
|
|
116
|
+
"",
|
|
117
|
+
"## Development",
|
|
118
|
+
"",
|
|
119
|
+
"```bash",
|
|
120
|
+
"npm install",
|
|
121
|
+
"npm run dev",
|
|
122
|
+
"```",
|
|
123
|
+
"",
|
|
124
|
+
"## Production",
|
|
125
|
+
"",
|
|
126
|
+
"```bash",
|
|
127
|
+
"npm run build",
|
|
128
|
+
"npm run start",
|
|
129
|
+
"```",
|
|
130
|
+
"",
|
|
131
|
+
].join("\n");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Generate a new smaoog app into `targetDir`. `js` selects the JavaScript variant
|
|
135
|
+
// (derived from the TypeScript template). Returns the list of created
|
|
136
|
+
// app-relative paths.
|
|
137
|
+
export async function scaffold({ targetDir, appName, js = false }) {
|
|
138
|
+
await mkdir(targetDir, { recursive: true });
|
|
139
|
+
const created = [];
|
|
140
|
+
|
|
141
|
+
const write = async (rel, contents) => {
|
|
142
|
+
const dest = join(targetDir, rel);
|
|
143
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
144
|
+
await writeFile(dest, contents);
|
|
145
|
+
created.push(rel);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
for (const file of await walk(TEMPLATE_DIR)) {
|
|
149
|
+
let rel = relative(TEMPLATE_DIR, file).split("\\").join("/");
|
|
150
|
+
|
|
151
|
+
// tsconfig only belongs to the TypeScript app.
|
|
152
|
+
if (rel === "tsconfig.json" && js) continue;
|
|
153
|
+
// npm strips a literal .gitignore from a published package, so the template
|
|
154
|
+
// ships it as `gitignore`; restore the dot on scaffold.
|
|
155
|
+
if (rel === "gitignore") rel = ".gitignore";
|
|
156
|
+
|
|
157
|
+
if (isBinary(file)) {
|
|
158
|
+
const dest = join(targetDir, rel);
|
|
159
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
160
|
+
await cp(file, dest);
|
|
161
|
+
created.push(rel);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
let content = await readFile(file, "utf8");
|
|
166
|
+
if (js) {
|
|
167
|
+
rel = jsRename(rel);
|
|
168
|
+
content = toJsContent(content);
|
|
169
|
+
}
|
|
170
|
+
await write(rel, content);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await write("package.json", packageJson(appName, js));
|
|
174
|
+
await write("README.md", readme(appName));
|
|
175
|
+
|
|
176
|
+
created.sort();
|
|
177
|
+
return created;
|
|
178
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ClientEntry, ReactRefresh, Stylesheet } from "smaoog";
|
|
2
|
+
import type { DocumentProps } from "smaoog";
|
|
3
|
+
|
|
4
|
+
export default function Document({ children, head }: DocumentProps) {
|
|
5
|
+
return (
|
|
6
|
+
<html lang="en">
|
|
7
|
+
<head>
|
|
8
|
+
<meta charSet="utf-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
10
|
+
<link rel="icon" href="/favicon.ico" />
|
|
11
|
+
<Stylesheet href="src/app.css" />
|
|
12
|
+
{head}
|
|
13
|
+
<ReactRefresh />
|
|
14
|
+
</head>
|
|
15
|
+
<body>
|
|
16
|
+
<main id="root">{children}</main>
|
|
17
|
+
<ClientEntry src="src/client-entry.ts" />
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { SmaoogRequest, SmaoogResponse } from "smaoog";
|
|
2
|
+
|
|
3
|
+
type Props = { renderedAt: string };
|
|
4
|
+
|
|
5
|
+
// A route can export a handler per HTTP method. This GET runs on the server and
|
|
6
|
+
// passes its result to the component below as props. So the HTML you receive is
|
|
7
|
+
// already rendered (view source to confirm), no client fetch required.
|
|
8
|
+
export function GET(req: SmaoogRequest, res: SmaoogResponse<Props>) {
|
|
9
|
+
return res.render({ renderedAt: new Date().toLocaleString() });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const meta = {
|
|
13
|
+
title: "smaoog app",
|
|
14
|
+
description: "A new app built with smaoog.",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default function Home({ renderedAt }: Props) {
|
|
18
|
+
return (
|
|
19
|
+
<main className="flex min-h-screen flex-col items-center justify-center gap-4 bg-slate-50 text-slate-900">
|
|
20
|
+
<h1 className="text-4xl font-bold">Welcome to smaoog</h1>
|
|
21
|
+
<p className="text-slate-600">Server-rendered at {renderedAt}.</p>
|
|
22
|
+
<p className="text-slate-600">
|
|
23
|
+
Edit{" "}
|
|
24
|
+
<code className="rounded bg-slate-200 px-1.5 py-0.5">
|
|
25
|
+
src/routes/index
|
|
26
|
+
</code>{" "}
|
|
27
|
+
and reload.
|
|
28
|
+
</p>
|
|
29
|
+
</main>
|
|
30
|
+
);
|
|
31
|
+
}
|