问题
I'm trying to set up a monorepo using yarn. I'm confused as to how to set up typescript with project references such that things resolve properly.
For example, if I have a folder structure like
/cmd
/client
And I want cmd
to depend on client
I could have:
cmd/tsconfig.json
:
{
"compilerOptions": {
"types": ["reflect-metadata", "jest"],
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"declaration": true,
"importHelpers": true,
"composite": true,
"target": "esnext"
"sourceRoot": "src",
"outDir": "dist"
},
"references": [
{
"path": "../client"
}
],
"include": [
"src/**/*"
]
}
with a package.json
{
"name": "cmd",
"version": "1.0.0",
"dependencies": {
"client": "^1.0.0",
}
}
In this model both cmd
and client
get compiled with an outDir
and sourceRoot
field set in their tsconfig. This means all their compiled javascript goes into the dist/
subfolder of cmd/dist
and client/dist
If now I try and reference a class from client
into cmd
like
import Foo from 'client/src/foo'
The IDE is perfectly happy to resolve this since it seems that its mapped via the typescript references
property.
However, the compiled javascript boils down to a
const foo_1 = require("client/src/foo");
However, the actual built javascript is in client/dist/src/foo
, so at runtime this never works.
On the flip side, if I don't use sourceRoots and outDirs and have the javascript inlined with the typescript files at the same folder everything does work (but makes the repo dirty and requires custom gitignores to exclude things)
Can anyone shed any light on how to properly set up a typescript 3.x monorepo with yarn workspaces such that things just work?
回答1:
I created a Github Repository to make it easier to follow the following code description:
Code Description
TypeScript Project References
make it possible to compile a TypeScript project that consist of multiple smaller TypeScript projects, each project having a tsconfig.json
file. (Source: Project References Documentation)
TypeScript Setup
We have a root tsconfig.json
file that only manages its sub-projects. The references
property specifies the directories that each contain a valid tsconfig.json
file. If we now build the project with the --build
option (tsc --build tsconfig.json
) then we specified the projects which should be compiled, but we didn't specified the build order in which the projects should be compiled.
{
"references": [
{ "path": "./client" },
{ "path": "./cmd" }
],
"files": [],
"include": [],
"exclude": ["**/node_modules"]
}
To correctly specify the build order we need to add a references
property to the cmd/tsconfig.json
file. This tells the compiler that it first needs to compile client/
before we compile cmd/
:
cmd/tsconfig.json
:
{
"extends": "../tsconfig.packages.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"references": [
{
"path": "../client"
}
]
}
Build order
client/
^
|
|
cmd/
Node Setup
Best practice is that each sub-project has its own package.json
file with the main property and the name set. In our example both packages (cmd/
and client/
) have a main
property pointing to the index.js
file in the TypeScript outDir directory (cmd/dist/index.js
and client/dist/index.js
).
Project structure:
tsconfig.json
cmd/
tsconfig.json
package.json
src/
index.ts
dist/ #artifacts
index.js
client/
tsconfig.json
package.json
src/
index.ts
dist/ #artifacts
index.js
client/packages.json
{
"name": "client",
"version": "1.0.0",
"main": "dist/index",
...
}
It is important that we add the client/
as dependency to the cmd/packages.json
so the module resolution algorithm can find the client/dist/index.js
when we import it in our TypeScript code import Foo from 'client';
:
cmd/packages.json
{
"name": "cmd",
"version": "1.0.0",
"main": "dist/index",
"dependencies": {
"client": "1.0.0" // important
}
}
cmd/src/index.ts
import Foo from 'client';
console.log(Foo())
Yarn Setup
The yarn setup is easy. Yarn adds all packages under node_modules
instead of:
cmd/node_modules
client/node_modules
To enable yarn workspaces add the workspaces
property and the private: true
property to the <root>/package.json
file.
<root>/package.json
{
"private": true,
"workspaces": [
"cmd",
"client"
],
"name": "yarn_workplace",
"version": "1.0.0"
...
}
The cmd/
and client/
packages are symlinked
under the <root>/node_modules/
directory:
Notes
- To enable code navigation one has to first build the project
- Every package lives on its own. The
cmd/
package uses the definition fileclient/dist/index.d.ts
for type information instead of using the the TypeScript files directly.
回答2:
I've set up a monorepo I with some configurations I've always use in my projects using Yarn Workspaces and Typescript in this repository.
I don't know if this set up solves your problem, but you don't need to specify your packages at Typescript config. When you use Yarn Workspaces, it links all your packages in root node_modules
that you defined in your workspace package property in your root package.json
:
"workspaces": {
"packages": [
"packages/**/*"
],
"nohoist": []
}
After yarn install
, the root node_modules
has client
and cmd
as linked folder.
With this configuration, you can simply import any package in any package inside the Workspace. For instance:
// cmd/src/index.ts
import { name } from 'client';
const otherName = 'cmd' + name;
console.log(otherName);
来源:https://stackoverflow.com/questions/57679322/how-to-use-yarn-workspaces-with-typescript-and-out-folders