Search code examples
typescriptwebpackmonorepolernayarn-workspaces

Yarn workspaces - monorepo - react 17, react 18, nestjs, shared/common layer


I want to have a monorepo using yarn workspaces in which I hold:

  • a react 17 app
  • a react 18 app
  • a nestjs app
  • a shared layer with common functions for all of them (e.g: date format func)

I cloned this project https://github.com/mightyhorst/medium-react-nestjs-monorepo and it manages to share types between them. I had to add typescript and react stuff to nohoist so it doesn't hoist the wrong versions and it works fine - both react apps & nest can use the shared types. The problem comes when I add a function in the common layer.

workspaces/common/src/index.ts

WORKS

export interface User {
  email: string
}

DOESN'T WORK

export interface User {
  email: string
}
export function hello() { console.log('world') }

I get this error when running any react app (although there is nothing wrong with the type):

../common/src/model.ts 4:7
Module parse failed: Unexpected token (4:7)
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);
| 
> export interface User{
|     email: string;
|     firstName?: string;

I get this error when running nestjs:

export * from "./model";
^^^^^^

SyntaxError: Unexpected token 'export'
    at wrapSafe (internal/modules/cjs/loader.js:984:16)
    at Module._compile (internal/modules/cjs/loader.js:1032:27)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1097:10)
    at Module.load (internal/modules/cjs/loader.js:933:32)
    at Function.Module._load (internal/modules/cjs/loader.js:774:14)
    at Module.require (internal/modules/cjs/loader.js:957:19)
    at require (internal/modules/cjs/helpers.js:88:18)
    at Object.<anonymous> (/home/elbit/Projects/medium-react-nestjs-monorepo/workspaces/nestjs/dist/src/main.js:5:18)
    at Module._compile (internal/modules/cjs/loader.js:1068:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:1097:10)

I feel like this is a common thing but I cannot find the solution.


Solution

  • I got it working using:

    • yarn-workspaces without lerna to handle all the packages
    • set "workspaces": { "nohoist": ["**"] } within each package (in package.json)
    • craco to handle common package and aliases (via craco-alias)
    • @ef-carbon/tspm to resolve tsconfig.compilerOptions.paths for any package that isn't React

    My craco.config.js

    const path = require("path");
    const { getLoader, loaderByName } = require("@craco/craco");
    const CracoAlias = require("craco-alias");
    
    const packages = [];
    packages.push(path.join(__dirname, "../common"));
    
    module.exports = {
      webpack: {
        configure: (webpackConfig, arg) => {
          const { isFound, match } = getLoader(
            webpackConfig,
            loaderByName("babel-loader")
          );
          if (isFound) {
            const include = Array.isArray(match.loader.include)
              ? match.loader.include
              : [match.loader.include];
    
            match.loader.include = include.concat(packages);
          }
    
          return webpackConfig;
        },
      },
      plugins: [
        {
          plugin: CracoAlias,
          options: {
            source: "tsconfig",
            baseUrl: ".",
            tsConfigPath: "./tsconfig.paths.json",
          },
        },
      ],
    };
    
    

    My tsconfig.json of a non-react app

    {
      "compilerOptions": {
        "allowJs": true,
        "baseUrl": "./",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "isolatedModules": true,
        "module": "commonjs",
        "moduleResolution": "node",
        "outDir": "build",
        "paths": { "@ui/common/*": ["../common/src/*"] },
        "resolveJsonModule": true,
        "rootDirs": [".", "../common/build"],
        "skipLibCheck": true,
        "strict": true,
        "target": "es5"
      },
      "include": ["src", "../common/src"]
    }