NiGhTTraX / ts-monorepo

Template for setting up a TypeScript monorepo
MIT License
1.5k stars 169 forks source link

Typescript inside NPM workspace is not type-checked on compilation (CRA) #74

Closed AlanJereb closed 3 years ago

AlanJereb commented 3 years ago

Thank you for your documentation.

I'm a week and a half stuck with this problem, all my Google links are purple, and my StackOverflow questions hasn't received any answers. Maybe you will know how to solve this issue.

Using techniques from this repo everything compiles without errors, even when **common/*** (shared) code contains missing types.

Our project structure:

 |-- apps          
   |-- native      
   |-- web
     |-- package.json
     |-- tsconfig.json
     |-- tsconfig..paths.json        
 |-- common        
   |-- models
     |-- package.json
     |-- tsconfig.json
   |-- connectors     
     |-- package.json
     |-- tsconfig.json
   |-- store         
     |-- package.json
     |-- tsconfig.json
   |-- types      
     |-- package.json
     |-- tsconfig.json    
   |-- utils         
     |-- package.json
     |-- tsconfig.json 
 -- package.json
 -- tsconfig.json
 -- tsconfig.paths.json

Root package.json

{
  "name": "@flowbase/client",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "workspaces": [
    "./common/*",
    "./apps/*"
  ],
  "devDependencies": {
    "@typescript-eslint/parser": "^4.26.0"
  }
}

Root tsconfig.json

{
  "extends": "./tsconfig.paths.json",
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@flowbase/*": ["common/*"],
    }
  }
}

Root tsconfig.paths.json

{
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "es2015",
      "dom",
      "dom.iterable",
      "esnext",
      "webworker"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "types": [
      "jest"
    ]
  }
}

common/storepackage.json - example, they are all the same, only dependencies changing

{
  "name": "@flowbase/store",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "main": "index.js",
  "dependencies": {
    "@flowbase/types": "1.0.0",
    "@flowbase/utils": "1.0.0",
    "@flowbase/connectors": "1.0.0",
    "@flowbase/apimodels": "1.0.0",
    "@microsoft/signalr": "5.0.1",
    "@types/lodash": "^4.14.165",
    "@types/react": "^17.0.3",
    "@types/redux-logger": "^3.0.8",
    "axios": "^0.21.1",
    "lodash": "^4.17.20",
    "msw": "^0.21.3",
    "react": "^17.0.2",
    "react-app-polyfill": "^1.0.6",
    "react-redux": "^7.2.3",
    "typescript": "^4.2.3"
  },
  "devDependencies": {
    "@types/jest": "^26.0.21",
    "eslint-plugin-react": "^7.23.1",
    "jest": "^26.6.3",
    "jest-json-schema": "^2.1.0",
    "jest-localstorage-mock": "^2.4.9",
    "tslint": "^5.20.1",
    "tslint-react": "^4.1.0"
  }
}

apps/web package.json

{
  "name": "@flowbase/web-client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@flowbase/types": "1.0.0",
    "@flowbase/store": "1.0.0",
    "@flowbase/connectors": "1.0.0",
    "@flowbase/apimodels": "1.0.0",
    "@flowbase/utils": "1.0.0",
    "@monaco-editor/react": "^3.7.4",
    "@types/lodash": "^4.14.165",
    "@types/node": "^12.20.6",
    "@types/react": "^17.0.3",
    "@types/react-dom": "^17.0.3",
    "axios": "^0.21.1",
    "cronstrue": "^1.110.0",
    "lodash": "^4.17.20",
    "monaco-editor": "^0.17.1",
    "normalize.css": "^8.0.1",
    "pdfjs-dist": "2.8.335",
    "react": "^17.0.2",
    "react-app-polyfill": "^1.0.6",
    "react-datepicker": "^3.1.3",
    "react-dom": "^17.0.2",
    "react-redux": "^7.2.3",
    "react-scripts": "^4.0.3",
    "streamsaver": "^2.0.5",
    "style-scoped": "^0.2.1",
    "typescript": "^4.2.3",
    "yargs": "^15.4.1"
  },
  "scripts": {
    "start": "set NODE_ENV=development && node generate-config.js && rescripts start",
    "mock": "set MOCK_BACKEND='1' && rescripts start",
    "build": "set NODE_ENV=production && node generate-config.js && npm run generate-icons && rescripts build",
    "unit": "set NODE_ENV=development && set NODE_OPTIONS=--unhandled-rejections=warn && rescripts test --env=jsdom --watchAll=false --coverage",
    "unit-interactive": "set NODE_ENV=development && set NODE_OPTIONS=--unhandled-rejections=warn && rescripts test --env=jsdom",
    "e2e": "jest -c jest.config.js --runInBand",
    "e2e-debug": "set PWDEBUG=1 && jest -c jest.config.js --runInBand",
    "test": "npm run unit && npm run e2e",
    "generate-icons": "node generate-icons.js",
    "eject": "rescripts eject",
    "lint": "eslint ./src/**/*.ts ./src/**/*.tsx",
    "electron": "electron ."
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      ">0.2%",
      "not dead",
      "not ie <= 11",
      "not op_mini all"
    ]
  },
  "devDependencies": {
    "@rescripts/cli": "0.0.16",
    "@types/jest": "^26.0.21",
    "@types/jest-image-snapshot": "^4.1.3",
    "@types/react-datepicker": "^3.1.8",
    "@types/react-jsonschema-form": "^1.7.3",
    "@types/react-redux": "^7.1.12",
    "@types/react-router-dom": "^4.3.5",
    "@types/streamsaver": "^2.0.0",
    "babel-plugin-module-resolver": "^4.1.0",
    "electron": "^12.0.2",
    "eslint-plugin-react": "^7.23.1",
    "fs-extra": "^8.1.0",
    "jest": "^26.6.3",
    "jest-image-snapshot": "^4.3.0",
    "jest-json-schema": "^2.1.0",
    "jest-localstorage-mock": "^2.4.9",
    "jest-playwright-preset": "^1.5.1",
    "mathsass": "^0.11.0",
    "msw": "^0.21.3",
    "node-sass": "^5.0.0",
    "playwright": "^1.11.1",
    "svg-sprite": "^1.5.0",
    "ts-jest": "^26.5.4",
    "tsconfig-paths-webpack-plugin": "^3.5.1",
    "tslint": "^5.20.1",
    "tslint-react": "^4.1.0",
    "vinyl": "^2.2.0"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "main": "./electron-start.js"
}

apps/web tsconfig.json

{
  "extends": "./tsconfig.paths.json",
  "compilerOptions": {
    "target": "es5",
    "lib": [
      "es2015",
      "dom",
      "dom.iterable",
      "esnext",
      "webworker"
    ],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "types": [
      "jest",
      "jest-playwright-preset",
      "expect-playwright"
    ]
  },
  "include": [
    "src"
  ]
}

apps/web tsconfig.paths.json

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

We used rescripts instead of react-app-rewired, but the result is the same.

module.exports = [{
    const applyRuleFunctionRecursive = (fn, rule, parent) => {
      if (typeof rule !== "object") {
        return;
      }
      if (Array.isArray(rule)) {
        rule.forEach(r => applyRuleFunctionRecursive(fn, r, rule));
      } else if (rule.hasOwnProperty("use")) {
        applyRuleFunctionRecursive(fn, rule.use, rule);
      } else if (rule.hasOwnProperty("oneOf")) {
        applyRuleFunctionRecursive(fn, rule.oneOf, rule);
      } else {
        fn(rule, parent);
      }
    }
    const extendBabel = (plugin, parent) => {
      if (plugin.loader && plugin.loader.indexOf("babel-loader") !== -1) {
        plugin.include = undefined;
        plugin.exclude = /node_modules/;
        plugin.options.presets.push(
            ["@babel/preset-env", { targets: { node: "current" } }],
            "@babel/preset-typescript",
        );
        if (plugin.options.plugins) {
          plugin.options.plugins.push(
            [
              "module-resolver",
              {
                alias: {
                  '@flowbase': '../../common/',
                },
              },
            ],
          );
        }
      }
    }
    config.resolve.plugins.pop();
    config.resolve.plugins.push(new tsConfigPathsPlugin());

    applyRuleFunctionRecursive(extendBabel, config.module.rules, config);
    return config;
  }, 
}];

After npm install in the root folder, navigating into "apps/web" and npm start everything compiles without errors.

The problem is, that if I intentionally comment out a type import inside any of common/* workspaces, it still compiles without errors (doesn't type check). However, doing the same inside apps/web/ throws an error for missing types. (does type check)

Sorry for the long post, I really hope you can point us in the right direction.

NiGhTTraX commented 3 years ago

Hey @AlanJereb, thanks for reporting this! I managed to reproduce it with the CRA example in this repo and pinpointed it to a bug in ForkTsCheckerWebpackPlugin. Upgrading to the latest version and using it in the overrides seems to solve the problem. Let me know if this works for you!

AlanJereb commented 3 years ago

Hey @AlanJereb, thanks for reporting this! I managed to reproduce it with the CRA example in this repo and pinpointed it to a bug in ForkTsCheckerWebpackPlugin. Upgrading to the latest version and using it in the overrides seems to solve the problem. Let me know if this works for you!

Thank you so much! You solved the issue.