1

Hi I'm building a React app with SSR. Server and client part both written in typescript and transpiled separately. Here the app structure for the better understanding:

enter image description here

Here the simplified webpack configs for the server and client bundles:

// webpack.client.js

module.exports = {
  mode: "development",
  resolve: {
    modules: ["src", "static", "node_modules"],
    extensions: [".ts", ".tsx", ".js", ".jsx"],
  },

  entry: [
    "./src/client/index.tsx"
  ],

  output: {
    filename: "bundle.js",
    path: PUBLIC_PATH,
  },

  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: "ts-loader",
          }
        ],
      },
    ]
  },
  plugins: [
    new webpack.DefinePlugin({ IS_SERVER: false })
  ]
};

Server config looks pretty much the except for the target and externals

//webpack.server.js
const config = {
  mode: "development",
  resolve: {
    modules: ["src", "static", "node_modules"],
    extensions: [".ts", ".tsx", ".js", ".jsx"],
  },

  externals: [webpackNodeExternals()],
  target: 'node',

  entry: [
    "./src/server/index.ts"
  ],

  output: {
    filename: "bundle.js",
    path: SERVER_BUILD_PATH
  },

  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [
          {
            loader: "ts-loader",
          }
        ],
      },
    ]
  },

  plugins: [
    new webpack.DefinePlugin({ IS_SERVER: true })
  ]
};

In the server code I got a renderer function which renders React application to string.

// renderer.tsx
import React from "react";
import { renderToString } from "react-dom/server";
import { App } from "client/App";

const html = (app) => `
  <html>
    <head>
    </head>
    <body>
      <div id="root">${app}</div>
      <script src="/public/bundle.js"></script>
    </body>
  </html>
`;

export async function renderer(req) {
  const app = renderToString(<App />);
  return html(app);
}

Which then returns to the client by the express server.

//index.ts
app.get("*", async (req, res) => {
  const content = await renderer(req);
  res.send(content);
});

As you see both parts need to transpile React app. The question is - how can I reuse transpiled client code in the server bundle so that server config only need to transpile index.ts and renderer.tsx?

Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
Ivan Semochkin
  • 8,649
  • 3
  • 43
  • 75

2 Answers2

0

You can use webpack-merge package to do that

Here is my example

const merge = require('webpack-merge');
const baseConfig = require('./webpack.config.js');
const webpack = require("webpack");

module.exports = merge(baseConfig, {
    plugins: [
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('development'),
                'BASE_URL': JSON.stringify('http://localhost:5000/')
            }
        })
    ],
    watch: true
});
Tony Ngo
  • 19,166
  • 4
  • 38
  • 60
  • Thanks for the answer. But `webpack-merge` solves a different problem. I want to reuse transpiled bundle not the config file. – Ivan Semochkin Oct 30 '19 at 12:07
0

This can be easily done using resolve.alias: https://webpack.js.org/configuration/resolve/#resolvealias

Simply move the helpers directory up in the app's directory hierarchy and import from it.

In your case, I would also like to redesign the app using a single webpack.config.js file at the root level of your app in which you can combine the client and server configurations using the multi-compiler feature and respect the principle "Do not repeat yourself": https://stackoverflow.com/a/43689505/2569746

Max_Payne
  • 142
  • 1
  • 1
  • 12