From 05b9ef01ebd2cb2c35e18994aaa90680b4483d8e Mon Sep 17 00:00:00 2001 From: pat Date: Thu, 24 Apr 2025 11:16:31 +0800 Subject: [PATCH 1/9] Added with-nestjs folder, updated package.json --- examples/with-nestjs/components/Edit.tsx | 7 +++ examples/with-nestjs/package.json | 32 +++++++++++ examples/with-nestjs/pages/about.tsx | 28 ++++++++++ examples/with-nestjs/pages/home.tsx | 34 ++++++++++++ examples/with-nestjs/pages/page.css | 7 +++ examples/with-nestjs/public/global.css | 70 ++++++++++++++++++++++++ examples/with-nestjs/public/react.svg | 1 + examples/with-nestjs/scripts/build.ts | 41 ++++++++++++++ examples/with-nestjs/scripts/develop.ts | 47 ++++++++++++++++ examples/with-nestjs/scripts/start.ts | 58 ++++++++++++++++++++ examples/with-nestjs/tsconfig.json | 13 +++++ 11 files changed, 338 insertions(+) create mode 100644 examples/with-nestjs/components/Edit.tsx create mode 100644 examples/with-nestjs/package.json create mode 100644 examples/with-nestjs/pages/about.tsx create mode 100644 examples/with-nestjs/pages/home.tsx create mode 100644 examples/with-nestjs/pages/page.css create mode 100644 examples/with-nestjs/public/global.css create mode 100644 examples/with-nestjs/public/react.svg create mode 100644 examples/with-nestjs/scripts/build.ts create mode 100644 examples/with-nestjs/scripts/develop.ts create mode 100644 examples/with-nestjs/scripts/start.ts create mode 100644 examples/with-nestjs/tsconfig.json diff --git a/examples/with-nestjs/components/Edit.tsx b/examples/with-nestjs/components/Edit.tsx new file mode 100644 index 0000000..69a41ea --- /dev/null +++ b/examples/with-nestjs/components/Edit.tsx @@ -0,0 +1,7 @@ +export default function Edit({ name }: { name: string }) { + return ( +

+ Edit pages/{name}.tsx and save to test HMR +

+ ) +} \ No newline at end of file diff --git a/examples/with-nestjs/package.json b/examples/with-nestjs/package.json new file mode 100644 index 0000000..63d64d3 --- /dev/null +++ b/examples/with-nestjs/package.json @@ -0,0 +1,32 @@ +{ + "type": "module", + "name": "reactus-with-nestjs", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "tsx scripts/build.ts", + "dev": "tsx scripts/develop.ts", + "start": "tsx scripts/start.ts" + }, + "dependencies": { + "@nestjs/common": "^11.1.0", + "@nestjs/core": "^11.1.0", + "@nestjs/platform-express": "^11.1.0", + "react": "19.0.0", + "react-dom": "19.0.0", + "reactus": "^0.2.9", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.2", + "sirv": "3.0.1" + }, + "devDependencies": { + "@types/node": "22.9.3", + "@types/react": "19.0.10", + "@types/react-dom": "19.0.4", + "@vitejs/plugin-react": "4.3.4", + "ts-node": "10.9.2", + "tsx": "4.19.3", + "typescript": "5.7.2", + "vite": "6.1.1" + } +} diff --git a/examples/with-nestjs/pages/about.tsx b/examples/with-nestjs/pages/about.tsx new file mode 100644 index 0000000..969e59e --- /dev/null +++ b/examples/with-nestjs/pages/about.tsx @@ -0,0 +1,28 @@ +import './page.css'; +import Edit from '../components/Edit'; + +export function Head({ styles = [] }: { styles?: string[] }) { + return ( + <> + About Reactus + + + + {styles.map((href, index) => ( + + ))} + + ) +} + +export default function AboutPage() { + return ( + <> +

About Reactus

+
+ + Home Page +
+ + ) +} \ No newline at end of file diff --git a/examples/with-nestjs/pages/home.tsx b/examples/with-nestjs/pages/home.tsx new file mode 100644 index 0000000..9eaf715 --- /dev/null +++ b/examples/with-nestjs/pages/home.tsx @@ -0,0 +1,34 @@ +import './page.css'; +import { useState } from 'react'; +import Edit from '../components/Edit'; + +export function Head({ styles = [] }: { styles?: string[] }) { + return ( + <> + Reactus + + + + {styles.map((href, index) => ( + + ))} + + ) +} + +export default function HomePage() { + const [count, setCount] = useState(0) + + return ( + <> +

React + Reactus with NestJS!

+
+ + + About Reactus +
+ + ) +} \ No newline at end of file diff --git a/examples/with-nestjs/pages/page.css b/examples/with-nestjs/pages/page.css new file mode 100644 index 0000000..30e8b0c --- /dev/null +++ b/examples/with-nestjs/pages/page.css @@ -0,0 +1,7 @@ +:root { display: initial; } +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} \ No newline at end of file diff --git a/examples/with-nestjs/public/global.css b/examples/with-nestjs/public/global.css new file mode 100644 index 0000000..bcf1636 --- /dev/null +++ b/examples/with-nestjs/public/global.css @@ -0,0 +1,70 @@ +:root { + display: none; + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/examples/with-nestjs/public/react.svg b/examples/with-nestjs/public/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/examples/with-nestjs/public/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/examples/with-nestjs/scripts/build.ts b/examples/with-nestjs/scripts/build.ts new file mode 100644 index 0000000..aa109d4 --- /dev/null +++ b/examples/with-nestjs/scripts/build.ts @@ -0,0 +1,41 @@ +//node +import path from 'node:path'; +//reactus +import { build } from 'reactus'; + +async function builder() { + const cwd = process.cwd(); + const engine = build({ + cwd, + //path where to save assets (css, images, etc) + assetPath: path.join(cwd, 'public/assets'), + //path where to save and load (live) the client scripts (js) + clientPath: path.join(cwd, 'public/client'), + //path where to save and load (live) the server script (js) + pagePath: path.join(cwd, '.build/pages') + }); + + await engine.set('@/pages/home'); + await engine.set('@/pages/about'); + + const responses = [ + ...await engine.buildAllClients(), + ...await engine.buildAllAssets(), + ...await engine.buildAllPages() + ].map(response => { + const results = response.results; + if (typeof results?.contents === 'string') { + results.contents = results.contents.substring(0, 100) + ' ...'; + } + return results; + }); + + //console.log(responses); + //fix for unused variable :) + if (responses.length) return; +} + +builder().catch(e => { + console.error(e); + process.exit(1); +}); \ No newline at end of file diff --git a/examples/with-nestjs/scripts/develop.ts b/examples/with-nestjs/scripts/develop.ts new file mode 100644 index 0000000..a716c85 --- /dev/null +++ b/examples/with-nestjs/scripts/develop.ts @@ -0,0 +1,47 @@ +//node +import { createServer } from 'node:http'; +//reactus +import { dev } from 'reactus'; + +async function develop() { + const cwd = process.cwd(); + const engine = dev({ + cwd, + basePath: '/', + watchIgnore: [ '**/.build/**' ], + //client script route prefix used in the document markup + //ie. /client/[id][extname] + // + // + clientRoute: '/client' + }); + + const server = createServer(async (req, res) => { + //handles public, assets and hmr + await engine.http(req, res); + //if middleware was triggered + if (res.headersSent) return; + // home page + if (req.url === '/') { + res.setHeader('Content-Type', 'text/html'); + res.end(await engine.render('@/pages/home', { title: 'Home' })); + return; + //about page + } else if (req.url === '/about') { + res.setHeader('Content-Type', 'text/html'); + res.end(await engine.render('@/pages/about')); + return; + } + res.end('404 Not Found'); + }); + + server.listen(3000, () => { + console.log('Server running at http://localhost:3000/'); + }); +} + +develop().catch(e => { + console.error(e); + process.exit(1); +}); + diff --git a/examples/with-nestjs/scripts/start.ts b/examples/with-nestjs/scripts/start.ts new file mode 100644 index 0000000..db25b07 --- /dev/null +++ b/examples/with-nestjs/scripts/start.ts @@ -0,0 +1,58 @@ +//node +import { createServer } from 'node:http'; +import path from 'node:path'; +import sirv from 'sirv'; +//reactus +import { serve } from 'reactus'; + +async function start() { + const cwd = process.cwd(); + const engine = serve({ + cwd, + //ie. /client/[id][extname] + // + // + clientRoute: '/client', + //path where to load the server script (js) + pagePath: path.join(cwd, '.build/pages'), + //css route prefix used in the document markup + //ie. /assets/[id][extname] + // + // + cssRoute: '/assets' + }); + // Init `sirv` handler + const assets = sirv(path.join(cwd, 'public'), { + maxAge: 31536000, // 1Y + immutable: true + }); + + const server = createServer(async (req, res) => { + // home page + if (req.url === '/') { + res.setHeader('Content-Type', 'text/html'); + res.end(await engine.render('@/pages/home')); + return; + //about page + } else if (req.url === '/about') { + res.setHeader('Content-Type', 'text/html'); + res.end(await engine.render('@/pages/about')); + return; + } + //static asset server + assets(req, res); + //if static asset was triggered + if (res.headersSent) return; + res.end('404 Not Found'); + }); + + server.listen(3000, () => { + console.log('Server running at http://localhost:3000/'); + }); +} + +start().catch(e => { + console.error(e); + process.exit(1); +}); + diff --git a/examples/with-nestjs/tsconfig.json b/examples/with-nestjs/tsconfig.json new file mode 100644 index 0000000..d6be85e --- /dev/null +++ b/examples/with-nestjs/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "reactus/tsconfig/client", + "compilerOptions": { + "moduleResolution": "bundler", + "outDir": ".build", + }, + "include": [ + "scripts/**/*.ts", + "components/**/*.tsx" , + "pages/**/*.tsx" + ], + "exclude": [ "dist", "node_modules", "tests" ] +} \ No newline at end of file From 5d2849953e047ed79bcaaaa4b54ea9740c01c5e1 Mon Sep 17 00:00:00 2001 From: pat Date: Thu, 24 Apr 2025 11:31:36 +0800 Subject: [PATCH 2/9] Initialize NestJS application structure --- examples/with-nestjs/src/app.module.ts | 8 ++++++++ examples/with-nestjs/src/app.service.ts | 8 ++++++++ examples/with-nestjs/src/main.ts | 8 ++++++++ 3 files changed, 24 insertions(+) create mode 100644 examples/with-nestjs/src/app.module.ts create mode 100644 examples/with-nestjs/src/app.service.ts create mode 100644 examples/with-nestjs/src/main.ts diff --git a/examples/with-nestjs/src/app.module.ts b/examples/with-nestjs/src/app.module.ts new file mode 100644 index 0000000..373d5e5 --- /dev/null +++ b/examples/with-nestjs/src/app.module.ts @@ -0,0 +1,8 @@ +import { Module } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Module({ + providers: [AppService], + exports: [AppService] +}) +export class AppModule {} diff --git a/examples/with-nestjs/src/app.service.ts b/examples/with-nestjs/src/app.service.ts new file mode 100644 index 0000000..40970a1 --- /dev/null +++ b/examples/with-nestjs/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getMessage(): string { + return 'Hello from NestJS service!'; + } +} diff --git a/examples/with-nestjs/src/main.ts b/examples/with-nestjs/src/main.ts new file mode 100644 index 0000000..13cad38 --- /dev/null +++ b/examples/with-nestjs/src/main.ts @@ -0,0 +1,8 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + await app.listen(3000); +} +bootstrap(); From aa48711af734ffe98b37c489503bc0daa14e2255 Mon Sep 17 00:00:00 2001 From: pat Date: Thu, 24 Apr 2025 11:32:59 +0800 Subject: [PATCH 3/9] Added Reactus dev server with NestJS --- examples/with-nestjs/scripts/develop.ts | 48 +++++++++++++------------ 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/examples/with-nestjs/scripts/develop.ts b/examples/with-nestjs/scripts/develop.ts index a716c85..ad746b7 100644 --- a/examples/with-nestjs/scripts/develop.ts +++ b/examples/with-nestjs/scripts/develop.ts @@ -1,47 +1,51 @@ -//node -import { createServer } from 'node:http'; -//reactus +import { NestFactory } from '@nestjs/core'; +import { AppModule } from '../src/app.module'; import { dev } from 'reactus'; +import { createServer, IncomingMessage, ServerResponse } from 'http'; -async function develop() { - const cwd = process.cwd(); +async function develop() { + // Create NestJS app without body parsing + const app = await NestFactory.create(AppModule, { + bodyParser: false, + }); + await app.init(); + + // Set up Reactus dev server const engine = dev({ - cwd, + cwd: process.cwd(), basePath: '/', - watchIgnore: [ '**/.build/**' ], - //client script route prefix used in the document markup - //ie. /client/[id][extname] - // - // - clientRoute: '/client' + watchIgnore: ['**/.build/**'], + clientRoute: '/client', }); - const server = createServer(async (req, res) => { - //handles public, assets and hmr + // Handle HTTP requests + const server = createServer(async (req: IncomingMessage, res: ServerResponse) => { await engine.http(req, res); - //if middleware was triggered if (res.headersSent) return; - // home page + + // Server-side render routes if (req.url === '/') { res.setHeader('Content-Type', 'text/html'); res.end(await engine.render('@/pages/home', { title: 'Home' })); return; - //about page - } else if (req.url === '/about') { + } + + if (req.url === '/about') { res.setHeader('Content-Type', 'text/html'); res.end(await engine.render('@/pages/about')); return; } + + res.statusCode = 404; res.end('404 Not Found'); }); server.listen(3000, () => { - console.log('Server running at http://localhost:3000/'); + console.log('Server running at http://localhost:3000'); }); } -develop().catch(e => { - console.error(e); +develop().catch(err => { + console.error(err); process.exit(1); }); - From 5e074c20e96239860962a356fec8a8dd82cc4132 Mon Sep 17 00:00:00 2001 From: pat Date: Thu, 24 Apr 2025 11:33:40 +0800 Subject: [PATCH 4/9] Added Reactus prod server with NestJS --- examples/with-nestjs/scripts/start.ts | 58 +++++++++++++++------------ 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/examples/with-nestjs/scripts/start.ts b/examples/with-nestjs/scripts/start.ts index db25b07..efd500a 100644 --- a/examples/with-nestjs/scripts/start.ts +++ b/examples/with-nestjs/scripts/start.ts @@ -1,48 +1,57 @@ -//node -import { createServer } from 'node:http'; +import { createServer, IncomingMessage, ServerResponse } from 'node:http'; import path from 'node:path'; import sirv from 'sirv'; -//reactus + import { serve } from 'reactus'; +import { NestFactory } from '@nestjs/core'; +import { AppModule } from '../src/app.module'; +import { AppService } from '../src/app.service'; + async function start() { const cwd = process.cwd(); + + // Start NestJS without listening (manual server) + const app = await NestFactory.create(AppModule, { bodyParser: false }); + await app.init(); + + // Get NestJS service + const appService = app.get(AppService); + + // Set up Reactus renderer const engine = serve({ cwd, - //ie. /client/[id][extname] - // - // clientRoute: '/client', - //path where to load the server script (js) pagePath: path.join(cwd, '.build/pages'), - //css route prefix used in the document markup - //ie. /assets/[id][extname] - // - // - cssRoute: '/assets' + cssRoute: '/assets', }); - // Init `sirv` handler + + // Serve public assets const assets = sirv(path.join(cwd, 'public'), { - maxAge: 31536000, // 1Y - immutable: true + maxAge: 31536000, + immutable: true, }); - const server = createServer(async (req, res) => { - // home page + // Create HTTP server + const server = createServer(async (req: IncomingMessage, res: ServerResponse) => { if (req.url === '/') { + const message = appService.getMessage(); res.setHeader('Content-Type', 'text/html'); - res.end(await engine.render('@/pages/home')); + res.end(await engine.render('@/pages/home', { message })); return; - //about page - } else if (req.url === '/about') { + } + + // Server-side render routes + if (req.url === '/about') { res.setHeader('Content-Type', 'text/html'); res.end(await engine.render('@/pages/about')); return; } - //static asset server + assets(req, res); - //if static asset was triggered if (res.headersSent) return; + + res.statusCode = 404; res.end('404 Not Found'); }); @@ -51,8 +60,7 @@ async function start() { }); } -start().catch(e => { - console.error(e); +start().catch((err) => { + console.error(err); process.exit(1); }); - From 4236763666fd79631b255f843f8f93c8326d903b Mon Sep 17 00:00:00 2001 From: pat Date: Thu, 24 Apr 2025 11:35:35 +0800 Subject: [PATCH 5/9] Added NestJS scripts to package.json --- package.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package.json b/package.json index d60606d..c106a7b 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,10 @@ "http:dev": "yarn --cwd examples/with-http dev", "http:start": "yarn --cwd examples/with-http start", + "nestjs:build": "yarn --cwd examples/with-nestjs build", + "nestjs:dev": "yarn --cwd examples/with-nestjs dev", + "nestjs:start": "yarn --cwd examples/with-nestjs start", + "tailwind:build": "yarn --cwd examples/with-tailwind build", "tailwind:dev": "yarn --cwd examples/with-tailwind dev", "tailwind:start": "yarn --cwd examples/with-tailwind start", From 09e1c886d33dc3ef3522e5539b493afa8a2e62ca Mon Sep 17 00:00:00 2001 From: pat Date: Fri, 16 May 2025 11:43:01 +0800 Subject: [PATCH 6/9] Removed wrong implementation of Nest --- examples/with-nestjs/components/Edit.tsx | 7 --- examples/with-nestjs/package.json | 32 ----------- examples/with-nestjs/pages/about.tsx | 28 ---------- examples/with-nestjs/pages/home.tsx | 34 ------------ examples/with-nestjs/pages/page.css | 7 --- examples/with-nestjs/public/global.css | 70 ------------------------ examples/with-nestjs/public/react.svg | 1 - examples/with-nestjs/scripts/build.ts | 41 -------------- examples/with-nestjs/scripts/develop.ts | 51 ----------------- examples/with-nestjs/scripts/start.ts | 66 ---------------------- examples/with-nestjs/src/app.module.ts | 8 --- examples/with-nestjs/src/app.service.ts | 8 --- examples/with-nestjs/src/main.ts | 8 --- examples/with-nestjs/tsconfig.json | 13 ----- 14 files changed, 374 deletions(-) delete mode 100644 examples/with-nestjs/components/Edit.tsx delete mode 100644 examples/with-nestjs/package.json delete mode 100644 examples/with-nestjs/pages/about.tsx delete mode 100644 examples/with-nestjs/pages/home.tsx delete mode 100644 examples/with-nestjs/pages/page.css delete mode 100644 examples/with-nestjs/public/global.css delete mode 100644 examples/with-nestjs/public/react.svg delete mode 100644 examples/with-nestjs/scripts/build.ts delete mode 100644 examples/with-nestjs/scripts/develop.ts delete mode 100644 examples/with-nestjs/scripts/start.ts delete mode 100644 examples/with-nestjs/src/app.module.ts delete mode 100644 examples/with-nestjs/src/app.service.ts delete mode 100644 examples/with-nestjs/src/main.ts delete mode 100644 examples/with-nestjs/tsconfig.json diff --git a/examples/with-nestjs/components/Edit.tsx b/examples/with-nestjs/components/Edit.tsx deleted file mode 100644 index 69a41ea..0000000 --- a/examples/with-nestjs/components/Edit.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Edit({ name }: { name: string }) { - return ( -

- Edit pages/{name}.tsx and save to test HMR -

- ) -} \ No newline at end of file diff --git a/examples/with-nestjs/package.json b/examples/with-nestjs/package.json deleted file mode 100644 index 63d64d3..0000000 --- a/examples/with-nestjs/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "type": "module", - "name": "reactus-with-nestjs", - "version": "1.0.0", - "private": true, - "scripts": { - "build": "tsx scripts/build.ts", - "dev": "tsx scripts/develop.ts", - "start": "tsx scripts/start.ts" - }, - "dependencies": { - "@nestjs/common": "^11.1.0", - "@nestjs/core": "^11.1.0", - "@nestjs/platform-express": "^11.1.0", - "react": "19.0.0", - "react-dom": "19.0.0", - "reactus": "^0.2.9", - "reflect-metadata": "^0.2.2", - "rxjs": "^7.8.2", - "sirv": "3.0.1" - }, - "devDependencies": { - "@types/node": "22.9.3", - "@types/react": "19.0.10", - "@types/react-dom": "19.0.4", - "@vitejs/plugin-react": "4.3.4", - "ts-node": "10.9.2", - "tsx": "4.19.3", - "typescript": "5.7.2", - "vite": "6.1.1" - } -} diff --git a/examples/with-nestjs/pages/about.tsx b/examples/with-nestjs/pages/about.tsx deleted file mode 100644 index 969e59e..0000000 --- a/examples/with-nestjs/pages/about.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import './page.css'; -import Edit from '../components/Edit'; - -export function Head({ styles = [] }: { styles?: string[] }) { - return ( - <> - About Reactus - - - - {styles.map((href, index) => ( - - ))} - - ) -} - -export default function AboutPage() { - return ( - <> -

About Reactus

-
- - Home Page -
- - ) -} \ No newline at end of file diff --git a/examples/with-nestjs/pages/home.tsx b/examples/with-nestjs/pages/home.tsx deleted file mode 100644 index 9eaf715..0000000 --- a/examples/with-nestjs/pages/home.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import './page.css'; -import { useState } from 'react'; -import Edit from '../components/Edit'; - -export function Head({ styles = [] }: { styles?: string[] }) { - return ( - <> - Reactus - - - - {styles.map((href, index) => ( - - ))} - - ) -} - -export default function HomePage() { - const [count, setCount] = useState(0) - - return ( - <> -

React + Reactus with NestJS!

-
- - - About Reactus -
- - ) -} \ No newline at end of file diff --git a/examples/with-nestjs/pages/page.css b/examples/with-nestjs/pages/page.css deleted file mode 100644 index 30e8b0c..0000000 --- a/examples/with-nestjs/pages/page.css +++ /dev/null @@ -1,7 +0,0 @@ -:root { display: initial; } -#root { - max-width: 1280px; - margin: 0 auto; - padding: 2rem; - text-align: center; -} \ No newline at end of file diff --git a/examples/with-nestjs/public/global.css b/examples/with-nestjs/public/global.css deleted file mode 100644 index bcf1636..0000000 --- a/examples/with-nestjs/public/global.css +++ /dev/null @@ -1,70 +0,0 @@ -:root { - display: none; - font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; -} - -a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; -} -a:hover { - color: #535bf2; -} - -body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; -} - -h1 { - font-size: 3.2em; - line-height: 1.1; -} - -button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; -} -button:hover { - border-color: #646cff; -} -button:focus, -button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; -} - -@media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } -} \ No newline at end of file diff --git a/examples/with-nestjs/public/react.svg b/examples/with-nestjs/public/react.svg deleted file mode 100644 index 6c87de9..0000000 --- a/examples/with-nestjs/public/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/examples/with-nestjs/scripts/build.ts b/examples/with-nestjs/scripts/build.ts deleted file mode 100644 index aa109d4..0000000 --- a/examples/with-nestjs/scripts/build.ts +++ /dev/null @@ -1,41 +0,0 @@ -//node -import path from 'node:path'; -//reactus -import { build } from 'reactus'; - -async function builder() { - const cwd = process.cwd(); - const engine = build({ - cwd, - //path where to save assets (css, images, etc) - assetPath: path.join(cwd, 'public/assets'), - //path where to save and load (live) the client scripts (js) - clientPath: path.join(cwd, 'public/client'), - //path where to save and load (live) the server script (js) - pagePath: path.join(cwd, '.build/pages') - }); - - await engine.set('@/pages/home'); - await engine.set('@/pages/about'); - - const responses = [ - ...await engine.buildAllClients(), - ...await engine.buildAllAssets(), - ...await engine.buildAllPages() - ].map(response => { - const results = response.results; - if (typeof results?.contents === 'string') { - results.contents = results.contents.substring(0, 100) + ' ...'; - } - return results; - }); - - //console.log(responses); - //fix for unused variable :) - if (responses.length) return; -} - -builder().catch(e => { - console.error(e); - process.exit(1); -}); \ No newline at end of file diff --git a/examples/with-nestjs/scripts/develop.ts b/examples/with-nestjs/scripts/develop.ts deleted file mode 100644 index ad746b7..0000000 --- a/examples/with-nestjs/scripts/develop.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { NestFactory } from '@nestjs/core'; -import { AppModule } from '../src/app.module'; -import { dev } from 'reactus'; -import { createServer, IncomingMessage, ServerResponse } from 'http'; - -async function develop() { - // Create NestJS app without body parsing - const app = await NestFactory.create(AppModule, { - bodyParser: false, - }); - await app.init(); - - // Set up Reactus dev server - const engine = dev({ - cwd: process.cwd(), - basePath: '/', - watchIgnore: ['**/.build/**'], - clientRoute: '/client', - }); - - // Handle HTTP requests - const server = createServer(async (req: IncomingMessage, res: ServerResponse) => { - await engine.http(req, res); - if (res.headersSent) return; - - // Server-side render routes - if (req.url === '/') { - res.setHeader('Content-Type', 'text/html'); - res.end(await engine.render('@/pages/home', { title: 'Home' })); - return; - } - - if (req.url === '/about') { - res.setHeader('Content-Type', 'text/html'); - res.end(await engine.render('@/pages/about')); - return; - } - - res.statusCode = 404; - res.end('404 Not Found'); - }); - - server.listen(3000, () => { - console.log('Server running at http://localhost:3000'); - }); -} - -develop().catch(err => { - console.error(err); - process.exit(1); -}); diff --git a/examples/with-nestjs/scripts/start.ts b/examples/with-nestjs/scripts/start.ts deleted file mode 100644 index efd500a..0000000 --- a/examples/with-nestjs/scripts/start.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { createServer, IncomingMessage, ServerResponse } from 'node:http'; -import path from 'node:path'; -import sirv from 'sirv'; - -import { serve } from 'reactus'; - -import { NestFactory } from '@nestjs/core'; -import { AppModule } from '../src/app.module'; -import { AppService } from '../src/app.service'; - -async function start() { - const cwd = process.cwd(); - - // Start NestJS without listening (manual server) - const app = await NestFactory.create(AppModule, { bodyParser: false }); - await app.init(); - - // Get NestJS service - const appService = app.get(AppService); - - // Set up Reactus renderer - const engine = serve({ - cwd, - clientRoute: '/client', - pagePath: path.join(cwd, '.build/pages'), - cssRoute: '/assets', - }); - - // Serve public assets - const assets = sirv(path.join(cwd, 'public'), { - maxAge: 31536000, - immutable: true, - }); - - // Create HTTP server - const server = createServer(async (req: IncomingMessage, res: ServerResponse) => { - if (req.url === '/') { - const message = appService.getMessage(); - res.setHeader('Content-Type', 'text/html'); - res.end(await engine.render('@/pages/home', { message })); - return; - } - - // Server-side render routes - if (req.url === '/about') { - res.setHeader('Content-Type', 'text/html'); - res.end(await engine.render('@/pages/about')); - return; - } - - assets(req, res); - if (res.headersSent) return; - - res.statusCode = 404; - res.end('404 Not Found'); - }); - - server.listen(3000, () => { - console.log('Server running at http://localhost:3000/'); - }); -} - -start().catch((err) => { - console.error(err); - process.exit(1); -}); diff --git a/examples/with-nestjs/src/app.module.ts b/examples/with-nestjs/src/app.module.ts deleted file mode 100644 index 373d5e5..0000000 --- a/examples/with-nestjs/src/app.module.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Module } from '@nestjs/common'; -import { AppService } from './app.service'; - -@Module({ - providers: [AppService], - exports: [AppService] -}) -export class AppModule {} diff --git a/examples/with-nestjs/src/app.service.ts b/examples/with-nestjs/src/app.service.ts deleted file mode 100644 index 40970a1..0000000 --- a/examples/with-nestjs/src/app.service.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Injectable } from '@nestjs/common'; - -@Injectable() -export class AppService { - getMessage(): string { - return 'Hello from NestJS service!'; - } -} diff --git a/examples/with-nestjs/src/main.ts b/examples/with-nestjs/src/main.ts deleted file mode 100644 index 13cad38..0000000 --- a/examples/with-nestjs/src/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { NestFactory } from '@nestjs/core'; -import { AppModule } from './app.module'; - -async function bootstrap() { - const app = await NestFactory.create(AppModule); - await app.listen(3000); -} -bootstrap(); diff --git a/examples/with-nestjs/tsconfig.json b/examples/with-nestjs/tsconfig.json deleted file mode 100644 index d6be85e..0000000 --- a/examples/with-nestjs/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "reactus/tsconfig/client", - "compilerOptions": { - "moduleResolution": "bundler", - "outDir": ".build", - }, - "include": [ - "scripts/**/*.ts", - "components/**/*.tsx" , - "pages/**/*.tsx" - ], - "exclude": [ "dist", "node_modules", "tests" ] -} \ No newline at end of file From 3f419205362620a422af8095a5cdd74613fa00fe Mon Sep 17 00:00:00 2001 From: pat Date: Fri, 16 May 2025 12:03:52 +0800 Subject: [PATCH 7/9] Create Nest example --- examples/with-nest/README.md | 79 +++++++++++++++++++ examples/with-nest/components/Edit.tsx | 9 +++ examples/with-nest/nest-cli.json | 8 ++ examples/with-nest/package.json | 71 +++++++++++++++++ examples/with-nest/pages/about.tsx | 29 +++++++ examples/with-nest/pages/home.tsx | 34 ++++++++ examples/with-nest/pages/page.css | 7 ++ examples/with-nest/public/global.css | 70 ++++++++++++++++ examples/with-nest/scripts/build.ts | 41 ++++++++++ examples/with-nest/src/app.controller.ts | 25 ++++++ examples/with-nest/src/app.module.ts | 11 +++ examples/with-nest/src/app.service.ts | 8 ++ examples/with-nest/src/main.ts | 47 +++++++++++ .../with-nest/src/reactus/reactus.service.ts | 39 +++++++++ examples/with-nest/tsconfig.build.json | 4 + examples/with-nest/tsconfig.json | 24 ++++++ 16 files changed, 506 insertions(+) create mode 100644 examples/with-nest/README.md create mode 100644 examples/with-nest/components/Edit.tsx create mode 100644 examples/with-nest/nest-cli.json create mode 100644 examples/with-nest/package.json create mode 100644 examples/with-nest/pages/about.tsx create mode 100644 examples/with-nest/pages/home.tsx create mode 100644 examples/with-nest/pages/page.css create mode 100644 examples/with-nest/public/global.css create mode 100644 examples/with-nest/scripts/build.ts create mode 100644 examples/with-nest/src/app.controller.ts create mode 100644 examples/with-nest/src/app.module.ts create mode 100644 examples/with-nest/src/app.service.ts create mode 100644 examples/with-nest/src/main.ts create mode 100644 examples/with-nest/src/reactus/reactus.service.ts create mode 100644 examples/with-nest/tsconfig.build.json create mode 100644 examples/with-nest/tsconfig.json diff --git a/examples/with-nest/README.md b/examples/with-nest/README.md new file mode 100644 index 0000000..9e5b652 --- /dev/null +++ b/examples/with-nest/README.md @@ -0,0 +1,79 @@ +

+ Nest Logo +

+ +[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 +[circleci-url]: https://circleci.com/gh/nestjs/nest + +

A progressive Node.js framework for building efficient and scalable server-side applications.

+

+NPM Version +Package License +NPM Downloads +CircleCI +Discord +Backers on Open Collective +Sponsors on Open Collective + Donate us + Support us + Follow us on Twitter +

+ + +## Description + +[Nest](https://github.com/nestjs/nest) framework with Reactus and React. + +## Project setup + +```bash +$ yarn +``` + +## Compile and run the project + +```bash +# development +$ yarn start + +# watch mode (nest start --watch) +$ yarn start:dev + +# production mode +$ yarn start:prod +``` + +## Run tests + +```bash +# unit tests +$ yarn test +``` + +## Resources + +Check out a few resources that may come in handy when working with NestJS: + +- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. +- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). +- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). +- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. +- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). +- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). +- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). +- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). + +## Support + +Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). + +## Stay in touch + +- Author - [Kamil Myƛliwiec](https://twitter.com/kammysliwiec) +- Website - [https://nestjs.com](https://nestjs.com/) +- Twitter - [@nestframework](https://twitter.com/nestframework) + +## License + +Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). diff --git a/examples/with-nest/components/Edit.tsx b/examples/with-nest/components/Edit.tsx new file mode 100644 index 0000000..5a0c205 --- /dev/null +++ b/examples/with-nest/components/Edit.tsx @@ -0,0 +1,9 @@ +import React from 'react' + +export default function Edit({ name }: { name: string }) { + return ( +

+ Edit pages/{name}.tsx and save to test HMR +

+ ) + } \ No newline at end of file diff --git a/examples/with-nest/nest-cli.json b/examples/with-nest/nest-cli.json new file mode 100644 index 0000000..f9aa683 --- /dev/null +++ b/examples/with-nest/nest-cli.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://json.schemastore.org/nest-cli", + "collection": "@nestjs/schematics", + "sourceRoot": "src", + "compilerOptions": { + "deleteOutDir": true + } +} diff --git a/examples/with-nest/package.json b/examples/with-nest/package.json new file mode 100644 index 0000000..9934a3a --- /dev/null +++ b/examples/with-nest/package.json @@ -0,0 +1,71 @@ +{ + "name": "reactus-with-nest", + "version": "1.0.0", + "private": true, + "license": "UNLICENSED", + "type": "module", + "scripts": { + "build": "tsx scripts/build.ts", + "start": "nest start", + "start:dev": "nest start --watch", + "start:debug": "nest start --debug --watch", + "start:prod": "NODE_ENV=production nest start --prod", + "test": "jest --config jest.config.mjs", + "test:watch": "jest --watch", + "test:cov": "jest --coverage", + "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", + "test:e2e": "jest --config ./test/jest-e2e.json" + }, + "dependencies": { + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "@nestjs/platform-express": "^11.0.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "reactus": "^0.2.10", + "reflect-metadata": "^0.2.2", + "rxjs": "^7.8.1" + }, + "devDependencies": { + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", + "@swc/cli": "^0.6.0", + "@swc/core": "^1.10.7", + "@types/express": "^5.0.0", + "@types/jest": "^29.5.14", + "@types/node": "^22.15.3", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.3", + "@types/supertest": "^6.0.2", + "@vitejs/plugin-react": "^4.4.1", + "globals": "^16.0.0", + "jest": "^29.7.0", + "source-map-support": "^0.5.21", + "supertest": "^7.0.0", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", + "ts-node": "^10.9.2", + "tsconfig-paths": "^4.2.0", + "tsx": "^4.19.4", + "typescript": "^5.8.3", + "vite": "^6.3.4" + }, + "jest": { + "moduleFileExtensions": [ + "js", + "json", + "ts" + ], + "rootDir": "src", + "testRegex": ".*\\.spec\\.ts$", + "transform": { + "^.+\\.(t|j)s$": "ts-jest" + }, + "collectCoverageFrom": [ + "**/*.(t|j)s" + ], + "coverageDirectory": "../coverage", + "testEnvironment": "node" + } +} diff --git a/examples/with-nest/pages/about.tsx b/examples/with-nest/pages/about.tsx new file mode 100644 index 0000000..f7aecb8 --- /dev/null +++ b/examples/with-nest/pages/about.tsx @@ -0,0 +1,29 @@ +import './page.css'; +import React from 'react'; +import Edit from '../components/Edit.js'; + +export function Head({ styles = [] }: { styles?: string[] }) { + return ( + <> + About Reactus + + + + {styles.map((href, index) => ( + + ))} + + ) +} + +export default function AboutPage() { + return ( + <> +

About Reactus

+
+ + Home Page +
+ + ) +} \ No newline at end of file diff --git a/examples/with-nest/pages/home.tsx b/examples/with-nest/pages/home.tsx new file mode 100644 index 0000000..9708233 --- /dev/null +++ b/examples/with-nest/pages/home.tsx @@ -0,0 +1,34 @@ +import './page.css'; +import React, { useState } from 'react'; +import Edit from '../components/Edit.js'; + +export function Head({ styles = [] }: { styles?: string[] }) { + return ( + <> + Reactus + + + + {styles.map((href, index) => ( + + ))} + + ) +} + +export default function HomePage() { + const [count, setCount] = useState(0) + + return ( + <> +

React + Reactus with NestJS!

+
+ + + About Reactus +
+ + ) +} \ No newline at end of file diff --git a/examples/with-nest/pages/page.css b/examples/with-nest/pages/page.css new file mode 100644 index 0000000..30e8b0c --- /dev/null +++ b/examples/with-nest/pages/page.css @@ -0,0 +1,7 @@ +:root { display: initial; } +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} \ No newline at end of file diff --git a/examples/with-nest/public/global.css b/examples/with-nest/public/global.css new file mode 100644 index 0000000..bcf1636 --- /dev/null +++ b/examples/with-nest/public/global.css @@ -0,0 +1,70 @@ +:root { + display: none; + font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +a { + font-weight: 500; + color: #646cff; + text-decoration: inherit; +} +a:hover { + color: #535bf2; +} + +body { + margin: 0; + display: flex; + place-items: center; + min-width: 320px; + min-height: 100vh; +} + +h1 { + font-size: 3.2em; + line-height: 1.1; +} + +button { + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + background-color: #1a1a1a; + cursor: pointer; + transition: border-color 0.25s; +} +button:hover { + border-color: #646cff; +} +button:focus, +button:focus-visible { + outline: 4px auto -webkit-focus-ring-color; +} + +@media (prefers-color-scheme: light) { + :root { + color: #213547; + background-color: #ffffff; + } + a:hover { + color: #747bff; + } + button { + background-color: #f9f9f9; + } +} \ No newline at end of file diff --git a/examples/with-nest/scripts/build.ts b/examples/with-nest/scripts/build.ts new file mode 100644 index 0000000..aa109d4 --- /dev/null +++ b/examples/with-nest/scripts/build.ts @@ -0,0 +1,41 @@ +//node +import path from 'node:path'; +//reactus +import { build } from 'reactus'; + +async function builder() { + const cwd = process.cwd(); + const engine = build({ + cwd, + //path where to save assets (css, images, etc) + assetPath: path.join(cwd, 'public/assets'), + //path where to save and load (live) the client scripts (js) + clientPath: path.join(cwd, 'public/client'), + //path where to save and load (live) the server script (js) + pagePath: path.join(cwd, '.build/pages') + }); + + await engine.set('@/pages/home'); + await engine.set('@/pages/about'); + + const responses = [ + ...await engine.buildAllClients(), + ...await engine.buildAllAssets(), + ...await engine.buildAllPages() + ].map(response => { + const results = response.results; + if (typeof results?.contents === 'string') { + results.contents = results.contents.substring(0, 100) + ' ...'; + } + return results; + }); + + //console.log(responses); + //fix for unused variable :) + if (responses.length) return; +} + +builder().catch(e => { + console.error(e); + process.exit(1); +}); \ No newline at end of file diff --git a/examples/with-nest/src/app.controller.ts b/examples/with-nest/src/app.controller.ts new file mode 100644 index 0000000..bf2535d --- /dev/null +++ b/examples/with-nest/src/app.controller.ts @@ -0,0 +1,25 @@ +import { Controller, Get, Res } from '@nestjs/common'; +import { ReactusService } from './reactus/reactus.service.js'; +import { Response } from 'express'; + +@Controller() +export class AppController { + constructor(private readonly reactusService: ReactusService) {} + + @Get('/') + async home(@Res() res: Response) { + res.setHeader('Content-Type', 'text/html'); + const html = await this.reactusService.render('@/pages/home', { + title: 'Home', + }); + res.end(html); + } + @Get('/about') + async about(@Res() res: Response) { + res.setHeader('Content-Type', 'text/html'); + const html = await this.reactusService.render('@/pages/about', { + title: 'About', + }); + res.end(html); + } +} diff --git a/examples/with-nest/src/app.module.ts b/examples/with-nest/src/app.module.ts new file mode 100644 index 0000000..e218222 --- /dev/null +++ b/examples/with-nest/src/app.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { AppController } from './app.controller.js'; +import { AppService } from './app.service.js'; +import { ReactusService } from './reactus/reactus.service.js'; + +@Module({ + imports: [], + controllers: [AppController], + providers: [AppService, ReactusService], +}) +export class AppModule {} diff --git a/examples/with-nest/src/app.service.ts b/examples/with-nest/src/app.service.ts new file mode 100644 index 0000000..927d7cc --- /dev/null +++ b/examples/with-nest/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getHello(): string { + return 'Hello World!'; + } +} diff --git a/examples/with-nest/src/main.ts b/examples/with-nest/src/main.ts new file mode 100644 index 0000000..9980097 --- /dev/null +++ b/examples/with-nest/src/main.ts @@ -0,0 +1,47 @@ +import { NestFactory } from '@nestjs/core'; +import { AppModule } from './app.module.js'; +import { NestExpressApplication } from '@nestjs/platform-express'; +import { ReactusService } from './reactus/reactus.service.js'; +import { join } from 'path'; +import sirv from 'sirv'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + const express = app.getHttpAdapter().getInstance(); + const cwd = process.cwd(); + + const reactusService = app.get(ReactusService); + + // Serve static assets in production + if (process.env.NODE_ENV === 'production') { + express.use( + '/client', + sirv(join(cwd, 'public/client'), { maxAge: 31536000, immutable: true }) + ); + express.use( + '/assets', + sirv(join(cwd, 'public/assets'), { maxAge: 31536000, immutable: true }) + ); + express.use( + '/public', + sirv(join(cwd, 'public'), { maxAge: 31536000, immutable: true }) + ); + } + + // In dev mode, use the Reactus service for rendering and asset handling + express.use(async (req, res, next) => { + if (process.env.NODE_ENV !== 'production') { + // Handle assets and rendering in dev mode + await reactusService.handleAssets(req, res); + } + if (!res.headersSent) next(); + }); + + await app.listen(process.env.PORT ?? 3000); + console.log(`Server is listening at http://localhost:${process.env.PORT ?? 3000}`); +} + +bootstrap().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/with-nest/src/reactus/reactus.service.ts b/examples/with-nest/src/reactus/reactus.service.ts new file mode 100644 index 0000000..0966eff --- /dev/null +++ b/examples/with-nest/src/reactus/reactus.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@nestjs/common'; +import { IncomingMessage } from 'http'; +import { dev, serve } from 'reactus'; +import { SR } from 'reactus/types'; +import path from 'path'; + +@Injectable() +export class ReactusService { + private readonly engine: + | ReturnType + | ReturnType; + + private readonly isDev = process.env.NODE_ENV !== 'production'; + + constructor() { + const cwd = process.cwd(); + + if (this.isDev) { + this.engine = dev({ cwd }); + } else { + this.engine = serve({ + cwd, + clientRoute: '/client', + pagePath: path.join(cwd, '.build/pages'), + cssRoute: '/assets', + }); + } + } + + async render(path: string, props: Record = {}): Promise { + return this.engine.render(path, props); + } + + async handleAssets(req: IncomingMessage, res: SR) { + if (this.isDev && 'http' in this.engine) { + await this.engine.http(req, res); + } + } +} diff --git a/examples/with-nest/tsconfig.build.json b/examples/with-nest/tsconfig.build.json new file mode 100644 index 0000000..64f86c6 --- /dev/null +++ b/examples/with-nest/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig.json", + "exclude": ["node_modules", "test", "dist", "**/*spec.ts"] +} diff --git a/examples/with-nest/tsconfig.json b/examples/with-nest/tsconfig.json new file mode 100644 index 0000000..a55bde1 --- /dev/null +++ b/examples/with-nest/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "jsx": "react", + "module": "nodenext", + "moduleResolution": "nodenext", + "declaration": true, + "removeComments": true, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "allowSyntheticDefaultImports": true, + "target": "ES2023", + "sourceMap": true, + "outDir": "./dist", + "baseUrl": "./", + "incremental": true, + "skipLibCheck": true, + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": false, + "strictBindCallApply": false, + "noFallthroughCasesInSwitch": false + } + } + \ No newline at end of file From 089961429805d781c74ca494035bfd6251c61c7e Mon Sep 17 00:00:00 2001 From: pat Date: Fri, 16 May 2025 12:08:06 +0800 Subject: [PATCH 8/9] Added unit tests --- examples/with-nest/jest.config.mjs | 11 ++++ examples/with-nest/src/app.controller.spec.ts | 59 +++++++++++++++++++ .../src/reactus/reactus.service.spec.ts | 18 ++++++ 3 files changed, 88 insertions(+) create mode 100644 examples/with-nest/jest.config.mjs create mode 100644 examples/with-nest/src/app.controller.spec.ts create mode 100644 examples/with-nest/src/reactus/reactus.service.spec.ts diff --git a/examples/with-nest/jest.config.mjs b/examples/with-nest/jest.config.mjs new file mode 100644 index 0000000..bca8be1 --- /dev/null +++ b/examples/with-nest/jest.config.mjs @@ -0,0 +1,11 @@ +export default { + preset: 'ts-jest/presets/default-esm', + testEnvironment: 'node', + extensionsToTreatAsEsm: ['.ts'], + transform: { + '^.+\\.ts$': ['ts-jest', { useESM: true }] + }, + moduleNameMapper: { + '^(\\.{1,2}/.*)\\.js$': '$1' + } +}; diff --git a/examples/with-nest/src/app.controller.spec.ts b/examples/with-nest/src/app.controller.spec.ts new file mode 100644 index 0000000..676cc6c --- /dev/null +++ b/examples/with-nest/src/app.controller.spec.ts @@ -0,0 +1,59 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller.js'; +import { AppService } from './app.service.js'; +import { ReactusService } from './reactus/reactus.service.js'; +import { Response } from 'express' + +describe('AppController', () => { + let appController: AppController; + let reactusServiceMock: Partial; + + beforeEach(async () => { + reactusServiceMock = { + render: jest.fn().mockResolvedValue('Mocked Page'), + }; + + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [ + AppService, + { + provide: ReactusService, + useValue: reactusServiceMock, + }, + ], + }).compile(); + + appController = app.get(AppController); + }); + + it('should be defined', () => { + expect(appController).toBeDefined(); + }); + + it('should render the home page', async () => { + const res = { + setHeader: jest.fn(), + end: jest.fn(), + } as unknown as Response; + + await appController.home(res); + + expect(reactusServiceMock.render).toHaveBeenCalledWith('@/pages/home', { title: 'Home' }); + expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'text/html'); + expect(res.end).toHaveBeenCalledWith('Mocked Page'); + }); + + it('should render the about page', async () => { + const res = { + setHeader: jest.fn(), + end: jest.fn(), + } as unknown as Response; + + await appController.about(res); + + expect(reactusServiceMock.render).toHaveBeenCalledWith('@/pages/about', { title: 'About' }); + expect(res.setHeader).toHaveBeenCalledWith('Content-Type', 'text/html'); + expect(res.end).toHaveBeenCalledWith('Mocked Page'); + }); +}); diff --git a/examples/with-nest/src/reactus/reactus.service.spec.ts b/examples/with-nest/src/reactus/reactus.service.spec.ts new file mode 100644 index 0000000..74f40b1 --- /dev/null +++ b/examples/with-nest/src/reactus/reactus.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ReactusService } from './reactus.service.js'; + +describe('ReactusService', () => { + let service: ReactusService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ReactusService], + }).compile(); + + service = module.get(ReactusService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); From 28bab80b8980457b665f833ca121079b52b6a648 Mon Sep 17 00:00:00 2001 From: pat Date: Fri, 16 May 2025 12:08:50 +0800 Subject: [PATCH 9/9] Updated Nest scripts in package.json --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c106a7b..8e1bfcc 100644 --- a/package.json +++ b/package.json @@ -15,9 +15,9 @@ "http:dev": "yarn --cwd examples/with-http dev", "http:start": "yarn --cwd examples/with-http start", - "nestjs:build": "yarn --cwd examples/with-nestjs build", - "nestjs:dev": "yarn --cwd examples/with-nestjs dev", - "nestjs:start": "yarn --cwd examples/with-nestjs start", + "nest:build": "yarn --cwd examples/with-nest build", + "nest:dev": "yarn --cwd examples/with-nest start", + "nest:start": "yarn --cwd examples/with-nest start:prod", "tailwind:build": "yarn --cwd examples/with-tailwind build", "tailwind:dev": "yarn --cwd examples/with-tailwind dev",