9

I have a basic create-react-app TypeScript project (client). Just directory up, I have a server and a shared folder. Inside the shared folder I have a number of interfaces and enums that I share between the server and the client.

I wish to link this shared folder to both the client and server packages.

First, I went to shared and ran $ yarn link and then ran $ yarn link shared from both the client and the server folders.

The server is as happy as can be, and for the most part the client is too. However, as soon as I use one of the enums from the shared directory in the client, I get an error:

../shared/src/models/Roles.ts 4:0
Module parse failed: The keyword 'enum' is reserved (4:0)
File was processed with these loaders:
 * ./node_modules/@pmmmwh/react-refresh-webpack-plugin/loader/index.js
You may need an additional loader to handle the result of these loaders.
| $RefreshSetup$(module.id);
| 
> enum Roles {
|     RW_ORG = "rw_org", // can modifiy organization and it's users, nothing else

I'm importing it like so: import {Roles} from "shared"; but have tried numerous other ways as well.

I'm exporting it from the shared index.ts like so

import Roles from "./models/Roles";

export type { 
    // all of my interfaces
};
export { Roles }

All of my interfaces are usable, so I don't understand. What the hell is going on here?

foxtrotuniform6969
  • 3,527
  • 7
  • 28
  • 54

5 Answers5

12

Well, it turns out that these errors are all cause by create-react-app's default webpack.config.js. If you navigate to code node_modules/react-scripts/config/webpack.config.js you fill find a line include: paths.appSrc which basically limits Babel to the src/ folder of the react app itself.

That means, if you've yarn linked a folder outside of it, Babel will not transpile it to normal JS, and thus React cannot use it.

There are two hacky solutions, but I would like a better one.

  1. Manually (or via a build script) delete the include: paths.appSrc line from react-scripts every time you install a node module
  2. Make a script that copies the external directory into your React src directory every time the external directory is modified.

I really wish there were an official way around this...

foxtrotuniform6969
  • 3,527
  • 7
  • 28
  • 54
  • 1
    I got into a similar problem, and solved it by putting the ORIGINAL (shared) folder inside the react app source and the linked folder inside the server which doesn't care about this as the react-script does :) – Yochai Lehman Feb 27 '21 at 13:07
  • @YochailLehman Yes, that's another possible solution. Honestly though, the best thing to do is just to do one of these options until you're ready to make your shared directory into an actual module. At that point you should build the shared module when you make changes and install it as a local package (or a hosted package somewhere) rather than rely on linking or anything. – foxtrotuniform6969 Feb 27 '21 at 17:51
  • 1
    I automated it with @craco/craco: https://gist.github.com/PhilippMolitor/00f427d12a9c5bca84309058d88846b7 – Philipp Molitor Jul 21 '21 at 10:56
  • 1
    You can also use `include: [paths.appSrc, /\.(ts|tsx)$/]` – Marcin Wanago Oct 18 '21 at 16:04
8

Based on @foxtrotuniform6969's answer, i created a @cracro/craco configuration that gets rid of the misbehaving setting by itself.

module.exports = {
  webpack: {
    configure: (webpackConfig) => ({
      ...webpackConfig,
      module: {
        ...webpackConfig.module,
        rules: webpackConfig.module.rules.map((rule) => {
          if (!rule.oneOf) return rule;
          return {
            ...rule,
            oneOf: rule.oneOf.map((ruleObject) => {
              if (
                !new RegExp(ruleObject.test).test('.ts') ||
                !ruleObject.include
              )
                return ruleObject;
              return { ...ruleObject, include: undefined };
            }),
          };
        }),
      },
    }),
  },
};

https://gist.github.com/PhilippMolitor/00f427d12a9c5bca84309058d88846b7

Philipp Molitor
  • 387
  • 4
  • 14
3

It is possible to automatically remove the include path setting mentioned in the other answer using react-app-rewired.

The following config-overrides.js works for react-scripts:4.0.3 and causes babel to also transpile files in node-modules.

// config-overrides.js
module.exports = function override(config, env) {
  // This line might break with other react-script versions
  delete config.module.rules[1].oneOf[2].include
  return config
}
notclive
  • 2,897
  • 1
  • 17
  • 14
Emiswelt
  • 3,909
  • 1
  • 38
  • 56
2

The other answers to this question suggest removing the include in react-scripts' webpack.config (either with craco or react-app-rewired). I found this worked with yarn start, but when I made a production build with yarn build I got the error Uncaught ReferenceError: exports is not defined at runtime.

Instead of removing the include, I had to add the other project's src in addition to the existing src directory. Here's my config-overrides.js to be used with react-app-rewired.

For react-scripts 4:

const path = require("path");

module.exports = function override(config) {
    // See https://stackoverflow.com/questions/65893787/create-react-app-with-typescript-and-npm-link-enums-causing-module-parse-failed.
    config.module.rules[1].oneOf[2].include = [
        path.join(__dirname, './src'),
        path.join(__dirname, '../backend/src')
    ];
    return config
}

For react-scripts 5:

const path = require("path");

module.exports = function override(config) {
    // See https://stackoverflow.com/questions/65893787/create-react-app-with-typescript-and-npm-link-enums-causing-module-parse-failed.
    config.module.rules[1].oneOf[3].include = [
        path.join(__dirname, './src'),
        path.join(__dirname, '../backend/src')
    ];
    return config
}
notclive
  • 2,897
  • 1
  • 17
  • 14
1

My craco config, does the same thing as Phil Mo's version as far as I can tell but is easier to understand

module.exports = {
    webpack: {
        configure: (webpackConfig) => {
            webpackConfig.module.rules[0].oneOf.find(
                ({ test: t }) =>
                    t != null &&
                    !Array.isArray(t) &&
                    t.toString().includes('ts')
            ).include = undefined

            return webpackConfig
        }
    }
}
erik
  • 150
  • 2
  • 6