Support for NodeJS 12.7+ package exports
rbuckton opened this issue ยท 203 comments
NodeJS 12.7 added support for a (currently experimental) feature for custom package imports and exports in package.json
: https://github.com/jkrems/proposal-pkg-exports/
In short, this feature allows a package author to redirect exports in their package to alternate locations:
{
"name": "pkg",
/* [...] */
"exports": {
"./foo": "./target.js",
"./bar/": "./dist/nested/dir/"
}
}
This is currently only available when --experiemental-exports
is passed to NodeJS, however we should continue to track the development of this feature as it progresses.
Yeah, I know - I don't think we should implement support for it till it's stabilized - things like how .
works are still being discussed in the modules group.
I agree, this issue exists primarily to serve as a place for us to track the progress of this feature in NodeJS.
Given node is now at v12.11.1 and I believe v12 will enter LTS soon has this functionality stabilised enough to warrant inclusion in typescript module resolution now?
This will prove very helpful when working with yarn simlinked monorepos as the existing types
field isn't enough when there are multiple files not in the package root dir.
We spoke about it in the modules wg on Wednesday - it's going to unflag with es modules as a whole (even the cjs support), so it can be a reliable fallback-allowing mechanism for pre-esm node. It... Should... Unflag during the node 12 lifetime. But that hasn't happened yet, and details are still being fleshed out~
Looks like this has unflagged in 13.2.0: https://github.com/nodejs/node/blob/v13.2.0/doc/changelogs/CHANGELOG_V13.md#notable-changes
(with support for exports
)
Node 14 (next LTS) is scheduled for release 2020-04-21 and I'm guessing it will support exports unflagged as node 13 does and at that point I know I will want to use it :-). Is typescript planning to support exports in the same timeframe as the node 14 release?
Yeah, we've just been waiting for it to stabilize a bit, since it's still experimental, even if it's unflagged (it emits a warning when it's used in resolution), since we don't want to need to support an old experimental version and a final version (and it does still see significant change - there's discussion about removing the main
fallback right now).
Is there a way around this for the time being? Perhaps manually linking up modules via an index.d.ts
?
I'm using path based modules such as @okdecm/my-package/Databases/Postgres
and thus have no index to specify as my main
.
The code now works using the exports
option, but tooling such as Visual Studio Code still can't resolve the modules for referencing (due to current lack of TypeScript support).
It'd be ideal if there were a way to explicitly set these in the mean time.
@okdecm, try setting the baseUrl
and paths
. If @my/bar
depends on @my/foo
then you might have a tsconfig like this:
// packages/bar/tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"rootDir": "src",
"outDir": "dist/lib",
"composite": true,
"paths": {
"@my/foo": [
"../foo/dist/lib/index"
],
"@my/foo/models": [
"../foo/dist/lib/a/b/c/d/models"
],
"@my/foo/*": [
"../foo/dist/lib/*"
]
}
},
"references": [{ "path": "../foo/tsconfig.json" }]
}
The paths
should point to the emitted file. Remember that the paths
are relative to the baseUrl
so it might make sense to set the baseUrl
a parent folder.
One of the ideas for conditional exports was to allow things like types
per exported path.
{
"name": "pkg",
/* [...] */
"exports": {
"./foo": {
"types": "./types/foo.d.ts",
"default": "./target.js"
},
"./bar/": {
"types": "./types/bar.d.ts",
"default": "./dist/nested/dir/"
}
}
}
This would be great to see in a potential TypeScript integration. :)
Any updates on this?
I'm not sure I understand what the obstacle is? I would think that TS just needs to continue looking at the types
/typings
field, and load it as is. It should up to the author to declare multiple module
definitions within that single d.ts
file. Important TS fields/paths should not be scattered throughout the package.json
file โ too much room for error.
I have examples of this already. I'd expect these to Just Work โข๏ธ :
This would be backwards compatible too, because a definition file that doesn't contain a declare module
wrapper already assumes that the definition is the default / applies to the entire package.
An interesting tidbit is that if you load use a submodule inside a JS file, eg kleur/colors
, within VSCode, the submodule's types are picked up and inferred correctly. Writing the same code inside a .ts
file short circuits everything to any
type.
import * as colors from 'kleur/colors';
colors.r
// (js) has code completions
// (ts) *crickets*
this exports
field is stable in node api now
so it's the time to ts support it
any update?
Sort-of. Basic usecases are pretty stable, but a lot of conditional-related cases are still under discussion. Anyways, we've missed the mark for inclusion in 4.0, so 4.1 would be the earliest you'd see it.
Would TypeScript itself love to have similar options so we can decide the entry modules of a TypeScript project?
// Lib/tsconfig.json
{compilerOptions: {
exports: [
"index.ts" // You can only import the `index.ts` from project `Lib`
]
}}
// App/tsconfig.json
{references: [
"path": "<path-to-project-Lib>"
]}
// App/x.ts
import "<path-to-project-Lib>"; // OK
import "<path-to-project-Lib>/hid"; // Type checking error: no such module `<path-to-project-Lib>/hid`
Resolve (and dependents) support is being worked on
browserify/resolve#224
Is there any in-progress work on this and/or are there roadblocks that would need solving?
With node-v14 sceduled to enter Active LTS in under three months, i'd suspect the number of people wanting to have a feature like this to rise at an increasing rate
Is there a way around this for the time being?
I'm currently writing a module composed of submodules only, it's TypeScript compiled for browser, node cjs, node esm. Using package.json conditional exports (wildcard syntax from v14.13.0).
Here's my workaround until TS supports the "exports" resolution scheme
Consuming such module I couldn't figure out how to get intellisense working, nor how to even consume the module from typescript since the types wouldn't load when i import submoduleB from 'module/submoduleB'
. "typings" or "types" are not working, they point to a file, not a folder, i can't get TS to compile my module structure into a single .d.ts file. So i enabled --moduleResolution
to see what's up and just ended up abusing the typesVersions
field to get what i need.
Now my module consumers can
import submoduleB from 'module/submoduleB'
in both ESM JS and TSconst submoduleB = require('module/submoduleB')
- get intellisense
- get types
- ๐ดshake ๐
And most importantly, I can only bundle the type definition ONCE, even though the modules are available multiple times using different module syntaxes. I don't need to include them next to each of the module flavours.
My published folder structure
module/
โโโ dist/
โ โโโ browser/
โ โ โโโ submoduleA/
โ โ โ โโโ index.js
โ โ โโโ submoduleB/
โ โ โโโ index.js
โ โโโ node/
โ | โโโ cjs/
โ | โ โโโ submoduleA/
โ | โ โ โโโ index.js
โ | โ โโโ submoduleB/
โ | โ โโโ index.js
โ | โโโ esm/
โ | โโโ package.json ({"type": "module"})
โ | โโโ submoduleA/
โ | โ โโโ index.js
โ | โโโ submoduleB/
โ | โโโ index.js
| โโโ types/
| โโโ submoduleA/
| โ โโโ index.d.ts
| โโโ submoduleB/
| โโโ index.d.ts
โโโ package.json
My package.json contents (important ones)
{
"exports": {
"./*": {
"import": "./dist/node/esm/*.js",
"browser": "./dist/browser/*.js",
"require": "./dist/node/cjs/*.js"
}
},
"typesVersions": {
"*": { "*": ["./types/*"] }
},
"files": [
"dist"
]
}
Example resolution: (Click to expand)
โฏ npx tsc --traceResolution --skipLibCheck --target ES2020 --module ES2020 --moduleResolution node some.ts
======== Resolving module 'josev2/jwe/compact' from '/Users/panva/repo/esm/some.ts'. ========
Explicitly specified module resolution kind: 'NodeJs'.
Loading module 'josev2/jwe/compact' from 'node_modules' folder, target file type 'TypeScript'.
Found 'package.json' at '/Users/panva/repo/esm/node_modules/josev2/package.json'.
'package.json' has a 'typesVersions' field with version-specific path mappings.
'package.json' has a 'typesVersions' entry '*' that matches compiler version '4.0.3', looking for a pattern to match module name 'jwe/compact'.
Module name 'jwe/compact', matched pattern '*'.
Trying substitution './types/*', candidate module location: './types/jwe/compact'.
File '/Users/panva/repo/esm/node_modules/josev2/types/jwe/compact.d.ts' exist - use it as a name resolution result.
Resolving real path for '/Users/panva/repo/esm/node_modules/josev2/types/jwe/compact.d.ts', result '/Users/panva/repo/esm/node_modules/josev2/types/jwe/compact.d.ts'.
======== Module name 'josev2/jwe/compact' was successfully resolved to '/Users/panva/repo/esm/node_modules/josev2/types/jwe/compact.d.ts' with Package ID 'josev2/types/jwe/compact.d.ts@1.0.0'. ========
@weswigham Why this feature has not been added to 4.1? Is it possible to add it to 4.2?
Node v14 is going LTS in 10 days, this is the only thing currently stopping me from using the new exports
so far, would love to see this implemented.
I think we'll see a spike of people wanting to use this soon, any idea when/if we can expect support for it?
Any updates on this?
This will be great! I can't wait!! Thanks for all the great work on TS so far TS team.
We just encountered this when using exports
on a package. The types did not work for <package-name>/test
, so we had to manually add paths
so that Typescript could find the correct location for the type definitions.
Looking forward to this issue being solved as well ๐
Just a follow up to my previous comment(s) โ this approach will always work:
// package.json
{
"name": "foobar",
"types": "index.d.ts", // root/main module types only
"files": [
"*.d.ts", // root types
"sub1", // all `foobar/sub1` files
"sub2", // all `foobar/sub2` files
"dist" // all `foobar` files
],
"exports": {
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./sub1": {
"import": "./sub1/index.mjs",
"require": "./sub1/index.js"
},
"./sub2": {
"import": "./sub2/index.mjs",
"require": "./sub2/index.js"
},
"./package.json": "./package.json"
},
// ...
}
And then the built file structure:
foobar
โโโ dist
โ โโโ index.js
โ โโโ index.mjs
โโโ sub1
โ โโโ index.js
โ โโโ index.mjs
โ โโโ index.d.ts # `foobar/sub1` types only
โโโ sub2
โ โโโ index.js
โ โโโ index.mjs
โ โโโ index.d.ts # `foobar/sub2` types only
โโโ index.d.ts # `foobar` (main) types only
โโโ package.json
TypeScript gives index.d.ts
the same default-resolver behavior that Node.js gives index.js
. This means the sub1/index.d.ts
and sub2/index.d.ts
is important. So when you import { foo } from 'foobar/sub1'
the sub1
directory is accessed and the index.d.ts
within it is autoloaded.
The name & location of the main module's definitions (/index.d.ts
in tree above) does not matter, since the "types"
field points to its location.
@lukeed another workaround similar to yours works even if you don't want to change your directory structure. For example, if sub1
is actually in a src/sub1
directory, and exports
has "./sub1": "./src/sub1/index.js"
, your workaround won't work, as your workaround needs the source directory structure to conform to the directory structure exported.
The workaround is to add a package.json
in ./sub1
, with two fields: main
, and types
, that point to /src/sub1/index.js
and /src/sub1/index.d.ts
respectively (or wherever your decide to put your .d.ts
files).
I find that this option is better as it decouples your source code directory structure from the exports
path structure.
It's equally tied to directory structure -- you're just juggling/maintaining more files to make it work.
What I presented will work. If you start changing things, you "void the warranty" so to speak ๐
Hey @lukeed, thanks for the awesome solution about module exporting. Currently I'm trying to go with your second approach, but TypeScript refuses to recognize the typing files for some reason. Can you take a look to see if I'm doing anything wrong? I've gone so far to add the index.d.ts
file directly in the files
, but I still get a Cannot find module 'helpers/modules' or its corresponding type declarations.
error
Importing from helpers/lib/modules
works properly and also importing from helpers/modules
works if I suppress the TypeScript compiler with // @ts-ignore
. so it seems that for some reason the typings are not recognized. Thanks for the help!
@Shiroh1ge For this, I think you need to take the approach @giltayar just shared. Unlike my example, you are nesting/aliasing directories, which means that the "helpers/modules" doesn't actually line up with the directory structure.
My approach worked because Node.js allows the helpers/modules
import, due to your defined ./modules
entry, but then TS goes spelunking through the filesystem looking for a ./helpers/modules((.d)?.ts|/index(.d)?.ts
match. Won't find anything since everything exists elsewhere.
Again, the approach @giltayar shared would require that you create a helpers/modules/package.json
file with a types
field. (I think you might only need main
field I'd you're not using a bundler (eg Rollup) and are using TS compiler on its own...not sure)
I think as part of this TS would need to support conditional exports
https://nodejs.org/api/packages.html#packages_conditional_exports
https://webpack.js.org/guides/package-exports/#conditions
I think there would be a new option in tsconfig like targetConditions : ("node" | "browser" | "development" | "production")[]
Hi @giltayar. I've been trying your workaround on a package that I'm developing, but I can't get it to work. My directory structure:
my-package/
package.json (contains "main" and "exports")
lib/
index.js
index.d.ts
app/
index.js
index.d.ts
package.json (added as per your suggestion)
I can use the package in Node.js with require('my-package/app')
but importing it in TS doesn't work:
main.ts:1:25 - error TS2307: Cannot find module 'my-package/app' or its corresponding type declarations.
1 import { getApps } from 'my-package/app';
Any thoughts on what I might be missing?
I've been working on a tool that standardizes the NPM package building process called Packemon (https://packemon.dev).
It helps alleviate the subpath exports problem by using Rollup and creating single build output files (and associated type files using --generateDeclaration=api
) based on input entry files. Might be a solution to all the problems listed above.
@hiranya911 you put the package.json in the app directory under lib when it should be in a top level app directory.
One of the ideas for conditional exports was to allow things like
types
per exported path.{ "name": "pkg", /* [...] */ "exports": { "./foo": { "types": "./types/foo.d.ts", "default": "./target.js" }, "./bar/": { "types": "./types/bar.d.ts", "default": "./dist/nested/dir/" } } }This would be great to see in a potential TypeScript integration. :)
Some tools can consume typescript directly, for example esbuild
. Though perhaps out of scope for this discussion, have any of those present given thought to how those files might be mapped?
I've tentatively used an esbuild
condition in some of my projects.
Thanks @giltayar. That solution worked for me. For anybody else interested, here's my package layout to work around the issue in question.
my-package/
package.json (contains "main" and "exports")
app/
package.json (contains "main" and "types"; points to ../lib/app directory)
lib/
index.js
index.d.ts
app/
index.js
index.d.ts
Yeah, this is getting a more painful now that Webpack 5 obeys exports
.
We currently can not use libraries with exports
in a TypeScript project with Webpack 5 (without inconvenience of configuring TypeScript paths
).
I've been fiddling with paths
for a half hour, and no luck. This is too difficult for people that don't use paths
.
Currently I publish my TS packages using "main" and "types" pointing to the .ts file, but I'm hoping exports
will eventually be a semantically cleaner way to do this (I don't publish TS transpiled down to JS because I can't assume what target people use, and don't want to add bloat in their builds by transpiling to ES5).
@hiranya911 Your solution was a huge help for me. Paying it forward, here are a few details that weren't quite explicit.
My project has two sub-packages (foo
, bar
) -- equivalent to app
in the original example -- which show up in the top-level package.json
as:
{
...
"types": "dist/index.d.ts",
"files": [
"dist",
"*.d.ts",
"foo",
"bar"
],
"exports": {
".": "./dist/index.js",
"./foo": "./dist/path/to/foo.js",
"./bar": "./dist/path/to/bar.js",
"./package.json": "./package.json"
},
...
}
Each of the these sub-packages has a foo/package.json
and bar/package.json
file at the top-level. These look like:
{
"main": "../dist/path/to/foo.js",
"types": "../dist/path/to/foo.d.ts"
}
The types
mapping here is absolutely essential. I'm not certain that we really need the main
mapping since it is a duplicate of the value in the top-level package.json
.
A few questions that I've encountered related to this:
- How do we foresee
exports
working together withtypes
andtypesVersions
? - I see some overlap with
exports + workspaces
(now supported by both yarn and npm) and Typescript Project References. Are they different enough to coexist?
This is workaround for subpath exports using typesVersions
.
https://github.com/teppeis/typescript-subpath-exports-workaround
package.json
{
"main": "dist/index.js",
"types": "dist-types/index.d.ts",
"exports": {
".": "./dist/index.js",
"./exported": "./dist/exported.js"
},
"typesVersions": {
"*": {
"exported": ["dist-types/exported"]
}
}
}
This config exports only the package root and ./exported
.
// Pass
import "typescript-subpath-exports-workaround"
import "typescript-subpath-exports-workaround/exported"
// Error
import "typescript-subpath-exports-workaround/not-exported"
import "typescript-subpath-exports-workaround/dist/exported"
Did anyone manage to get it working for plain JavaScript to make VSCode support Intellisense with exports?
This is workaround for subpath exports using typesVersions.
https://github.com/teppeis/typescript-subpath-exports-workaround
This work very well with the Typescript server used by VSCode, we got full support, but when webpack is compiling it (using ts-loader
) we got an import error
Module not found: Error: Can't resolve '@bidule/package/submodule' in 'src/file.ts'
@flibustier In my test project, it works with webpack (ts-loader).
If you need help, you can raise an issue in my repo.
Would also be great if conditionals could be used to provide types for different versions of typescript. It's annoying not being able to let users take advantage of new typescript powers when publishing libraries just to maintain backwards compatibility.
Edit: Already exists: https://www.typescriptlang.org/docs/handbook/declaration-files/publishing.html#version-selection-with-typesversions
This is workaround for subpath exports using typesVersions.
https://github.com/teppeis/typescript-subpath-exports-workaroundThis work very well with the Typescript server used by VSCode, we got full support, but when webpack is compiling it (using
ts-loader
) we got an import errorModule not found: Error: Can't resolve '@bidule/package/submodule' in 'src/file.ts'
I got same issue while trying to use a package with the typesVersions
workaround at some Angular 11 project (which uses webpack 4 under the hood). Any ideas?
what's the progress on this? This is a critical issue I think.
what's the progress on this? This is a critical issue I think.
Looks like its expected to be part of 4.3, set to release in May.
#42762
I just tried 4.3-beta but couldn't get it working. I tried to find some unit test or pull request for it, but no luck either. Is this really implemented? If so, is there an example somewhere? @DanielRosenwasser
I think this is a priority for TypeScript, as the whole Ecosystem will gradually migrate CommonJS modules to ESM.
And I think personnally that the outputed code from TypeScript should be as close as the source code, if the target is recent like ES2020
.
My guess is that this was originally planned for 4.3 but as it doesn't appear to have landed yet likely will be out in 4.4.
Yes, we don't expect this to land for 4.3. Iteration plans describe intent to work on specific items, not necessarily that we will complete them in that time (nor is there a full commitment given that sometimes other stuff comes up as well). We are actively discussing the feature, and because each part of the ES modules story in Node is so co-mingled with each other part, it needs to be thought out carefully.
I'm speculating that types
and typesVersions
can be unified under package exports, with something like this:
{
"exports": {
".": {
"module": "./main.js",
"types": {
"typesVersion:<=4.0": "./main.ts3.d.ts",
"default": "./main.d.ts"
}
},
"./utils": {
"module": "./lib/utils.js",
"require": "./lib/utils.cjs",
"types": {
"typesVersion:<=4.2": "./lib/types/utils.ts41.d.ts",
"default": "./lib/types/utils.d.ts"
}
}
}
}
any updates on this?
@rayfoss I don't know if it will help you, but after reading this issue this is the workaround that worked for me:
In my library I want to export 2 things to my clients:
import { A, B, C } from 'lib';
import { I1, I2, I3 } from 'lib/icons';
in my package.json I have
"exports": {
".": "./dist/index.js",
"./icons": "./dist/icons/index.js"
},
doing this makes it work on the JS side, but typescript complained on missing the types. To make sure ts was able to find the types on the codebases where I use the library I changed the tsconfig.json to have this
"compilerOptions": {
"baseUrl": ".",
"paths": {
"lib/icons": [
"./node_modules/lib/dist/icons" # I have the types index.d.ts in that directory as well
]
}
}
It's not ideal because you need to edit the client typescript configuration, but if you control both, like in my case, it's a decent workaround.
(also I am not an expert on this stuff so it might have side-effects I don't know ๐)
@marcoacierno that works fine if you are the only consumer of that package. If you publish that in NPM no one will be able to use it without hacking their build reference paths.
We really need the typescript compiler to handle this automatically
@isc30 yeah this is what I meant when I said that I control both :) The library is installed via NPM and just updating the tsconfig works so if you have a package that you use in other sites (our use case is a styleguide published on NPM that is then downloaded by our projects)
Otherwise, you could just document it in your library documentation explaining how to make typing works I guess ๐คท as I said, not ideal
My workaround, that does not require tinkering with tsconfig.json by my lib's consumers:
E.g. for
"exports": {
".": "./dist/index.js",
"./react": "./dist/react/index.js"
}
I just put a file called react.d.ts
into the project root with the following content:
export * from './dist/react';
This can be used by the lib's consumer out-of-the-box as import { whatever } from 'yourlib/react';
Workarounds as of today: not a single one.
I have found a workaround using the package.json setting typesVersions:
{
"exports": {
".": "./dist/index.js",
"./*": "./dist/*.js",
},
"types": "./dist/index.d.ts",
"typesVersions": {
"*": {
"dist/index.d.ts": [ "dist/index.d.ts" ],
"*": [ "dist/*" ]
}
}
}
With this setup, typescript correctly finds both import pkg from 'pkg'
and import sub from 'pkg/sub'
Notice that the workaround doesn't play well with TS Language Server
autocompletions (it won't hint for proper imports for example), so there is work to be done in this area
I went through the trouble of converting everything way from subpath import patterns, only to realise that doesn't work well for scripts (pwd). Turns out, only the VSCode linter and TSC was having issues with them... everything else worked as expected, as ts-node in doesn't natively path resolution, node does.
This loaded with ts-node's esm transpile-only loader for me today. I can't get it to pass VSCode and TSC linting.
#src
is defined in package.json, "Yohan"
is read from an imported #src/utils.js
ts file.
https://codesandbox.io/s/mocha-native-esm-ts-ctt5k?file=/src/utils.ts
Summary:
#!/usr/bin/env -S node --loader ts-node/esm/transpile-only
import { pick } from '#src/utils.js' // ts
// package.json: "imports": { "#src/*": "./src/*" }
-- I encountered this issue again two days later... I forgot I had written this... jeez. This issue must die... I will find a solution.
in the meantime multiple vendors added support for package.json exports
:
- node experimental in v12 LTS (2019)
- node stable support in v14 LTS (2020)
- webpack 5
- rollup
- vite
- snowpack
- preact WMR
could you please summarise the current blockers for typescript?
would it be possible to add support behind a feature flag?
Since all of them support custom conditions, it is crucial for TypeScript also support custom conditions, in addition to types
and typesVersion
. That could be an option in tsconfig/tsc
like moduleResulutionCustomConditions: string[]
.
Please add support for this...
ES modules are the future, and the faster we move, the faster JavaScript & TypeScript can get out of this mess
Temporary workaround i've found
package.json
{
"type": "module",
"exports": {
".": "./dist/index.js",
"./*": "./dist/*"
},
"typesVersions": {
"*": {
"./dist/index.d.ts": [ "./dist/index.d.ts" ],
"*": [ "dist/*" ]
}
}
}
run like so:
node --experimental-specifier-resolution=node --loader ts-node/esm ./tests.test.ts
Feedback:
I don't like the fact that I need to include .js
in imports (the reason I used the node experimental specifier). It feels weird to import a typescript file with the .js
extension. I know TypeScript doesn't care, but I should be able to import without an extension, and have typescript automatically append it to files in the compiled output
Feedback:
I don't like the fact that I need to include.js
in imports (the reason I used the node experimental specifier). It feels weird to import a typescript file with the.js
extension. I know TypeScript doesn't care, but I should be able to import without an extension, and have typescript automatically append it to files in the compiled output
@AlbertMarashi That's neither here nor there; see #16577
I don't like the fact that I need to include
.js
in imports (the reason I used the node experimental specifier).
@AlbertMarashi If you add the .js
extension after the asterisk in the export, you dont need to include it in the import:
{
"exports": {
".": "./dist/index.js",
"./*": "./dist/*.js"
}
}
I tried the following setup:
{
"type": "module",
"exports": {
".": "./dist/index.js",
"./*": "./dist/*"
},
"typesVersions": {
"*": {
"*": [ "dist/*", "dist/*/index.d.ts" ]
}
}
}
Now everything seems to work
One thing to keep in mind: If you (or the library consumers) want to be able to import/require package.json
, it must be added to the exports
declaration.
Does this issue also count towards the subpath imports?
eg:
A feature in node 14 is you can define imports in your package.json and alias your relative paths in your package.json. It would be great if intellisense resolves these so using them would be even better.
An example of package.json imports:
{
"name": "",
"version": "1",
"description": "",
"main": "index.js",
"scripts": {},
"keywords": [],
"author": "",
"license": "",
"dependencies": {},
"devDependencies": {},
"imports": {
"#utils/*": "./utils/*",
"#colors": "./commanddata/colors.json"
}
}
Then call with require('#utils/myUtil.js');
This already works, but no intellisense is available
@MaxTechnics probably you can solve this by using paths
in tsconfig.json
https://stackoverflow.com/questions/43281741/how-to-use-paths-in-tsconfig-json
Note that paths is a compiler option that works mostly for intellisense, but doesn't change import paths in output files. For the latter purpose it is common to use something like Webpack Aliases, or typescript paths plugin + ttypescript, or something else
@MaxTechnics probably you can solve this by using
paths
intsconfig.json
https://stackoverflow.com/questions/43281741/how-to-use-paths-in-tsconfig-json
Note that paths is a compiler option that works mostly for intellisense, but doesn't change import paths in output files. For the latter purpose it is common to use something like Webpack Aliases, or typescript paths plugin + ttypescript, or something else
This worked! Thank you very much! Now i can finally say 'death to long relative paths'
This worked! Thank you very much! Now i can finally say 'death to long relative paths'
Be aware of some limitations when using auto import: #31173 (comment)
In fact i'm considering going back to relative imports since is quite annoying (the auto import adds invalid path i have to fix manually)
I don't really use auto imports so for me it's not really a problem, my projects are also relatively small most of the time.
We stabilized package features, including package exports, over a year ago.
Subpath patterns were stabilized shortly after.
Do you at least allow PRs to implement this feature?
It's being worked on~
Is the work targeted for inclusion in a particular release?
@conartist6 the next version according to the iteration plan.
@weswigham, for clarity - are Node.js-style exports something you're trying to address as a part of the next release (within module: node12
)?
For example, will it enable consuming/creating "dual CommonJS/ES module packages"?
"exports": {
"import": "./index.mjs",
"require": "./index.cjs"
},
Looking at the above references, a lot of people think so, and wanted to clear this up. Thanks for all of the great work!
Yes, it's intended to be a part of that.
Fixed by the addition of moduleResolution: node12
with #45884 which supports these~
@weswigham I couldn't find any discussion or description about supporting the types in the package.json exports, is the syntax going to be like the comment in this thread? a "types" property?
{
"name": "pkg",
/* [...] */
"exports": {
"./foo": {
"types": "./types/foo.d.ts",
"default": "./target.js"
},
"./bar/": {
"types": "./types/bar.d.ts",
"default": "./dist/nested/dir/"
}
}
}
Typescript being able to understand a package that has the "exports" map field was the main reason for this issue
They are! A types
condition is looked up, as are conditions akin to types@>=4.2
!
@flibustier In my test project, it works with webpack (ts-loader). If you need help, you can raise an issue in my repo.
@teppeis I created a simple React app and able to import from typescript-subpath-exports-workaround
but not from typescript-subpath-exports-workaround/exported
. It is unable to find the module in the latter case.
Any help/pointers would be appreciated.
with typescript@beta
it looks like not working now
@JiangWeixian The beta works for me. Just make sure compilerOptions.module
equals node12
and compilerOptions.moduleResolution
is undefined.
@JiangWeixian The beta works for me. Just make sure
compilerOptions.module
equalsnode12
andcompilerOptions.moduleResolution
is undefined.
thx, it works
They are! A
types
condition is looked up, as are conditions akin totypes@>=4.2
!
@weswigham I noticed that a types
path must be .d.ts
and not .ts
(pointing to source file). Any reason that can't be supported?
@weswigham I noticed that a
types
path must be.d.ts
and not.ts
(pointing to source file). Any reason that can't be supported?
This is very disappointing - this will require us to introduce a build step in our monorepo development environment
@aleclarson @joshuat .ts
files are not the same as .d.ts
. Declarations omit types that aren't part of the public/visible API, while using the source files force consumers to all install all dependencies that are required for type information, which isn't typically what you want. Declarations avoid this.
@weswigham Should the types
field be used if no types
condition exists in the exports
field? It seems to not be happening in the beta.
types
is like a TS main
- you can set it, but it'll only be used in ts versions that don't respect exports
. When there is exports
, we have to be able to find the types via exports
, too.
That makes it very difficult for me to upgrade. There are plenty of packages out there already using exports but not including exports.types (and still using the top-level types property). I kind of expected this would work.
One great example: dotenv. It's broken in TS 4.5 even though it could work. See https://github.com/motdotla/dotenv/blob/27dfd3f034ce00b1daa72effbd91dd7788aced48/package.json#L12
@JiangWeixian The beta works for me. Just make sure
compilerOptions.module
equalsnode12
andcompilerOptions.moduleResolution
is undefined.
Can you provide a code example? I tried almost every possible configuration option but I could not get it working.
@JiangWeixian The beta works for me. Just make sure
compilerOptions.module
equalsnode12
andcompilerOptions.moduleResolution
is undefined.Can you provide a code example? I tried almost every possible configuration option but I could not get it working.
{
"compilerOptions": {
"allowJs": true,
"alwaysStrict": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"jsx": "preserve",
"lib": ["dom", "es2017"],
"module": "node12",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"strict": true,
"target": "es5",
"baseUrl": ".",
"noImplicitThis": true,
"paths": {
"~/*": ["./*"],
"@/*": ["pages/*"]
},
"isolatedModules": true
}
}
I use this tsconfig.json
on my nextjs project, just make sure vscode
use typescript@4.5
. (But there is some editor lint error, maybe fixed if vscode@latest released)
What is the current status of this issue?
Is exports
long been supported in TypeScript installations, or is it a new thing, or is it not supported yet?
Will it work in most TypeScript installations without any additonal configuration?
I also heard of this workaround:
"typesVersions": {
"*": {
"export1": ["build/export1.d.ts"],
"export2": ["build/export2.d.ts"]
}
}
Does it work? Will it work in most TypeScript installations without any additonal configuration?
Update:
TypeScript requires the following flag to be set manually in tsconfig.json
in order for Node ES Modules to be enabled:
{
"compilerOptions": {
"module": "nodenext",
}
}
Then, when providing separate "typings" for a library, use the types
sub-property of exports
entries to specify the path to the "typings" file.
In such case, types
must be the first sub-property.
{
"type": "module",
"exports": {
".": {
"types": "./index.d.ts",
"import": "./index.js",
"require": "./index.cjs"
}
}
}
Also, in *.d.ts
files, any relative imports have to use the full file extension.
So, change things like:
import ... from './other-file'
or:
import ... from './other-file.d'
to:
import ... from './other-file.d.ts'
I've previously tested fully-specified file paths in *.d.ts
in non-ESM mode and it didn't work, so perhaps importing by a full *.d.ts
path inside a *.d.ts
file only works for Node ES modules and doesn't work in non-ESM mode.
The following types path is assumed implicitly by TypeScript compiler.
{ "type": "module", "exports": { ".": { // "types": "./index.d.ts", "import": "./index.js", "require": "./index.cjs" } } }
I believe in this case it will look for index.d.ts
for import
and index.d.cts
for require
.
So, change things like:
import ... from './other-file'or:
import ... from './other-file.d'to:
import ... from './other-file.d.ts'
No, change them to './other-file.js'
(or .mjs
or .cjs
where appropriate).
I believe in this case it will look for
index.d.ts
forimport
andindex.d.cts
forrequire
.
I see, there'd have to be separate folders for esm
and cjs
then, with a package.json
in each โ one with type: module
and the other without.
No, change them to
./other-file.js
(or .mjs or .cjs where appropriate).
Yeah, I was referring to TypeScript "typings" exclusively, not to TypeScript code in general.
I don't use TypeScript but people usually ask for "typings" so I provide separate ones, and in those *.d.ts
files import
ing by a full path doesn't work for some weird reason, but importing by an incomplete path like *.d
or *
works.