-
Notifications
You must be signed in to change notification settings - Fork 523
Description
I've been working to set up Bazel with ts_project
in a large, custom monorepo (i.e. not using something like Lerna or yarn workspaces). We recently resolved a challenge and I'd like to publicly document it here in case it helps someone else (or if someone sees a better solution for us). Feel free to close this issue.
Our monorepo is set up so that we have a "frontend" directory with a lot of projects as subdirectories under "frontend". The structure is roughly like this:
frontend/
frontend/project-a/
frontend/project-b/
frontend/project-c/
...
Imports inside the same project use relative paths:
import foo from './foo';
And imports from other projects use custom colon-import paths:
import someOtherProject from ':some-other-project';
import anotherThing from ':a-different-project/anotherThing';
We've been running TypeScript without Bazel for a while, and had these custom imports wired up via the following path mapping in our base tsconfig:
{
"compilerOptions": {
"paths": {
":*": [
"./frontend/*",
],
}
}
}
When setting up Bazel, we initially expanded this path mapping to look more like this:
{
"compilerOptions": {
"paths": {
":*": [
"./frontend/*",
// Bazel generates the .d.ts files in the bazel-out directory, so we
// need to tell TypeScript that it might be able to find them there.
//
// This hack was inspired by formatjs:
// https://github.com/formatjs/formatjs/blob/268d6aef6fc08180a0be691b631eb1cdfb68ca31/tsconfig.json#L19-L41
// And the rootDirs note in the docs here:
// https://bazelbuild.github.io/rules_nodejs/TypeScript.html#ts_project
"./bazel-out/darwin-fastbuild/bin/frontend/*",
"./bazel-out/k8-fastbuild/bin/frontend/*",
"./bazel-out/x64_windows-fastbuild/bin/frontend/*",
"./bazel-out/darwin-dbg/bin/frontend/*",
"./bazel-out/k8-dbg/bin/frontend/*",
"./bazel-out/x64_windows-dbg/bin/frontend/*",
],
}
}
}
This seemed to work okay for some of our projects, however, once we started getting into more complex projects that depended on types that were built from a different project, we started seeing errors in our Bazel ts_project
output like this:
frontend/complex-project/foo/bar.ts(27,16): error TS2571: Object is of type 'unknown'.
It seemed that these types ended up as unknown
instead of the actual types we expected.
We looked at the .d.ts files that ts_project
built for these types, and found code that started like this:
import Foo from ':some-dependency/foo/bar';
// ...
Was built into this:
// In a file in frontend/complex-project
// ...
export declare const FooType: import("../../bazel-out/darwin-fastbuild/bin/frontend/some-dependency/foo/bar").Foo<Bar>;
// ...
We expected the import
path to look something like "../../some-dependency/foo/bar"
but instead got "../../bazel-out/darwin-fastbuild/bin/frontend/some-dependency/foo/bar"
, which doesn't really exist. As a result, TypeScript infers it as any
which probably gets converted to an unknown somewhere along the line via the generics used here.
After a bit of messing around, we believe that we were able to arrive at an okay solution: add rootDirs
and an extra paths
mapping like this:
{
"compilerOptions": {
"rootDirs": [
"../..",
// Ensure that the relative paths in types produced by bazel get turned into
// "frontend/" paths.
"../../bazel-out/darwin-fastbuild/bin",
"../../bazel-out/k8-fastbuild/bin",
"../../bazel-out/x64_windows-fastbuild/bin",
"../../bazel-out/darwin-dbg/bin",
"../../bazel-out/k8-dbg/bin",
"../../bazel-out/x64_windows-dbg/bin",
],
"paths": {
":*": [
"./frontend/*",
// Bazel generates the .d.ts files in the bazel-out directory, so we
// need to tell TypeScript that it might be able to find them there.
//
// This hack was inspired by formatjs:
// https://github.com/formatjs/formatjs/blob/268d6aef6fc08180a0be691b631eb1cdfb68ca31/tsconfig.json#L19-L41
// And the rootDirs note in the docs here:
// https://bazelbuild.github.io/rules_nodejs/TypeScript.html#ts_project
"./bazel-out/darwin-fastbuild/bin/frontend/*",
"./bazel-out/k8-fastbuild/bin/frontend/*",
"./bazel-out/x64_windows-fastbuild/bin/frontend/*",
"./bazel-out/darwin-dbg/bin/frontend/*",
"./bazel-out/k8-dbg/bin/frontend/*",
"./bazel-out/x64_windows-dbg/bin/frontend/*",
],
// WARNING: Don't use frontend/ imports, use colon instead. This is for bazel build
// type resolution: The rootDirs above produce "frontend/" paths in the .d.ts files
// produced by bazel, and then we remap those paths back into bazel-out here.
// Note this does mean you can never install a package named "frontend", but that's
// probably fine since this package is unlikely to be something we'd install:
// https://www.npmjs.com/package/frontend
"frontend/*": [
"./bazel-out/darwin-fastbuild/bin/frontend/*",
"./bazel-out/k8-fastbuild/bin/frontend/*",
"./bazel-out/x64_windows-fastbuild/bin/frontend/*",
"./bazel-out/darwin-dbg/bin/frontend/*",
"./bazel-out/k8-dbg/bin/frontend/*",
"./bazel-out/x64_windows-dbg/bin/frontend/*",
],
}
}
}
This configuration causes our special colon imports to be converted from ":some-project"
to "frontend/some-project"
, which then works because of the second "frontend/*"
path mapping.
The main caveat that we've thought of with this approach is that it means we can't just install and use a package named "frontend", but that seems unlikely to cause us any problems.