create-react-app: TSX files in adjacent yarn workspace packages result in Module parse failed: Unexpected token. You need an appropriate TypeScript loader

Describe the bug

react-scripts cannot load tsx files that are in adjacent yarn workspace packages.

Module parse failed: Unexpected token. You may need an appropriate loader to handle this file type

Did you try recovering your dependencies?

Yes, yarn --version 1.22.4

Environment

Environment Info:

  current version of create-react-app: 3.4.1
  running from /home/ty/.nvm/versions/node/v10.20.1/lib/node_modules/create-react-app

  System:
    OS: Linux 5.6 Arch Linux
    CPU: (8) x64 Intel(R) Core(TM) i5-8305G CPU @ 2.80GHz
  Binaries:
    Node: 10.20.1 - ~/.nvm/versions/node/v10.20.1/bin/node
    Yarn: 1.22.4 - ~/.nvm/versions/node/v10.20.1/bin/yarn
    npm: 6.14.4 - ~/.nvm/versions/node/v10.20.1/bin/npm
  Browsers:
    Chrome: Not Found
    Firefox: Not Found
  npmPackages:
    react: Not Found
    react-dom: Not Found
    react-scripts: Not Found
  npmGlobalPackages:
    create-react-app: 3.4.1

Current behavior

react-scripts can load TypeScript files with ts or tsx extensions when those files are located in the same ‘home’ package as react-scripts (the web package in the demo). react-scripts can load ts files in packages outside of the home package so long as the baseUrl parameter is set to ./ in tsconfig.json. react-scripts cannot load tsx files outside of the home package even if the jsx parameter is set to react.

Steps to reproduce

  1. Initialize a yarn monorepo
  2. Create a react app as a package in that monorepo with the typescript template create-react-app web --template typescript
  3. Create a package components alongside the previously created web package and add typescript and a tsconfig.json
  4. Add the components package to web’s dependencies.
  5. In src/components create an index.ts file with a default export function that logs something to the console (i.e. no JSX).
  6. Import that function in web and log to the console at run time.
  7. Experience no error.
  8. Change the src/components/index.ts to src/components/index.tsx and make the function run some JSX.
  9. Update web to render the exported JSX function.
  10. Experience the error.

Expected behavior

CRA should be able to import and run JSX (i.e. tsx) code from adjacent packages.

Actual behavior

CRA is only able to import and run ts code and not tsx code.

../components/src/index.tsx 4:2
Module parse failed: Unexpected token (4:2)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| 
| const App = () => (
>   <div>Hello world</div>
| );
|

Attempted Troubleshooting

I tried adding ts-loader using react-app-rewired but that resulted in an error where Typescript was emitting nothing, a result of CRA automatically setting the noEmit flag to true at compile time. Ejecting is a rabbit hole I never want to go down.

Reproducible demo

A demo of the bug is available at woodpav/server-side-react-typescript-monorepo#bug. As described it is a yarn workspace monorepo with two packages: Web and Components. Web contains react-scripts and Components contains JSX components. Both packages have a tsconfig.json that extends tsconfig.base.json.

To run the demo:

  1. git checkout bug
  2. yarn install
  3. cd packages/web
  4. yarn start
.
├── package.json
├── packages
│   ├── components
│   │   ├── package.json
│   │   ├── src
│   │   │   └── index.tsx
│   │   └── tsconfig.json 
│   └── web
│       ├── node_modules
│       ├── package.json
│       ├── public
│       │   ├── favicon.ico
│       │   ├── index.html
│       │   ├── manifest.json
│       │   └── robots.txt
│       ├── README.md
│       ├── src
│       │   ├── index.tsx
│       │   └── react-app-env.d.ts
│       └── tsconfig.json
├── tsconfig.base.json
└── yarn.lock

7 directories, 15 files

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 42
  • Comments: 29 (1 by maintainers)

Commits related to this issue

Most upvoted comments

Importing uncompiled code from other packages in a monorepo is not currently supported by Create React App.

I am using craco and this is what worked for me:

// File craco.config.js
const CracoLessPlugin = require('craco-less');
const path = require('path');
module.exports = {
  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          const oneOfRule = webpackConfig.module.rules.find(
            (rule) => rule.oneOf,
          );
          if (oneOfRule) {
            const tsxRule = oneOfRule.oneOf.find(
              (rule) => rule.test && rule.test.toString().includes('tsx'),
            );

            const newIncludePaths = [
              // relative path to my yarn workspace library  
              path.resolve(__dirname, '../../libs/domain/'),
            ];
            if (tsxRule) {
              if (Array.isArray(tsxRule.include)) {
                tsxRule.include = [...tsxRule.include, ...newIncludePaths];
              } else {
                tsxRule.include = [tsxRule.include, ...newIncludePaths];
              }
            }
          }
          return webpackConfig;
        },
      },
    },
  ],
};

If anyone else has struggled with this I have a configuration that worked with react-app-rewired using this structure:

packages/
    storybook/
    app/
    app2/

where I want to import components from storybook to be used in both apps.

Here is the config-overrides.js file:

module.exports = (config) => {
  // Remove the ModuleScopePlugin which throws when we try to import something
  // outside of src/.
  config.resolve.plugins.pop();

  // Let Babel compile outside of src/.
  const tsRule = config.module.rules[2].oneOf[1];
  tsRule.include = undefined;
  tsRule.exclude = /node_modules/;

  return config;
};

Thanks for this, certainly saved me a lot of headache!

Worth noting that in my case (with all latest versions at time of writing) that I did not need to remove the ModuleScopePlugin and that the rule selector needed tweaking. I am using yarn workspaces without Lerna.

This is what worked for me in config-overrides.js with react-app-rewired.

module.exports = (config) => {
  // Let Babel compile outside of src/.
  const tsRule = config.module.rules[1].oneOf[2];
  tsRule.include = undefined;
  tsRule.exclude = /node_modules/;

  return config;
};

Thanks 😄

Importing uncompiled code from other packages in a monorepo is not currently supported by Create React App.

This is what we were asking for. Doesn’t it make sense to leave this open to track it? Unless you’re saying that you’ll NEVER support it and that people needing this should eject or modify the config.

Solved this issue by migrating to vite. It’s also crazy fast compared to cra, so it’s win-win.

@iansu

Importing uncompiled code from other packages in a monorepo is not currently supported by Create React App.

Well, that’s why we. users, have created this issue report.

I have a working react-app-rewired config available at woodpav/typescript-monorepo-server-side-react. I maintain that this should be built-in behavior of CRA but until the devs release a fix (or someone writes a PR), you can use the code on #fix to use TypeScript and CRA in a monorepo.

Running the demo

The fix is on #fix but checkout #master if you want to experience the bug.

git clone git@github.com:woodpav/server-side-react-typescript-monorepo.git
cd server-side-react-typescript-monorepo/
git checkout fix
yarn
cd packages/web
yarn start

config-overrides.js

module.exports = function override(config, env) {
  let babelLoader = config.module.rules[2].oneOf[1];

  // This is the magic line.
  // Adjust what it replaces to make it work for your monorepo structure.
  // It works by loading typescript with CRA's config that is
  // included in the packages/ folder and not just in the /packages/web/src folder.
  babelLoader.include = babelLoader.include.replace('/web/src', '');

  config.module.rules[2].oneOf[1] = babelLoader;
  return config;
}

EDIT

I original ran into this issue when attempting to create a TypeScript monorepo that uses Server Side Rendering. If you would like to use my boilerplate, clone woodpav/server-side-react-typescript-monorepo run the following commands:

git clone git@github.com:woodpav/server-side-react-typescript-monorepo.git
cd server-side-react-typescript-monorepo/
git checkout dev
yarn 
cd packages/web 
yarn start 
cd ../server
yarn start 

With CRA5 above solution didn’t work for me right away so had to do the following craco.config.js. (Approach is taken from nx setup guide)

const path = require('path');
const TsConfigPathsPlugin = require('tsconfig-paths-webpack-plugin');
const ModuleScopePlugin = require('react-dev-utils/ModuleScopePlugin');
module.exports = {
  webpack: {
    configure: (config) => {
      // Remove guard against importing modules outside of \`src\`.
      // Needed for workspace projects.
      config.resolve.plugins = config.resolve.plugins.filter(
        (plugin) => !(plugin instanceof ModuleScopePlugin)
      );

      // Add support for importing workspace projects.
      config.resolve.plugins.push(
        new TsConfigPathsPlugin({
          configFile: path.resolve(__dirname, 'tsconfig.json'),
          extensions: ['.ts', '.tsx', '.js', '.jsx'],
          mainFields: ['module', 'main'],
        })
      );
      const oneOfRule = config.module.rules.find(
        (rule) => rule.oneOf,
      );
      // Replace include option for babel loader with exclude
      // so babel will handle workspace projects as well.
      oneOfRule.oneOf.forEach((r) => {
        if (r.loader && r.loader.indexOf('babel') !== -1) {
          r.exclude = /.yarn/;
          delete r.include;
        }
      });

      return config;
    },
  }
};

And have tsconfig.json paths pointing at shared workspace components like:

{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@yournamespace/components": ["../../components/src/*"],
    }
  }
}

For me, this configuration worked:

module.exports = (config) => {
  const tsRule = config.module.rules[1].oneOf[2];
  tsRule.include = undefined;
  tsRule.exclude = /node_modules/;

  return config;
};

If anyone else has struggled with this I have a configuration that worked with react-app-rewired using this structure:

packages/
    storybook/
    app/
    app2/

where I want to import components from storybook to be used in both apps.

Here is the config-overrides.js file:

module.exports = (config) => {
  // Remove the ModuleScopePlugin which throws when we try to import something
  // outside of src/.
  config.resolve.plugins.pop();

  // Let Babel compile outside of src/.
  const tsRule = config.module.rules[2].oneOf[1];
  tsRule.include = undefined;
  tsRule.exclude = /node_modules/;

  return config;
};

I’m having the same issue, after added my module to webpack config (I’m not using craco, I ejected) it’s giving the following issue:

Support for the experimental syntax ‘flow’ isn’t currently enabled Add @babel/plugin-transform-flow-strip-types (https://git.io/vb49g) to the ‘plugins’ section of your Babel config to enable transformation. image

Looks like the above react-app-rewired method is not working with the current versions. As of react-scripts@4.0.3 and react-app-rewired@2.1.8 the solution for me is:

config-overrides.js

const path = require('path');

// A list of paths to transpile
const nodeModulesToTranspileRel = ["../../external/this_is_a_module_we_must_transpile/src"]
const nodeModulesToTranspileAbs = nodeModulesToTranspileRel.map(name => path.resolve(__dirname, name))

module.exports = (config) => {
  // Find the babel-loader rule
  const babelLoaderRule = config.module.rules[1].oneOf.find(rule => rule.loader.includes("babel-loader"))

  // Add the paths we want to transpile
  babelLoaderRule.include = [
    babelLoaderRule.include,
    ...nodeModulesToTranspileAbs
  ]

  return config;
};

+1, same issue here. Trying to re-use logic from api package in simple yarn workspace project.

├── package.json
├── packages
|  ├── api
|  |  ├── package.json
|  |  └── src
|  |     └── ipleak-client.ts <-- Re-use this module...
|  └── ui
|     ├── package.json
|     ├── src
|     |  ├── api
|     |  |  ├── api.ts <-- ...in here
|     |  |  └── index.ts
|     └── tsconfig.json
└── yarn.lock
Module parse failed: Unexpected token (4:7)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| 
| // Generic fetcher type accepting node-fetch and lib.dom/fetch
> export type Fetcher = <T = {}>(
|     url: string
| ) => Promise<{

Having the same issue, even with .ts files, with baseUrl set to ./.

I’ve got a Typescript CRA project that has a dependency of a second Typescript CRA project which has "files": ["src"], in package.json. I’m able to import the types, but this error shows up when I try to import a function.

I am using craco and this is what worked for me:

// File craco.config.js
const CracoLessPlugin = require('craco-less');
const path = require('path');
module.exports = {
  plugins: [
    {
      plugin: {
        overrideWebpackConfig: ({ webpackConfig }) => {
          const oneOfRule = webpackConfig.module.rules.find(
            (rule) => rule.oneOf,
          );
          if (oneOfRule) {
            const tsxRule = oneOfRule.oneOf.find(
              (rule) => rule.test && rule.test.toString().includes('tsx'),
            );

            const newIncludePaths = [
              // relative path to my yarn workspace library  
              path.resolve(__dirname, '../../libs/domain/'),
            ];
            if (tsxRule) {
              if (Array.isArray(tsxRule.include)) {
                tsxRule.include = [...tsxRule.include, ...newIncludePaths];
              } else {
                tsxRule.include = [tsxRule.include, ...newIncludePaths];
              }
            }
          }
          return webpackConfig;
        },
      },
    },
  ],
};

This solution worked for me without craco in my TS CRA react-rewired repo

From what I understand of this, Yarn workspace packages have trouble importing uncompiled TSX files across packages. So you need to provide paths to include in TSX rules.

@MrBlenny it still works for me, react-scripts@4.0.3