Compare commits

...

15 Commits

Author SHA1 Message Date
7a0e512bbd
Merge pull request #5 from aleleba/PR-988743
PR-988743: Adding support to Prefix url and updating packages.
2023-10-17 12:59:41 -06:00
8e38ade168 PR-988743: Adding support to Prefix url and updating packages. 2023-10-17 18:56:22 +00:00
7cd2cf23b8
Merge pull request #4 from aleleba/PR-445060
PR-445060: updating packages.
2023-10-09 23:30:58 -06:00
97975b1096 PR-445060: updating packages. 2023-10-10 05:26:53 +00:00
c7cc511f13
Merge pull request #3 from aleleba/PR-225148
PR-225148: updating packages.
2023-10-09 23:22:10 -06:00
424c73f30c PR-225148: updating packages. 2023-10-10 05:18:44 +00:00
2c451c214f
Merge pull request #2 from aleleba/PR-394587
PR-394587: Adding support to custom host.
2023-10-06 22:45:08 -06:00
47314b8e5b PR-394587: Adding support to custom host. 2023-10-07 04:42:03 +00:00
34236b7738
Merge pull request #1 from aleleba/PR-149165
PR-149165: adding testing pipelines and npx cli command.
2023-10-06 21:58:16 -06:00
618ceabb97 PR-149165: Fixing cypress. 2023-10-07 03:55:24 +00:00
a61eade18c PR-149165: deleting E2E testing. 2023-10-07 03:52:17 +00:00
31fbb4d839 PR-149165: adding cypress folder. 2023-10-07 03:43:44 +00:00
07ca1746ba PR-149165: adding testing pipelines and npx cli command. 2023-10-07 03:39:22 +00:00
3ea012cf93 Configure the build and the start of the app in prod. Updating to version 0.1.1 2023-10-07 00:55:40 +00:00
ec020257e0 Adding Redux to project, updating to version 0.1.0 2023-10-06 23:13:57 +00:00
30 changed files with 17499 additions and 103 deletions

View File

@ -2,9 +2,7 @@
ENV= #Default production ENV= #Default production
#App Port #App Port
PORT= #Default 80 PORT= #Default 80
#PUBLIC URL #Host
PUBLIC_URL= #Default 'auto' HOST= #Default localhost
#Prefix URL #Prefix URL
PREFIX_URL= #Default '' PREFIX_URL= #Default ''
#ONLY EXACT PATH
ONLY_EXACT_PATH= #Default false

40
.github/workflows/npm-publish.yml vendored Normal file
View File

@ -0,0 +1,40 @@
name: NPM testing and publish package
on:
push:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm test
cypress-run-component:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v5 # use the explicit version number
with:
component: true
publish-npm:
needs: [ build, cypress-run-component ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm publish --access=public
env:
NODE_AUTH_TOKEN: ${{secrets.npm_token}}

44
.github/workflows/npm-test.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Testing package
on:
pull_request:
branches: ['*']
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm test
cypress-run-component:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v5 # use the explicit version number
with:
component: true
test-build-package:
needs: [ test, cypress-run-component ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build:frontend

3
.gitignore vendored
View File

@ -7,6 +7,7 @@ build
src/server/main src/server/main
src/server/react-server src/server/react-server
src/server/web/dist src/server/web/dist
src/server/web/utils src/server/web/conversion
src/server/web/getstate
#cypress #cypress
cypress/videos cypress/videos

View File

@ -1,6 +1,6 @@
# Create React SSR # 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. This project aims to have a starter kit for creating a new React app with Server Side Rendering with a backend in go 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. 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.
@ -8,15 +8,15 @@ Tech(Library or Framework) | Version |
--- | --- | --- | --- |
React (Render Library) | 18.2.0 React (Render Library) | 18.2.0
Redux (Global State Management) | 4.2.1 Redux (Global State Management) | 4.2.1
React Router DOM (Routing) | 6.15.0 React Router DOM (Routing) | 6.17.0
Jest (Testing) | 29.6.4 Jest (Testing) | 29.7.0
Cypress (E2E Testing) | 13.1.0 Cypress (E2E Testing) | 13.3.1
Typescript | 5.2.2 Typescript | 5.2.2
## Setup ## Setup
To create a new project run in the terminal: To create a new project run in the terminal:
``` ```
npx @aleleba/create-react-ssr app-name npx @aleleba/create-react-go-ssr app-name
``` ```
Then run: Then run:
``` ```
@ -29,34 +29,33 @@ This is an exaple of config.
ENV= #Default production ENV= #Default production
#App Port #App Port
PORT= #Default 80 PORT= #Default 80
#PUBLIC URL #Host
PUBLIC_URL= #Default 'auto' HOST= #Default localhost
#Prefix URL #Prefix URL
PREFIX_URL= #Default '' 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. The default environment is production and the app port defauld is 80.
### For Development ### For Development
In the terminal run: In the terminal run:
``` ```
npm run start:dev npm run start-frontend:dev
npm run start-server:dev
``` ```
The ENV enviroment variable should be "development" and choose the port of your preference with the enviroment variable PORT. 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: You will find the root component on:
``` ```
scr/frontend/components/App.tsx src/frontend/components/App.tsx
``` ```
You will find the Initial Component on: You will find the Initial Component on:
``` ```
scr/frontend/components/InitialComponent.tsx src/frontend/components/InitialComponent.tsx
``` ```
The manage of the routes you should find on: The manage of the routes you should find on:
``` ```
scr/routes src/routes
``` ```
It is using "useRoutes" hook for working, more information for this here: (https://reactrouter.com/docs/en/v6/api#useroutes) It is using "useRoutes" hook for working, more information for this here: (https://reactrouter.com/docs/en/v6/api#useroutes)

110
bin/cli.js Normal file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env node
const { execSync } = require('child_process');
var fs = require('fs');
const isWin = process.platform === 'win32';
const runCommand = command => {
try{
execSync(`${command}`, {stdio: 'inherit'});
} catch (e) {
console.error(`Failed to execute ${command}`, e);
return false;
}
return true;
};
const runCommandWithOutput = command => {
try{
return execSync(`${command}`);
} catch (e) {
console.error(`Failed to execute ${command}`, e);
return false;
}
};
const replaceTextOnFile = ({
file,
textToBeReplaced,
textReplace,
arrOfObjectsBeReplaced
}) => {
let data;
try{
data = fs.readFileSync(file, 'utf8');
} catch (e) {
console.error(`Failed to read file ${file}`, e);
return false;
}
let result;
if(arrOfObjectsBeReplaced){
arrOfObjectsBeReplaced.forEach( obj => {
if(result){
result = result.replace(obj.textToBeReplaced, obj.textReplace).replace(/^\s*[\r\n]/gm, ' ');
}else{
result = data.replace(obj.textToBeReplaced, obj.textReplace).replace(/^\s*[\r\n]/gm, ' ');
}
});
}else{
result = data.replace(textToBeReplaced, textReplace).replace(/^\s*[\r\n]/gm, ' ');
}
try{
console.log('text changed');
fs.writeFileSync(file, result, 'utf8');
} catch (e) {
console.error(`Failed to read file ${file}`, e);
return false;
}
};
const repoName = process.argv[2];
const gitCheckoutCommand = `git clone --depth 1 https://github.com/aleleba/create-react-go-ssr ${repoName}`;
console.log(`Cloning the repository with name ${repoName}`);
const checkedOut = runCommand(gitCheckoutCommand);
if(!checkedOut) process.exit(-1);
const actualVersion = runCommandWithOutput(`cd ${repoName} && node -p "require('./package.json').version"`).toString().trim();
const installDepsCommand = `cd ${repoName} && npm install`;
const cleanGitHistoryCommand = `cd ${repoName} && rm -rf .git && git init && git add --all -- ":!.github" ":!bin" && git commit -m "Initial commit"`;
const cleanGitHistoryCommandWindows = `cd ${repoName} && rmdir .git /s /q && git init && git add --all -- ":!.github" ":!bin" && git commit -m "Initial commit"`;
const deleteFoldersCommand = `cd ${repoName} && rm -rf .github && rm -rf bin`;
const deleteFoldersCommandWindows = `cd ${repoName} && rmdir .github /s /q && rmdir bin /s /q`;
console.log(`Installing dependencies for ${repoName}`);
const installedDeps = runCommand(installDepsCommand);
if(!installedDeps) process.exit(-1);
console.log(`Replacing Json data for ${repoName}`);
replaceTextOnFile({
file: `./${repoName}/package.json`,
arrOfObjectsBeReplaced: [
{
textToBeReplaced: '"bin": "./bin/cli.js",',
textReplace: ''
},
{
textToBeReplaced: `"version": "${actualVersion}",`,
textReplace: '"version": "0.0.1",'
},
{
textToBeReplaced: '"name": "@aleleba/create-react-go-ssr",',
textReplace: `"name": "${repoName}",`
}
]
});
console.log(`Cleaning History of Git for ${repoName}`);
const cleanGitHistory = isWin ? runCommand(cleanGitHistoryCommandWindows) : runCommand(cleanGitHistoryCommand);
if(!cleanGitHistory) process.exit(-1);
console.log('Congratulations! You are ready. Follow the following commands to start');
console.log(`cd ${repoName}`);
console.log('Create a .env file with ENV=development(default: production), PORT=3000 (default: 80), HOST=domain.com (default: localhost), PREFIX_URL= (default: is empty)');
console.log('Then you can run: npm start-frontend:dev');
console.log('Then you can run: npm start-server:dev');
const deleteFolders = isWin ? runCommand(deleteFoldersCommandWindows) : runCommand(deleteFoldersCommand);
if(!deleteFolders) process.exit(-1);

View File

@ -4,6 +4,7 @@ export const deFaultValues = {
PUBLIC_URL: 'auto', PUBLIC_URL: 'auto',
PREFIX_URL: '', PREFIX_URL: '',
ONLY_EXACT_PATH: false, ONLY_EXACT_PATH: false,
HOST: 'localhost',
}; };
export const config = { export const config = {
@ -12,6 +13,7 @@ export const config = {
PUBLIC_URL: process.env.PUBLIC_URL ? process.env.PUBLIC_URL : deFaultValues.PUBLIC_URL, 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, 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, ONLY_EXACT_PATH: process.env.ONLY_EXACT_PATH ? process.env.ONLY_EXACT_PATH === 'true' : deFaultValues.ONLY_EXACT_PATH,
HOST: process.env.HOST ? process.env.HOST : deFaultValues.HOST,
}; };
export default config; export default config;

20
cypress/e2e/App.test.ts Normal file
View File

@ -0,0 +1,20 @@
describe('Initial Component Tests', () => {
it('Will show the Initial Component page.', () => {
cy.visit('/');
cy.get('a').contains('Other Component');
});
it('Will Redirect to Other Component page.', () => {
cy.visit('/');
cy.get('a').contains('Other Component').click();
cy.get('a').contains('Initial Component');
});
it('Will show the Other Component page.', () => {
cy.visit('/other-component');
cy.get('a').contains('Initial Component');
});
it('Will Redirect to Initial Component page.', () => {
cy.visit('/other-component');
cy.get('a').contains('Initial Component').click();
cy.get('a').contains('Other Component');
});
});

View File

@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<base href="/__cypress/src/" />
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@ -0,0 +1,42 @@
// ***********************************************************
// This example support/component.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
//Importing global styles
import '../../src/frontend/styles/global.scss';
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')
import { mount } from 'cypress/react18';
// Augment the Cypress namespace to include type definitions for
// your custom command.
// Alternatively, can be defined in cypress/support/component.d.ts
// with a <reference path="./component" /> at the top of your spec.
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', mount);
// Example use:
// cy.mount(<MyComponent />)

20
cypress/support/e2e.ts Normal file
View File

@ -0,0 +1,20 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands';
// Alternatively you can use CommonJS syntax:
// require('./commands')

16890
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,15 +1,14 @@
{ {
"name": "@aleleba/create-react-go-ssr", "name": "@aleleba/create-react-go-ssr",
"version": "0.0.1", "version": "1.0.4",
"description": "Starter Kit of server side render of react", "description": "Starter Kit of server side render of react with backend in go",
"bin": "./bin/cli.js", "bin": "./bin/cli.js",
"main": "src/server/index",
"scripts": { "scripts": {
"start": "webpack watch --config webpack.config.ts", "start": "cd build/server && ./react-server",
"buildTsxToString": "webpack --config webpack.config.convert.ts", "start-frontend:dev": "webpack watch --config webpack.config.ts",
"start:dev": "rm -rf build && webpack --mode=development --config webpack.config.dev.server.ts", "start-server:dev": "cd src/server && go run main.go",
"start:dev-win": "(if exist build rmdir /s /Q build) && webpack --mode=development --config webpack.config.dev.server.ts", "build": "webpack --config webpack.config.ts && cd src/server && go build && mkdir ../../build && cp -r ./ ../../build/server && cp -r ../routes ../../build/routes && rm -rf ./react-server",
"build": "webpack-cli --config webpack.config.ts", "build:frontend": "webpack --config webpack.config.ts",
"lint": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx", "lint": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx",
"lint:fix": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx --fix", "lint:fix": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx --fix",
"test": "jest", "test": "jest",
@ -26,6 +25,8 @@
"keywords": [ "keywords": [
"create react app", "create react app",
"react", "react",
"go",
"golang",
"ssr", "ssr",
"typescript", "typescript",
"redux" "redux"
@ -33,9 +34,9 @@
"author": "Alejandro Lembke Barrientos", "author": "Alejandro Lembke Barrientos",
"license": "MIT", "license": "MIT",
"bugs": { "bugs": {
"url": "https://github.com/aleleba/create-react-ssr/issues" "url": "https://github.com/aleleba/create-react-go-ssr/issues"
}, },
"homepage": "https://github.com/aleleba/create-react-ssr#readme", "homepage": "https://github.com/aleleba/create-react-go-ssr#readme",
"dependencies": { "dependencies": {
"@babel/register": "^7.22.15", "@babel/register": "^7.22.15",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
@ -45,11 +46,11 @@
"ignore-styles": "^5.0.1", "ignore-styles": "^5.0.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.1.2", "react-redux": "^8.1.3",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.17.0",
"react-router-hash-link": "^2.4.3", "react-router-hash-link": "^2.4.3",
"redux": "^4.2.1", "redux": "^4.2.1",
"webpack": "^5.88.2", "webpack": "^5.89.0",
"webpack-manifest-plugin": "^5.0.0", "webpack-manifest-plugin": "^5.0.0",
"workbox-background-sync": "^7.0.0", "workbox-background-sync": "^7.0.0",
"workbox-broadcast-update": "^7.0.0", "workbox-broadcast-update": "^7.0.0",
@ -65,45 +66,45 @@
"workbox-streams": "^7.0.0" "workbox-streams": "^7.0.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.22.17", "@babel/core": "^7.23.2",
"@babel/preset-env": "^7.22.15", "@babel/preset-env": "^7.23.2",
"@babel/preset-react": "^7.22.15", "@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.22.15", "@babel/preset-typescript": "^7.23.2",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11", "@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@redux-devtools/extension": "^3.2.5", "@redux-devtools/extension": "^3.2.5",
"@testing-library/jest-dom": "^6.1.3", "@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.0.0", "@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3", "@testing-library/user-event": "^14.5.1",
"@types/jest": "^29.5.4", "@types/jest": "^29.5.5",
"@types/node": "^20.6.0", "@types/node": "^20.8.6",
"@types/react": "^18.2.21", "@types/react": "^18.2.28",
"@types/react-dom": "^18.2.7", "@types/react-dom": "^18.2.13",
"@types/webpack": "^5.28.2", "@types/webpack": "^5.28.3",
"@types/webpack-hot-middleware": "^2.25.6", "@types/webpack-hot-middleware": "^2.25.7",
"@types/webpack-node-externals": "^3.0.0", "@types/webpack-node-externals": "^3.0.2",
"@typescript-eslint/eslint-plugin": "^6.6.0", "@typescript-eslint/eslint-plugin": "^6.8.0",
"@typescript-eslint/parser": "^6.6.0", "@typescript-eslint/parser": "^6.8.0",
"babel-jest": "^29.6.4", "babel-jest": "^29.7.0",
"babel-loader": "^9.1.3", "babel-loader": "^9.1.3",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"compression-webpack-plugin": "^10.0.0", "compression-webpack-plugin": "^10.0.0",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1", "css-loader": "^6.8.1",
"css-minimizer-webpack-plugin": "^5.0.1", "css-minimizer-webpack-plugin": "^5.0.1",
"cypress": "^13.1.0", "cypress": "^13.3.1",
"eslint": "^8.49.0", "eslint": "^8.51.0",
"eslint-plugin-react": "^7.33.2", "eslint-plugin-react": "^7.33.2",
"eslint-webpack-plugin": "^4.0.1", "eslint-webpack-plugin": "^4.0.1",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"html-webpack-plugin": "^5.5.3", "html-webpack-plugin": "^5.5.3",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^29.6.4", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.6.4", "jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3", "jest-fetch-mock": "^3.0.3",
"mini-css-extract-plugin": "^2.7.6", "mini-css-extract-plugin": "^2.7.6",
"react-refresh": "^0.14.0", "react-refresh": "^0.14.0",
"resolve-ts-aliases": "^1.0.1", "resolve-ts-aliases": "^1.0.1",
"sass": "^1.66.1", "sass": "^1.69.3",
"sass-loader": "^13.3.2", "sass-loader": "^13.3.2",
"style-loader": "^3.3.3", "style-loader": "^3.3.3",
"terser-webpack-plugin": "^5.3.9", "terser-webpack-plugin": "^5.3.9",

View File

@ -0,0 +1,27 @@
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import initialStateReducer from '../frontend/reducers/initialState';
import setStore from '../frontend/setStore';
export const ProviderMock = ({ children, initialState }: { children: unknown, initialState?: unknown}) => {
let initialStateMock = initialStateReducer;
if(initialState !== undefined){
initialStateMock = initialState as unknown as typeof initialStateReducer;
}
const history = createMemoryHistory();
const store = setStore({ initialState: initialStateMock });
return(
<Provider store={store}>
<Router location={history.location} navigator={history}>
{children as JSX.Element}
</Router>
</Provider>
);
};
export default ProviderMock;

View File

@ -0,0 +1,2 @@
export const fileMock = '';
module.exports = fileMock;

1
src/__mocks__/index.ts Normal file
View File

@ -0,0 +1 @@
export * from './ProviderMock';

View File

@ -1,15 +1,19 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import PrincipalRoutes from './PrincipalRoutes'; import PrincipalRoutes from './PrincipalRoutes';
import { config } from '../../../config';
const App = () => { const App = () => {
const { PREFIX_URL } = config;
if(config.ENV === 'development') {
useEffect(() => { useEffect(() => {
const ws = new WebSocket('wss://xs70kvlc-3000.use.devtunnels.ms/ws'); const ws = new WebSocket(`wss://${config.HOST}${PREFIX_URL}/ws`);
ws.onmessage = (event) => { ws.onmessage = (event) => {
if (event.data === 'reload') { if (event.data === 'reload') {
window.location.reload(); window.location.reload();
} }
}; };
}, []); }, []);
}
return <PrincipalRoutes />; return <PrincipalRoutes />;
}; };

View File

@ -1,11 +1,15 @@
import React from 'react'; import React from 'react';
import './InitialComponent.scss'; import './InitialComponent.scss';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
const InitialComponent = () => ( const InitialComponent = ({ hello }: { hello: string }) => {
return(
<div className="App"> <div className="App">
<header className="App-header"> <header className="App-header">
<img src="assets/img/logo.svg" className="App-logo" alt="logo" /> <img src="assets/img/logo.svg" className="App-logo" alt="logo" />
<p>This is the text from the store of redux: <strong>{hello}</strong></p>
<p> <p>
Edit <code>src/frontend/InitialComponent.jsx</code> and save to reload. Edit <code>src/frontend/InitialComponent.jsx</code> and save to reload.
</p> </p>
@ -21,5 +25,12 @@ const InitialComponent = () => (
</header> </header>
</div> </div>
); );
};
export default InitialComponent; const mapStateToProps = (state) => {
return {
hello: state.testReducer.hello
};
};
export default connect(mapStateToProps)(InitialComponent);

View File

@ -1,15 +1,23 @@
import React from 'react'; import React from 'react';
//Redux
import { Provider } from 'react-redux';
import setStore from '../setStore';
import initialState from '../reducers/initialState';
import { renderToString } from 'react-dom/server'; import { renderToString } from 'react-dom/server';
import { StaticRouter } from 'react-router-dom/server'; import { StaticRouter } from 'react-router-dom/server';
import App from '../components/App'; import App from '../components/App';
const url = process.argv[2]; const url = process.argv[2];
const store = setStore({ initialState });
const render = () => { const render = () => {
return renderToString( return renderToString(
<Provider store={store}>
<StaticRouter location={`${url}`} > <StaticRouter location={`${url}`} >
<App /> <App />
</StaticRouter> </StaticRouter>
</Provider>
); );
}; };

View File

@ -0,0 +1,5 @@
import setStore from '../setStore';
import initialState from '../reducers/initialState';
const store = setStore({ initialState });
const preloadedState = store.getState();
console.log(preloadedState);

View File

@ -10,7 +10,7 @@ import { config } from '../../config';
import './styles/global.scss'; import './styles/global.scss';
import App from './components/App'; import App from './components/App';
// import serviceWorkerRegistration from '../../serviceWorkerRegistration'; import serviceWorkerRegistration from '../../serviceWorkerRegistration';
declare global { declare global {
interface Window { interface Window {
@ -72,9 +72,9 @@ ENV === 'production' && hydrateRoot(container,
</Provider> </Provider>
); */ ); */
/* if((ENV) && (ENV === 'production')){ if((ENV) && (ENV === 'production')){
serviceWorkerRegistration(); serviceWorkerRegistration();
} */ }
/*if(module.hot){ /*if(module.hot){
module.hot.accept(); module.hot.accept();

View File

@ -1,2 +1,3 @@
const initialState = {}; import { IInitialState } from './';
const initialState: IInitialState = {};
export default initialState; export default initialState;

View File

@ -6,8 +6,9 @@ import (
) )
func LoadEnv() { func LoadEnv() {
err := godotenv.Load(".env") err := godotenv.Load("../../.env")
if err != nil { if err != nil {
log.Fatal("Error loading .env file") // log.Fatal("Error loading .env file")
log.Println("Is no .env file")
} }
} }

View File

@ -21,6 +21,10 @@ func main() {
//Getting the port from the environment //Getting the port from the environment
port := os.Getenv("PORT") port := os.Getenv("PORT")
if(port == "") {
port = "80"
}
paths := utils.GetRoutes() paths := utils.GetRoutes()
e := echo.New() e := echo.New()

View File

@ -0,0 +1,37 @@
package utils
import (
"fmt"
"os/exec"
)
func GetPreloadedState() string {
// Set the path to the jsxToString.js file
jsFilePath := "./web/getstate/getPreloadedState.js"
// Create the command to run Node.js with the jsxToString.js file
cmd := exec.Command("node", jsFilePath)
// 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)
}

View File

@ -7,7 +7,7 @@ import (
func JsxToString(url string) string { func JsxToString(url string) string {
// Set the path to the jsxToString.js file // Set the path to the jsxToString.js file
jsFilePath := "./web/utils/tsxToString.js" jsFilePath := "./web/conversion/tsxToString.js"
// Create the command to run Node.js with the jsxToString.js file // Create the command to run Node.js with the jsxToString.js file
cmd := exec.Command("node", jsFilePath, url) cmd := exec.Command("node", jsFilePath, url)

View File

@ -32,6 +32,7 @@ func RegisterHandlers(e *echo.Echo, paths []string) {
//return c.File(filePath) //return c.File(filePath)
url := c.Request().URL.String() url := c.Request().URL.String()
component := utils.JsxToString(url) component := utils.JsxToString(url)
preloadedState := utils.GetPreloadedState();
html := `<!DOCTYPE html> html := `<!DOCTYPE html>
<html lang="es"> <html lang="es">
<head> <head>
@ -47,9 +48,9 @@ func RegisterHandlers(e *echo.Echo, paths []string) {
</head> </head>
<body> <body>
<div id="app">`+ component +`</div> <div id="app">`+ component +`</div>
<!-- <script> <script>
window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')} window.__PRELOADED_STATE__ = JSON.stringify(`+ preloadedState+`).replace(/</g, '\\u003c')
</script> --> </script>
<script src="assets/app-frontend.js" type="text/javascript"></script> <script src="assets/app-frontend.js" type="text/javascript"></script>
<script src="assets/vendor-vendors.js" type="text/javascript"></script> <script src="assets/vendor-vendors.js" type="text/javascript"></script>
</body> </body>

View File

@ -1,5 +1,6 @@
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import dotenv from 'dotenv'
import { config as envConfig } from './config'; import { config as envConfig } from './config';
import webpack from 'webpack'; import webpack from 'webpack';
import CompressionWebpackPlugin from 'compression-webpack-plugin'; import CompressionWebpackPlugin from 'compression-webpack-plugin';
@ -15,8 +16,9 @@ import { resolveTsAliases } from 'resolve-ts-aliases';
const ROOT_DIR = path.resolve(__dirname); const ROOT_DIR = path.resolve(__dirname);
const resolvePath = (...args) => path.resolve(ROOT_DIR, ...args); const resolvePath = (...args) => path.resolve(ROOT_DIR, ...args);
const BUILD_DIR = resolvePath(__dirname + '/src/server/web/dist'); const BUILD_DIR = resolvePath(__dirname + '/src/server/web/dist');
const BUILD_DIR_CONVERSION = resolvePath(__dirname + '/src/server/web/utils'); const BUILD_DIR_CONVERSION = resolvePath(__dirname + '/src/server/web/conversion');
//const { InjectManifest } = require('workbox-webpack-plugin'); const BUILD_DIR_GET_STATE = resolvePath(__dirname + '/src/server/web/getstate');
const { InjectManifest } = require('workbox-webpack-plugin');
//const nodeExternals = require('webpack-node-externals'); //const nodeExternals = require('webpack-node-externals');
const alias = resolveTsAliases(path.resolve('tsconfig.json')); const alias = resolveTsAliases(path.resolve('tsconfig.json'));
@ -42,6 +44,17 @@ if(fs.existsSync(`${ROOT_DIR}/public/img`)){
}); });
} }
// call dotenv and it will return an Object with a parsed key
const env = dotenv.config().parsed;
// reduce it to a nice object, the same as before
const envKeys = Object.keys(dotenv.config({}).parsed || {}).reduce((prev, next) => {
if (dotenv.config().parsed && env && env[next]) {
prev[`process.env.${next}`] = JSON.stringify(env[next]);
}
return prev;
}, {});
const configReact = { const configReact = {
entry: { entry: {
frontend: `${ROOT_DIR}/src/frontend/index.tsx`, frontend: `${ROOT_DIR}/src/frontend/index.tsx`,
@ -112,15 +125,16 @@ const configReact = {
}), }),
new ESLintPlugin(), new ESLintPlugin(),
new webpack.EnvironmentPlugin({ new webpack.EnvironmentPlugin({
envKeys,
...envConfig, ...envConfig,
}), }),
new CopyPlugin({ new CopyPlugin({
patterns: copyPatterns patterns: copyPatterns
}), }),
/*new InjectManifest({ new InjectManifest({
swSrc: './service-worker.ts', swSrc: './service-worker.ts',
swDest: 'service-worker.js', swDest: 'service-worker.js',
}),*/ }),
], ],
optimization: { optimization: {
minimize: true, minimize: true,
@ -200,9 +214,6 @@ const configTSXConversion = {
], ],
}), }),
new ESLintPlugin(), new ESLintPlugin(),
new webpack.EnvironmentPlugin({
...envConfig,
}),
], ],
optimization: { optimization: {
minimize: true, minimize: true,
@ -213,4 +224,65 @@ const configTSXConversion = {
}, },
}; };
export default [configReact, configTSXConversion]; const configGetPreloadedState = {
entry: {
getPreloadedState: `${ROOT_DIR}/src/frontend/getPreloadedState/getPreloadedState.ts`,
},
output: {
path: BUILD_DIR_GET_STATE,
//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(),
],
optimization: {
minimize: true,
minimizer: [
new CssMinimizerPlugin(),
new TerserPlugin(),
],
},
};
export default [configReact, configTSXConversion, configGetPreloadedState];