Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to have TSLint resolve indirect typing dependencies with Yarn workspaces?

Context

Yarn workspaces provide for a convenient way to depend on packages in a mono-repo. When package A depends on package B, the interfaces etc. defined in package B are appropriately resolved in package A.

Problem

I am facing an issue where if package B is depending on an external library, but that external library lacks typings, and therefore package B has created its own some-library.d.ts file. When using tslint to lint package A, this custom definition file is resolved properly for expressions in package B, but not for expressions in package A that are working with types from package B.

I've pushed a simplified example of this problem here:

https://github.com/tommedema/tslint-yarn-workspaces

The core of it is as follows.

packages/a/src/index.ts

// tslint:disable:no-console

import { someDependedFn } from 'b'

export const someDependingFn = (): void => {
  const someNr = someDependedFn('pascal-case-me')
  console.log(someNr)
}

packages/b/src/index.ts

import camelCase from 'camelcase'

export const someDependedFn = (str: string): string => {
  const camelStr = camelCase(str, { pascalCase: true })

  return camelStr
}

packages/b/src/typings/camelcase/index.d.ts

// Type definitions for camelcase 5.0
// Project: https://github.com/sindresorhus/camelcase

// tslint:disable only-arrow-functions completed-docs

declare module 'camelcase' {
  export default function camelCase(
    strs: string | string[],
    options: {
      pascalCase?: boolean
    }
  ): string
}

Now if you change directory to package a and run yarn build, it works just fine. But if you run yarn lint, it will throw:

$ tslint -p tsconfig.json

ERROR: packages/b/src/index.ts[4, 20]: Unsafe use of expression of type 'any'.
ERROR: packages/b/src/index.ts[6, 10]: Unsafe use of expression of type 'any'.

TSLint does not recognize the typings that are depended on by package B, but it only complains about this when running tslint from package A (not expected). Inside package B, tslint does not complain (as expected).

Question

Of course I could manually add the typings of camelcase inside package A, but that seems like an obvious violation of separation of concerns: package A is not supposed to know that package B depends on package camelcase, or X or Y. It is only supposed to know about package B's public API, i.e. the dependedFn.

How can I setup tslint such that it correctly resolves these indirect typing definitions when using yarn workspaces?

like image 737
Tom Avatar asked Jul 19 '18 19:07

Tom


1 Answers

You can make TSLint work in your case by removing these lines from tsconfig.json:

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

These lines tell TypeScript compiler and TSLint that they should not treat your modules a and b as packages when you import them, but rather they should resolve individual TypeScript files using baseUrl and paths parameters and then compile individual TypeScript files. This behavior is documented in Module Resolution -> Path Mapping section of TypeScript documentation:

https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping

Instead, if I understood you right, you want to treat a and b as independent packages. To achieve this you should remove path mapping, and then TypeScript and TSLint will treat them as npm packages.

UPDATE (based on discussion in comments)

In your project you run TSLint using command:

tslint -p tsconfig.json but you run TSC using command:

tsc src/index.ts --outDir dist

Your TSLint uses TypeScript compiler API to do checks, based on rules from tsconfig.json. But your TypeScript compiler do not use tsconfig.json rules. In real-world projects both commands will use tsconfig.json

When you start using tsconfig.json for compilation too you will get the same problem with resolving 2nd degree dependencies types as you have with TSLint:

$ tsc -p tsconfig.json
../b/src/index.ts:1:23 - error TS7016: Could not find a declaration file for module 'camelcase'. '/home/victor/work/tslint-yarn-workspaces.org/node_modules/camelcase/index.js' implicitly has an 'any' type.
  Try `npm install @types/camelcase` if it exists or add a new declaration (.d.ts) file containing `declare module 'camelcase';`

1 import camelCase from 'camelcase'
                    ~~~~~~~~~~~

This happens because path-mapped module imports compiled differently by design, then normal imports from node_modules according to TypeScript documentation https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping as specified in the first part of the answer.

I would recommend using normal imports in your project to not have troubles with tools:

  1. Have "watch": "lerna run --parallel -- watch" script in workspace root package.json
  2. Have "watch": "tsc -p tsconfig.json -w" in workspace packages.
  3. Whenever you make changes to your project - start TypeScript compiler in watch mode in every package by running npm run watch in the workspace root.
like image 57
Viktor Vlasenko Avatar answered Nov 03 '22 01:11

Viktor Vlasenko