htmv 0.0.18 → 0.0.26

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.
@@ -0,0 +1,27 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [ main ]
6
+ pull_request:
7
+
8
+ jobs:
9
+ build:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - uses: actions/checkout@v4
14
+
15
+ - name: Setup Bun
16
+ uses: oven-sh/setup-bun@v1
17
+ with:
18
+ bun-version: latest
19
+
20
+ - name: Install dependencies
21
+ run: bun install
22
+
23
+ - name: Lint
24
+ run: npm run lint
25
+
26
+ - name: Run TypeScript build
27
+ run: npm run build
@@ -0,0 +1,42 @@
1
+ name: Publish to npm
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write # Required for OIDC
10
+ contents: read
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+
19
+ - uses: actions/setup-node@v4
20
+ with:
21
+ node-version: '20'
22
+ registry-url: 'https://registry.npmjs.org'
23
+
24
+ - name: Update npm
25
+ run: npm install -g npm@latest
26
+
27
+ - name: Setup Bun
28
+ uses: oven-sh/setup-bun@v1
29
+ with:
30
+ bun-version: latest
31
+
32
+ - name: Install dependencies
33
+ run: bun install
34
+
35
+ - name: Lint
36
+ run: npm run lint
37
+
38
+ - name: Build (tsc)
39
+ run: npm run build
40
+
41
+ - name: Publish to npm
42
+ run: npm publish --provenance
package/README.md CHANGED
@@ -77,5 +77,36 @@ Supported methods currently are:
77
77
  - DELETE
78
78
  - ALL (add `default` keyword)
79
79
 
80
+ # Renaming folders
81
+ If you wish to rename either the `views`, `routes` or `public` folders you can do so in `index.ts` as follows:
82
+ ```ts
83
+ setup({
84
+ routes: path.join(dirPath, "my_custom_routes_folder"),
85
+ views: path.join(dirPath, "stuff", "views"),
86
+ public: path.join(dirPath, "static"),
87
+ port: 3000,
88
+ });
89
+ ```
90
+ Just change the string for the new name you wish for. Note that when doing so `htmv gen` will now need `--path` flag passed to know where to find them.
91
+
92
+ # Code generation
93
+ Do you often forget how to write boilerplate code? Why not just let HTMV do it for you?
94
+ As you know, HTMV comes with the CLI tool you used when creating the project. But it also has the command `htmv gen` which allows you to generate a basic template for a view or route.
95
+
96
+ The syntax is as follows:
97
+ ```bash
98
+ bunx htmv@latest gen {TYPE} {NAME} {OPTIONS}
99
+ ```
100
+ For example, to create a view called MyCoolView:
101
+ ```bash
102
+ bunx htmv@latest gen view MyCoolView
103
+ ```
104
+ Running this will generate a view in the `views` directory of your project. The other remaining type is `route` which works the same way but creates the route in your `routes` directory.
105
+
106
+ However, note that if you have changed the name of your folders HTMV will be unable to find them and you'll have to manually specify the folder you wish for the generated file to be placed in as follows:
107
+ ```bash
108
+ bunx htmv@latest gen view MyCoolView --path cool_stuff/my_custom_views_folder
109
+ ```
110
+
80
111
  # Hot reloading
81
112
  Having to restart the server every time you make a change can be quite tedious. HTMV takes care of this thanks to Bun. Just develop with `bun dev` and it should work out of the box! Note that this does not include hot reloading in the browser. As of now, you have to refresh the page to see new changes. It doesn't update in real time.
package/biome.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.3/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "includes": ["**", "!!**/dist"]
10
+ },
11
+ "formatter": {
12
+ "enabled": true,
13
+ "indentStyle": "tab"
14
+ },
15
+ "linter": {
16
+ "enabled": true,
17
+ "rules": {
18
+ "recommended": true
19
+ }
20
+ },
21
+ "javascript": {
22
+ "formatter": {
23
+ "quoteStyle": "double"
24
+ }
25
+ },
26
+ "assist": {
27
+ "enabled": true,
28
+ "actions": {
29
+ "source": {
30
+ "organizeImports": "on"
31
+ }
32
+ }
33
+ }
34
+ }
package/bun.lock CHANGED
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "lockfileVersion": 1,
3
+ "configVersion": 0,
3
4
  "workspaces": {
4
5
  "": {
5
6
  "name": "htmv",
package/dist/app.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { Elysia } from "elysia";
2
+ export declare function createApp(publicPath: string): Elysia<"", {
3
+ decorator: {};
4
+ store: {};
5
+ derive: {};
6
+ resolve: {};
7
+ }, {
8
+ typebox: {};
9
+ error: {};
10
+ }, {
11
+ schema: {};
12
+ standaloneSchema: {};
13
+ macro: {};
14
+ macroFn: {};
15
+ parser: {};
16
+ response: {};
17
+ }, {}, {
18
+ derive: {};
19
+ resolve: {};
20
+ schema: {};
21
+ standaloneSchema: {};
22
+ response: {};
23
+ }, {
24
+ derive: {};
25
+ resolve: {};
26
+ schema: {};
27
+ standaloneSchema: {};
28
+ response: {};
29
+ } & {
30
+ derive: {};
31
+ resolve: {};
32
+ schema: {};
33
+ standaloneSchema: {};
34
+ response: {};
35
+ }>;
package/dist/app.js ADDED
@@ -0,0 +1,7 @@
1
+ import staticPlugin from "@elysiajs/static";
2
+ import { Elysia } from "elysia";
3
+ export function createApp(publicPath) {
4
+ return new Elysia().use(staticPlugin({
5
+ assets: publicPath,
6
+ }));
7
+ }
package/dist/index.d.ts CHANGED
@@ -1,14 +1,3 @@
1
- export declare function view(view: string, props: Record<string, unknown>): Promise<Response>;
2
- export type RouteParams = {
3
- query: Record<string, string>;
4
- request: Request;
5
- params: Record<string, string>;
6
- };
7
- type Paths = {
8
- routes: string;
9
- views: string;
10
- public: string;
11
- port: number;
12
- };
1
+ import type { Paths } from "./types";
2
+ export { view } from "./views";
13
3
  export declare function setup(paths: Paths): Promise<void>;
14
- export {};
package/dist/index.js CHANGED
@@ -1,63 +1,13 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import staticPlugin from "@elysiajs/static";
4
- import { Elysia } from "elysia";
5
- let viewsPath = "";
6
- export async function view(view, props) {
7
- if (viewsPath === "")
8
- throw new Error("Views folder path not yet configured. Use `Htmv.setup` before rendering a view.");
9
- const filePath = path.join(viewsPath, `${view}.html`);
10
- const code = await fs.readFile(filePath, "utf-8");
11
- const replacedCode = code.replace(/{(.+)}/g, (_, propName) => {
12
- return props[propName];
13
- });
14
- return new Response(replacedCode, {
15
- headers: { "Content-Type": "text/html; charset=utf-8" },
16
- });
17
- }
1
+ import { createApp } from "./app";
2
+ import { registerRoutes } from "./routing";
3
+ import { setViewsPath } from "./views";
4
+ export { view } from "./views";
18
5
  export async function setup(paths) {
19
- viewsPath = paths.views;
20
- const app = new Elysia().use(staticPlugin({
21
- assets: paths.public,
22
- }));
6
+ setViewsPath(paths.views);
7
+ const app = createApp(paths.public);
23
8
  await registerRoutes(app, paths.routes);
24
9
  app.listen(paths.port);
25
10
  console.log("");
26
11
  console.log(`HTMV running on port ${paths.port}! 🎉`);
27
12
  console.log(`http://localhost:${paths.port}`);
28
13
  }
29
- async function registerRoutes(app, baseDir, prefix = "/") {
30
- const entries = await fs.readdir(baseDir, { withFileTypes: true });
31
- for (const entry of entries) {
32
- const fullPath = path.join(baseDir, entry.name);
33
- if (entry.isDirectory()) {
34
- await registerRoutes(app, fullPath, path.join(prefix, entry.name));
35
- continue;
36
- }
37
- if (entry.name !== "index.ts")
38
- continue;
39
- const module = (await import(fullPath));
40
- const defaultFn = module.default;
41
- if (defaultFn && typeof defaultFn === "function") {
42
- app.all(prefix, async ({ request, query, params }) => {
43
- const result = await defaultFn({ request, query, params });
44
- return result;
45
- });
46
- console.log(`Registered ${fullPath} on ${prefix} route with method all`);
47
- }
48
- for (const propName in module) {
49
- const prop = module[propName];
50
- if (typeof prop !== "function")
51
- continue;
52
- const fn = prop;
53
- const name = fn.name.toLowerCase();
54
- if (!["get", "post", "put", "patch", "delete"].includes(name))
55
- continue;
56
- app[name](prefix, async ({ request, query, params }) => {
57
- const result = await fn({ request, query, params });
58
- return result;
59
- });
60
- console.log(`Registered ${fullPath} on ${prefix} route with method ${name}`);
61
- }
62
- }
63
- }
@@ -0,0 +1,2 @@
1
+ import type Elysia from "elysia";
2
+ export declare function registerRoutes(app: Elysia, baseDir: string, prefix?: string): Promise<void>;
@@ -0,0 +1,37 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ export async function registerRoutes(app, baseDir, prefix = "/") {
4
+ const entries = await fs.readdir(baseDir, { withFileTypes: true });
5
+ for (const entry of entries) {
6
+ const fullPath = path.join(baseDir, entry.name);
7
+ if (entry.isDirectory()) {
8
+ await registerRoutes(app, fullPath, path.join(prefix, entry.name));
9
+ continue;
10
+ }
11
+ if (entry.name !== "index.ts")
12
+ continue;
13
+ const module = (await import(fullPath));
14
+ const defaultFn = module.default;
15
+ if (defaultFn && typeof defaultFn === "function") {
16
+ app.all(prefix, async ({ request, query, params }) => {
17
+ const result = await defaultFn({ request, query, params });
18
+ return result;
19
+ });
20
+ console.log(`Registered ${fullPath} on ${prefix} route with method all`);
21
+ }
22
+ for (const propName in module) {
23
+ const prop = module[propName];
24
+ if (typeof prop !== "function")
25
+ continue;
26
+ const fn = prop;
27
+ const name = fn.name.toLowerCase();
28
+ if (!["get", "post", "put", "patch", "delete"].includes(name))
29
+ continue;
30
+ app[name](prefix, async ({ request, query, params }) => {
31
+ const result = await fn({ request, query, params });
32
+ return result;
33
+ });
34
+ console.log(`Registered ${fullPath} on ${prefix} route with method ${name}`);
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,2 @@
1
+ export declare function setViewsPath(path: string): void;
2
+ export declare function view(view: string, props: Record<string, unknown>): Promise<Response>;
package/dist/views.js ADDED
@@ -0,0 +1,24 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ let viewsPath = "";
4
+ export function setViewsPath(path) {
5
+ viewsPath = path;
6
+ }
7
+ export async function view(view, props) {
8
+ if (viewsPath === "")
9
+ throw new Error("Views folder path not yet configured. Use `Htmv.setup` before rendering a view.");
10
+ const filePath = path.join(viewsPath, `${view}.html`);
11
+ const code = await fs.readFile(filePath, "utf-8");
12
+ const replacedCode = code
13
+ .replace(/{(.+)}/g, (_, propName) => {
14
+ return props[propName];
15
+ })
16
+ .replace(/<Isset\s+(\w+)>([\s\S]*?)<\/Isset>/g, (_, propName, innerContent) => {
17
+ if (props[propName] !== undefined && props[propName] !== null)
18
+ return innerContent;
19
+ return "";
20
+ });
21
+ return new Response(replacedCode, {
22
+ headers: { "Content-Type": "text/html; charset=utf-8" },
23
+ });
24
+ }
package/package.json CHANGED
@@ -1,21 +1,30 @@
1
1
  {
2
- "name": "htmv",
3
- "main": "dist/index.js",
4
- "type": "module",
5
- "version": "0.0.18",
6
- "devDependencies": {
7
- "@biomejs/biome": "2.3.3",
8
- "@types/bun": "latest"
9
- },
10
- "peerDependencies": {
11
- "typescript": "^5"
12
- },
13
- "dependencies": {
14
- "@elysiajs/node": "^1.4.2",
15
- "@elysiajs/static": "^1.4.6",
16
- "elysia": "^1.4.18"
17
- },
18
- "bin": {
19
- "htmv": "dist/cli/cli.js"
20
- }
2
+ "name": "htmv",
3
+ "main": "dist/index.js",
4
+ "type": "module",
5
+ "version": "0.0.26",
6
+ "devDependencies": {
7
+ "@biomejs/biome": "2.3.3",
8
+ "@types/bun": "latest"
9
+ },
10
+ "peerDependencies": {
11
+ "typescript": "^5"
12
+ },
13
+ "dependencies": {
14
+ "@elysiajs/node": "^1.4.2",
15
+ "@elysiajs/static": "^1.4.6",
16
+ "elysia": "^1.4.18"
17
+ },
18
+ "bin": {
19
+ "htmv": "dist/cli/cli.js"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc",
23
+ "lint": "biome check",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/Fabrisdev/htmv"
29
+ }
21
30
  }
package/src/app.ts ADDED
@@ -0,0 +1,10 @@
1
+ import staticPlugin from "@elysiajs/static";
2
+ import { Elysia } from "elysia";
3
+
4
+ export function createApp(publicPath: string) {
5
+ return new Elysia().use(
6
+ staticPlugin({
7
+ assets: publicPath,
8
+ }),
9
+ );
10
+ }
package/src/index.ts CHANGED
@@ -1,87 +1,16 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import staticPlugin from "@elysiajs/static";
4
- import { Elysia } from "elysia";
1
+ import { createApp } from "./app";
2
+ import { registerRoutes } from "./routing";
3
+ import type { Paths } from "./types";
4
+ import { setViewsPath } from "./views";
5
5
 
6
- let viewsPath = "";
6
+ export { view } from "./views";
7
7
 
8
- export async function view(view: string, props: Record<string, unknown>) {
9
- if (viewsPath === "")
10
- throw new Error(
11
- "Views folder path not yet configured. Use `Htmv.setup` before rendering a view.",
12
- );
13
- const filePath = path.join(viewsPath, `${view}.html`);
14
- const code = await fs.readFile(filePath, "utf-8");
15
- const replacedCode = code.replace(/{(.+)}/g, (_, propName) => {
16
- return props[propName] as string;
17
- });
18
- return new Response(replacedCode, {
19
- headers: { "Content-Type": "text/html; charset=utf-8" },
20
- });
21
- }
22
-
23
- export type RouteParams = {
24
- query: Record<string, string>;
25
- request: Request;
26
- params: Record<string, string>;
27
- };
28
-
29
- type Paths = {
30
- routes: string;
31
- views: string;
32
- public: string;
33
- port: number;
34
- };
35
8
  export async function setup(paths: Paths) {
36
- viewsPath = paths.views;
37
- const app = new Elysia().use(
38
- staticPlugin({
39
- assets: paths.public,
40
- }),
41
- );
42
-
9
+ setViewsPath(paths.views);
10
+ const app = createApp(paths.public);
43
11
  await registerRoutes(app, paths.routes);
44
12
  app.listen(paths.port);
45
13
  console.log("");
46
14
  console.log(`HTMV running on port ${paths.port}! 🎉`);
47
15
  console.log(`http://localhost:${paths.port}`);
48
16
  }
49
-
50
- async function registerRoutes(app: Elysia, baseDir: string, prefix = "/") {
51
- const entries = await fs.readdir(baseDir, { withFileTypes: true });
52
- for (const entry of entries) {
53
- const fullPath = path.join(baseDir, entry.name);
54
- if (entry.isDirectory()) {
55
- await registerRoutes(app, fullPath, path.join(prefix, entry.name));
56
- continue;
57
- }
58
- if (entry.name !== "index.ts") continue;
59
- const module = (await import(fullPath)) as Record<string, unknown>;
60
- const defaultFn = module.default;
61
- if (defaultFn && typeof defaultFn === "function") {
62
- app.all(prefix, async ({ request, query, params }) => {
63
- const result = await defaultFn({ request, query, params });
64
- return result;
65
- });
66
- console.log(`Registered ${fullPath} on ${prefix} route with method all`);
67
- }
68
- for (const propName in module) {
69
- const prop = module[propName];
70
- if (typeof prop !== "function") continue;
71
- const fn = prop as RouteFn;
72
- const name = fn.name.toLowerCase();
73
- if (!["get", "post", "put", "patch", "delete"].includes(name)) continue;
74
- app[name as "get"](prefix, async ({ request, query, params }) => {
75
- const result = await fn({ request, query, params });
76
- return result;
77
- });
78
- console.log(
79
- `Registered ${fullPath} on ${prefix} route with method ${name}`,
80
- );
81
- }
82
- }
83
- }
84
-
85
- type RouteFn = (
86
- _: RouteParams,
87
- ) => Promise<Response> | Response | Promise<string> | string;
package/src/routing.ts ADDED
@@ -0,0 +1,43 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import type Elysia from "elysia";
4
+ import type { RouteFn } from "./types";
5
+
6
+ export async function registerRoutes(
7
+ app: Elysia,
8
+ baseDir: string,
9
+ prefix = "/",
10
+ ) {
11
+ const entries = await fs.readdir(baseDir, { withFileTypes: true });
12
+ for (const entry of entries) {
13
+ const fullPath = path.join(baseDir, entry.name);
14
+ if (entry.isDirectory()) {
15
+ await registerRoutes(app, fullPath, path.join(prefix, entry.name));
16
+ continue;
17
+ }
18
+ if (entry.name !== "index.ts") continue;
19
+ const module = (await import(fullPath)) as Record<string, unknown>;
20
+ const defaultFn = module.default;
21
+ if (defaultFn && typeof defaultFn === "function") {
22
+ app.all(prefix, async ({ request, query, params }) => {
23
+ const result = await defaultFn({ request, query, params });
24
+ return result;
25
+ });
26
+ console.log(`Registered ${fullPath} on ${prefix} route with method all`);
27
+ }
28
+ for (const propName in module) {
29
+ const prop = module[propName];
30
+ if (typeof prop !== "function") continue;
31
+ const fn = prop as RouteFn;
32
+ const name = fn.name.toLowerCase();
33
+ if (!["get", "post", "put", "patch", "delete"].includes(name)) continue;
34
+ app[name as "get"](prefix, async ({ request, query, params }) => {
35
+ const result = await fn({ request, query, params });
36
+ return result;
37
+ });
38
+ console.log(
39
+ `Registered ${fullPath} on ${prefix} route with method ${name}`,
40
+ );
41
+ }
42
+ }
43
+ }
package/src/types.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type RouteParams = {
2
+ query: Record<string, string>;
3
+ request: Request;
4
+ params: Record<string, string>;
5
+ };
6
+
7
+ export type Paths = {
8
+ routes: string;
9
+ views: string;
10
+ public: string;
11
+ port: number;
12
+ };
13
+
14
+ export type RouteFn = (
15
+ _: RouteParams,
16
+ ) => Promise<Response> | Response | Promise<string> | string;
package/src/views.ts ADDED
@@ -0,0 +1,32 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+
4
+ let viewsPath = "";
5
+
6
+ export function setViewsPath(path: string) {
7
+ viewsPath = path;
8
+ }
9
+
10
+ export async function view(view: string, props: Record<string, unknown>) {
11
+ if (viewsPath === "")
12
+ throw new Error(
13
+ "Views folder path not yet configured. Use `Htmv.setup` before rendering a view.",
14
+ );
15
+ const filePath = path.join(viewsPath, `${view}.html`);
16
+ const code = await fs.readFile(filePath, "utf-8");
17
+ const replacedCode = code
18
+ .replace(/{(.+)}/g, (_, propName) => {
19
+ return props[propName] as string;
20
+ })
21
+ .replace(
22
+ /<Isset\s+(\w+)>([\s\S]*?)<\/Isset>/g,
23
+ (_, propName, innerContent) => {
24
+ if (props[propName] !== undefined && props[propName] !== null)
25
+ return innerContent;
26
+ return "";
27
+ },
28
+ );
29
+ return new Response(replacedCode, {
30
+ headers: { "Content-Type": "text/html; charset=utf-8" },
31
+ });
32
+ }
package/tsconfig.json CHANGED
@@ -1,33 +1,32 @@
1
1
  {
2
- "compilerOptions": {
3
- // Environment setup & latest features
4
- "lib": ["ESNext"],
5
- "target": "ESNext",
6
- "module": "Preserve",
7
- "moduleDetection": "force",
8
- "allowJs": true,
2
+ "compilerOptions": {
3
+ // Environment setup & latest features
4
+ "lib": ["ESNext"],
5
+ "target": "ESNext",
6
+ "module": "Preserve",
7
+ "moduleDetection": "force",
8
+ "allowJs": true,
9
9
 
10
- // Bundler mode
11
- "moduleResolution": "bundler",
12
- "verbatimModuleSyntax": true,
10
+ // Bundler mode
11
+ "moduleResolution": "bundler",
12
+ "verbatimModuleSyntax": true,
13
13
 
14
- // Best practices
15
- "strict": true,
16
- "skipLibCheck": true,
17
- "noFallthroughCasesInSwitch": true,
18
- "noUncheckedIndexedAccess": true,
19
- "noImplicitOverride": true,
14
+ // Best practices
15
+ "strict": true,
16
+ "skipLibCheck": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "noUncheckedIndexedAccess": true,
19
+ "noImplicitOverride": true,
20
20
 
21
- // Some stricter flags (disabled by default)
22
- "noUnusedLocals": false,
23
- "noUnusedParameters": false,
24
- "noPropertyAccessFromIndexSignature": false,
25
-
26
- // Config for npm
27
- "noEmit": false, // Changed to false to allow it to compile
28
- "outDir": "./dist",
29
- "declaration": true, //generates .d.ts
30
-
31
- },
32
- "include": ["src"]
21
+ // Some stricter flags (disabled by default)
22
+ "noUnusedLocals": false,
23
+ "noUnusedParameters": false,
24
+ "noPropertyAccessFromIndexSignature": false,
25
+
26
+ // Config for npm
27
+ "noEmit": false, // Changed to false to allow it to compile
28
+ "outDir": "./dist",
29
+ "declaration": true //generates .d.ts
30
+ },
31
+ "include": ["src"]
33
32
  }