Skip to content
This repository has been archived by the owner on Jun 8, 2023. It is now read-only.

Feature/200401/test ssr/imagine10255 #1

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,46 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/core": "^7.7.7",
"@babel/preset-env": "^7.7.7",
"@babel/preset-react": "^7.7.4",
"@babel/register": "^7.7.7",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@types/express": "^4.17.4",
"@types/jest": "^24.0.0",
"@types/node": "^12.0.0",
"@types/react": "^16.9.0",
"@types/react-dom": "^16.9.0",
"@types/react-redux": "^7.1.7",
"@types/react-router-dom": "^5.1.3",
"babel-plugin-transform-assets": "^1.0.2",
"babel-preset-react-app": "^9.1.2",
"customize-cra": "^0.9.1",
"express": "^4.17.1",
"ignore-styles": "^5.0.1",
"react": "^16.13.1",
"react-app-rewired": "^2.1.5",
"react-dom": "^16.13.1",
"react-redux": "^7.1.3",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "3.4.1",
"redux": "^4.0.5",
"source-map-loader": "^0.2.4",
"ts-loader": "^6.2.2",
"ts-node": "^8.8.1",
"tsconfig-paths-webpack-plugin": "^3.2.0",
"typescript": "~3.7.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"start-server2": "NODE_ENV=production ts-node --project tsconfig.json server/bootstrap.js",
"start-server": "NODE_ENV=production node server/bootstrap.js"
},
"eslintConfig": {
"extends": "react-app"
Expand Down
75 changes: 38 additions & 37 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -1,43 +1,44 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script>window.__INITIAL_STATE__ = __REDUX__</script>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
26 changes: 26 additions & 0 deletions server.tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"baseUrl": "./src",
"paths": {
"*": ["*", "./src/@types/*"]
},
"outDir": "./dist",
"target": "es5",
"typeRoots": ["./src/@types", "./node_modules/@types"],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noImplicitAny": false,
"moduleResolution": "node",
"strictNullChecks": false,
"jsx": "react",
"noEmit": true
},
"exclude": ["node_modules"]
}
22 changes: 22 additions & 0 deletions server/bootstrap.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
require('ignore-styles');

require('@babel/register')({
"presets": [
["react-app", { "flow": false, "typescript": true }],
],
"plugins": [
"@babel/plugin-transform-modules-commonjs",
[
"transform-assets",
{
"extensions": [
"css",
"svg"
],
"name": "static/media/[name].[hash:8].[ext]"
}
]
]
});

require('./index');
13 changes: 13 additions & 0 deletions server/html.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const html = ({ body }: { body: string }) => `
<!DOCTYPE html>
<html>
<head>
</head>
<body style="margin:0">
<div id="app">${body}</div>
</body>
<script src="js/client.js" defer></script>
</html>
`;

export default html;
32 changes: 32 additions & 0 deletions server/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
const express = require('express');
const path = require('path');

const PORT = 3000;

const reactRenderer = require('./react-renderer');

/**
* initialize the application and create the routes
*/
const app = express();

/**
* "/path-in-out-routes-arr" should always serve our server rendered page;
* otherwise, continue with next handlers
*/

/**
* Set the location of the static assets (ie the js bundle generated by webapck)
*/
app.use('/build', express.static(path.resolve(__dirname, '../build')))
app.use('/static', express.static(path.resolve(__dirname, '../build/static')))

app.get('/*', reactRenderer.render());

/**
* Since this is the last non-error-handling
* middleware use()d, we assume 404, as nothing else
* responded.
*/

app.listen(PORT, () => console.log(`Example app listening on port ${PORT}!`));
83 changes: 83 additions & 0 deletions server/react-renderer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const React = require('react')
const renderToString = require('react-dom/server').renderToString;
const matchPath = require('react-router').matchPath;
const path = require('path');
const fs = require('fs');
const configureStore = require('../src/configure-store').default;

const initialState = {
todos: [
{
id: 0,
text: 'Task in initialState from server',
completed: false
},
],
};

/**
* Import our main App component
* Remember it's exported as ES6 module, so to require it, you must call .default
*/
const App = require('../src/App').default;

exports = module.exports;

exports.render = () => {
return (req, res, next) => {

/**
* Take routes collection and see if it's a valid app's route
*/


/**
* Point to the html file created by CRA's build tool and open it
*/
const filePath = path.resolve(__dirname, '..', 'build', 'index.html');

fs.readFile(filePath, 'utf8', (err, htmlData) => {
if (err) {
console.error('err', err);
return res.status(404).end(); // WARNING: This 404 will be handled by Express server and won't be your React 404 component.
}

const location = req.url;


/**
* Set the app's response to 200 OK (https://httpstatuses.com/200)
*/
res.writeHead(200, { 'Content-Type': 'text/html' })
console.log(`SSR of ${req.path}`);

const store = configureStore(initialState);

/**
* Convert JSX code to a HTML string that can be rendered server-side with
* `renderToString` a method provided by ReactDOMServer
*
* This sets up the app so that calling ReactDOM.hydrate() will preserve the
* rendered HTML and only attach event handlers.
* (https://reactjs.org/docs/react-dom-server.html#rendertostring)
*/
const jsx = <App store={store} location={location}/>
const reactDom = renderToString(jsx);

/**
* inject the rendered app and it state
* into our html and send it
*/
return res.end(
htmlData.replace(
'<div id="root"></div>',
`<div id="root">${reactDom}</div>`
).replace(
'__REDUX__',
JSON.stringify(store.getState())
)
);
});
}

};
7 changes: 6 additions & 1 deletion src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@

.App-header {
background-color: #282c34;
min-height: 100vh;
min-height: 40vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
margin-bottom: 2vmin;
}

.App-header p {
margin: 1.5vmin;
}

.App-link {
Expand Down
46 changes: 46 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React from 'react';
import { Provider } from 'react-redux';
import Home from './components/Home.tsx';
import Page from './components/Page.tsx';
import { Route, Switch, BrowserRouter } from 'react-router-dom';
import { StaticRouter } from 'react-router';

import './App.css';

const NoMatch = () => (
<div>
<h1>404</h1>
React Page Not Found
</div>
);

const AppRoutes = ({ store }) => (
<Switch>
<Route path="/" component={Home} exact />
{/* <Route path="/page" component={Page} exact /> */}
<Route path="/page" render={() => {
return <Page />;
}} exact />
<Route render={NoMatch} />
</Switch>
)

function App (props) {
return (
<Provider store={props.store}>
{
props.location
? (
<StaticRouter location={props.location} context={{}}>
<AppRoutes store={props.store}/>
</StaticRouter>
) : (
<BrowserRouter>
<AppRoutes/>
</BrowserRouter>
)
}
</Provider>
);
}
export default App;
2 changes: 1 addition & 1 deletion src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import * as React from 'react';
import { render } from '@testing-library/react';
import App from './App';

Expand Down
Loading