commit d4f2474436f809d5295f40fb46dc967590000078 Author: Alejandro Lembke Barrientos Date: Fri Sep 22 16:56:31 2023 +0000 First commit creating version 0.0.1 diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..8e494e5 --- /dev/null +++ b/.babelrc @@ -0,0 +1,7 @@ +{ + "presets": [ + ["@babel/preset-env", {"targets": {"node": "current"}}], + "@babel/preset-react", + "@babel/preset-typescript" + ] +} \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..f90dd93 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +#Environment +ENV= #Default production +#App Port +PORT= #Default 80 +#PUBLIC URL +PUBLIC_URL= #Default 'auto' +#Prefix URL +PREFIX_URL= #Default '' +#ONLY EXACT PATH +ONLY_EXACT_PATH= #Default false \ No newline at end of file diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..8a28323 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,10 @@ +#Build +build +#Webpack +webpack.config.ts +webpack.config.dev.ts +webpack.config.dev.server.ts +webpack.cy.config.ts +#Service Worker +service-worker.ts +serviceWorkerRegistration.ts \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..c17b153 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,52 @@ +module.exports = { + 'env': { + 'browser': true, + 'node': true, + 'es2021': true + }, + 'extends': [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:@typescript-eslint/recommended' + ], + 'parser': '@typescript-eslint/parser', + 'parserOptions': { + 'ecmaFeatures': { + 'jsx': true + }, + 'ecmaVersion': 'latest', + 'sourceType': 'module' + }, + 'plugins': [ + 'react', + '@typescript-eslint' + ], + 'rules': { + 'indent': [ + 'error', + 'tab' + ], + 'linebreak-style': [ + 'error', + 'unix' + ], + 'quotes': [ + 'error', + 'single' + ], + 'semi': [ + 'error', + 'always' + ], + 'eol-last': [ + 'error', + 'always' + ], + '@typescript-eslint/no-var-requires': 0, + }, + 'settings': { + 'react': { + 'version': 'detect', + } + } +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d29ae59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +#node_modules ignore +node_modules +#.envs +.env +#builds +build +src/server/main +src/server/react-server +src/server/web/dist +src/server/web/utils +#cypress +cypress/videos \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..62ec352 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 Alejandro Lembke Barrientos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/PRNameGenerator.ts b/PRNameGenerator.ts new file mode 100644 index 0000000..d6a30a5 --- /dev/null +++ b/PRNameGenerator.ts @@ -0,0 +1,11 @@ +const PRName = function () { + let ID = ''; + // let characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + const characters = '0123456789'; + for ( let i = 0; i < 6; i++ ) { + ID += characters.charAt(Math.floor(Math.random() * 10)); + } + return 'PR-'+ID; +}; + +console.log(PRName()); diff --git a/README.md b/README.md new file mode 100644 index 0000000..d7680f6 --- /dev/null +++ b/README.md @@ -0,0 +1,78 @@ +# Create React SSR + +This project aims to have a starter kit for creating a new React app with Server Side Rendering and tools that generally go along with it. + +It is not a project like create-react-app, create-react-app is used as a starter kit that handles all your scripts underneath, this is a project for developers who want more control over their application. + +Tech(Library or Framework) | Version | +--- | --- | +React (Render Library) | 18.2.0 +Redux (Global State Management) | 4.2.1 +React Router DOM (Routing) | 6.15.0 +Jest (Testing) | 29.6.4 +Cypress (E2E Testing) | 13.1.0 +Typescript | 5.2.2 + +## Setup +To create a new project run in the terminal: +``` +npx @aleleba/create-react-ssr app-name +``` +Then run: +``` +cd app-name +``` +You will need to create a new .env file at the root of the project for global config. +This is an exaple of config. +``` +#Environment +ENV= #Default production +#App Port +PORT= #Default 80 +#PUBLIC URL +PUBLIC_URL= #Default 'auto' +#Prefix URL +PREFIX_URL= #Default '' +#ONLY EXACT PATH +ONLY_EXACT_PATH= #Default false +``` +The default environment is production, the app port defauld is 80 and the default public url is "auto", use prefix url if you want a prefix on base url, use exact path to validate if you want to have strict exact paths. + +### For Development +In the terminal run: +``` +npm run start:dev +``` +The ENV enviroment variable should be "development" and choose the port of your preference with the enviroment variable PORT. + +You will find the root component on: +``` +scr/frontend/components/App.tsx +``` +You will find the Initial Component on: +``` +scr/frontend/components/InitialComponent.tsx +``` + +The manage of the routes you should find on: +``` +scr/routes +``` +It is using "useRoutes" hook for working, more information for this here: (https://reactrouter.com/docs/en/v6/api#useroutes) + +This will start the app in development mode, also it have Hot Reloading! +Enjoy coding! + +### For Production +In the terminal run: +``` +npm run build +``` +It will create a build folder and run: +``` +npm start +``` +This will start the app. + +## Cheers +Hope you enjoy this proyect! Sincerely Alejandro Lembke Barrientos. diff --git a/config/index.ts b/config/index.ts new file mode 100644 index 0000000..b36bb24 --- /dev/null +++ b/config/index.ts @@ -0,0 +1,17 @@ +export const deFaultValues = { + ENV: 'production', + PORT: 80, + PUBLIC_URL: 'auto', + PREFIX_URL: '', + ONLY_EXACT_PATH: false, +}; + +export const config = { + ENV: process.env.ENV ? process.env.ENV : deFaultValues.ENV, + PORT: process.env.PORT ? process.env.PORT : deFaultValues.PORT, + PUBLIC_URL: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : deFaultValues.PUBLIC_URL, + PREFIX_URL: process.env.PREFIX_URL ? process.env.PREFIX_URL : deFaultValues.PREFIX_URL, + ONLY_EXACT_PATH: process.env.ONLY_EXACT_PATH ? process.env.ONLY_EXACT_PATH === 'true' : deFaultValues.ONLY_EXACT_PATH, +}; + +export default config; diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000..6a51df3 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'cypress'; +import webpackConfig from './webpack.cy.config'; + +export default defineConfig({ + env: {}, + e2e: { + /*setupNodeEvents(on, config) { + // implement node event listeners here + },*/ + baseUrl: 'http://localhost:3000', + specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}', + experimentalRunAllSpecs: true, + }, + component: { + specPattern: 'src/**/*.cy.{js,jsx,ts,tsx}', + devServer: { + framework: 'react', + bundler: 'webpack', + webpackConfig: webpackConfig, + }, + viewportWidth: 1280, + viewportHeight: 720, + } +}); diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..f94867b --- /dev/null +++ b/jest.config.js @@ -0,0 +1,19 @@ +const { pathsToModuleNameMapper } = require('ts-jest'); +const { compilerOptions } = require('./tsconfig'); + +const aliases = pathsToModuleNameMapper(compilerOptions.paths, { + prefix: '' +}); + +module.exports = { + setupFilesAfterEnv: ['/setupTest.ts'], + testPathIgnorePatterns: ['/node_modules/', '\\.cy.(js|jsx|ts|tsx)$'], + testEnvironment: 'jsdom', + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], + modulePathIgnorePatterns: ['/cypress/'], + moduleNameMapper: { + ...aliases, + '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '/src/__mocks__/fileMock.ts', + '\\.(css|sass|scss|less)$': 'identity-obj-proxy', + } +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..72930fd --- /dev/null +++ b/package.json @@ -0,0 +1,122 @@ +{ + "name": "@aleleba/create-react-go-ssr", + "version": "0.0.1", + "description": "Starter Kit of server side render of react", + "bin": "./bin/cli.js", + "main": "src/server/index", + "scripts": { + "start": "webpack watch --config webpack.config.ts", + "buildTsxToString": "webpack --config webpack.config.convert.ts", + "start:dev": "rm -rf build && webpack --mode=development --config webpack.config.dev.server.ts", + "start:dev-win": "(if exist build rmdir /s /Q build) && webpack --mode=development --config webpack.config.dev.server.ts", + "build": "webpack-cli --config webpack.config.ts", + "lint": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx", + "lint:fix": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx --fix", + "test": "jest", + "test:watch": "jest --watch", + "check-updates": "npx npm-check-updates -u && npm i", + "cy:open": "npx cypress open", + "cy:run": "cypress run", + "cy:run-component": "cypress run --headless --component" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/aleleba/create-react-ssr.git" + }, + "keywords": [ + "create react app", + "react", + "ssr", + "typescript", + "redux" + ], + "author": "Alejandro Lembke Barrientos", + "license": "MIT", + "bugs": { + "url": "https://github.com/aleleba/create-react-ssr/issues" + }, + "homepage": "https://github.com/aleleba/create-react-ssr#readme", + "dependencies": { + "@babel/register": "^7.22.15", + "dotenv": "^16.3.1", + "express": "^4.18.2", + "helmet": "^7.0.0", + "history": "^5.3.0", + "ignore-styles": "^5.0.1", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-redux": "^8.1.2", + "react-router-dom": "^6.15.0", + "react-router-hash-link": "^2.4.3", + "redux": "^4.2.1", + "webpack": "^5.88.2", + "webpack-manifest-plugin": "^5.0.0", + "workbox-background-sync": "^7.0.0", + "workbox-broadcast-update": "^7.0.0", + "workbox-cacheable-response": "^7.0.0", + "workbox-core": "^7.0.0", + "workbox-expiration": "^7.0.0", + "workbox-google-analytics": "^7.0.0", + "workbox-navigation-preload": "^7.0.0", + "workbox-precaching": "^7.0.0", + "workbox-range-requests": "^7.0.0", + "workbox-routing": "^7.0.0", + "workbox-strategies": "^7.0.0", + "workbox-streams": "^7.0.0" + }, + "devDependencies": { + "@babel/core": "^7.22.17", + "@babel/preset-env": "^7.22.15", + "@babel/preset-react": "^7.22.15", + "@babel/preset-typescript": "^7.22.15", + "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", + "@redux-devtools/extension": "^3.2.5", + "@testing-library/jest-dom": "^6.1.3", + "@testing-library/react": "^14.0.0", + "@testing-library/user-event": "^14.4.3", + "@types/jest": "^29.5.4", + "@types/node": "^20.6.0", + "@types/react": "^18.2.21", + "@types/react-dom": "^18.2.7", + "@types/webpack": "^5.28.2", + "@types/webpack-hot-middleware": "^2.25.6", + "@types/webpack-node-externals": "^3.0.0", + "@typescript-eslint/eslint-plugin": "^6.6.0", + "@typescript-eslint/parser": "^6.6.0", + "babel-jest": "^29.6.4", + "babel-loader": "^9.1.3", + "clean-webpack-plugin": "^4.0.0", + "compression-webpack-plugin": "^10.0.0", + "copy-webpack-plugin": "^11.0.0", + "css-loader": "^6.8.1", + "css-minimizer-webpack-plugin": "^5.0.1", + "cypress": "^13.1.0", + "eslint": "^8.49.0", + "eslint-plugin-react": "^7.33.2", + "eslint-webpack-plugin": "^4.0.1", + "file-loader": "^6.2.0", + "html-webpack-plugin": "^5.5.3", + "identity-obj-proxy": "^3.0.0", + "jest": "^29.6.4", + "jest-environment-jsdom": "^29.6.4", + "jest-fetch-mock": "^3.0.3", + "mini-css-extract-plugin": "^2.7.6", + "react-refresh": "^0.14.0", + "resolve-ts-aliases": "^1.0.1", + "sass": "^1.66.1", + "sass-loader": "^13.3.2", + "style-loader": "^3.3.3", + "terser-webpack-plugin": "^5.3.9", + "ts-jest": "^29.1.1", + "typescript": "^5.2.2", + "url-loader": "^4.1.1", + "webpack-cli": "^5.1.4", + "webpack-dev-middleware": "^6.1.1", + "webpack-dev-server": "^4.15.1", + "webpack-hot-middleware": "^2.25.4", + "webpack-node-externals": "^3.0.0", + "webpack-shell-plugin-next": "^2.3.1", + "workbox-webpack-plugin": "^7.0.0", + "workbox-window": "^7.0.0" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..a11777c Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/img/logo.svg b/public/img/logo.svg new file mode 100644 index 0000000..9dfc1c0 --- /dev/null +++ b/public/img/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..62140b9 --- /dev/null +++ b/public/index.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + App + + +
+ + + + + \ No newline at end of file diff --git a/public/logo192.png b/public/logo192.png new file mode 100644 index 0000000..fc44b0a Binary files /dev/null and b/public/logo192.png differ diff --git a/public/logo512.png b/public/logo512.png new file mode 100644 index 0000000..a4e47a6 Binary files /dev/null and b/public/logo512.png differ diff --git a/public/manifest.json b/public/manifest.json new file mode 100644 index 0000000..8508431 --- /dev/null +++ b/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "React App", + "name": "React App SSR Sample", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": "/", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" + } \ No newline at end of file diff --git a/service-worker.ts b/service-worker.ts new file mode 100644 index 0000000..c91ff17 --- /dev/null +++ b/service-worker.ts @@ -0,0 +1,61 @@ +// This service worker can be customized! +// See https://developers.google.com/web/tools/workbox/modules +// for the list of available Workbox modules, or add any other +// code you'd like. +// You can also remove this file if you'd prefer not to use a +// service worker, and the Workbox build step will be skipped. + +import { clientsClaim } from 'workbox-core'; +import { ExpirationPlugin } from 'workbox-expiration'; +import { precacheAndRoute } from 'workbox-precaching'; //createHandlerBoundToURL +import { registerRoute } from 'workbox-routing'; +import { StaleWhileRevalidate, NetworkFirst } from 'workbox-strategies'; + +clientsClaim(); + +// This allows the web app to trigger skipWaiting via +// @ts-ignore:next-line +self.skipWaiting(); + +// Precache all of the assets generated by your build process. +// Their URLs are injected into the manifest variable below. +// This variable must be present somewhere in your service worker file, +// even if you decide not to use precaching. See https://cra.link/PWA +// @ts-ignore:next-line +precacheAndRoute(self.__WB_MANIFEST); + +// An example runtime caching route for requests that aren't handled by the +// precache, in this case same-origin .png requests like those from in public/ +registerRoute( + // Add in any other file extensions or routing criteria as needed. + ({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst. + new StaleWhileRevalidate({ + cacheName: 'images', + plugins: [ + // Ensure that once this runtime cache reaches a maximum size the + // least-recently used images are removed. + new ExpirationPlugin({ maxEntries: 50 }), + ], + }) +); + +// Any other custom service worker logic can go here. + +//Estrategy for Default. It should be at the end. +registerRoute(/^https?.*/, new NetworkFirst(), 'GET'); + +//Wait for Notification. +self.addEventListener('push', function (e) { + + // @ts-ignore:next-line + const data = e.data.json(); + + // @ts-ignore:next-line + registration.showNotification(data.title, { + body: data.message, + icon: 'favicon.ico' + }); + +}); + +console.log('hello from service-worker.js the new one.'); diff --git a/serviceWorkerRegistration.ts b/serviceWorkerRegistration.ts new file mode 100644 index 0000000..abd873a --- /dev/null +++ b/serviceWorkerRegistration.ts @@ -0,0 +1,21 @@ +import { Workbox } from 'workbox-window'; +import packageJson from './package.json'; + +const serviceWorkerRegistration = () => { + if ('serviceWorker' in navigator) { + const wb = new Workbox('service-worker.js'); + + wb.addEventListener('installed', event => { + if(event.isUpdate){ + if(confirm('New app update is avaible, Click Ok to refresh')){ + window.location.reload(); + }; + console.log(`Se actualiza la app a version ${packageJson.version}`); + }; + }); + + wb.register(); + }; +}; + +export default serviceWorkerRegistration; \ No newline at end of file diff --git a/setupTest.ts b/setupTest.ts new file mode 100644 index 0000000..21f0c55 --- /dev/null +++ b/setupTest.ts @@ -0,0 +1,20 @@ +// jest-dom adds custom jest matchers for asserting on DOM nodes. +// allows you to do things like: +// expect(element).toHaveTextContent(/react/i) +// learn more: https://github.com/testing-library/jest-dom +import '@testing-library/jest-dom'; + +//import fetch Mock +import fetchMock from 'jest-fetch-mock'; +fetchMock.enableMocks(); + +//Fixing Pollyfill for react-slick +window.matchMedia = +window.matchMedia || +function() { + return { + matches: false, + addListener: () => {/**/}, + removeListener: () => {/**/} + }; +}; diff --git a/src/@types/express/index.d.ts b/src/@types/express/index.d.ts new file mode 100644 index 0000000..301ddee --- /dev/null +++ b/src/@types/express/index.d.ts @@ -0,0 +1,9 @@ +import * as express from "express" + +declare global { + namespace Express { + interface Request { + hashManifest?: Record + } + } +} \ No newline at end of file diff --git a/src/@types/index.d.ts b/src/@types/index.d.ts new file mode 100644 index 0000000..156473a --- /dev/null +++ b/src/@types/index.d.ts @@ -0,0 +1,4 @@ +declare module "*.svg" { + const content: any; + export default content; +} diff --git a/src/frontend/actions/index.ts b/src/frontend/actions/index.ts new file mode 100644 index 0000000..b95c148 --- /dev/null +++ b/src/frontend/actions/index.ts @@ -0,0 +1,9 @@ +import test, { TTest } from './testAction'; + +export type TAction = TTest + +const actions = { + test +} + +export default actions \ No newline at end of file diff --git a/src/frontend/actions/testAction.ts b/src/frontend/actions/testAction.ts new file mode 100644 index 0000000..30593bf --- /dev/null +++ b/src/frontend/actions/testAction.ts @@ -0,0 +1,25 @@ +export enum ActionTypesTest { + ChangeHello = 'CHANGE_HELLO' +} + +export interface IChangeHello { + type: ActionTypesTest.ChangeHello + payload: IChangeHelloPayload +} + +export interface IChangeHelloPayload { + hello: any | undefined +} + +export type TTest = IChangeHello + +const changeHello = (payload: string) => ({ + type: ActionTypesTest.ChangeHello, + payload +}) + +const actions = { + changeHello +} + +export default actions \ No newline at end of file diff --git a/src/frontend/components/App.tsx b/src/frontend/components/App.tsx new file mode 100644 index 0000000..cd9f890 --- /dev/null +++ b/src/frontend/components/App.tsx @@ -0,0 +1,17 @@ +import React, { useEffect } from 'react'; +import PrincipalRoutes from './PrincipalRoutes'; + +const App = () => { + useEffect(() => { + const ws = new WebSocket('wss://xs70kvlc-3000.use.devtunnels.ms/ws'); + ws.onmessage = (event) => { + if (event.data === 'reload') { + window.location.reload(); + } + }; + }, []); + + return ; +}; + +export default App; diff --git a/src/frontend/components/InitialComponent.scss b/src/frontend/components/InitialComponent.scss new file mode 100644 index 0000000..a988b49 --- /dev/null +++ b/src/frontend/components/InitialComponent.scss @@ -0,0 +1,38 @@ +.App { + text-align: center; +} + +.App-logo { + height: 40vmin; + pointer-events: none; +} + +@media (prefers-reduced-motion: no-preference) { + .App-logo { + animation: App-logo-spin infinite 20s linear; + } +} + +.App-header { + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; +} + +.App-link { + color: #61dafb; +} + +@keyframes App-logo-spin { + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} \ No newline at end of file diff --git a/src/frontend/components/InitialComponent.tsx b/src/frontend/components/InitialComponent.tsx new file mode 100644 index 0000000..fb05136 --- /dev/null +++ b/src/frontend/components/InitialComponent.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import './InitialComponent.scss'; +import { Link } from 'react-router-dom'; + +const InitialComponent = () => ( +
+
+ logo +

+ Edit src/frontend/InitialComponent.jsx and save to reload. +

+ + Learn React + + Other Component +
+
+); + +export default InitialComponent; diff --git a/src/frontend/components/OtherComponent.tsx b/src/frontend/components/OtherComponent.tsx new file mode 100644 index 0000000..b249292 --- /dev/null +++ b/src/frontend/components/OtherComponent.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +// import logo from '../logo.svg'; +import './InitialComponent.scss'; +import { Link } from 'react-router-dom'; + +const OtherComponent = () => ( +
+
+ logo +

+ Edit src/frontend/OtherComponent.jsx and save to reload. +

+ Initial Component +
+
+); + +export default OtherComponent; diff --git a/src/frontend/components/PrincipalRoutes.tsx b/src/frontend/components/PrincipalRoutes.tsx new file mode 100644 index 0000000..73bf9ef --- /dev/null +++ b/src/frontend/components/PrincipalRoutes.tsx @@ -0,0 +1,11 @@ +//Router +import { useRoutes } from 'react-router-dom'; +//Routes +import routes from '../../routes'; + +const PrincipalRoutes = () => { + let element = useRoutes(routes); + return element; +}; + +export default PrincipalRoutes; diff --git a/src/frontend/components/__tests__/App.test.cy.tsx b/src/frontend/components/__tests__/App.test.cy.tsx new file mode 100644 index 0000000..face8a4 --- /dev/null +++ b/src/frontend/components/__tests__/App.test.cy.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { ProviderMock } from '@mocks'; +import App from '@components/App'; + +describe('Testing Card Component', () => { + beforeEach(() => { + cy.mount( + + + + ); + }); + it('Show Text', () => { + cy.get('p').contains('Edit src/frontend/InitialComponent.jsx and save to reload.'); + }); +}); diff --git a/src/frontend/components/__tests__/App.test.tsx b/src/frontend/components/__tests__/App.test.tsx new file mode 100644 index 0000000..6e03a0d --- /dev/null +++ b/src/frontend/components/__tests__/App.test.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import { ProviderMock } from '@mocks'; +import App from '@components/App'; + +describe(' Component', () => { + beforeEach(() => { + fetchMock.resetMocks(); + }); + + it('Should render root Component', async () => { + fetchMock.mockResponseOnce(JSON.stringify({ + //First Data Fetch + data: 'data' + })); + + render( + + + + ) + }) +}) \ No newline at end of file diff --git a/src/frontend/converter/tsxToString.tsx b/src/frontend/converter/tsxToString.tsx new file mode 100644 index 0000000..9f77ebc --- /dev/null +++ b/src/frontend/converter/tsxToString.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { renderToString } from 'react-dom/server'; +import { StaticRouter } from 'react-router-dom/server'; +import App from '../components/App'; + +const url = process.argv[2]; + +const render = () => { + return renderToString( + + + + ); +}; + +console.log(render()); diff --git a/src/frontend/index.tsx b/src/frontend/index.tsx new file mode 100644 index 0000000..885e5a8 --- /dev/null +++ b/src/frontend/index.tsx @@ -0,0 +1,81 @@ +import React from 'react'; +import { hydrateRoot, createRoot } from 'react-dom/client'; +// Router +import { BrowserRouter as Router } from 'react-router-dom'; +// Redux +import { Provider } from 'react-redux'; +import { IInitialState } from './reducers/index'; +import setStore from './setStore'; +import { config } from '../../config'; + +import './styles/global.scss'; +import App from './components/App'; +// import serviceWorkerRegistration from '../../serviceWorkerRegistration'; + +declare global { + interface Window { + __PRELOADED_STATE__?: IInitialState; + } +} + +declare global { + interface NodeModule { + hot?: IHot; + } +} + +interface IHot { + accept: unknown +} + +const { ENV, PREFIX_URL } = config; + +const preloadedState = window.__PRELOADED_STATE__; +const store = setStore({ initialState: preloadedState }); + +delete window.__PRELOADED_STATE__; + +const container = document.getElementById('app')!; + +if(ENV === 'development') { + const root = createRoot(container); + root.render( + + + + + + ); +} + +// add "const root" to be able to rerender. +ENV === 'production' && hydrateRoot(container, + + + + + , + // Add this comment to update later app and remove warning + /* { + onRecoverableError: (error) => { + console.error("recoverable", error); + } + }, */ +); + +// Use root.render to update later the app +/* root.render( + + + + + +); */ + +/* if((ENV) && (ENV === 'production')){ + serviceWorkerRegistration(); +} */ + +/* if(module.hot){ + module.hot.accept(); +} */ diff --git a/src/frontend/reducers/index.ts b/src/frontend/reducers/index.ts new file mode 100644 index 0000000..29d9721 --- /dev/null +++ b/src/frontend/reducers/index.ts @@ -0,0 +1,14 @@ +import { combineReducers } from 'redux'; +import testReducer from './testReducer'; +import { IChangeHelloPayload } from '../actions/testAction'; + +export interface IInitialState { + testReducer?: IChangeHelloPayload | undefined +} + +const rootReducer = combineReducers({ + // Here comes the reducers + testReducer +}); + +export default rootReducer; diff --git a/src/frontend/reducers/initialState.ts b/src/frontend/reducers/initialState.ts new file mode 100644 index 0000000..411deaa --- /dev/null +++ b/src/frontend/reducers/initialState.ts @@ -0,0 +1,2 @@ +const initialState = {}; +export default initialState; diff --git a/src/frontend/reducers/testReducer.ts b/src/frontend/reducers/testReducer.ts new file mode 100644 index 0000000..6c27a59 --- /dev/null +++ b/src/frontend/reducers/testReducer.ts @@ -0,0 +1,20 @@ +import { TAction } from '../actions'; + +const initialState = { + hello: 'world' +}; + +const testReducer = (state = initialState, action: TAction) => { + switch (action.type){ + case 'CHANGE_HELLO': { + const newHello = action.payload.hello; + return { + hello: newHello + }; + } + default: + return state; + } +}; + +export default testReducer; diff --git a/src/frontend/setStore.ts b/src/frontend/setStore.ts new file mode 100644 index 0000000..ace5fe7 --- /dev/null +++ b/src/frontend/setStore.ts @@ -0,0 +1,27 @@ +// Redux +import { legacy_createStore as createStore} from 'redux'; //, applyMiddleware +// import { Provider } from 'react-redux'; +import { composeWithDevTools as composeWithDevToolsWeb } from '@redux-devtools/extension'; +import { config } from '../../config'; +import reducer, { IInitialState } from './reducers'; + + +const { ENV } = config; + +const composeEnhancers = composeWithDevToolsWeb({ + // Specify here name, actionsBlacklist, actionsCreators and other options +}); + +const setStore = ({ initialState }: { initialState: IInitialState | undefined }) => { + const store = ENV === 'development' ? createStore( + reducer, + initialState, + composeEnhancers(), + ) : createStore( + reducer, + initialState, + ); + return store; +}; + +export default setStore; diff --git a/src/frontend/styles/global.scss b/src/frontend/styles/global.scss new file mode 100644 index 0000000..fda9030 --- /dev/null +++ b/src/frontend/styles/global.scss @@ -0,0 +1,5 @@ +$base-color: #282c34; + +body { + background-color: $base-color; +} diff --git a/src/routes/index.tsx b/src/routes/index.tsx new file mode 100644 index 0000000..0bac04a --- /dev/null +++ b/src/routes/index.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import InitialComponent from '../frontend/components/InitialComponent'; +import OtherComponent from '../frontend/components/OtherComponent'; + +const OTHER_COMPONENT = { + path: '/other-component', + element: +}; + +const INITIAL_COMPONENT = { + path: '/', + element: , +}; + +export default [ INITIAL_COMPONENT, OTHER_COMPONENT ]; diff --git a/src/server/config/config.go b/src/server/config/config.go new file mode 100644 index 0000000..881dd9b --- /dev/null +++ b/src/server/config/config.go @@ -0,0 +1,13 @@ +package config + +import ( + "log" + "github.com/joho/godotenv" +) + +func LoadEnv() { + err := godotenv.Load(".env") + if err != nil { + log.Fatal("Error loading .env file") + } +} \ No newline at end of file diff --git a/src/server/go.mod b/src/server/go.mod new file mode 100644 index 0000000..d7f93e0 --- /dev/null +++ b/src/server/go.mod @@ -0,0 +1,49 @@ +module aleleba/react-server + +go 1.21.1 + +require github.com/joho/godotenv v1.5.1 + +require github.com/gofiber/fiber/v2 v2.49.1 + +require ( + cloudeng.io/os v0.0.0-20230309184059-9263072b1423 // indirect + cloudeng.io/webapp v0.0.0-20230913164637-56a6ca867a22 // indirect + github.com/andybalholm/brotli v1.0.5 // indirect + github.com/clarkmcc/go-typescript v0.7.0 // indirect + github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/dop251/goja v0.0.0-20230919151941-fc55792775de // indirect + github.com/fasthttp/websocket v1.5.3 // indirect + github.com/fatih/color v1.9.0 // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/githubnemo/CompileDaemon v1.4.0 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/gofiber/adaptor/v2 v2.2.1 // indirect + github.com/gofiber/template v1.8.2 // indirect + github.com/gofiber/template/html/v2 v2.0.5 // indirect + github.com/gofiber/utils v1.1.0 // indirect + github.com/gofiber/websocket/v2 v2.2.1 // indirect + github.com/google/pprof v0.0.0-20230207041349-798e818bf904 // indirect + github.com/google/uuid v1.3.1 // indirect + github.com/gopherjs/gopherjs v1.17.2 // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/labstack/echo/v4 v4.11.1 // indirect + github.com/labstack/gommon v0.4.0 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/radovskyb/watcher v1.0.7 // indirect + github.com/rivo/uniseg v0.4.4 // indirect + github.com/robertkrimen/otto v0.2.1 // indirect + github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasthttp v1.49.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + github.com/valyala/tcplisten v1.0.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/net v0.12.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.11.0 // indirect + gopkg.in/sourcemap.v1 v1.0.5 // indirect +) diff --git a/src/server/go.sum b/src/server/go.sum new file mode 100644 index 0000000..64a75a4 --- /dev/null +++ b/src/server/go.sum @@ -0,0 +1,212 @@ +cloudeng.io/algo v0.0.0-20221118174443-5eef698118a6/go.mod h1:dgJhQ6JXLysnBQG09/5Q56rwJfgyKXlnDbKzko9MhdA= +cloudeng.io/algo v0.0.0-20230304024356-5d6b315e83f0/go.mod h1:80eC8gNGsJP8ZBw+xCIh1kIpt77az1W7fJ+zqLRiE64= +cloudeng.io/algo v0.0.0-20230307023515-8a194fbc7867/go.mod h1:80eC8gNGsJP8ZBw+xCIh1kIpt77az1W7fJ+zqLRiE64= +cloudeng.io/cmdutil v0.0.0-20230130231933-fe585c604aed/go.mod h1:nX3v2San8WLgYYmtom/+1GxmfKh3wWSyvnLvoxUz784= +cloudeng.io/cmdutil v0.0.0-20230309184059-9263072b1423/go.mod h1:HOYjjVIvIbRF5li5EIzqyFYxlzPmxNskXO74HDICgk8= +cloudeng.io/debug v0.0.0-20230105021008-948d22470af5/go.mod h1:UDKUtuaCh0+iaDe5+i70G3l3liexOPeU3h0lyUeAt+o= +cloudeng.io/errors v0.0.7/go.mod h1:xWamLL6tn3roKI6MRRFkw1jUkJL9s7CJzFYfaxuhHZk= +cloudeng.io/errors v0.0.8/go.mod h1:xWamLL6tn3roKI6MRRFkw1jUkJL9s7CJzFYfaxuhHZk= +cloudeng.io/file v0.0.0-20230221162436-db7c7fd2cf04/go.mod h1:kcAJeuW9/AL/sw/sySiIoF+BHjDKcHawqTPyDAdwmrQ= +cloudeng.io/file v0.0.0-20230306215119-e71b407605cc/go.mod h1:1wtCIz069qanFkcbpb8F3msDf+W7G3Wjw5Om+KISyuQ= +cloudeng.io/file v0.0.0-20230309184059-9263072b1423/go.mod h1:Ow6f4Wu+6U8XQFP4U2Kr9izv+oU/jVMZyuCTmf6FaOc= +cloudeng.io/io v0.0.0-20230309184059-9263072b1423/go.mod h1:6Scpv2/l9yI5tTE0esMr5SN1bemcRhm4d+RR6tCeH1M= +cloudeng.io/net v0.0.0-20230214072348-e50d5014b274/go.mod h1:13BN0nf3e35fdeiFH0pgduqEhOgWLMvnhYtRYg4WKE8= +cloudeng.io/net v0.0.0-20230304024356-5d6b315e83f0/go.mod h1:D0bZpZHSBIlyvBG/i+JOubQDjxbYJX/eas8v9byykfc= +cloudeng.io/net v0.0.0-20230307023515-8a194fbc7867/go.mod h1:b7cZgzjeLZxVX1DJAEEQSdI9zLhruVy9XG20voObvKk= +cloudeng.io/os v0.0.0-20221118174443-5eef698118a6/go.mod h1:hhGubbGRTXGbe6T0ZU2RSXfCo1Ktvzfb0BcpkK4xqvY= +cloudeng.io/os v0.0.0-20230304024356-5d6b315e83f0/go.mod h1:Ak4HQwvBx5/6w5XT9VKS1BekYSi6QQAEvlbOtzN7eJA= +cloudeng.io/os v0.0.0-20230307023515-8a194fbc7867/go.mod h1:gBS7WALqIPNN8ovv1DIuf5/MlQ90mKDjMSjwN/ZvoD4= +cloudeng.io/os v0.0.0-20230309184059-9263072b1423 h1:+COcYDTwuT4AhsSq2G/BreMJgky2J9atZpVfGnh+Now= +cloudeng.io/os v0.0.0-20230309184059-9263072b1423/go.mod h1:6qBe7uAG6enJ7Nt+4Z8ddua57LYK4pnlmaDMGJxa/3s= +cloudeng.io/path v0.0.4/go.mod h1:HTU24UoiQVjzSjN+K2kD8hpkBnoLGZh0OITJr3kfxUk= +cloudeng.io/path v0.0.8/go.mod h1:S9dU9pNDkx0EUIHY+Pcyfzs3/cItCI9lyClkwqv0PtM= +cloudeng.io/sync v0.0.8/go.mod h1:76qdZzMQSN+iPeQxY9MSbnSELKQmcd9E6pnfRgWgN8s= +cloudeng.io/sys v0.0.0-20211030052257-ec4c6f8b878e/go.mod h1:fNIWqX26NzdShrH3lBfxzPhP1jju7JOt7Rg8PxBvYvA= +cloudeng.io/sys v0.0.0-20221118174443-5eef698118a6/go.mod h1:fNIWqX26NzdShrH3lBfxzPhP1jju7JOt7Rg8PxBvYvA= +cloudeng.io/sys v0.0.0-20230221162436-db7c7fd2cf04/go.mod h1:DPEAeWYUvJ684GySwz0pzPtYxaPnZ7dyz1NHUcdNCEk= +cloudeng.io/sys v0.0.0-20230304024356-5d6b315e83f0/go.mod h1:R5nydEKnw8t2BjpQH87CGG59WEBb2rcRRTsqz1Ak5/s= +cloudeng.io/sys v0.0.0-20230306215119-e71b407605cc/go.mod h1:Lt8onmpJi8I5Xt2EyHtIfPkn4YaH12n4ehTr199ZGjY= +cloudeng.io/sys v0.0.0-20230307023515-8a194fbc7867/go.mod h1:Lt8onmpJi8I5Xt2EyHtIfPkn4YaH12n4ehTr199ZGjY= +cloudeng.io/text v0.0.11/go.mod h1:99L3CQ55YhUy2+lHlFPowYyCoXO86fmkvNtcMT2X3GU= +cloudeng.io/webapp v0.0.0-20230913164637-56a6ca867a22 h1:q1HmQki39a8BC9qPu62b3OnGsk0kohKzZseATT/8PpU= +cloudeng.io/webapp v0.0.0-20230913164637-56a6ca867a22/go.mod h1:udLHBvexojKGB0JgZjJW3ZzDaSCqNU566KTbpK9EFmM= +github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= +github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= +github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= +github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clarkmcc/go-typescript v0.7.0 h1:3nVeaPYyTCWjX6Lf8GoEOTxME2bM5tLuWmwhSZ86uxg= +github.com/clarkmcc/go-typescript v0.7.0/go.mod h1:IZ/nzoVeydAmyfX7l6Jmp8lJDOEnae3jffoXwP4UyYg= +github.com/cosnicolaou/pudge v1.0.6/go.mod h1:PSbC4eylkssiQ+gAE+AWaMQSo41g/otqflfbcsMrupk= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20211115154819-26ebff68a7d5 h1:7eyn0Cp9ezNbo2Vb4ttgJyWsFrRWP3oyHEw4PHKYlps= +github.com/dop251/goja v0.0.0-20211115154819-26ebff68a7d5/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20230919151941-fc55792775de h1:lA38Xtzr1Wo+iQdkN2E11ziKXJYRxLlzK/e2/fdxoEI= +github.com/dop251/goja v0.0.0-20230919151941-fc55792775de/go.mod h1:QMWlm50DNe14hD7t24KEqZuUdC9sOTy8W6XbCU1mlw4= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= +github.com/fasthttp/websocket v1.5.3 h1:TPpQuLwJYfd4LJPXvHDYPMFWbLjsT91n3GpWtCQtdek= +github.com/fasthttp/websocket v1.5.3/go.mod h1:46gg/UBmTU1kUaTcwQXpUxtRwG2PvIZYeA8oL6vF3Fs= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/githubnemo/CompileDaemon v1.4.0 h1:z96Qu4tj+RzRfF+L7f1O6E8ion5JQlisWeXWc2wzwDQ= +github.com/githubnemo/CompileDaemon v1.4.0/go.mod h1:/G125r3YBIp6rcXtCZfiEHwFzcl7GSsNSwylxSNrkMA= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/gofiber/adaptor/v2 v2.2.1 h1:givE7iViQWlsTR4Jh7tB4iXzrlKBgiraB/yTdHs9Lv4= +github.com/gofiber/adaptor/v2 v2.2.1/go.mod h1:AhR16dEqs25W2FY/l8gSj1b51Azg5dtPDmm+pruNOrc= +github.com/gofiber/fiber/v2 v2.49.1 h1:0W2DRWevSirc8pJl4o8r8QejDR8TV6ZUCawHxwbIdOk= +github.com/gofiber/fiber/v2 v2.49.1/go.mod h1:nPUeEBUeeYGgwbDm59Gp7vS8MDyScL6ezr/Np9A13WU= +github.com/gofiber/template v1.8.2 h1:PIv9s/7Uq6m+Fm2MDNd20pAFFKt5wWs7ZBd8iV9pWwk= +github.com/gofiber/template v1.8.2/go.mod h1:bs/2n0pSNPOkRa5VJ8zTIvedcI/lEYxzV3+YPXdBvq8= +github.com/gofiber/template/html/v2 v2.0.5 h1:BKLJ6Qr940NjntbGmpO3zVa4nFNGDCi/IfUiDB9OC20= +github.com/gofiber/template/html/v2 v2.0.5/go.mod h1:RCF14eLeQDCSUPp0IGc2wbSSDv6yt+V54XB/+Unz+LM= +github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= +github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= +github.com/gofiber/websocket/v2 v2.2.1 h1:C9cjxvloojayOp9AovmpQrk8VqvVnT8Oao3+IUygH7w= +github.com/gofiber/websocket/v2 v2.2.1/go.mod h1:Ao/+nyNnX5u/hIFPuHl28a+NIkrqK7PRimyKaj4JxVU= +github.com/google/pprof v0.0.0-20221219190121-3cb0bae90811/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904 h1:4/hN5RUoecvl+RmJRE2YxKWtnnQls6rQjjW5oV7qg2U= +github.com/google/pprof v0.0.0-20230207041349-798e818bf904/go.mod h1:uglQLonpP8qtYCYyzA+8c/9qtqgA3qsXGYqCPKARAFg= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v1.17.2 h1:fQnZVsXk8uxXIStYb0N4bGk7jeyTalG/wsZjQ25dO0g= +github.com/gopherjs/gopherjs v1.17.2/go.mod h1:pRRIvn/QzFLrKfvEz3qUuEhtE/zLCWfreZ6J5gM2i+k= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= +github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= +github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= +github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/radovskyb/watcher v1.0.7 h1:AYePLih6dpmS32vlHfhCeli8127LzkIgwJGcwwe8tUE= +github.com/radovskyb/watcher v1.0.7/go.mod h1:78okwvY5wPdzcb1UYnip1pvrZNIVEIh/Cm+ZuvsUYIg= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0= +github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk= +github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.49.0 h1:9FdvCpmxB74LH4dPb7IJ1cOSsluR07XG3I1txXWwJpE= +github.com/valyala/fasthttp v1.49.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= +github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/src/server/main.go b/src/server/main.go new file mode 100644 index 0000000..d494302 --- /dev/null +++ b/src/server/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "os" + "fmt" + "net/http" + "path/filepath" + "github.com/labstack/echo/v4" + "github.com/gorilla/websocket" + "github.com/fsnotify/fsnotify" + "aleleba/react-server/config" + "aleleba/react-server/web" + "aleleba/react-server/utils" +) + +func init() { + config.LoadEnv() +} + +func main() { + //Getting the port from the environment + port := os.Getenv("PORT") + + paths := utils.GetRoutes() + + e := echo.New() + + // Watch for file changes in the ./web/dist directory + watcher, err := fsnotify.NewWatcher() + if err != nil { + panic(err) + } + defer watcher.Close() + + if err := filepath.Walk("./web", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + if err := watcher.Add(path); err != nil { + return err + } + } + return nil + }); err != nil { + panic(err) + } + + // Create a websocket upgrader + upgrader := websocket.Upgrader{} + + // Handle websocket connections + e.GET("/ws", func(c echo.Context) error { + // Upgrade the HTTP connection to a websocket connection + ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) + if err != nil { + return err + } + + // Watch for file changes in the ./web/dist directory + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Write == fsnotify.Write { + fmt.Println("modified file:", event.Name) + + // Send a message to the client to reload the page + if err := ws.WriteMessage(websocket.TextMessage, []byte("reload")); err != nil { + fmt.Println("error sending message:", err) + } + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + fmt.Println("error:", err) + } + } + }() + + // Read messages from the client (not used in this example) + /*for { + _, _, err := ws.ReadMessage() + if err != nil { + fmt.Println("error reading message:", err) + break + } + }*/ + + return nil + }) + + web.RegisterHandlers(e, paths) + + // Add your custom api routes here. + // Example: + e.GET("/api", func(c echo.Context) error { + return c.String(http.StatusOK, "Hello, World!") + }) + + e.Logger.Fatal(e.Start(":" + port)) +} \ No newline at end of file diff --git a/src/server/utils/jsxToStringExecuter.go b/src/server/utils/jsxToStringExecuter.go new file mode 100644 index 0000000..5015e05 --- /dev/null +++ b/src/server/utils/jsxToStringExecuter.go @@ -0,0 +1,37 @@ +package utils + +import ( + "fmt" + "os/exec" +) + +func JsxToString(url string) string { + // Set the path to the jsxToString.js file + jsFilePath := "./web/utils/tsxToString.js" + + // Create the command to run Node.js with the jsxToString.js file + cmd := exec.Command("node", jsFilePath, url) + + // Get a pipe to the standard input of the Node.js process + stdin, err := cmd.StdinPipe() + if err != nil { + fmt.Println(err) + return "" + } + + // Write the URL parameter to the standard input of the Node.js process + fmt.Fprintf(stdin, "%s\n", url) + + // Close the standard input pipe + stdin.Close() + + // Get the output and error of the Node.js process + output, err := cmd.CombinedOutput() + if err != nil { + fmt.Println(err) + return string(output) + } + + // Return the output of the Node.js process + return string(output) +} \ No newline at end of file diff --git a/src/server/utils/readRoutes.go b/src/server/utils/readRoutes.go new file mode 100644 index 0000000..361c13d --- /dev/null +++ b/src/server/utils/readRoutes.go @@ -0,0 +1,31 @@ +package utils + +import ( + "os" + "regexp" + "github.com/clarkmcc/go-typescript" +) + +func GetRoutes () []string { + // Read the contents of the TypeScript file + content, err := os.ReadFile("../routes/index.tsx") + if err != nil { + panic(err) + } + + // Transpile the TypeScript code to JavaScript + jsCode, err := typescript.TranspileString(string(content)) + if err != nil { + panic(err) + } + + // Extract the "path" values from the transpiled JavaScript code + re := regexp.MustCompile(`path:\s*['"]([^'"]+)['"]`) + matches := re.FindAllStringSubmatch(jsCode, -1) + paths := make([]string, 0) + for _, match := range matches { + paths = append(paths, match[1]) + } + + return paths +} \ No newline at end of file diff --git a/src/server/web/web.go b/src/server/web/web.go new file mode 100644 index 0000000..75440bc --- /dev/null +++ b/src/server/web/web.go @@ -0,0 +1,61 @@ +package web + +import ( + "embed" + //"path/filepath" + //"fmt" + "net/http" + "github.com/labstack/echo/v4" + "aleleba/react-server/utils" +) + +// embed the dist folder +var ( + //go:embed dist/* + dist embed.FS + + indexHTML embed.FS + distDirFS = echo.MustSubFS(dist, "dist") + //distIndexHtml = echo.MustSubFS(indexHTML, "dist") +) + +func RegisterHandlers(e *echo.Echo, paths []string) { + // Print the "path" values + for _, path := range paths { + //e.FileFS(path, "index.html", distIndexHtml) + //e.StaticFS(path, distDirFS) + e.Static(path, "web/dist") + e.GET(path, func(c echo.Context) error { + // Construct the file path + //filePath := filepath.Join("web", "dist", "index.html") + // Return the HTML file + //return c.File(filePath) + url := c.Request().URL.String() + component := utils.JsxToString(url) + html := ` + + + + + + + + + + + App + + +
`+ component +`
+ + + + + ` + return c.HTMLBlob(http.StatusOK, []byte(html)) + }) + } + +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5765297 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,57 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "noEmit": false, + "jsx": "react-jsx", + "experimentalDecorators": true, + "esModuleInterop": true, + "isolatedModules": false, + "typeRoots" : ["./src/@types", "./node_modules/@types"], + "types": [ + "react", + "react-dom", + "node" + ], + "sourceMap": false, + "baseUrl": ".", + "outDir": "build", + "skipLibCheck": true, + "noImplicitAny": false, + "paths": { + "@components/*": ["src/frontend/components/*"], + "@components": ["src/frontend/components"], + "@styles/*": ["src/frontend/styles/*"], + "@styles": ["src/frontend/styles"], + "@actions/*": ["src/frontend/actions/*"], + "@actions": ["src/frontend/actions"], + "@reducers/*": ["src/frontend/reducers"], + "@reducers": ["src/frontend/reducers"], + "@config/*": ["config/*"], + "@config": ["config"], + "@mocks/*": ["src/__mocks__/*"], + "@mocks": ["src/__mocks__"] + } + }, + "include": [ + "." + ], + "exclude": [ + "node_modules", + "build", + "PRNameGenerator.ts", + "cypress.config.ts" + ] +} \ No newline at end of file diff --git a/webpack.config.ts b/webpack.config.ts new file mode 100644 index 0000000..8dd20fc --- /dev/null +++ b/webpack.config.ts @@ -0,0 +1,216 @@ +import path from 'path'; +import fs from 'fs'; +import { config as envConfig } from './config'; +import webpack from 'webpack'; +import CompressionWebpackPlugin from 'compression-webpack-plugin'; +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; +import TerserPlugin from 'terser-webpack-plugin'; +import { WebpackManifestPlugin } from 'webpack-manifest-plugin'; +import { CleanWebpackPlugin } from 'clean-webpack-plugin'; +import ESLintPlugin from 'eslint-webpack-plugin'; +import CopyPlugin from 'copy-webpack-plugin'; +import { resolveTsAliases } from 'resolve-ts-aliases'; + +const ROOT_DIR = path.resolve(__dirname); +const resolvePath = (...args) => path.resolve(ROOT_DIR, ...args); +const BUILD_DIR = resolvePath(__dirname + '/src/server/web/dist'); +const BUILD_DIR_CONVERSION = resolvePath(__dirname + '/src/server/web/utils'); +//const { InjectManifest } = require('workbox-webpack-plugin'); +//const nodeExternals = require('webpack-node-externals'); +const alias = resolveTsAliases(path.resolve('tsconfig.json')); + +const copyPatterns = [ + { + from: `${ROOT_DIR}/public/manifest.json`, to: '', + }, + { + from: `${ROOT_DIR}/public/favicon.ico`, to: '', + }, + { + from: `${ROOT_DIR}/public/logo192.png`, to: '', + }, + { + from: `${ROOT_DIR}/public/logo512.png`, to: '', + }, + +]; + +if(fs.existsSync(`${ROOT_DIR}/public/img`)){ + copyPatterns.push({ + from: `${ROOT_DIR}/public/img`, to: 'assets/img', + }); +} + +const configReact = { + entry: { + frontend: `${ROOT_DIR}/src/frontend/index.tsx`, + }, + output: { + path: BUILD_DIR, + //filename: 'assets/app-[name]-[fullhash].js', + filename: 'assets/app-[name].js', + publicPath: envConfig.PUBLIC_URL, + }, + resolve: { + extensions: ['.js', '.jsx','.ts','.tsx', '.json'], + alias, + }, + mode: 'production', + module: { + rules: [ + { + test: /\.(js|jsx|ts|tsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + }, + }, + { + test: /\.(css|sass|scss)$/, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader', + ], + }, + { + test: /\.(png|jpg|jpeg|gif|svg|ico|mp4|avi|ttf|otf|eot|woff|woff2|pdf)$/, + loader: 'file-loader', + options: { + name: 'assets/[name].[ext]', + }, + }, + { + test: /\.(ttf|otf|eot|woff|woff2)$/, + loader: 'url-loader', + options: { + name: 'assets/fonts/[name].[ext]', + esModule: false, + }, + }, + ], + }, + plugins: [ + new CompressionWebpackPlugin({ + test: /\.(js|css)$/, + filename: '[path][base].gz', + }), + new MiniCssExtractPlugin({ + //filename: 'assets/[name]-[fullhash].css', + filename: 'assets/[name].css', + }), + new WebpackManifestPlugin({ + fileName: 'assets/manifest-hash.json', + publicPath: envConfig.PREFIX_URL, + }), + new CleanWebpackPlugin({ + cleanOnceBeforeBuildPatterns: [ + '**/*', + '!server/**', + ], + }), + new ESLintPlugin(), + new webpack.EnvironmentPlugin({ + ...envConfig, + }), + new CopyPlugin({ + patterns: copyPatterns + }), + /*new InjectManifest({ + swSrc: './service-worker.ts', + swDest: 'service-worker.js', + }),*/ + ], + optimization: { + minimize: true, + minimizer: [ + new CssMinimizerPlugin(), + new TerserPlugin(), + ], + splitChunks: { + chunks: 'async', + cacheGroups: { + vendors: { + name: 'vendors', + chunks: 'all', + reuseExistingChunk: true, + priority: 1, + //filename: 'assets/vendor-[name]-[fullhash].js', + filename: 'assets/vendor-[name].js', + enforce: true, + test (module, chunks){ + const name = module.nameForCondition && module.nameForCondition(); + return chunks.name !== 'vendors' && /[\\/]node_modules[\\/]/.test(name); + }, + }, + }, + }, + }, +}; + +const configTSXConversion = { + entry: { + tsxToString: `${ROOT_DIR}/src/frontend/converter/tsxToString.tsx`, + }, + output: { + path: BUILD_DIR_CONVERSION, + //filename: 'assets/app-[name]-[fullhash].js', + filename: '[name].js', + publicPath: envConfig.PUBLIC_URL, + globalObject: 'this', + }, + resolve: { + extensions: ['.js', '.jsx','.ts','.tsx', '.json'], + alias, + }, + mode: 'production', + module: { + rules: [ + { + test: /\.(js|jsx|ts|tsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + }, + }, + { + test: /\.(css|sass|scss)$/, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader', + ], + }, + ], + }, + plugins: [ + new CompressionWebpackPlugin({ + test: /\.(js|css)$/, + filename: '[path][base].gz', + }), + new MiniCssExtractPlugin({ + //filename: 'assets/[name]-[fullhash].css', + filename: '[name].css', + }), + new CleanWebpackPlugin({ + cleanOnceBeforeBuildPatterns: [ + '**/*', + '!server/**', + ], + }), + new ESLintPlugin(), + new webpack.EnvironmentPlugin({ + ...envConfig, + }), + ], + optimization: { + minimize: true, + minimizer: [ + new CssMinimizerPlugin(), + new TerserPlugin(), + ], + }, +}; + +export default [configReact, configTSXConversion]; diff --git a/webpack.cy.config.ts b/webpack.cy.config.ts new file mode 100644 index 0000000..9676086 --- /dev/null +++ b/webpack.cy.config.ts @@ -0,0 +1,97 @@ +import path from 'path'; +import fs from 'fs'; +import { config as envConfig } from './config'; +import webpack from 'webpack'; +import HtmlWebpackPlugin from 'html-webpack-plugin'; +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import { CleanWebpackPlugin } from 'clean-webpack-plugin'; +import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; +import TerserPlugin from 'terser-webpack-plugin'; +import ESLintPlugin from 'eslint-webpack-plugin'; +import CopyPlugin from 'copy-webpack-plugin'; +import { resolveTsAliases } from 'resolve-ts-aliases'; + +const alias = resolveTsAliases(path.resolve('tsconfig.json')); +const copyPatterns: {from: string, to: string}[] = []; + +const copyFromUrl = `${path.resolve(__dirname)}/public/img`; +const copyToUrl = 'assets/img'; + +if(fs.existsSync(copyFromUrl)){ + copyPatterns.push({ + from: copyFromUrl, to: copyToUrl, + }); +} + +export default { + entry: './src/frontend/components/index.tsx', + resolve: { + extensions: ['.js', '.jsx','.ts','.tsx', '.json'], + alias, + }, + mode: 'development', + output: { + path: path.resolve(__dirname, 'build'), + }, + target: 'web', + plugins: [ + new CleanWebpackPlugin(), + new MiniCssExtractPlugin({ + filename: 'index.css', + }), + new ESLintPlugin(), + new webpack.EnvironmentPlugin({ + ...envConfig, + }), + new CopyPlugin({ + patterns: copyPatterns + }), + new HtmlWebpackPlugin({ + template: path.join(__dirname, 'build', 'index.html'), + }), + new webpack.ProvidePlugin({ + React: 'react', + }), + ], + module: { + rules: [ + { + test: /\.(js|jsx|ts|tsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + }, + }, + { + test: /\.(css|sass|scss)$/, + use: [ + MiniCssExtractPlugin.loader, + 'css-loader', + 'sass-loader', + ], + }, + { + test: /\.(png|jpg|jpeg|gif|svg|ico|mp4|avi|ttf|otf|eot|woff|woff2|pdf)$/, + loader: 'file-loader', + options: { + name: 'assets/[name].[ext]', + }, + }, + { + test: /\.(ttf|otf|eot|woff|woff2)$/, + loader: 'url-loader', + options: { + name: 'assets/fonts/[name].[ext]', + esModule: false, + }, + }, + ] + }, + optimization: { + minimize: true, + minimizer: [ + new CssMinimizerPlugin(), + new TerserPlugin(), + ], + }, +};