I got a package.json where I export different scripts using the exports field.
"exports": {
".": {
"default": "./dist/main.es.js",
"require": "./dist/main.cjs.js",
"types": "./dist/main.d.ts"
},
"./utils": {
"default": "./dist/utils.es.js",
"require": "./dist/utils.cjs.js",
"types": "./dist/utils.d.ts"
},
"./segments/*": {
"default": "./dist/webvtt/segments/*.es.js",
"require": "./dist/webvtt/segments/*.cjs.js",
"types": "./dist/webvtt/segments/*.d.js"
}
}
The file structure is as follow
dist
├── main.cjs.js
├── main.d.ts
├── main.es.js
├── utils.cjs.js
├── utils.d.ts
├── utils.es.js
├── vite.svg
├── vtt.cjs.js
├── vtt.d.ts
├── vtt.es.js
└── webvtt
├── segments
├── Comment.cjs.js
├── Comment.d.ts
├── Comment.es.js
├── Cue.cjs.js
├── Cue.d.ts
├── Cue.es.js
├── Header.cjs.js
├── Header.d.ts
├── Header.es.js
├── Segment.cjs.js
├── Segment.d.ts
├── Segment.es.js
├── Style.cjs.js
├── Style.d.ts
└── Style.es.js
In VSCode, it now shows the utils and segments as exported paths

However, when importing scripts from segments it doesn't show which scripts I can import from that path.

But if I continue, and import any of the scripts from the segments folder, it works fine.

How can I make the IntelliSense show me the scripts I can import from the segments path?
The repo containing the source can be found here
https://github.com/codeit-ninja/js-vtt
Meaning, you have set certain fields in both your package.json & tsconfig.json files that conflict with other settings within those same files. The most notable was the way you have configured your project to resolve modules, which I'll explain.
Like many other package maintainers, you have opted to add modular support for both "ECMAScript Modules" (aka ESM) as well as "Common-JS Modules" (aka CJS). Also like many package maintainers, your failing to configure your package such that both modules exist harmoniously with one another, to be more specific: The way that you have configured module resolution for TS (in other words, the tsconfig.json field "moduleResolution") is different from how Node.js is currently being configured to resolve modules in your package.json configuration.
You also have not explicitly defined a build for both ESM & CJS, furthermore; you have no entry point defined for your module when it is being consumed via an import, nor for when it is consumed via a CJS require() method.
So its important to just clarify what Module loaders are, and specifically what "Module Loaders are to Node.js". Module loaders are the syntax used by developers to indicate the want a certain resource to be consumed. Node.js understands two different loaders, both of which you are probably familiar with.
fs library, simply for example purposes only. I chose fs simply because it is familiar to many.import * as fs from 'node:fs';Its important to note that this loader is not only syntactically different, but it is also mechanically different as it is asynchronous.
const fs = require('node:fs');require loader is synchronous.That asynchronous, and synchronous difference between the two loaders makes the two modules incompatible in most situations (any exceptions are beyond the topics that we are covering).
package.json fileNow, with the above said, lets look at your package.json file.Inside of your package.json file you have set the "type" field as shown bellow.
{
"type": "module"
}
Setting "type" as "module" in and of itself is not a bad thing, however, the way you set this field will affect the way that Node.js handles your emitted JavaScript project (and really, the JS files are the actual mechanics right? TS is more or less just a statically typed blue print in a sense). Looking at it that way, you can easily see that it is extremely important to be aware of how Node.js is working. It is important that you configure TypeScript to work in harmony with node, or else you will experience undesired results, like "your intellisense not working as expected".
"type" field to "module" causes Node to treat .js files as ESM JavaScript"type" to "commonjs", or omitting it altogether, will configure node to treat .js files as CommonJS JavaScript..mjs files are always loaded as ESM, despite the value of the nearest package.json file's "type" field..cjs files are always loaded as CommonJS, despite the value of the nearest package.json file's "type" field.There is more than one mistake in your configuration, however, the most notable, and easiest to fix, is the value you set for the tsconfig.json field "moduleResolution". Your "moduleResolution" field, is currently configured as shown bellow. tsconfig.json to ["node"][1], as can be seen below...
"compilerOptions": {
"moduleResolution": "node"
}
It should be noted that setting "moduleResolution" changes (in part) the resolution strategy used by TypeScript. In your case, you set it to "node", which tells TS to mimic Node's CommonJS module resolution algorithm`.
So, in other words, if you have your package.json's "type" field set to "module", your telling node.js to resolve ".js" files as ECMAScript modules, which as stated above, is an Asynchronous way of resolving modules.
And if you have set "moduleResolution" in your tsconfig.json file to "node", you are telling TypeScript to mimic the algorithm used by "commonjs" modules, which is a synchronous resolution strategy that follows a completely different spec than ESM.
Do you see how your project is not configured to resolve modules harmoniously?
I don't know all the details on how Intellisense works for TypeScript in VS Code, but I do know it involves a language server, which is powered by TSC itself, and the way that TSC is configured to resolve modules is going to have an affect on the way Intellisense behaves when importing & exporting files. It won't break IntelliSense, but if a resource cannot be resolved in an import statement, IntelliSense is going to show you a list without the "whatever it is" your looking for.
Also, I don't know why your exporting your types, that is also going to affect things. You should export your types by setting the "types" field in your package.json file.
according to TypeScript documentation:
TypeScript will use a field in package.json named
typesto mirror the purpose of "main" - the compiler will use it to find the “main” definition file to consult.
"types" has an alias, which is "typings""You want to set the two like this:
// "tsconfig.json"
{
"compilerOptions": {
"declaration": true,
"declarationDir": "./types"
}
}
// "package.json"
{
"types": "./types"
}
I do the follwoing
| Module Type | FilePaths |
|---|---|
CJS |
"builds/CommonJS" |
ESM |
"builds/ECMAScript" |
| Module Type | FilePaths |
|---|---|
CJS |
"./builds/CommonJS/main.cjs" |
ESM |
"./builds/ECMAScript/main.mjs" |
.
├── lib
│ └── sum-lib.ts
|
├── main.cts
└── main.mts
.
├── CommonJS
│ ├── lib
│ ├── main.cjs
│ └── package.json <-------- Notice the package.json?
|
└── ECMAScript
├── lib
├── main.mjs
└── package.json <-------- Notice the package.json?
The CommonJS & ECMAScript directories both contain a package.json, that doesn't include the projects base-dir, when including the base package.json file, the project has 3 package.json files all together. You can get by with only 2, but I like 3, it makes the configuration more robust, and harder to break when changes are introduced to the projects file-structure.
The package.json files in the CommonJS & ECMAScript directories are simple — very very simple — they contain only what is absolutely needed.
And just FYI, I am showing you the package.json files because it looks like your not defining a commonjs module anywhere, which can only be done using a package.json file, and each module type needs to be defined by its own package.json file. I see that you set a CJS entry, and have CJS files, but no explicit configuration pointing to a CJS module which is also enough to BREAK YOUR INTELLISENSE (i used bold because it answers the direct question).
The extra package.json files in the build I showed you using file trees looks like this.
// ECMAScript/package.json
{
"type": "module"
}
// CommonJS/package.json
{
"type": "commonjs"
}
package.json file /*
> "package.json"
> I left out dependencies, repo url, and other things, and left the
important settings */
{
"name": "rgb-interface",
"author": "Andrew Chambers <[email protected]>",
"version": "0.0.3",
"type": "module",
"types": "types",
"exports": {
"import": "./builds/ECMAScript/main.mjs", // ESM Entry
"require": "./builds/CommonJS/main.cjs" // CJS Entry
},
}
}
// I didn't leave out "main", I just don't define it, as exports defines my entry points"
There is only one ".mts" file & one ".cts" file, the rest are simply ".ts". So long as I define the project's modules correctly, so that the same algorithm is being used by typescript & node.js, everything works harmoniously. I can import from the same files to main.cts & main.mts without maintaining seperate cts & mts versions of the same file. One source, and 2 builds.
You have to configure typescript to build a CJS build, and to build an ESM build. There are several different concepts that all achieve the samething, but they each look & work very differently from each other. The most simple way in my opinion takes advantage of newer TS 3.X features that were introduced for the purpose of emitting multiple builds, henceforth, the $ tsc --build command + flag.
reference the two different builds.{
"files": [],
"references": [
{ "path": "tsconfig.esm.json" },
{ "path": "tsconfig.cjs.json" }],
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": ".Cache/tsc.buildinfo.json",
// "listEmittedFiles": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"listEmittedFiles": true
}
}
then use two other tsconfig files for the builds.
// "tsconfig.esm.json"
{
"files": [
"src/main.mts",
"src/test/esm-color-format.test.mts",
"src/lib/ansi-static.ts",
"src/lib/color-log.ts",
"src/lib/ansi.ts"
],
"exclude": ["node_modules", "**/*.cts"],
"compilerOptions": {
// ECMAS Module + ES2021 + Node-16LTS
"target": "ES2021",
"module": "Node16",
"moduleResolution": "Node16",
"esModuleInterop": false,
// STRUCTURE
"outDir": "builds/ECMAScript",
"declarationDir": "types",
"rootDir": "src",
"sourceRoot": "src",
"composite": true, // <-- must be on
// EMISSIONS
"declaration": true,
"declarationMap": true, // maps add extra intellisense features
"sourceMap": true, // both "*.d.ts.map" and "*.js.map" files
"inlineSources": true,
"noEmitOnError": true,
"noEmit": false, // Can be used when you want to emit only one build
}
// tsconfig.cjs.json
{
"files": [
"src/main.cts",
"src/test/cjs-color-format.test.cts",
"src/lib/ansi-static.ts",
"src/lib/color-log.ts",
"src/lib/ansi.ts"
],
"exclude": ["node_modules", "**/*.mts"],
"compilerOptions": {
// CommonJS + Node-8 + ES5 (Early Support)
"target": "ES5",
"module": "CommonJS",
"moduleResolution": "node",
"esModuleInterop": true,
// STRUCTURE
"outDir": "builds/CommonJS",
"declarationDir": "types",
"rootDir": "src",
"sourceRoot": "src",
"composite": true,
// EMISSIONS
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"inlineSources": true,
"noEmitOnError": true,
"noEmit": false,
}
}
Believe it or not, I removed all the type checks and other stuff not required to get all intellisense features supported that I could, and emit a dual esm & cjs project.
Anyways, hopefully this helps
I want to point my package.json exports['.'].import key at my lib directory (where my source lives) and have done with it.
That works just fine, except that Intellisense breaks in VS Code.
This happens because VS Code is parsing package.json to figure out where your typedefs are, and apparently VS Code has not yet caught up to the new export-mapping syntax!
Remember the main key? It still works, and if you use it alongside exports, Node.js will just ignore it. But VS Code will pick it up and use it to power your Intellisense!
For example:
{
...,
"exports": {
".": {
"import": "./lib/index.js", // where I write my code
"require": "./dist/default/lib/index.js" // Babel transpiler target
}
},
"main": "./lib/index.js", // where VS code looks for Intellisense
...
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With