指定 URL 路径作为手动构建的 REACT SSR 应用程序的变量(即不使用 NextJS )

Specify URL path as a variable for a REACT SSR applications built manually (i.e. NOT using NextJS )

注意:我的 SSR 应用程序没有使用 NextJS。我也没有使用 create-react-app 来创建我的应用程序。

我有一个手工构建的 React 服务器端渲染 (SSR) 应用程序(相对于使用像 create-react-app 这样的工具)。我使用 WebPack 来捆绑服务器端代码和客户端代码。 我学习了优秀的 Udemy 课程 https://www.udemy.com/course/server-side-rendering-with-react-and-redux/ 以了解如何创建 React SSR 应用程序

我的申请

应用结构

webpack.base.config.js

module.exports = {
  module: {
    rules: [
      {
        test: /\.(png|jpe?g|gif)$/i,
        use: 'file-loader',
      },
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          cwd: __dirname,
        },
      },
    ],
  },

  resolve: {
    extensions: ['.js', '.jsx'],
  },
};

webpack.client.config.js

const path = require('path');
const { merge } = require('webpack-merge');
const CopyPlugin = require('copy-webpack-plugin');
const baseConfig = require('./webpack.base.config.js');

const config = {

  entry: './src/client/client.jsx',

  output: {
    filename: 'client-bundle.js',
    path: path.resolve(__dirname, 'public'),
  },

  plugins: [
    new CopyPlugin({
      patterns: [
        {
          from: path.resolve(__dirname, 'src', 'styles'),
          to: path.resolve(__dirname, 'public'),
        },
      ],
    }),
  ],
};

module.exports = merge(baseConfig, config);

webpack.server.config.js

const path = require('path');
const { merge } = require('webpack-merge');
const webpackNodeExternals = require('webpack-node-externals');
const baseConfig = require('./webpack.base.config.js');

const config = {
  target: 'node',

  entry: './src/server/server.js',

  output: {
    filename: 'server-bundle.js',
    path: path.resolve(__dirname, 'build'),
  },

  externals: [webpackNodeExternals()],
};

module.exports = merge(baseConfig, config);

路线

{
    ...Home,
    path: '/',
    exact: true,
},
{
    ...Page1,
    path: '/page1',
    exact: true,
},

客户端路由

<BrowserRouter>
  ...
</BrowserRouter>

服务器端路由

<StaticRouter context={context} location={req.path}>
    ...
</StaticRouter>

服务器端生成的HTML模板

<html>
  <head>
    <link rel="stylesheet" type="text/css" href="styles.css">
  </head>
  <body>
    <div id="root">${content}</div>
    <script src="client-bundle.js"></script>
  </body>
</html>

package.json 脚本

"scripts": {
  "start": "node build/server-bundle.js",
  "build": "npm-run-all --parallel prod:build*",
  "prod:build-server-bundle": "webpack --config webpack.server.config.js",
  "prod:build-client-bundle": "webpack --config webpack.client.config.js",

  "dev": "npm-run-all --parallel dev:*",
  "dev:run-server": "nodemon --watch build --exec node build/server-bundle.js",
  "dev:build-server-bundle": "webpack --config webpack.server.config.js --watch",
  "dev:build-client-bundle": "webpack --config webpack.client.config.js --watch",
  "lint": "eslint ./src --ext .js,.jsx"
},

运行我的申请

我运行 本地应用程序使用

npm run dev

我的申请 URL 因此

http://localhost:/     
http://localhost:/page1

我的要求

我希望我的应用程序有一个可自定义的 URL 路径,例如“/a/b”,这样我的 URL 将是

http://localhost:/a/b 
http://localhost:/a/b/page1

或者如果我的路径是“xyz”,我的 URLs 将是

http://localhost:/xyz 
http://localhost:/xyz/page1

如何在我的 React SSR 应用程序中启用自定义基本路径。

我试过的

我在 HTML 和路由器中的应用程序中硬编码了一个路径,即

<html>
  <head>
    <link rel="stylesheet" type="text/css" href="a/b/styles.css">
  </head>
  <body>
    <div id="root">${content}</div>
    <script src="a/b/client-bundle.js"></script>
  </body>
</html>


<BrowserRouter basename="a/b/">     
  ...
</BrowserRouter>


<StaticRouter context={context} location={req.path} basename="a/b/"> 
   ...
</StaticRouter>

但是这样不行,要走下面任一路

http://localhost http://localhost/a/b

呈现我的主页时没有应用样式表,也没有客户端包。这是因为以下都找不到,return a 404

http://localhost/a/b/styles.css
http://localhost/a/b/client-bundle.js

此外,如果我使用 link 调用路由器,样式和客户端包的 URL 有两次路径,即

client side navigation to 
   http://localhost:8080/a/b/contact

means styles and client-bundle request urls are
    http://localhost/a/b/a/b/styles.css
    http://localhost/a/b/a/b/client-bundle.js

您可以简单地添加一个环境变量 basePath,然后使用它来设置您的路线。

路线

{
    ...Home,
    path: `${process.env.basePath}/`,
    exact: true,
},
{
    ...Page1,
    path: `${process.env.basePath}/page1`,
    exact: true,
},

现在,如果您的 basePath 是“/a/b”,您的索引组件将在 yourdomain/a/b/ 上可用,而 page1 将在 yourdomain/a/b/page1

上可用

Hassaan Tauqir 的 post 我在上面标记为答案帮助我改进了解决方案。感谢哈桑。

package.json

更改生产环境的脚本以指定 BASE_URL。

"prod:build-server-bundle": "cross-env BASE_URL=/a/b webpack --config webpack.server.config.js",
"prod:build-client-bundle": "cross-env BASE_URL=/a/b webpack --config webpack.client.config.js",

注意:您必须使用“cross-env”,否则这不适用于所有操作系统,因此我必须先安装“cross-env”

npm install cross-env

我保留了 DEVELOPMENT 脚本不变,因为我在本地测试时不需要路径

"dev:build-server-bundle": "webpack --config webpack.server.config.js --watch",
"dev:build-client-bundle": "webpack --config webpack.client.config.js --watch",

webpack.base.config.js

正在读BASE_URL

“BASE_URL”可在“webpack.base.config.js”中访问。我添加了代码,以便我可以处理是否使用尾部斜杠指定的“BASE_URL”。

// Set up the BASE_URL parameter, ensuring it does not have a trailing slash
let BASE_URL = '';
if (process.env.BASE_URL) {
  BASE_URL = process.env.BASE_URL.toString().trim();
  if (BASE_URL.substr(-1) === '/') {
    BASE_URL = BASE_URL.substr(0, BASE_URL.length - 1);
  }
}

公共路径

在“module.exports”中添加“输出”部分并添加“publicPath”设置。 “publicPath”允许您指定应用程序中所有资产的基本路径,例如,我使用以下代码在我的应用程序中引用了图像。

import myImage from '../images/myImage.png';
....
<img src={myImage } alt="myImage " />
....
 

“publicPath”必须以斜杠结尾,因此如果我们有一个 BASE_URL 我会附加一个 / 否则我将其留空。

output: {
    publicPath: (BASE_URL) ? `${BASE_URL}/` : '',
},
  

有关“publicPath”的详细信息,请参阅https://webpack.js.org/guides/public-path/

webpack.DefinePlugin

在“module.exports”中添加“webpack.DefinePlugin”设置要传递给应用程序重置的环境变量

plugins: [
    new webpack.DefinePlugin({
      'process.env.BASE_URL': JSON.stringify(BASE_URL),
    }),
],    

有关“DefaultPlugin”的详细信息,请参阅https://webpack.js.org/plugins/define-plugin/

服务器端路由

将“basename”添加到服务器端路由器,其值为webpack配置文件中“DefinePlugin”中指定的变量

<StaticRouter context={context} location={req.path} basename={process.env.BASE_URL}>
    ...
</StaticRouter>

客户端路由

将“basename”添加到客户端路由器,其值为webpack配置文件中“DefinePlugin”中指定的变量

<BrowserRouter basename={process.env.BASE_URL}>
    ...
</BrowserRouter>