mirror of
				https://github.com/aleleba/create-react-ssr.git
				synced 2025-10-30 13:40:45 -06:00 
			
		
		
		
	PR-753737: Cambiando la carpeta src a client y moviendo todo a una carpeta dentro de client llamada src.
This commit is contained in:
		
							
								
								
									
										8
									
								
								client/.babelrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/.babelrc
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|     "presets": [ | ||||
|         "@babel/preset-env", | ||||
|         "@babel/preset-react", | ||||
|         "@babel/preset-typescript" | ||||
|         // "@babel/preset-flow" | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										6
									
								
								client/.env.example
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								client/.env.example
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| #Environment | ||||
| ENV= #Default production | ||||
| #App Port | ||||
| PORT= #Default 80 | ||||
| #PUBLIC URL | ||||
| PUBLIC_URL= #Default / | ||||
							
								
								
									
										10
									
								
								client/.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								client/.eslintignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #Build | ||||
| build | ||||
| #Webpack | ||||
| webpack.config.js | ||||
| webpack.config.dev.js | ||||
| #Server | ||||
| /server/index.js | ||||
| #Service Worker | ||||
| service-worker.js | ||||
| serviceWorkerRegistration.ts | ||||
							
								
								
									
										51
									
								
								client/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								client/.eslintrc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| 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' | ||||
| 		] | ||||
| 	}, | ||||
| 	'settings': { | ||||
| 		'react': { | ||||
| 			'version': 'detect', | ||||
| 		} | ||||
| 	} | ||||
| }; | ||||
							
								
								
									
										14
									
								
								client/PRNameGenerator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								client/PRNameGenerator.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| 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()); | ||||
|  | ||||
| export default PRName; | ||||
|    | ||||
							
								
								
									
										8
									
								
								client/config/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/config/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| const config = { | ||||
| 	env: process.env.ENV ? process.env.ENV : 'production', | ||||
| 	port: process.env.PORT ? process.env.PORT : 80, | ||||
| 	// portDev: process.env.PORT_DEV ? process.env.PORT_DEV : 3000, | ||||
| 	// portReloadDev: process.env.PORT_RELOAD_DEV, | ||||
| }; | ||||
|  | ||||
| module.exports = { config: config }; | ||||
							
								
								
									
										20011
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										20011
									
								
								client/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										95
									
								
								client/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								client/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,95 @@ | ||||
| { | ||||
|   "name": "create-react-ssr", | ||||
|   "version": "1.0.0", | ||||
|   "description": "Starter Kit de server side render de react", | ||||
|   "main": "src/server/index", | ||||
|   "scripts": { | ||||
|     "start": "ts-node src/server", | ||||
|     "start:dev": "nodemon --exec 'ts-node' src/server/index -e js,json,ts,tsx,jsx", | ||||
|     "build": "webpack-cli --config webpack.config.js", | ||||
|     "lint": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx", | ||||
|     "lint:fix": "eslint ./ --ext .js --ext .ts --ext .jsx --ext .tsx --fix", | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "repository": { | ||||
|     "type": "git", | ||||
|     "url": "git+https://github.com/aleleba/create-react-ssr.git" | ||||
|   }, | ||||
|   "keywords": [ | ||||
|     "react", | ||||
|     "ssr" | ||||
|   ], | ||||
|   "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.17.7", | ||||
|     "asset-require-hook": "^1.2.0", | ||||
|     "dotenv": "^16.0.0", | ||||
|     "express": "^4.17.3", | ||||
|     "helmet": "^5.0.2", | ||||
|     "ignore-styles": "^5.0.1", | ||||
|     "react": "^18.0.0", | ||||
|     "react-dom": "^18.0.0", | ||||
|     "react-redux": "^8.0.0", | ||||
|     "react-router-dom": "^6.3.0", | ||||
|     "react-router-hash-link": "^2.4.3", | ||||
|     "redux": "^4.1.2", | ||||
|     "ts-node": "^10.7.0", | ||||
|     "webpack": "^5.72.0", | ||||
|     "webpack-manifest-plugin": "^5.0.0", | ||||
|     "workbox-background-sync": "^6.5.3", | ||||
|     "workbox-broadcast-update": "^6.5.3", | ||||
|     "workbox-cacheable-response": "^6.5.3", | ||||
|     "workbox-core": "^6.5.3", | ||||
|     "workbox-expiration": "^6.5.3", | ||||
|     "workbox-google-analytics": "^6.5.3", | ||||
|     "workbox-navigation-preload": "^6.5.3", | ||||
|     "workbox-precaching": "^6.5.3", | ||||
|     "workbox-range-requests": "^6.5.3", | ||||
|     "workbox-routing": "^6.5.3", | ||||
|     "workbox-strategies": "^6.5.3", | ||||
|     "workbox-streams": "^6.5.3" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@babel/core": "^7.17.9", | ||||
|     "@babel/preset-env": "^7.16.11", | ||||
|     "@babel/preset-flow": "^7.16.7", | ||||
|     "@babel/preset-react": "^7.16.7", | ||||
|     "@babel/preset-typescript": "^7.16.7", | ||||
|     "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5", | ||||
|     "@types/node": "^17.0.25", | ||||
|     "@types/react": "^18.0.5", | ||||
|     "@types/react-dom": "^18.0.1", | ||||
|     "@types/webpack-hot-middleware": "^2.25.6", | ||||
|     "@typescript-eslint/eslint-plugin": "^5.20.0", | ||||
|     "@typescript-eslint/parser": "^5.20.0", | ||||
|     "babel-loader": "^8.2.4", | ||||
|     "clean-webpack-plugin": "^4.0.0", | ||||
|     "compression-webpack-plugin": "^9.2.0", | ||||
|     "copy-webpack-plugin": "^10.2.4", | ||||
|     "css-loader": "^6.7.1", | ||||
|     "css-minimizer-webpack-plugin": "^3.4.1", | ||||
|     "eslint": "^8.13.0", | ||||
|     "eslint-plugin-react": "^7.29.4", | ||||
|     "eslint-webpack-plugin": "^3.1.1", | ||||
|     "mini-css-extract-plugin": "^2.6.0", | ||||
|     "nodemon": "^2.0.15", | ||||
|     "react-refresh": "^0.12.0", | ||||
|     "redux-devtools-extension": "^2.13.9", | ||||
|     "sass": "^1.50.0", | ||||
|     "sass-loader": "^12.6.0", | ||||
|     "style-loader": "^3.3.1", | ||||
|     "terser-webpack-plugin": "^5.3.1", | ||||
|     "typescript": "^4.6.3", | ||||
|     "webpack-cli": "^4.9.2", | ||||
|     "webpack-dev-middleware": "^5.3.1", | ||||
|     "webpack-dev-server": "^4.8.1", | ||||
|     "webpack-hot-middleware": "^2.25.1", | ||||
|     "workbox-webpack-plugin": "^6.5.3", | ||||
|     "workbox-window": "^6.5.3" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										8
									
								
								client/public/manifest.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								client/public/manifest.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|     "short_name": "App", | ||||
|     "name": "App", | ||||
|     "start_url": "/", | ||||
|     "display": "standalone", | ||||
|     "theme_color": "#ffffff", | ||||
|     "background_color": "#ffffff" | ||||
|   } | ||||
							
								
								
									
										82
									
								
								client/service-worker.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								client/service-worker.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,82 @@ | ||||
| // 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 | ||||
| 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 | ||||
| precacheAndRoute(self.__WB_MANIFEST); | ||||
|  | ||||
| // Set up App Shell-style routing, so that all navigation requests | ||||
| // are fulfilled with your index.html shell. Learn more at | ||||
| // https://developers.google.com/web/fundamentals/architecture/app-shell | ||||
| /* const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$'); | ||||
| registerRoute( | ||||
| 	// Return false to exempt requests from being fulfilled by index.html. | ||||
| 	({ request, url }) => { | ||||
| 		// If this isn't a navigation, skip. | ||||
| 		if (request.mode !== 'navigate') { | ||||
| 			return false; | ||||
| 		} // If this is a URL that starts with /_, skip. | ||||
|  | ||||
| 		if (url.pathname.startsWith('/_')) { | ||||
| 			return false; | ||||
| 		} // If this looks like a URL for a resource, because it contains // a file extension, skip. | ||||
|  | ||||
| 		if (url.pathname.match(fileExtensionRegexp)) { | ||||
| 			return false; | ||||
| 		} // Return true to signal that we want to use the handler. | ||||
|  | ||||
| 		return true; | ||||
| 	}, | ||||
| 	createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html') | ||||
| ); */ | ||||
|  | ||||
| // 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) { | ||||
|  | ||||
| 	const data = e.data.json(); | ||||
|  | ||||
| 	registration.showNotification(data.title, { | ||||
| 		body: data.message, | ||||
| 		icon: 'favicon.ico' | ||||
| 	}); | ||||
|  | ||||
| }); | ||||
|  | ||||
| console.log('hello from service-worker.js the new one.'); | ||||
							
								
								
									
										21
									
								
								client/serviceWorkerRegistration.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								client/serviceWorkerRegistration.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
							
								
								
									
										6
									
								
								client/src/frontend/components/App.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								client/src/frontend/components/App.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| import React from 'react'; | ||||
| import PrincipalRoutes from './PrincipalRoutes'; | ||||
|  | ||||
| const App = () => <PrincipalRoutes />; | ||||
|  | ||||
| export default App; | ||||
							
								
								
									
										5
									
								
								client/src/frontend/components/InitialComponent.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								client/src/frontend/components/InitialComponent.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| const InitialComponent = () => <h1>Hello React!</h1>; | ||||
|  | ||||
| export default InitialComponent; | ||||
							
								
								
									
										5
									
								
								client/src/frontend/components/OtherComponent.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								client/src/frontend/components/OtherComponent.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import React from 'react'; | ||||
|  | ||||
| const OtherComponent = () => <h1>Other Component!</h1>; | ||||
|  | ||||
| export default OtherComponent; | ||||
							
								
								
									
										11
									
								
								client/src/frontend/components/PrincipalRoutes.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								client/src/frontend/components/PrincipalRoutes.jsx
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||
							
								
								
									
										75
									
								
								client/src/frontend/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								client/src/frontend/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| import React from 'react'; | ||||
| import { hydrateRoot } from 'react-dom/client'; | ||||
| // Router | ||||
| import { BrowserRouter as Router } from 'react-router-dom'; | ||||
| // Redux | ||||
| import { Provider } from 'react-redux'; | ||||
| import { IInitialState } from './reducers/index.js'; | ||||
| import setStore from './setStore.js'; | ||||
| import { config } from '../../config'; | ||||
|  | ||||
| import './styles/global.sass'; | ||||
| 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: any | ||||
| } | ||||
|  | ||||
| const { env } = config; | ||||
|  | ||||
| const preloadedState = window.__PRELOADED_STATE__; | ||||
| const store = setStore({ initialState: preloadedState }); | ||||
|  | ||||
| delete window.__PRELOADED_STATE__; | ||||
|  | ||||
| const container = document.getElementById('app')!; | ||||
|  | ||||
| // add "const root" to be able to rerender. | ||||
| hydrateRoot(container, | ||||
| 	<Provider store={store}> | ||||
| 		<Router> | ||||
| 			<App /> | ||||
| 		</Router> | ||||
| 	</Provider>, | ||||
| 	// 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( | ||||
|     <Provider store={store}> | ||||
|         <Router> | ||||
|             <App /> | ||||
|         </Router> | ||||
|     </Provider> | ||||
| ); */ | ||||
|  | ||||
| // If you want your app to work offline and load faster, you can change | ||||
| // unregister() to register() below. Note this comes with some pitfalls. | ||||
| // Learn more about service workers: http://bit.ly/CRA-PWA | ||||
| //serviceWorker.register(); | ||||
|  | ||||
| if((env) && (env === 'production')){ | ||||
| 	serviceWorkerRegistration(); | ||||
| } | ||||
|  | ||||
| if(module.hot){ | ||||
| 	module.hot.accept(); | ||||
| } | ||||
							
								
								
									
										13
									
								
								client/src/frontend/reducers/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								client/src/frontend/reducers/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import { combineReducers } from 'redux'; | ||||
| import testReducer, { ITestReducer } from './testReducer'; | ||||
|  | ||||
| export interface IInitialState { | ||||
| 	testReducer?: ITestReducer | undefined | ||||
| } | ||||
|  | ||||
| const rootReducer = combineReducers({ | ||||
| 	// Here comes the reducers | ||||
| 	testReducer | ||||
| }); | ||||
|  | ||||
| export default rootReducer; | ||||
							
								
								
									
										2
									
								
								client/src/frontend/reducers/initialState.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								client/src/frontend/reducers/initialState.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| let initialState = {}; | ||||
| export default  initialState; | ||||
							
								
								
									
										22
									
								
								client/src/frontend/reducers/testReducer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								client/src/frontend/reducers/testReducer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| export interface ITestReducer { | ||||
| 	hello: any | undefined | ||||
| } | ||||
|  | ||||
| const initialState = { | ||||
| 	hello: 'world' | ||||
| }; | ||||
|  | ||||
| const testReducer = (state = initialState, action: { type: any; payload: { hello: any; }; }) => { | ||||
| 	switch (action.type){ | ||||
| 	case 'CHANGE_HELLO': { | ||||
| 		const newHello = action.payload.hello; | ||||
| 		return { | ||||
| 			hello: newHello | ||||
| 		}; | ||||
| 	} | ||||
| 	default: | ||||
| 		return state; | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default testReducer; | ||||
							
								
								
									
										26
									
								
								client/src/frontend/setStore.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								client/src/frontend/setStore.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // Redux | ||||
| import { createStore } from 'redux'; //, applyMiddleware | ||||
| // import { Provider } from 'react-redux'; | ||||
| import { composeWithDevTools as composeWithDevToolsWeb } from 'redux-devtools-extension'; | ||||
| import { config } from '../../config'; | ||||
| import reducer from './reducers'; | ||||
|  | ||||
| const { env } = config; | ||||
|  | ||||
| const composeEnhancers = composeWithDevToolsWeb({ | ||||
| 	// Specify here name, actionsBlacklist, actionsCreators and other options | ||||
| }); | ||||
|  | ||||
| const setStore = ({ initialState }) => { | ||||
| 	const store = env === 'development' ? createStore( | ||||
| 		reducer, | ||||
| 		initialState, | ||||
| 		composeEnhancers(), | ||||
| 	) : createStore( | ||||
| 		reducer, | ||||
| 		initialState, | ||||
| 	); | ||||
| 	return store; | ||||
| }; | ||||
|  | ||||
| export default setStore; | ||||
							
								
								
									
										6
									
								
								client/src/frontend/styles/global.sass
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								client/src/frontend/styles/global.sass
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| $base-color: #c6538c | ||||
| $color: rgba(black, 0.88) | ||||
|  | ||||
| body | ||||
|     background-color: $base-color | ||||
|     color: $color | ||||
							
								
								
									
										16
									
								
								client/src/routes/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								client/src/routes/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import React from 'react'; | ||||
| import InitialComponent from '../frontend/components/InitialComponent'; | ||||
| import OtherComponent from '../frontend/components/OtherComponent'; | ||||
|  | ||||
| const OTHER_COMPONENT = { | ||||
| 	path: '/other-component', | ||||
| 	element: <OtherComponent /> | ||||
| }; | ||||
|  | ||||
| const INITIAL_COMPONENT = { | ||||
| 	path: '/', | ||||
| 	element: <InitialComponent />, | ||||
| }; | ||||
|  | ||||
|  | ||||
| export default [ INITIAL_COMPONENT, OTHER_COMPONENT ]; | ||||
							
								
								
									
										13
									
								
								client/src/server/getHashManifest.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								client/src/server/getHashManifest.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import fs from 'fs'; | ||||
|  | ||||
| const getHashManifest = () => { | ||||
| 	try { | ||||
| 		const baseUrl = __dirname.replace(/\/client(.*)/,''); | ||||
| 		const fullURL = `${baseUrl}/client/build/assets/manifest-hash.json` ; | ||||
| 		return JSON.parse(fs.readFileSync(fullURL)); | ||||
| 	}catch(err){ | ||||
| 		console.error(err); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| export default getHashManifest; | ||||
							
								
								
									
										36
									
								
								client/src/server/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								client/src/server/index.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| require('dotenv').config(); | ||||
|  | ||||
| require('ignore-styles'); | ||||
|  | ||||
| //require('webpack-node-externals')(); | ||||
|  | ||||
| require('@babel/register')({ | ||||
| 	'presets': [ | ||||
| 		'@babel/preset-env', | ||||
| 		'@babel/preset-react', | ||||
| 		"@babel/preset-typescript", | ||||
| 		// '@babel/preset-flow', | ||||
| 	] | ||||
| }); | ||||
|  | ||||
| require('asset-require-hook')({ | ||||
| 	extensions: [ | ||||
| 		// images | ||||
| 		'jpg', | ||||
| 		'png', | ||||
| 		'svg', | ||||
| 		'gif', | ||||
| 		// videos | ||||
| 		'mp4', | ||||
| 		'avi', | ||||
| 		// typography | ||||
| 		'ttf', | ||||
| 		'otf', | ||||
| 		'eot', | ||||
| 		// files | ||||
| 		'pdf' | ||||
| 	], | ||||
| 	name: '/assets/[hash].[ext]', | ||||
| }); | ||||
|  | ||||
| require('./server'); | ||||
							
								
								
									
										121
									
								
								client/src/server/server.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								client/src/server/server.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,121 @@ | ||||
| //Dependencies of Server | ||||
| import express from 'express'; | ||||
| import { config } from '../../config'; | ||||
| import webpack from 'webpack'; | ||||
| import helmet from 'helmet'; | ||||
|  | ||||
| //Dependencies of HotReloading | ||||
| import webpackConfig from '../../webpack.config.dev'; | ||||
| import webpackDevMiddleware from 'webpack-dev-middleware'; | ||||
| import webpackHotMiddleware from 'webpack-hot-middleware'; | ||||
|  | ||||
| //Dependencies of SSR | ||||
| import React from 'react'; | ||||
| import { renderToString } from 'react-dom/server'; | ||||
| //Router | ||||
| import { StaticRouter } from 'react-router-dom/server'; | ||||
| import routes from '../routes'; | ||||
| //Redux | ||||
| import { Provider } from 'react-redux'; | ||||
| import setStore from '../frontend/setStore.js'; | ||||
| import initialState from '../frontend/reducers/initialState'; | ||||
| //Get Hashes | ||||
| import getHashManifest from './getHashManifest'; | ||||
| //App | ||||
| import App from '../frontend/components/App'; | ||||
|  | ||||
| const { env, port } = config; | ||||
|  | ||||
| const routesUrls = routes.map( route => route.path); | ||||
|  | ||||
| const app = express(); | ||||
|  | ||||
| if(env === 'development'){ | ||||
| 	const compiler = webpack(webpackConfig); | ||||
| 	const serverConfig = {  | ||||
| 		serverSideRender: true, | ||||
| 		publicPath: webpackConfig.output.publicPath, | ||||
| 	}; | ||||
|  | ||||
| 	app | ||||
| 		.use(webpackDevMiddleware(compiler, serverConfig)) | ||||
| 		.use(webpackHotMiddleware(compiler, { | ||||
| 			path: '/reload_wss', | ||||
| 			heartbeat: 1000, | ||||
| 		})); | ||||
| }else{ | ||||
| 	const baseUrl = __dirname.replace(/\/client(.*)/,''); | ||||
| 	const fullURL = `${baseUrl}/client/build` ; | ||||
| 	app | ||||
| 		.use((req, res, next) => { | ||||
| 			if(!req.hashManifest) req.hashManifest = getHashManifest(); | ||||
| 			next(); | ||||
| 		}) | ||||
| 		.use(express.static(fullURL)) | ||||
| 		.use(helmet()) | ||||
| 		.use(helmet.permittedCrossDomainPolicies()) | ||||
| 		.use(helmet({ | ||||
| 			contentSecurityPolicy: { | ||||
| 				directives: { | ||||
| 					...helmet.contentSecurityPolicy.getDefaultDirectives(), | ||||
| 					'script-src': ['\'self\'', '\'unsafe-inline\''],//"example.com" | ||||
| 				}, | ||||
| 			}, | ||||
| 		})) | ||||
| 		.disable('x-powered-by'); | ||||
| } | ||||
|  | ||||
| const setResponse = (html, preloadedState, manifest) => { | ||||
| 	const mainStyles = manifest ? manifest['main.css'] : 'assets/app.css'; | ||||
| 	const mainBuild = manifest ? manifest['main.js'] : 'assets/app.js'; | ||||
| 	const vendorBuild = manifest ? manifest['vendors.js'] : 'assets/vendor.js'; | ||||
|  | ||||
| 	return(` | ||||
|     <!DOCTYPE html> | ||||
|     <html lang="es"> | ||||
|         <head> | ||||
|             <meta charset="UTF-8"> | ||||
| 			<!-- <link rel="shortcut icon" href="favicon.ico"> --> | ||||
|             <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|             <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
| 			<meta name="theme-color" content="#000000"> | ||||
| 			<link rel="manifest" href="manifest.json"> | ||||
|             <link href="${mainStyles}" rel="stylesheet" type="text/css"></link> | ||||
|             <title>App</title> | ||||
|         </head> | ||||
|         <body> | ||||
|             <div id="app">${html}</div> | ||||
|             <script> | ||||
|                 window.__PRELOADED_STATE__ = ${JSON.stringify(preloadedState).replace(/</g, '\\u003c')} | ||||
|             </script> | ||||
|             <script src="${mainBuild}" type="text/javascript"></script> | ||||
|             <script src="${vendorBuild}" type="text/javascript"></script> | ||||
|         </body> | ||||
|     </html> | ||||
|     `); | ||||
| }; | ||||
|  | ||||
| const renderApp = (req, res, next) => { | ||||
| 	if(routesUrls.includes(req.url)){ | ||||
| 		const store = setStore({ initialState }); | ||||
| 		const preloadedState = store.getState(); | ||||
| 		const html = renderToString( | ||||
| 			<Provider store={store}> | ||||
| 				<StaticRouter location={req.url}> | ||||
| 					<App /> | ||||
| 				</StaticRouter> | ||||
| 			</Provider> | ||||
| 		); | ||||
| 		res.send(setResponse(html, preloadedState, req.hashManifest)); | ||||
| 	} | ||||
| 	next(); | ||||
| }; | ||||
|  | ||||
| app | ||||
| 	.get('*', renderApp); | ||||
|  | ||||
|  | ||||
| app.listen(port, (err) => { | ||||
| 	if(err) console.error(err); | ||||
| 	else console.log(`Server running on port ${port}`); | ||||
| }); | ||||
							
								
								
									
										46
									
								
								client/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								client/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| { | ||||
|   "extends": "ts-node/node12/tsconfig.json", | ||||
|   "ts-node": { | ||||
|     // It is faster to skip typechecking. | ||||
|     // Remove if you want ts-node to do typechecking. | ||||
|     "transpileOnly": true, | ||||
|     "compilerOptions": { | ||||
|       "module": "commonjs", | ||||
|     } | ||||
|   }, | ||||
|   "compilerOptions": { | ||||
|     "target": "es5", | ||||
|     "lib": [ | ||||
|       "dom", | ||||
|       "dom.iterable", | ||||
|       "esnext" | ||||
|     ], | ||||
|     "allowJs": true, | ||||
|     "skipLibCheck": true, | ||||
|     "esModuleInterop": true, | ||||
|     "allowSyntheticDefaultImports": true, | ||||
|     "strict": true, | ||||
|     "forceConsistentCasingInFileNames": true, | ||||
|     "noFallthroughCasesInSwitch": true, | ||||
|     "module": "esnext", | ||||
|     "moduleResolution": "node", | ||||
|     "resolveJsonModule": true, | ||||
|     "isolatedModules": true, | ||||
|     "noEmit": false, | ||||
|     "jsx": "react-jsx", | ||||
|     "experimentalDecorators": true, | ||||
|     "types": [ | ||||
|       "react/next",  | ||||
|       "react-dom/next",  | ||||
|       "node" | ||||
|     ], | ||||
|     "sourceMap": true, | ||||
|     "baseUrl": "**/*", | ||||
|   }, | ||||
|   "include": [ | ||||
|     "**/*" | ||||
|   ], | ||||
|   "exclude": [ | ||||
|    "node_modules"  | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										77
									
								
								client/webpack.config.dev.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								client/webpack.config.dev.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| const path = require('path'); | ||||
| const dotenv = require('dotenv').config(); | ||||
| const webpack = require('webpack'); | ||||
| const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | ||||
| const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); | ||||
| const ESLintPlugin = require('eslint-webpack-plugin'); | ||||
| const PUBLIC_URL = process.env.PUBLIC_URL || '/'; | ||||
|  | ||||
| module.exports = { | ||||
| 	entry: ['webpack-hot-middleware/client?path=/reload_wss&timeout=2000&reload=true&autoConnect=true', './src/frontend/index.tsx'], | ||||
| 	output: { | ||||
| 		path: path.resolve(__dirname, 'build'), | ||||
| 		filename: 'assets/app.js', | ||||
| 		publicPath: PUBLIC_URL, | ||||
| 	}, | ||||
| 	resolve: { | ||||
| 		extensions: ['.js', '.jsx','.ts','.tsx', '.json'], | ||||
| 		alias: { | ||||
| 			'@components': path.resolve(__dirname, 'src/frontend/components/'), | ||||
| 			'@styles': path.resolve(__dirname, 'src/frontend/styles/'), | ||||
| 		} | ||||
| 	}, | ||||
| 	devtool: 'inline-source-map', | ||||
| 	mode: 'development', | ||||
| 	context: __dirname, | ||||
| 	module: { | ||||
| 		rules: [ | ||||
| 			{ | ||||
| 				test: /\.(js|jsx|ts|tsx)$/, | ||||
| 				use: { | ||||
| 					loader: 'babel-loader', | ||||
| 					options: { plugins: ['react-refresh/babel'] } | ||||
| 				}, | ||||
| 				exclude: /node_modules/, | ||||
| 			}, | ||||
| 			{ | ||||
| 				test: /\.(css|sass|scss)$/, | ||||
| 				use: [ | ||||
| 					MiniCssExtractPlugin.loader, | ||||
| 					'css-loader', | ||||
| 					'sass-loader', | ||||
| 				],  | ||||
| 			}, | ||||
| 		] | ||||
| 	}, | ||||
| 	plugins: [ | ||||
| 		new webpack.HotModuleReplacementPlugin(), | ||||
| 		new ReactRefreshWebpackPlugin(), | ||||
| 		new MiniCssExtractPlugin({ | ||||
| 			filename: 'assets/app.css', | ||||
| 		}), | ||||
| 		new ESLintPlugin(), | ||||
| 		new webpack.DefinePlugin({ | ||||
| 			'process.env': JSON.stringify(dotenv.parsed), | ||||
| 			'process.env.PUBLIC_URL': JSON.stringify(PUBLIC_URL), | ||||
| 		}), | ||||
| 	], | ||||
| 	optimization: { | ||||
| 		splitChunks: { | ||||
| 			chunks: 'async', | ||||
| 			cacheGroups: { | ||||
| 				vendors: { | ||||
| 					name: 'vendors', | ||||
| 					chunks: 'all', | ||||
| 					reuseExistingChunk: true, | ||||
| 					priority: 1, | ||||
| 					filename: 'assets/vendor.js', | ||||
| 					enforce: true, | ||||
| 					test (module, chunks){ | ||||
| 						const name = module.nameForCondition && module.nameForCondition(); | ||||
| 						return chunks.name !== 'vendors' && /[\\/]node_modules[\\/]/.test(name);   | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| }; | ||||
							
								
								
									
										102
									
								
								client/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								client/webpack.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,102 @@ | ||||
| const path = require('path'); | ||||
| const dotenv = require('dotenv').config(); | ||||
| const webpack = require('webpack'); | ||||
| const CompressionWebpackPlugin = require('compression-webpack-plugin'); | ||||
| const MiniCssExtractPlugin = require('mini-css-extract-plugin'); | ||||
| const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); | ||||
| const TerserPlugin = require('terser-webpack-plugin'); | ||||
| const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); | ||||
| const { CleanWebpackPlugin } = require('clean-webpack-plugin'); | ||||
| const ESLintPlugin = require('eslint-webpack-plugin'); | ||||
| const CopyPlugin = require('copy-webpack-plugin'); | ||||
| const { InjectManifest } = require('workbox-webpack-plugin'); | ||||
| const PUBLIC_URL = process.env.PUBLIC_URL || '/'; | ||||
|  | ||||
| module.exports = { | ||||
| 	entry: './src/frontend/index.tsx', | ||||
| 	output: { | ||||
| 		path: path.resolve(__dirname, 'build'), | ||||
| 		filename: 'assets/app-[fullhash].js', | ||||
| 		publicPath: PUBLIC_URL, | ||||
| 	}, | ||||
| 	resolve: { | ||||
| 		extensions: ['.js', '.jsx','.ts','.tsx', '.json'], | ||||
| 		alias: { | ||||
| 			'@components': path.resolve(__dirname, 'src/frontend/components/'), | ||||
| 			'@styles': path.resolve(__dirname, 'src/frontend/styles/'), | ||||
| 		} | ||||
| 	}, | ||||
| 	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/app-[fullhash].css', | ||||
| 		}), | ||||
| 		new WebpackManifestPlugin({ | ||||
| 			fileName: 'assets/manifest-hash.json', | ||||
| 		}), | ||||
| 		new CleanWebpackPlugin(), | ||||
| 		new ESLintPlugin(), | ||||
| 		new webpack.DefinePlugin({ | ||||
| 			'process.env': JSON.stringify(dotenv.parsed), | ||||
| 			'process.env.PUBLIC_URL': JSON.stringify(PUBLIC_URL), | ||||
| 		}), | ||||
| 		new CopyPlugin({ | ||||
| 			patterns: [ | ||||
| 				{ | ||||
| 					from: './public/manifest.json', to: '', | ||||
| 				} | ||||
| 			] | ||||
| 		}), | ||||
| 		new InjectManifest({ | ||||
| 			swSrc: './service-worker.js', | ||||
| 			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-[fullhash].js', | ||||
| 					enforce: true, | ||||
| 					test (module, chunks){ | ||||
| 						const name = module.nameForCondition && module.nameForCondition(); | ||||
| 						return chunks.name !== 'vendors' && /[\\/]node_modules[\\/]/.test(name);   | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 	}, | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user