I suggest to use a linter for that job, no need to adjust the build step or use Project References.
eslint-plugin-import
is a quite popular ESLint plugin, compatible to TS and can do what you want. After having configured typescript-eslint (if not already done), you can play around with these rules:
Let's try with following project structure:
| .eslintrc.js
| package.json
| tsconfig.json
\---src
+---common
| common.ts
|
+---projectA
| a.ts
|
\---projectB
b.ts
.eslintrc.js:
module.exports = {
extends: ["plugin:import/typescript"],
parser: "@typescript-eslint/parser",
parserOptions: {
sourceType: "module",
project: "./tsconfig.json",
},
plugins: ["@typescript-eslint", "import"],
rules: {
"import/no-restricted-paths": [
"error",
{
basePath: "./src",
zones: [
// disallow import from projectB in common
{ target: "./common", from: "./projectB" },
// disallow import from projectB in projectA
{ target: "./projectA", from: "./projectB" },
],
},
],
"import/no-relative-parent-imports": "error",
},
};
Each zone consists of the target path and a from path. The target is the path where the restricted imports should be applied. The from path defines the folder that is not allowed to be used in an import.
Looking into file ./src/common/common.ts
:
import { a } from "../projectA/a"; // works
// Error: Unexpected path "../projectB/b" imported in restricted zone.
import { b } from "../projectB/b";
The import/no-relative-parent-imports
rule also complains for both imports, like for a.ts
:
Relative imports from parent directories are not allowed. Please either pass what you're importing through at runtime (dependency injection), move common.ts
to same directory as ../projectA/a
or consider making ../projectA/a
a package.
The third rule import/no-internal-modules
wasn't used, but I also list it here, as it can be very useful to restrict access to child folders/modules and emulate (at least) some kind of package internal modifier in TS.