Search code examples
reactjstypescriptexpressbabeljscreate-react-app

CRA with express server.js breaks after introducing typescript


tl;dr

I introduced typescript to a CRA app but keep getting the following error when trying to serve the built app

 SyntaxError: path\to\root\server\loader.js: Unexpected token, expected "</>/<=/>=" (130:28)

  128 |       let routeMarkup = frontloadServerRender(() =>
  129 |         renderToString(
> 130 |           <Loadable.Capture report={m => modules.push(m)}>
      |                             ^
  131 |             <Provider store={store}>
  132 |               <StaticRouter location={req.url} context={context}>

Full story

I have a React app I put together a while ago following this tutorial for setting up SSR and codesplitting without ejecting react-scripts.

I'm trying to add typescript to the project, and my hope is that I can enable it and use it going forward in new components. That is,I don't want to worry about changing every file from .js to .tsx and then debugging all the typescript errors that will arise.

I followed the guide to add typescript, and renamed src/index.js to src/index.tsx, and was still able to run my app with yarn start.

The problem is that I'm running into an error when I try to build and serve the app, and another error when I fix that first one.

First Error

First time I tried to run serve, I got this error

(node:20588) UnhandledPromiseRejectionWarning: path\to\project\src\app\routes\homepage\index.tsx:1
(function (exports, require, module, __filename, __dirname) { import React, { Component } from "react";
                                                                     ^^^^^
SyntaxError: Unexpected identifier

I figured this was a babel issue because I was using the server files from the tutorial, which didn't use typescript. Eventually I ended up with this config, which I'm requiring in server/index.js before requiring server/server.js

require("@babel/register")({
  ignore: [/\/(build|node_modules)\//],
  presets: ["@babel/preset-env", "@babel/preset-react"],
  plugins: [
    "@babel/plugin-transform-typescript",
    "@babel/plugin-syntax-dynamic-import",
    "@babel/plugin-proposal-export-default-from",
    "@babel/plugin-proposal-export-namespace-from",
    "@babel/plugin-proposal-class-properties",
    "react-loadable/babel"
  ]
});

Second error

That seemed to fix the first issue, but now I'm getting a different error I can't figure out

 SyntaxError: path\to\root\server\loader.js: Unexpected token, expected "</>/<=/>=" (130:28)

  128 |       let routeMarkup = frontloadServerRender(() =>
  129 |         renderToString(
> 130 |           <Loadable.Capture report={m => modules.push(m)}>
      |                             ^
  131 |             <Provider store={store}>
  132 |               <StaticRouter location={req.url} context={context}>

The problem now is that while the import statements were being parsed correctly, the app wasn't recognizing JSX syntax.

I tried renaming the file from .js to .jsx, but that didn't make a difference. Neither did changing the file extension to .ts or .tsx.

I also tried moving the React component to a separate file, RouteMarkup.jsx, and importing it into the loader, but that didn't help.

I've spent three hours now trying to debug this, but I'm at a loss trying to parse this situation. Hopefully someone can point me in the right direction.

Edit 1

I haven't changed much of the server setup of the tutorial, so if anyone wants to know the larger context of my code, you can check the sample project repo to see how the server and loader are set up.


Solution

  • Impossible to know exactly what this issue is coming from without full overview of the project or unless someone has had the exact error message before.

    Possibilities: Avoid using CRA's <% GLOBAL VARIABLE %> syntax. Babel + Typescript will only work if its implemented on your backend I.E Your backend actually gets compiled through babel.

    Solutions:

    In my honest opinion as someone who has done 1000+ hrs of configuring server side rendering it is by far the hardest thing to setup and manage yourself, The example's React has on their documentation website does in no way reflect anyone's actual real-world setups where you have css + code splitting + dynamic loading assets + authentication + need to hydrate redux from the server + need to have static routing on backend and dynamic routing on frontend.

    The solution is to consider something like NextJS9 where it has out-of-the-box Typescript support, inbuilt server (each file under /api/x becomes a route) automaticaly code splitting and server side rendering. (For reference https://nextjs.org/)

    If this solution doesn't work for you let me know and i will build a demo project with typescript + babel + frontend/backend + react.