Closed manuth closed 1 year ago
Hello @manuth! Thank you for the issue! Can I see an example to reproduce the problem? I just don't quite understand what's going on.
Sure - will do asap
TypeScript allows you to emit modules both written as CommonJS or as ESM. The corresponding setting is called module
:
https://www.typescriptlang.org/tsconfig#module
Files written in CommonJS can import ESModules using dynamic import()
s:
(await import("gulp-esbuild"))({});
Files written in ESM can import other ESModules using import statements:
import gulpEsbuild from "gulp-esbuild";
gulpEsbuild({});
module
SettingIn tsconfig.json
, When setting module
to CommonJS
, TypeScript will use the types
field (if present) or the main
field in your package.json
file in order to determine the corresponding types, so everything is working out just fine.
However, when setting module
to Node16
, NodeNext
or ES2022
, the module resolution of TypeScript changes.
Just like NodeJS, TypeScript will first check the file extension.
.cts
/.cjs
files will be considered CommonJS, .mts
/.mjs
will be considered as ESModule. For .ts
/.js
files, it will check the project's package.json
and decide, depending on whether the package.json
s type
is set to commonjs
or module
, whether the corresponding file is cjs or mjs.
If nothing else is specified (for example using the types
-projerty in package.json
or the exports["."].import.types
in package.json
), for .js
files, TypeScript will look for ".d.ts" files, for .cjs
files, TypeScript will look for .d.cts
Furthermore - and this is the point where the use of gulp-esbuild
breaks - TypeScript checks the exports
field and, based on whether the file performing the import is cjs or mjs, check the import
or require
field in the package.json
s exports
.
In the exports
field, people can specify custom paths to the type declarations if they'd like to do so:
{
"exports": {
".": {
"import": {
"types": "./index.d.ts", // TypeScript will resolve types to "[gulp-esbuild]/index.d.ts"
"default": "./index.mjs" // node will resolve "gulp-esbuild" to "[gulp-esbuild]/index.mjs"
}
}
}
For further reading, you might want to have a look at this: https://www.typescriptlang.org/docs/handbook/esm-node.html
This means, that, when import
ing (instead of require
ing) your module from an ESM file, TypeScript would resolve to [gulp-esbuild]/index.mjs
. Based on this, TypeScript will try to find its corresponding type declarations at [gup-esbuild]/index.d.mts
resulting in an error.
typescript
and gulp-esbuild
tsconfig.json
file which has its module
set to Node16
, NodeNext
or ES2022
(and its moduleResolution
undefined):
{
"compilerOptions": {
"module": "ES2022",
"lib": [
"ES2020"
],
"target": "ES6"
},
"include": [
"./src/**/*"
]
}
type
to module
or by creating an .mjs
filegulp-esbuild
module using an import
-statement:
import gulpEsbuild from "gulp-esbuild";
I tried your reproduction sequencing but I didn't face any types problems. What did I do wrong? Example here https://github.com/ym-project/gulp-esbuild-issue17
It's nothing big, really
TypeScript won't do actual type checking on .js
files on its own without tweaking the settings a little.
You might want to add something like this to your compilerOptions
:
{
"compilerOptions": {
"allowJs": true, // Allow JavaScript files to be part of your project
"checkJs": true, // Enable type checking in JavaScript files
/* With `outDir` unspecified, compiling a JavaScript file would cause it to overwrite itself.
* Adding this option will tell TypeScript that this project is not supposed to be compiled and
* thus not causing JavaScript files to overwrite themselves. */
"noEmit": true
}
}
Oh sorry... I just noticed that I said to create an .mjs
file where you'd actually have to create an .mts
file.
But anyways - tweaking tsconfig.json
will work as well.
Sorry for the inconvenience.
As I understand typescript documentation correctly, module resolution Classic
will work correctly only if the package has typing via @types/package
library.
You can run command tsc --noEmit --traceResolution
and see:
For gulp-esbuild
======== Resolving module 'gulp-esbuild' from '/home/ymdev/Dev/esbuild-test/src/file.mjs'. ========
Explicitly specified module resolution kind: 'Classic'.
File '/home/ymdev/Dev/esbuild-test/src/gulp-esbuild.ts' does not exist.
File '/home/ymdev/Dev/esbuild-test/src/gulp-esbuild.tsx' does not exist.
File '/home/ymdev/Dev/esbuild-test/src/gulp-esbuild.d.ts' does not exist.
File '/home/ymdev/Dev/esbuild-test/gulp-esbuild.ts' does not exist.
File '/home/ymdev/Dev/esbuild-test/gulp-esbuild.tsx' does not exist.
File '/home/ymdev/Dev/esbuild-test/gulp-esbuild.d.ts' does not exist.
File '/home/ymdev/Dev/gulp-esbuild.ts' does not exist.
File '/home/ymdev/Dev/gulp-esbuild.tsx' does not exist.
File '/home/ymdev/Dev/gulp-esbuild.d.ts' does not exist.
File '/home/ymdev/gulp-esbuild.ts' does not exist.
File '/home/ymdev/gulp-esbuild.tsx' does not exist.
File '/home/ymdev/gulp-esbuild.d.ts' does not exist.
File '/home/gulp-esbuild.ts' does not exist.
File '/home/gulp-esbuild.tsx' does not exist.
File '/home/gulp-esbuild.d.ts' does not exist.
File '/gulp-esbuild.ts' does not exist.
File '/gulp-esbuild.tsx' does not exist.
File '/gulp-esbuild.d.ts' does not exist.
Directory '/home/ymdev/Dev/esbuild-test/src/node_modules' does not exist, skipping all lookups in it.
File '/home/ymdev/Dev/esbuild-test/node_modules/@types/gulp-esbuild.d.ts' does not exist.
Directory '/home/ymdev/Dev/node_modules' does not exist, skipping all lookups in it.
Directory '/home/ymdev/node_modules' does not exist, skipping all lookups in it.
Directory '/home/node_modules' does not exist, skipping all lookups in it.
Directory '/node_modules' does not exist, skipping all lookups in it.
File '/home/ymdev/Dev/esbuild-test/src/gulp-esbuild.js' does not exist.
File '/home/ymdev/Dev/esbuild-test/src/gulp-esbuild.jsx' does not exist.
File '/home/ymdev/Dev/esbuild-test/gulp-esbuild.js' does not exist.
File '/home/ymdev/Dev/esbuild-test/gulp-esbuild.jsx' does not exist.
File '/home/ymdev/Dev/gulp-esbuild.js' does not exist.
File '/home/ymdev/Dev/gulp-esbuild.jsx' does not exist.
File '/home/ymdev/gulp-esbuild.js' does not exist.
File '/home/ymdev/gulp-esbuild.jsx' does not exist.
File '/home/gulp-esbuild.js' does not exist.
File '/home/gulp-esbuild.jsx' does not exist.
File '/gulp-esbuild.js' does not exist.
File '/gulp-esbuild.jsx' does not exist.
For react
======== Resolving module 'react' from '/home/ymdev/Dev/esbuild-test/src/file.mjs'. ========
Explicitly specified module resolution kind: 'Classic'.
File '/home/ymdev/Dev/esbuild-test/src/react.ts' does not exist.
File '/home/ymdev/Dev/esbuild-test/src/react.tsx' does not exist.
File '/home/ymdev/Dev/esbuild-test/src/react.d.ts' does not exist.
File '/home/ymdev/Dev/esbuild-test/react.ts' does not exist.
File '/home/ymdev/Dev/esbuild-test/react.tsx' does not exist.
File '/home/ymdev/Dev/esbuild-test/react.d.ts' does not exist.
File '/home/ymdev/Dev/react.ts' does not exist.
File '/home/ymdev/Dev/react.tsx' does not exist.
File '/home/ymdev/Dev/react.d.ts' does not exist.
File '/home/ymdev/react.ts' does not exist.
File '/home/ymdev/react.tsx' does not exist.
File '/home/ymdev/react.d.ts' does not exist.
File '/home/react.ts' does not exist.
File '/home/react.tsx' does not exist.
File '/home/react.d.ts' does not exist.
File '/react.ts' does not exist.
File '/react.tsx' does not exist.
File '/react.d.ts' does not exist.
Directory '/home/ymdev/Dev/esbuild-test/src/node_modules' does not exist, skipping all lookups in it.
Found 'package.json' at '/home/ymdev/Dev/esbuild-test/node_modules/@types/react/package.json'.
'package.json' does not have a 'typesVersions' field.
File '/home/ymdev/Dev/esbuild-test/node_modules/@types/react.d.ts' does not exist.
'package.json' does not have a 'typings' field.
'package.json' has 'types' field 'index.d.ts' that references '/home/ymdev/Dev/esbuild-test/node_modules/@types/react/index.d.ts'.
File '/home/ymdev/Dev/esbuild-test/node_modules/@types/react/index.d.ts' exist - use it as a name resolution result.
======== Module name 'react' was successfully resolved to '/home/ymdev/Dev/esbuild-test/node_modules/@types/react/index.d.ts' with Package ID '@types/react/index.d.ts@18.0.26'. ========
As you can see, typescript goes node_modules
folder only once and only for @types/*
package. So I think we can not fix this problem without @types/gulp-esbuild
package.
If we set "moduleResolution": "nodenext"
, we can fix this without new files. Just change package.json
file
...
"exports": {
"import": {
"types": "./index.d.ts",
"default": "./index.mjs"
},
"require": {
"types": "./index.d.ts",
"default": "./index.js"
}
},
// for old versions
"types": "index.d.ts",
"main": "index.js",
...
Yes but the index.d.ts
types mismatch index.mjs
a tiny bit.
You might notice the little differences in my PR
Namely:
gulpEsbuild
is not exported using an assignment (module.exports = gulpEsbuild
) but rather default-exported (export default gulpEsbuild
) as seen here:
gulpEsbuild
does not have a createGulpEsbuild
member. Rather, createGulpEsbuild
is exported separately as seen here:
https://github.com/ym-project/gulp-esbuild/blob/d169482c3ed50b70183d1d74dd2fa1e5abaed295/index.mjs#L331
index.d.mts
(taken from my PR) address this:
https://github.com/ym-project/gulp-esbuild/blob/2fa6abe09547eb1010a507cd1e50549cdb698462/index.d.mts#L2-L4
While index.d.ts
does not:
https://github.com/ym-project/gulp-esbuild/blob/d169482c3ed50b70183d1d74dd2fa1e5abaed295/index.d.ts#L19-L23
Ok. I have understood. I will check your PR in the evening and merge it.
Awesome! Thank you so much for taking time 😄
When trying to import this package from an ESModule TypeScript ecosystem, it won't work. TypeScript shows an error stating that the corresponding types could not be found.
This is caused by the
package.json
setting the file forimport
calls to beindex.mjs
. The setting mentioned before causes TypeScript to go search for the type declarations atgulp-esbuild/index.d.mts
.There are basically two ways on how to fix this: A separate
index.d.mts
file can be created or - if both type declarations match - the types can be set to resolve toindex.d.ts
:Sadly, the type declarations do not match (
index.js
uses an export assignment,index.mjs
uses a default export), so I think a separate type declaration file should be the proper solution.I'll go create a PR concerning this matter.