diff --git a/package-lock.json b/package-lock.json index be8504b..fcbbe4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@athenna/vite", - "version": "5.9.0", + "version": "5.10.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@athenna/vite", - "version": "5.9.0", + "version": "5.10.0", "license": "MIT", "dependencies": { "@fastify/middie": "^9.0.2", @@ -16,9 +16,11 @@ "devDependencies": { "@athenna/common": "^5.5.0", "@athenna/config": "^5.1.0", + "@athenna/ioc": "^5.0.0", "@athenna/test": "^5.2.0", "@athenna/tsconfig": "^5.0.0", "@athenna/view": "^5.1.0", + "@types/react-dom": "^19.0.3", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "commitizen": "^4.3.1", @@ -33,6 +35,7 @@ "husky": "^3.1.0", "lint-staged": "^12.5.0", "prettier": "^2.8.8", + "react-dom": "^19.0.0", "vite": "^6.0.6" }, "engines": { @@ -91,6 +94,18 @@ "node": ">=20.0.0" } }, + "node_modules/@athenna/ioc": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@athenna/ioc/-/ioc-5.0.0.tgz", + "integrity": "sha512-Czxc+1vyIpkx9fcTQQeKYCkNDjc5+ljKYC7UkNgFQJszQCfZjuUU+I+Y6goyWFvZZ0IdAGi67/bc9wlCP+c1Wg==", + "dev": true, + "dependencies": { + "awilix": "^10.0.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@athenna/test": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@athenna/test/-/test-5.2.0.tgz", @@ -1696,6 +1711,25 @@ "integrity": "sha512-JOqsl+ZoCpP4e8TDke9W79FDcSgPAR0l6pixx2JHkhnRjvShyYiAYw2LVsnA7K08Y6DeOnaU6ujmENO4os/cYg==", "dev": true }, + "node_modules/@types/react": { + "version": "19.0.7", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.7.tgz", + "integrity": "sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==", + "dev": true, + "peer": true, + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.3.tgz", + "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", + "dev": true, + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, "node_modules/@types/sinon": { "version": "10.0.20", "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.20.tgz", @@ -2304,6 +2338,19 @@ "fastq": "^1.17.1" } }, + "node_modules/awilix": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/awilix/-/awilix-10.0.2.tgz", + "integrity": "sha512-hFatb7eZFdtiWjjmGRSm/K/uxZpmcBlM+YoeMB3VpOPXk3xa6+7zctg3LRbUzoimom5bwGrePF0jXReO6b4zNQ==", + "dev": true, + "dependencies": { + "camel-case": "^4.1.2", + "fast-glob": "^3.3.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -3201,6 +3248,13 @@ "node": ">= 8" } }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, + "peer": true + }, "node_modules/csv-parser": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.1.0.tgz", @@ -8871,6 +8925,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "dev": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -9374,6 +9450,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "dev": true + }, "node_modules/secure-json-parse": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-3.0.1.tgz", diff --git a/package.json b/package.json index 544b07a..fedaf12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@athenna/vite", - "version": "5.9.0", + "version": "5.10.0", "description": "Vite plugin for Athenna Framework.", "license": "MIT", "author": "João Lenon ", @@ -59,9 +59,11 @@ "devDependencies": { "@athenna/common": "^5.5.0", "@athenna/config": "^5.1.0", + "@athenna/ioc": "^5.0.0", "@athenna/test": "^5.2.0", "@athenna/tsconfig": "^5.0.0", "@athenna/view": "^5.1.0", + "@types/react-dom": "^19.0.3", "@typescript-eslint/eslint-plugin": "^7.18.0", "@typescript-eslint/parser": "^7.18.0", "commitizen": "^4.3.1", @@ -76,6 +78,7 @@ "husky": "^3.1.0", "lint-staged": "^12.5.0", "prettier": "^2.8.8", + "react-dom": "^19.0.0", "vite": "^6.0.6" }, "c8": { diff --git a/src/index.ts b/src/index.ts index 2600f5f..e5fbc88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,4 +9,5 @@ export * from '#src/types' export * from '#src/vite/Vite' +export * from '#src/renders/React' export * from '#src/fastify/FastifyVite' diff --git a/src/renders/React.ts b/src/renders/React.ts new file mode 100644 index 0000000..4efeedd --- /dev/null +++ b/src/renders/React.ts @@ -0,0 +1,53 @@ +/** + * @athenna/vite + * + * (c) João Lenon + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import type { Vite } from '#src/vite/Vite' + +export class React { + /** + * Automatically compile a React component using Vite + * dev server and import it. In production the server + * manifest.json file will be read instead. + * + * @example + * ```ts + * const { createApp } = await React.loadComponent('src/resources/app/app.tsx') + * ``` + */ + public static async loadComponent(path: string): Promise { + const vite: Vite = ioc + .safeUse('Athenna/Core/Server') + .getVitePlugin() + .getVite() + + return vite.ssrLoadModule(path) + } + + /** + * Render a React component to an HTML string. + * + * @example + * ```ts + * const { createApp } = await React.loadComponent('src/resources/app/app.tsx') + * + * const htmlElement = await React.renderComponent(createApp(request.baseUrl)) + * ``` + */ + public static async renderComponent(component: any) { + const reactDom = await import('react-dom/server') + + if (!reactDom) { + throw new Error( + 'ReactDOM is not installed to process rendering, please run "npm install react react-dom".' + ) + } + + return reactDom.renderToString(component) + } +} diff --git a/src/types/fastify/FastifyViteOptions.ts b/src/types/fastify/FastifyViteOptions.ts index 08ee5a7..77c7d1a 100644 --- a/src/types/fastify/FastifyViteOptions.ts +++ b/src/types/fastify/FastifyViteOptions.ts @@ -76,14 +76,6 @@ export type FastifyViteOptions = { */ ssrBuildDirectory?: string - /** - * Path to the SSR manifest file relative from the root of - * the application. - * - * @default Path.public('assets/server/.vite/manifest.json') - */ - ssrManifestFile?: string - /** * A custom set of attributes to apply on all * script tags injected by edge `@vite()` tag. diff --git a/src/vite/Vite.ts b/src/vite/Vite.ts index 59b9371..8ee97db 100644 --- a/src/vite/Vite.ts +++ b/src/vite/Vite.ts @@ -490,7 +490,9 @@ export class Vite { } if (!this.ssrManifestCache) { - this.ssrManifestCache = this.readFileAsJSON(this.options.ssrManifestFile) + this.ssrManifestCache = this.readFileAsJSON( + `${this.options.ssrBuildDirectory}${path.sep}.vite${path.sep}manifest.json` + ) } return this.ssrManifestCache!