Wickoo
Wickoo

Reputation: 7397

Project references in TypeScript 3 with separate `outDir`

I'd like to make use of the project references features in TypeScript 3.1. The directory structure of my project originally looks like this after the compilation:

.
├── A
│   ├── a.ts
│   ├── dist
│   │   ├── A
│   │   │   └── a.js
│   │   └── Shared
│   │       └── shared.js
│   └── tsconfig.json
└── Shared
    ├── dist
    │   └── shared.js
    ├── shared.ts
    └── tsconfig.json

Contents of the Shared directory:

shared.ts:

export const name = "name";

tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",                          
    "module": "commonjs",                     
    "outDir": "dist",                        
    "strict": true
  }
}

Contents of the A directory:

a.ts:

import { name } from "../Shared/shared"; 

console.log(name);

tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "dist",
    "strict": true
  }
}

And I could successfully run it by running node dist/A/a.js in the A directory.

What I didn't like about this was that everything was getting copied into A's output directory. I thought project references are here to fix this problem.

To enable project references, I added the following line to Shared/tsconfig.json.

"composite": true

And the following to A/tsconfig.json:

"references": [
  { "path": "../Shared" }
]

Now when I compile, the directory structure is like the following, which is what I expected:

.
├── A
│   ├── a.ts
│   ├── dist
│   │   └── a.js
│   └── tsconfig.json
└── Shared
    ├── dist
    │   ├── shared.d.ts
    │   └── shared.js
    ├── shared.ts
    └── tsconfig.json

However, when I run the node dist/a.js in the A directory, I get the following Error:

module.js:538
    throw err;
    ^

Error: Cannot find module '../Shared/shared'

The reason is that in the generated a.js file, the reference to the imported module is not resolved properly:

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var shared_1 = require("../Shared/shared");
console.log(shared_1.name);

Is there a way to get it working without putting all output files into the same directory?

Or, is there a better way to organize my project to make use of project references?

Upvotes: 7

Views: 4130

Answers (2)

Tuan Pham
Tuan Pham

Reputation: 31

The official recommendation, if I understand it correctly, is to set up a master output directory for the entire composite project and have Shared and A subdirectories inside it, so the source files and output files can have the same relative layout.

Matt's answer is great. Following up on the comment above, here is one way to reconfigure A and Shared so the imported module works properly.

A/tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "dist/A",
    "strict": true
  },
  "references": [
    { "path": "../Shared" }
  ]
}

Shared/tsconfig.json:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "../A/dist/Shared",
    "composite": true,
    "strict": true
  }
}

The project structure after tsc --build:

The project structure after build

To run A, call node dist/A/a.js.

Upvotes: 0

Matt McCutchen
Matt McCutchen

Reputation: 30959

Indeed, getting relative import paths to work for both your source files and your output files is a pain. The official recommendation, if I understand it correctly, is to set up a master output directory for the entire composite project and have Shared and A subdirectories inside it, so the source files and output files can have the same relative layout. I don't know if that's what you meant you didn't want to do.

The only other option I know of is to have each component import the other components (in your case, you just have A importing Shared) using non-relative imports that point to output files (i.e., A/a.ts would import a path like Shared/dist/shared). Since you are using non-relative imports, the same import path appearing in either a source file or the corresponding output file resolves to the same target output file. Imports will not resolve in your IDE until you've built the composite project: this is a known limitation. Since tsc doesn't rewrite imports, you'll need to set up your runtime environment and/or use a bundler to handle the non-relative imports and, if necessary, set the baseUrl and paths TypeScript compiler options to match.

I'll be happy to help you through the details of either approach if needed; just let me know where you get stuck.

Upvotes: 11

Related Questions