Search code examples
typescriptnpmwebpackeslintmonorepo

In NPM workspaces Typescript fails to compile. In the rest of monorepository Typescript compiles correctly


We have refactored our project to be a mono repository (NPM Workspaces) and structure it like so:

 |-- apps          
   |-- native         <-- Not Workspace
   |-- web            <-- Not Workspace
 |-- common        
   |-- models         <-- Workspace
   |-- connectors     <-- Workspace
   |-- store          <-- Workspace
   |-- types          <-- Workspace
   |-- utils          <-- Workspace
 -- package-lock.json
 -- package.json

Our native and web apps use code from common and do not share code between them.

root package.json
-----------------
{
  "name": "@secret/client",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "workspaces": [
    "./common/*"
  ]
}

The problem

When compiling Typescript files, only the Typescript files in the common folder fail to compile and throw Parsing error: Unexpected token errors. The rest of Typescript files in native and web compile correctly. enter image description here

Webpack module.rules

[
  { parser: { requireEnsure: false } },
  {
    oneOf: [
      ...irrelevantRulesIntentionallyHidden,
      {
        test: /\.(js|mjs|jsx|ts|tsx)$/,
        include: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp',
        loader: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\babel-loader\\lib\\index.js',
        options: {
          customize: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\babel-preset-react-app\\webpack-overrides.js',
          presets: [
            [
              'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\babel-preset-react-app\\index.js',
              [Object]
            ]
          ],
          babelrc: false,
          configFile: false,
          cacheIdentifier: 'development:[email protected]:[email protected]:[email protected]:[email protected]',
          plugins: [
            [
              'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\babel-plugin-named-asset-import\\index.js',
              [Object]
            ],
            'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\react-refresh\\babel.js'
          ],
          cacheDirectory: true,
          cacheCompression: false,
          compact: false,
          sourceType: 'unambiguous'
        }
      },
      {
        test: /\.(js|mjs)$/,
        exclude: /@babel(?:\/|\\{1,2})runtime/,
        loader: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\babel-loader\\lib\\index.js',
        options: {
          babelrc: false,
          configFile: false,
          compact: false,
          presets: [
            [
              'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\babel-preset-react-app\\dependencies.js',
              [Object]
            ]
          ],
          cacheDirectory: true,
          cacheCompression: false,
          cacheIdentifier: 'development:[email protected]:[email protected]:[email protected]:[email protected]',
          sourceMaps: true,
          inputSourceMap: true,
          sourceType: 'unambiguous'
        },
        include: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp'
      },
    ] 
  }
]

ESLintWebpackPlugin

{
  key: 'ESLintWebpackPlugin',
  options: {
    extensions: [ 'js', 'mjs', 'jsx', 'ts', 'tsx' ],
    emitError: true,
    emitWarning: true,
    failOnError: true,
    formatter: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\react-scripts\\node_modules\\react-dev-utils\\eslintFormatter.js',
    eslintPath: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\eslint\\lib\\api.js',
    context: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp',
    cache: true,
    cacheLocation: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\.cache\\.eslintcache',
    cwd: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web',
    resolvePluginsRelativeTo: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\react-scripts\\config',
    baseConfig: {
      extends: [
        'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\eslint-config-react-app\\base.js'
      ],
      rules: {}
    },
    overrideConfig: {
      rules: {
        '@typescript-eslint/no-unused-vars': [ 0 ],
        'no-use-before-define': [ 0 ],
        'no-useless-escape': [ 0 ],
        'jsx-a11y/anchor-is-valid': [ 0 ],
        'unicode-bom': [ 0 ],
        'react/button-has-type': [ 2, [Object] ],
        'react/jsx-no-literals': [ 2, [Object] ]
      },
      plugins: [ 'react' ]
    },
    ignore: true
  },
  run: [Function: bound run] AsyncFunction
}

ForkTsCheckerWebpackPlugin

{
    eslint: false,
    eslintOptions: {},
    tsconfigPath: undefined,
    compiler: undefined,
    started: undefined,
    elapsed: undefined,
    cancellationToken: undefined,
    isWatching: false,
    checkDone: false,
    compilationDone: false,
    diagnostics: [],
    lints: [],
    eslintVersion: undefined,
    startAt: 0,
    nodeArgs: [],
    options: {
      typescript: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\typescript\\lib\\typescript.js',
      async: true,
      checkSyntacticErrors: true,
      resolveModuleNameModule: undefined,
      resolveTypeReferenceDirectiveModule: undefined,
      tsconfig: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\tsconfig.json',
      reportFiles: [Array],
      silent: true,
      formatter: undefined
    },
    ignoreDiagnostics: [],
    ignoreLints: [],
    ignoreLintWarnings: false,
    reportFiles: [
      '../**/src/**/*.{ts,tsx}',
      '**/src/**/*.{ts,tsx}',
      '!**/src/**/__tests__/**',
      '!**/src/**/?(*.)(spec|test).*',
      '!**/src/setupProxy.*',
      '!**/src/setupTests.*'
    ],
    logger: Object [console] {...}, <--- too long to paste
    typescriptPath: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\node_modules\\typescript\\lib\\typescript.js',
    typescriptVersion: '4.2.3',
    tsconfig: 'C:\\_git\\Secret\\src\\Secret.WebClient\\ClientApp\\apps\\web\\tsconfig.json',
    compilerOptions: {},
    vue: { compiler: 'vue-template-compiler', enabled: false },
    useTypescriptIncrementalApi: true,
    measureTime: false
  }

Example: common/connectors/package.json

{
  "name": "@secret/connectors",
  "version": "1.0.0",
  "private": true,
  "description": "",
  "main": "index.js",
  "dependencies": {
    "@types/lodash": "^4.14.165",
    "@types/react": "^17.0.3",
    "lodash": "^4.17.20",
    "react-redux": "^7.2.3",
    "typescript": "^4.2.3"
  },
  "devDependencies": {
    "eslint-plugin-react": "^7.23.1",
    "tslint": "^5.20.1",
    "tslint-react": "^4.1.0"
  }
}

Does anyone have experience with this? What do you think could be the solution?


Solution

  • Issue solved

    There is a bug in ForkTsCheckerWebpackPlugin create-react-app (CRA) uses. Updating it to the latest version (at the time of writing 6.2.10) and using this CRA override solves the issue:

    // Use newer version of ForkTSCheckerWebpackPlugin to type check
    // files across the monorepo.
    const forkTsCheckerWebpackPlugin = config.plugins.findIndex(
      (p) => p.reportFiles
    );
    if (forkTsCheckerWebpackPlugin !== -1) {
      config.plugins.splice(
        forkTsCheckerWebpackPlugin,
        1,
        new ForkTSCheckerWebpackPlugin({
          issue: {
            // The exclude rules are copied from CRA.
            exclude: [
              {
                file: "**/src/**/__tests__/**",
              },
              {
                file: "**/src/**/?(*.)(spec|test).*",
              },
              {
                file: "**/src/setupProxy.*",
              },
              {
                file: "**/src/setupTests.*",
              },
            ],
          },
        })
      );
    }
    

    Solution courtesy: @NiGhTTraX https://github.com/NiGhTTraX/ts-monorepo/issues/74