question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Resolving multiple package.json "main" fields

See original GitHub issue

TL;DR: A new compiler option mainFields for selecting multiple fields in package.json instead of just package.json#main.


There are lots of related issues to this one (which I link to below), but I want to focus on just this specific proposal.


Packages often look like this:

{
  "name": "my-package",
  "main": "dist/cjs/index.js",
  "module": "dist/esm/index.js"
  "source": "src/index.ts"
}

Notice how we have multiple fields which specify multiple entry points. These entry points all refer to the same code, just in different compile states and configurations.

Many tools use these fields in order to find the entry point that they care about. For example, tools like Webpack and Rollup will use package.json#module in order to find ES modules. Other tools will use fields like package.json#source (or src) for local package development.

While these fields aren’t part of the official Node module resolution algorithm. They are a community convention which has proven to be useful in lots of scenarios.

For TypeScript, one such scenario that this would be useful for is with multi-package repos or “monorepos”. These are repositories where the code for multiple npm packages exist and are symlinked together locally.

/project/
  package.json
  /packages/
    /package-one/
      package.json
      /node_modules/
        /package-two/ -> ../../package-two (symlink)
    /package-two/
      package.json

Inside each package, you’ll generally have a src/ directory that gets compiled to dist/

/package-two/
  package.json
  /src/
    index.ts
  /dist/
    index.js
    index.d.ts

Right now it is really painful to use TypeScript with one of these repos. This is because TypeScript will use the package.json#main to resolve to the packages dist folders. The problem with this is that the dist folders might not exist and if they do exist they might not be compiled from the most recent version of src.

To work around this today you can add a index.ts file in the root of each of your packages to point to the right location and make sure that the root index.ts file does not get shipped to npm.

/package-two/
  index.ts
  /src/index.ts
// package-two/index.ts
export * from './src/index'

It sucks that you need this file, and if you ever forget to create it in a new package, you’ll revert back to really crap behavior.

If, instead of all that, TypeScript supported a new compiler option mainFields which looked like:

{
  "compilerOptions": {
    "mainFields": ["source", "main"]
  }
}

Note: Webpack has this same configuration option

You could add package.json#source (in addition to package.json#main) and resolve it to the right location locally.

The algorithm would look like this:

For each mainField:

  1. Check if the package.json has a field with that name
  2. If the package.json does not have the field, continue to next mainField
  3. If it field exists, check for a file at that location.
  4. If no file at that location exists, continue to the next mainField
  5. If the file exists, use that file as the resolved module and stop looking

I think this is the relevant code:

https://github.com/Microsoft/TypeScript/blob/b363f4f9cd6ef98f9451ccdcc7321d151195200b/src/compiler/moduleNameResolver.ts#L987-L1014

Related Issues:

Issue Analytics

  • State:open
  • Created 6 years ago
  • Reactions:109
  • Comments:20 (3 by maintainers)

github_iconTop GitHub Comments

9reactions
aluanhaddadcommented, Jan 29, 2018

For providing intellisense, when no declarations are available, wouldn’t it always be better to resolve to the source, if available, than to the compiled output and would that be true irrespective of the source being written in TypeScript?

Even with --allowJs the compiled output is usually resolved when there are no type declarations. This does not only affect developers working with monorepos, it affects anyone consuming packages without declarations.

Of course, it depends on the package, but when using say, “Go to Definition”, it is very common to be taken to a UMD bundle and that is probably the least desirable result.

Having said that, there are too many of these damn fields!

Here is a totally non-exhaustive list of main fields that should be considered applicable

"main" "browser" "types" "module" "jsnext:main" "es2015" "unpkg" "typings"

7reactions
pie6kcommented, Oct 11, 2020

The solution that worked for me is keeping both uncompiled and compiled files in the same folder (lib) instead of having both src and dist.

TLDR: During development - all .js files are removed. During publishing - all .ts files are ignored.

Here is the flow:

For simplicity, let’s consider our package has only one, index.ts source file.

I keep it in <ROOT>/lib/index.ts or <ROOT>/packages/foo/lib/index.ts.

Than in package.json I pass "main": "./lib". Note I’m not passing exact file name. TS will be able to resolve to index.ts in development. Bundlers will be able to resolve to index.js in published package.

During development - I have a script that clears all .js files in lib (therefore you cannot use .js files in development).

This makes typescript properly point to ts file.

When releasing the package - I run build which will add .js files next to their .ts counterparts.

In .npmignore - I ignore all lib/**.ts files, so in final, published version there are only .js files in lib.

In ‘production’ - package.json main field which points to ./lib will properly resolve to ./lib/index.js.

In .gitignore - I ignore all lib/**.js files - so in my git repo I don’t have .js files in lib published if I forget to run my clean script before pushing.

Read more comments on GitHub >

github_iconTop Results From Across the Web

One project with multiple package.json files - Stack Overflow
Problem: From research I made so far it seems impossible to use different package.json files and node_modules folders inside the same project ...
Read more >
package.json - npm Docs
If your package is licensed under multiple common licenses, ... The main field is a module ID that is the primary entry point...
Read more >
Main property in package.json defines package entry point
First, Node looks for a package.json file and checks if it contains a main property. It will be used to point a file...
Read more >
Understanding dependency management with Node Modules
In your package.json add a resolutions field to specify the dependency and the version that should used. The version in each of the...
Read more >
Solving conflicts in package-lock.json - TkDodo's blog
json. The conflicts in package.json are usually easily solved (if there even are any), so I think it is tempting to just delete...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found