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?
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:
"watch": "lerna run --parallel -- watch"
script in workspace root package.json
"watch": "tsc -p tsconfig.json -w"
in workspace packages.npm run watch
in the workspace root.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With