mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-09-12 06:10:11 +02:00
Initial commit
This commit is contained in:
28
src/server/devServer.ts
Normal file
28
src/server/devServer.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Express } from "express"
|
||||
import chalk from "chalk"
|
||||
|
||||
import config from "../config"
|
||||
|
||||
export default (app: Express): void => {
|
||||
const webpack = require("webpack")
|
||||
const webpackConfig = require("../../webpack/client.config").default
|
||||
const compiler = webpack(webpackConfig)
|
||||
const instance = require("webpack-dev-middleware")(compiler, {
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
serverSideRender: true,
|
||||
})
|
||||
|
||||
app.use(instance)
|
||||
app.use(
|
||||
require("webpack-hot-middleware")(compiler, {
|
||||
log: false,
|
||||
path: "/__webpack_hmr",
|
||||
heartbeat: 10 * 1000,
|
||||
})
|
||||
)
|
||||
|
||||
instance.waitUntilValid(() => {
|
||||
const url = `http://${config.HOST}:${config.PORT}`
|
||||
console.info(chalk.green(`==> 🌎 Listening at ${url}`))
|
||||
})
|
||||
}
|
42
src/server/index.ts
Executable file
42
src/server/index.ts
Executable file
@@ -0,0 +1,42 @@
|
||||
import path from "path"
|
||||
import express from "express"
|
||||
import logger from "morgan"
|
||||
import compression from "compression"
|
||||
import helmet from "helmet"
|
||||
import hpp from "hpp"
|
||||
import favicon from "serve-favicon"
|
||||
import chalk from "chalk"
|
||||
|
||||
import devServer from "./devServer"
|
||||
import ssr from "./ssr"
|
||||
|
||||
import { getJAVGameList } from "../gsheets/jav"
|
||||
import config from "../config"
|
||||
|
||||
const app = express()
|
||||
|
||||
// Use helmet to secure Express with various HTTP headers
|
||||
app.use(helmet({ contentSecurityPolicy: false }))
|
||||
// Prevent HTTP parameter pollution
|
||||
app.use(hpp())
|
||||
// Compress all requests
|
||||
app.use(compression())
|
||||
|
||||
// Use for http request debug (show errors only)
|
||||
app.use(logger("dev", { skip: (_, res) => res.statusCode < 400 }))
|
||||
app.use(favicon(path.resolve(process.cwd(), "public/favicon.ico")))
|
||||
app.use(express.static(path.resolve(process.cwd(), "public")))
|
||||
|
||||
// Enable dev-server in development
|
||||
if (__DEV__) devServer(app)
|
||||
|
||||
// Google Sheets requests
|
||||
app.get("/javGames", getJAVGameList)
|
||||
|
||||
// Use React server-side rendering middleware
|
||||
app.get("*", ssr)
|
||||
|
||||
// @ts-expect-error
|
||||
app.listen(config.PORT, config.HOST, (error) => {
|
||||
if (error) console.error(chalk.red(`==> 😭 OMG!!! ${error}`))
|
||||
})
|
63
src/server/renderHtml.ts
Executable file
63
src/server/renderHtml.ts
Executable file
@@ -0,0 +1,63 @@
|
||||
import { ChunkExtractor } from "@loadable/server"
|
||||
import { HelmetData } from "react-helmet"
|
||||
import serialize from "serialize-javascript"
|
||||
import { minify } from "html-minifier"
|
||||
|
||||
export default (
|
||||
head: HelmetData,
|
||||
extractor: ChunkExtractor,
|
||||
htmlContent: string,
|
||||
initialState: typeof window.__INITIAL_STATE__
|
||||
): any => {
|
||||
const html = `
|
||||
<!doctype html>
|
||||
<html ${head.htmlAttributes.toString()}>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000" />
|
||||
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
|
||||
${head.title.toString()}
|
||||
${head.base.toString()}
|
||||
${head.meta.toString()}
|
||||
${head.link.toString()}
|
||||
|
||||
<!-- Insert bundled styles into <link> tag -->
|
||||
${extractor.getLinkTags()}
|
||||
${extractor.getStyleTags()}
|
||||
</head>
|
||||
<body>
|
||||
<!-- Insert the router, which passed from server-side -->
|
||||
<div id="react-view">${htmlContent}</div>
|
||||
|
||||
<!-- Store the initial state into window -->
|
||||
<script>
|
||||
// Use serialize-javascript for mitigating XSS attacks. See the following security issues:
|
||||
// http://redux.js.org/docs/recipes/ServerRendering.html#security-considerations
|
||||
window.__INITIAL_STATE__=${serialize(initialState)};
|
||||
</script>
|
||||
|
||||
<!-- Insert bundled scripts into <script> tag -->
|
||||
${extractor.getScriptTags()}
|
||||
${head.script.toString()}
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
// html-minifier configuration, refer to "https://github.com/kangax/html-minifier" for more configuration
|
||||
const minifyConfig = {
|
||||
collapseWhitespace: true,
|
||||
removeComments: true,
|
||||
trimCustomFragments: true,
|
||||
minifyCSS: true,
|
||||
minifyJS: true,
|
||||
minifyURLs: true,
|
||||
}
|
||||
|
||||
// Minify HTML in production
|
||||
return __DEV__ ? html : minify(html, minifyConfig)
|
||||
}
|
83
src/server/ssr.tsx
Normal file
83
src/server/ssr.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import path from "path"
|
||||
import { renderToString } from "react-dom/server"
|
||||
import { StaticRouter } from "react-router-dom"
|
||||
import { renderRoutes, matchRoutes } from "react-router-config"
|
||||
import { Provider } from "react-redux"
|
||||
import { ChunkExtractor } from "@loadable/server"
|
||||
import { Helmet } from "react-helmet"
|
||||
import chalk from "chalk"
|
||||
import { Request, Response, NextFunction } from "express"
|
||||
import { Action } from "@reduxjs/toolkit"
|
||||
|
||||
import createStore from "../store"
|
||||
import renderHtml from "./renderHtml"
|
||||
import routes from "../routes"
|
||||
|
||||
export default async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||
const { store } = createStore({ url: req.url })
|
||||
|
||||
// The method for loading data from server-side
|
||||
const loadBranchData = (): Promise<any> => {
|
||||
const branch = matchRoutes(routes, req.path)
|
||||
const promises = branch.map(({ route, match }) => {
|
||||
if (route.loadData)
|
||||
return Promise.all(
|
||||
route
|
||||
.loadData({
|
||||
params: match.params,
|
||||
getState: store.getState,
|
||||
req,
|
||||
res,
|
||||
})
|
||||
.map((item: Action) => store.dispatch(item))
|
||||
)
|
||||
|
||||
return Promise.resolve(null)
|
||||
})
|
||||
|
||||
return Promise.all(promises)
|
||||
}
|
||||
|
||||
try {
|
||||
// Load data from server-side first
|
||||
await loadBranchData()
|
||||
|
||||
const statsFile = path.resolve(process.cwd(), "public/loadable-stats")
|
||||
const extractor = new ChunkExtractor({ statsFile })
|
||||
|
||||
const staticContext: Record<string, any> = {}
|
||||
const App = extractor.collectChunks(
|
||||
<Provider store={store}>
|
||||
{/* Setup React-Router server-side rendering */}
|
||||
<StaticRouter location={req.path} context={staticContext}>
|
||||
{renderRoutes(routes)}
|
||||
</StaticRouter>
|
||||
</Provider>
|
||||
)
|
||||
|
||||
const initialState = store.getState()
|
||||
const htmlContent = renderToString(App)
|
||||
// head must be placed after "renderToString"
|
||||
// see: https://github.com/nfl/react-helmet#server-usage
|
||||
const head = Helmet.renderStatic()
|
||||
|
||||
// Check if the render result contains a redirect, if so we need to set
|
||||
// the specific status and redirect header and end the response
|
||||
if (staticContext.url) {
|
||||
res.status(301).setHeader("Location", staticContext.url)
|
||||
res.end()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Pass the route and initial state into html template, the "statusCode" comes from <NotFound />
|
||||
res.status(staticContext.statusCode === "404" ? 404 : 200).send(
|
||||
renderHtml(head, extractor, htmlContent, initialState)
|
||||
)
|
||||
} catch (error) {
|
||||
res.status(404).send("Not Found :(")
|
||||
console.error(chalk.red(`==> 😭 Rendering routes error: ${error}`))
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
Reference in New Issue
Block a user