Reputation: 36299
I'm trying to import package.json
in my TypeScript application:
import packageJson from '../package.json';
My tsconfig.json
contains the following:
{
"compilerOptions": {
"rootDir": "./src/"
"outDir": "./dist/",
"baseUrl": ".",
"resolveJsonModule": true
}
}
The problem is that when I compile this, I get
error TS6059: File '/path/to/package.json' is not under 'rootDir' '/path/to/app/src/'. 'rootDir' is expected to contain all source files.
I'm not sure I understand the issue, because both ./src/
and ./dist/
have the same parent ..
, so TypeScript could just leave alone the import '../package.json'
and it would work from either rootDir
or outDir
.
Anyway, I've tried the following, with unsatisfactory results:
rootDir
- compilation works, but the dist
will contain dist/src
, which I don't wantoutDir
- then src
gets polluted with .js
files (and .js.map
if sourceMap
was true)@ts-ignore
- compilation stops the the file that imports ../package.json
What's the workaround for this limitation, to keep generated files in dist
, and allow importing from the parent directory of rootDir
?
Upvotes: 86
Views: 54831
Reputation: 379
This might help:
// eslint-disable-next-line @typescript-eslint/no-require-imports
const packageJson = require('../package.json');
Upvotes: 0
Reputation: 15097
This is possible, and it turns out, not hard.
The reason the solution is not obvious is because Typescript relies on the rootDir
to decide the directory structure of the output (see this comment from Typescript's bossman), and only code included in the output or in package dependencies can be imported.
rootDir
to the root of your project, package.json
gets emitted to the root of outDir
and can be imported. But then your compiled src
files get written to outDir/src
.rootDir
to src
, files in there will compile to the root of outDir
. But now the compiler won't have a place to emit package.json
, so it issues "an error because the project appears to be misconfigured" (bossman's words).Each Typescript project is self-contained, defined by its own tsconfig
, with its own rootDir
. This lines up with the principle of encapsulation.
You can have multiple projects (e.g. a main and a set of libs) each in their own rootDir
and with their own tsconfig. Dependencies between them are declared in the dependent's tsconfig
using Typescript Project References.
I admit, the term "projects" is a poor one, as intuitively it refers to the whole shebang, but "modules" and "packages" are already taken. Think of them as subprojects and it will make more sense.
To solve your specific problem, we'll treat the src
directory and the root directory containing package.json
as separate projects. Each will have its own tsconfig
.
Give the src
dir its own project.
./src/tsconfig.json
:
{
"compilerOptions": {
"rootDir": ".",
"outDir": "../dist/",
"resolveJsonModule": true
},
"references": [ // this is how we declare a dependency from
{ "path": "../" } // this project to the one at the root dir`
]
}
Give the root dir its own project.
./tsconfig.json
:
{
"compilerOptions": {
"rootDir": ".",
"outDir": ".", // if out path for a file is same as its src path, nothing will be emitted
"resolveJsonModule": true,
"composite": true // required on the dependency project for references to work
},
"files": [ // by whitelisting the files to include, you avoid the default TS behavior, which
"package.json" // will include everything, resulting in `src` being included in both projects (bad)
]
}
run tsc --build src
and voilà!
This will build the src
project. Because it declares a reference to the root project, it will build that one also, but only if it is out of date. Because the root tsconfig has the same dir as the outDir
, tsc will simply do nothing to package.json
, the one file it is configured to compile.
You can isolate modules/libraries/sub-projects by putting them in their own subdirectory and giving them their own tsconfig.
You can manage dependencies explicitly using Project References, as well as modularize the build:
From the linked doc:
you can greatly improve build times
A long-awaited feature is smart incremental builds for TypeScript projects. In 3.0 you can use the
--build
flag withtsc
. This is effectively a new entry point fortsc
that behaves more like a build orchestrator than a simple compiler.Running
tsc --build
(tsc -b
for short) will do the following:
- Find all referenced projects
- Detect if they are up-to-date
- Build out-of-date projects in the correct order
Don’t worry about ordering the files you pass on the commandline -
tsc
will re-order them if needed so that dependencies are always built first.
enforce logical separation between components
organize your code in new and better ways.
It's also very easy:
A root tsconfig
for shared options and to build all
subprojects with a simple tsc --build
command
(with --force
to build them from scratch)
src/tsconfig.json
{
"compilerOptions": {
"outDir": ".", // prevents this tsconfig from compiling any files
// we want subprojects to inherit these options:
"target": "ES2019",
"module": "es2020",
"strict": true,
...
},
// configure this project to build all of the following:
"references": [
{ "path": "./common" }
{ "path": "./projectA" }
]
}
A "common" library that is prevented from importing from the other subprojects because it has no project references
src/common/tsconfig.json
{
"extends": "../tsconfig.json", //inherit from root tsconfig
// but override these:
"compilerOptions": {
"rootDir": ".",
"outDir": "../../build/common",
"resolveJsonModule": true,
"composite": true
}
}
A subproject that can import common because of the declared reference.
src/projectA/tsconfig.json
{
"extends": "../tsconfig.json", //inherit from root tsconfig
// but override these:
"compilerOptions": {
"rootDir": ".",
"outDir": "../../build/libA",
"resolveJsonModule": true,
"composite": true
},
"references": [
{ "path": "../common" }
]
}
Upvotes: 120
Reputation: 20838
There is a tidy three-step solution with Node 16+ LTS and TypeScript 4.7+ for packages that use ES modules instead of CommonJS.
The "imports"
field in package.json defines internal pseudo-packages that can be imported only from within your actual package. Define an internal import specifier, such as #package.json
in package.json:
{
"type": "module",
"exports": "./dist/index.js",
"imports": {
"#package.json": "./package.json"
}
}
Enable TypeScript support for ES modules and JSON imports in tsconfig.json:
{
"compilerOptions": {
"module": "nodenext",
// "moduleResolution" defaults to "nodenext", just made explicit here
"moduleResolution": "nodenext",
"resolveJsonModule": true,
}
}
Lastly, import #package.json
from your TypeScript module:
import packageJson from '#package.json' assert { type: 'json' };
console.log(packageJson);
Upvotes: 10
Reputation: 6382
When using // @ts-ignore
on top of the import
call and setting "rootDir": "./src"
it works. In this case enabling resolveJsonModule
will still work, but only for files under the ./src
. See: https://github.com/MatrixAI/TypeScript-Demo-Lib/pull/33 for how I applied it to our template repository. This way it is possible to import json files from within ./src
as normal, but when you import ../package.json
, you have to use // @ts-ignore
to ensure that TSC ignores it. It's a one-off special case so this works.
The reason it all works is because
setting https://www.typescriptlang.org/tsconfig#rootDir will force tsc not to infer the project root dir to be the src. And thus will enforce the expected dist
structure, while throwing warnings/errors on importing outside the rootDir
. But you can ignore these warnings.
Upvotes: 1
Reputation: 169
I solve this problem by using symlink: in windows:
cd src
mklink package.json ..\package.json
or in linux:
cd src
ln -s package.json ../package.json
Upvotes: -3
Reputation: 37337
We can set resolveJsonModule
to false and declare a module for *.json
inside typings.d.ts
which will require JSON files as modules and it will generate files without any directory structure inside the dist
directory.
monorepo\
├─ app\
│ ├─ src\
│ │ └─ index.ts
│ ├─ package.json
│ ├─ tsconfig.json
│ └─ typings.d.ts
└─ lib\
└─ package.json
app/typings.d.ts
declare module "*.json";
app/src/index.ts
// Import from app/package.json
import appPackageJson from '../package.json';
// Import from lib/package.json
import libPackageJson from '../../lib/package.json';
export function run(): void {
console.log(`App name "${appPackageJson.name}" with version ${appPackageJson.version}`);
console.log(`Lib name "${libPackageJson.name}" with version ${libPackageJson.version}`);
}
run();
app/package.json
contents{
"name": "my-app",
"version": "0.0.1",
...
}
lib/package.json
contents{
"name": "my-lib",
"version": "1.0.1",
...
}
Now if we compile the project using tsc
, we'll get the following dist
directory structure:
app\
└─ dist\
├─ index.d.ts
└─ index.js
And if we run it using node ./dist
, we'll get the output from both app
and lib
package.json
information:
$ node ./dist
App name "my-app" with version 0.0.1
Lib name "my-lib" with version 1.0.1
You can find the project repository here: https://github.com/clytras/typescript-monorepo
Upvotes: 30
Reputation: 16157
It is not possible for now. Typescript compiler try to keep your directory structure.
For example, your project look like:
src/
shared/
index.ts
index.ts
package.json
tsconfig.json
Your tsconfig.json
contains:
{
"compilerOptions": {
"outDir": "./build",
"module": "commonjs",
"target": "es6",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"noImplicitAny": true,
"sourceMap": true,
"resolveJsonModule": true,
"esModuleInterop": true
},
"include": [
"src/**/*"
]
}
As you see, the file does not include rootDir
property, but when you call tsc
command to compile the project, the output will look like:
build/
shared/
index.js
index.js
The output does not contain src
folder, because in my code, I just import and use inside src
folder, like:
src/index.ts
import someName from './shared';
then, build/index.js
will look like:
...
const shared_1 = __importDefault(require("./shared"));
...
as you see - require("./shared")
, this mean it working fine with build
folder structure.
Your "issue" appeared when you import a "outside" module
import packageJson from '../package.json';
So, what happen with "back" action - '../'? If you hope your output structure will be:
build/
package.json
index.js
then, how do they work with const packageJson = __importDefault(require("../package.json"));
. Then Typescript compiler try to keep project structure:
build/
package.json
src/
index.js
With a monorepo project, I think you need to create declaration files for each library, end then use references
setting in the tsconfig file. Ex:
./lib01
folder, the lib import ./lib02
in their code. Tsconfig file will be like:{
"compilerOptions": {
"declarationDir": "dist",
"rootDir": "src"
},
"include": ["src/**/*"],
"references": [ // here
{
"path": "../lib02"
}
]
}
tsconfig.json
{
"compilerOptions": {
"declarationDir": "dist",
"rootDir": "src",
"composite": true // importance.
}
}
Upvotes: 2
Reputation: 47
It depends on how and when you're reading "package.json". You can read it as file with NodeJS "fs" module at runtime, or just type const package = require("package.json").
In 2nd case Typescript will search it in root dir at compile time (refer to Typescript module resolution documentation).
You also can use "rootDirs" property instead of "rootDir" to specify array of root folders.
Upvotes: 1