Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Node Package Subpath Pattern Imports #44848

Closed
FossPrime opened this issue Jul 1, 2021 · 20 comments
Closed

Support Node Package Subpath Pattern Imports #44848

FossPrime opened this issue Jul 1, 2021 · 20 comments
Assignees
Labels
Domain: ES Modules The issue relates to import/export style module behavior Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript

Comments

@FossPrime
Copy link

FossPrime commented Jul 1, 2021

Bug Report

Typescript ignores Package Subpath Imports breaking the linter when using a loader. These "aliases" are not optional when you have multiple entry points or executables at different levels.

Related to #33079
Making a new bug as the conversation there is about exports... but the import side is also important and has a seperate set of solutions.

🔎 Search Terms

Module alias, import patterns, pwd, es modules, node

🕗 Version & Regression Information

Please keep and fill in the line that best applies:

  • This is the behavior in every version I tried

⏯ Playground Link

Running node 16 with ts-node loader in a native ES Module package.

Coming soon from runkit. Integration issues like this are tricky. But I've got the tools to make it so.

💻 Code

// /package.json
  "imports": {
    "#src/*": "./src/*"
  },
import getMongoClient from '#src/models/mongodb.js'

🙁 Actual behavior

"Cannot find module '#src/models/mongodb.js' or its corresponding type declarations.ts(2307)"

🙂 Expected behavior

Should work like regular javascript.

@sandersn sandersn added Domain: ES Modules The issue relates to import/export style module behavior Suggestion An idea for TypeScript Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. labels Jul 1, 2021
@why520crazy
Copy link

I would love to support this feature!

1 similar comment
@walkerkay
Copy link

I would love to support this feature!

@Cobertos
Copy link

Cobertos commented Dec 7, 2021

As a workaround, you can use the tsconfig.json paths property for now.

An example below an alias I use for the root of my project, #@

// package.json
"imports": {
  "#@/*": "./*"
},
// tsconfig.json
{
  "compilerOptions": {
    // ...
    "paths": {
      "#@/*": ["./*"]
    }
  }
}

@e1himself
Copy link

e1himself commented Jan 7, 2022

Thanks for the tip @Cobertos

However, this solution is only suitable for application development, when you don't need to export .d.ts files to an external consumer. As when using this for a library build, the generated .d.ts files will have these paths unprocessed. Consuming TS code will fail to compile.

See #26722

@jakub-g
Copy link

jakub-g commented Mar 31, 2022

Relevant link to nodejs "subpath imports" docs:

https://nodejs.org/docs/latest-v16.x/api/packages.html#subpath-exports

@jakub-g
Copy link

jakub-g commented Mar 31, 2022

If I'm reading things correctly, this actually shipped in TS 4.5 beta (link) as part of "ECMAScript Module Support in Node.js" but finally didn't make it into TS 4.5 stable (link)

The biggest change we’ve made since the beta is that ECMAScript module support for Node.js 12 has been deferred to a future release, and is now only available as an experimental flag in nightly releases.

Also, not yet in TS 4.6.

@joshuat
Copy link

joshuat commented Mar 31, 2022

@jakub-g Correct - here are related issues with more context:

@k3n
Copy link

k3n commented Apr 2, 2022

However, this solution is only suitable for application development, when you don't need to export .d.ts files to an external consumer.

It would appear that such behavior is expected:

In addition to the "exports" field, it is possible to define internal package import maps that only apply to import specifiers from within the package itself.

Perhaps the problem applies to subpath pattern exports, but that's not what is demonstrated in the details of this issue.

@thetutlage
Copy link

Another issue faced is the path during development and post build are not compatible. Given the following subpath imports.

"imports": {
  "#src/*": "./src/*"
}

If I compile my build with the outDir = build, the subpath should be looking into the build directory now and not the root of the project.

Not sure, if TypeScript can fix this behavior, but it is going to be a common theme, where the subpath imports need to look inside a separate directory post build.

@RyanCavanaugh
Copy link
Member

@weswigham let's try to make a call on this one based on what we know now

@weswigham
Copy link
Member

? We shipped export map support in 4.7, complete with pattern exports? Not sure there's anything else to do?

@RyanCavanaugh
Copy link
Member

If the answer is "we did it and it's done" that's perfect 👍

@thetutlage
Copy link

? We shipped export map support in 4.7, complete with pattern exports? Not sure there's anything else to do?

Isn't this issue talking about subpath imports?

@weswigham
Copy link
Member

weswigham commented Jun 21, 2022

We should support those equally as much - point them at your output file paths, since that's what you need at runtime in node to make the paths work in the built js.

@thetutlage
Copy link

We should support those equally as much - point them at your output file paths, since that's what you need at runtime in node to make the paths work in the built js.

But that makes the editor complain during development time since there is no output file.

Also, will be nice to see all this getting documented on the official website :)

@doronrosenberg
Copy link

Has anyone gotten this to work? Using latest typescript (4.7.4), I still am seeing the problem where subpath imports and tsconfig paths don't work together.

Repro is https://github.com/doronrosenberg/ts-subpath-imports, you can run tsc in the project and see that it can't resolve the imports from the subpath imports. Or am I doing something wrong?

@esdmr
Copy link

esdmr commented Aug 12, 2022

@doronrosenberg Subpath imports needs a moduleResolution of Node16 or NodeNext. Also, with the moduleResolution set, you then can remove the paths in tsconfig as they are redundant.

Other than the tsconfig, you have to fix the package.json: #internal/* should refer to the Javascript and not the Typescript, and #internal2 should explicitly specify index.js.

Also, in internal/index.ts, you would have to include the extensions for import, since Node16/NodeNext requires it.

I have sent a PR over at doronrosenberg/ts-subpath-imports#1.


As for the tsconfig paths not working, you should place them in the compilerOptions with a baseUrl.

@matthew-dean
Copy link

This definitely doesn't work / isn't done. Say, for example, you have this in your src/index.ts file:

import * from '#/components"

...and this in your tsconfig.json file:

"compilerOptions": {
    // ...
    "paths": {
      "#/*": [
        "src/*"
      ]
    }
}

And you export to dist/module and typings to dist/typescript. You can get the exported distribution JS to work by adding this to package.json:

  "imports": {
    "#/*": "dist/module/*"
  },

However, the exported .d.ts will be completely lost, making them meaningless. Even thought it was able to resolve things at first, it's still using TypeScript to resolve imports, and typescript is telling the .d.ts file and other consuming packages in the TypeScript ecosystem to resolve 'components' relative to src/, which it no longer is.

That said, the .d.ts part of this would / could be resolved if #4433 is ever addressed, which has been a broken part of the TypeScript ecosystem for some time. Essentially, TSC emits .d.ts files with broken (unresolved) module references.

@owenallenaz
Copy link

owenallenaz commented Jan 3, 2023

I'm struggling to get this feature implemented and it seems like there is mass confusion around the core naming. Subpath exports work fine. Subpath imports appear to be non-functional for most use-cases.

The core issue I'm having is that when I'm working within my own library I can safely do something like imports: { "#root/*": "./src" } and it works... almost. The code executes, the tests run, but vscode can't find the references. But once I compile my code and attempt to import my library somewhere else, it's totally busted.

Heres the relevant part of my package.json:

"imports": {
	"#root/*": "./src"
},
"exports": {
	".": {
		"types": "./dist/types/index.d.ts",
		"import": "./dist/esm/index.js",
		"require": "./dist/cjs/index.js"
	},
	"./createGetCallback": {
		"types": "./dist/types/createGetCallback.d.ts",
		"import": "./dist/esm/createGetCallback.js",
		"require": "./dist/cjs/createGetCallback.js"
	},
	"./lib/*": {
		"types": "./dist/types/lib/*.d.ts",
		"import": "./dist/esm/lib/*.js",
		"require": "./dist/cjs/lib/*.js"
	},
	"./pg": {
		"types": "./dist/types/pg/index.d.ts",
		"import": "./dist/esm/pg/index.js",
		"require": "./dist/cjs/pg/index.js"
	}
},

I believe the way to handle this condition is that we need TS to specify a community "condition" via the --conditions flag of TS. If specified, I could do something like:

"imports": {
  "#root/*": {
    "ts": "./src/*",
    "cjs": "./dist/cjs/*",
    "esm": "./dist/esm/*"
  }
}

I've tried a ton of different permutations and I just can't see to get them to work, some step of the toolchain always falls apart. If I do this in raw JS it works fine.

Edit:

I was able to figure out a semi-workaround for this, using the following mechanisms:

  • Declare imports: { "#root/*": "./src/*" }
  • Utilize typescript-transform-paths to transform out the #root calls as part of the build process. This converts my subpath import references to relative paths during build.
"plugins": [
	{ "transform": "typescript-transform-paths" },
	{ "transform": "typescript-transform-paths", "afterDeclarations": true }
]
  • Keep a paths reference duplicating the imports so "paths": { "#root/*": ["./src/*"] }, this ensures that vscode can find the content without issue.

So now when I run via ts-mocha, ts-node or tsnd it utilizes the subpath imports. I do not have to utilize tsconfig-paths for it to find my content.

@bmeck
Copy link

bmeck commented Feb 1, 2024

I made a demo about how to make this pattern a bit less complex https://github.com/bmeck/typescript-issue-44848-demo

While this works at runtime, it seems there is an outstanding bug per #57259

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: ES Modules The issue relates to import/export style module behavior Needs Proposal This issue needs a plan that clarifies the finer details of how it could be implemented. Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests