TypeStrong / ts-loader

TypeScript loader for webpack
https://johnnyreilly.com/ts-loader-goes-webpack-5
MIT License
3.45k stars 428 forks source link

TS2305: Module has no exported member #1070

Closed thekevinbrown closed 4 years ago

thekevinbrown commented 4 years ago

Expected Behaviour

I'm building my project using Webpack and ts-loader. My expectation was that if tsc was able to compile a set of files, then ts-loader should be able to as well.

Actual Behaviour

Building with tsc works.

❯ yarn build:tsc
yarn run v1.22.1
$ lerna run build
lerna notice cli v3.20.2
lerna info Executing command in 2 packages: "yarn run build"
lerna info run Ran npm script 'build' in '@reproduction/a' in 2.8s:
$ tsc
lerna info run Ran npm script 'build' in '@reproduction/b' in 1.6s:
$ tsc
lerna success run Ran npm script 'build' in 2 packages in 4.4s:
lerna success - @reproduction/a
lerna success - @reproduction/b
✨  Done in 5.52s.

But building with webpack doesn't.

❯ yarn build:webpack
yarn run v1.22.1
$ webpack
Hash: 53c9b05c76683b63c208
Version: webpack 4.42.0
Time: 1302ms
Built at: 03/17/2020 12:33:31 PM
 2 assets
Entrypoint main = index.js
[0] ./packages/b/src/index.ts 133 bytes {0} [built] [1 error]
[1] ./packages/a/lib/index.js 236 bytes {0} [built]
[2] ./packages/a/lib/some/index.js 238 bytes {0} [built]
[3] ./packages/a/lib/some/folder/index.js 236 bytes {0} [built]
[4] ./packages/a/lib/some/folder/test.js 145 bytes {0} [built]

ERROR in /Users/kevin/development/webpack-typescript-TS2305-reproduction/packages/b/src/index.ts
./packages/b/src/index.ts
[tsl] ERROR in /Users/kevin/development/webpack-typescript-TS2305-reproduction/packages/b/src/index.ts(1,10)
      TS2305: Module '"../../../../../../../Users/kevin/development/webpack-typescript-TS2305-reproduction/packages/a/lib"' has no exported member 'Test'.
error Command failed with exit code 2.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

I'm not sure why, as when I go to packages/a/lib there's an index.d.ts file which exports *, etc etc until you get to Test, which tsc recognises, but ts-loader doesn't seem to.

Steps to Reproduce the Problem

I've created a reproduction repo here: https://github.com/thekevinbrown/webpack-typescript-TS2305-reproduction

Steps

  1. Clone the repo
  2. yarn
  3. yarn build:tsc => Success
  4. yarn build:webpack => Error

Location of a Minimal Repository that Demonstrates the Issue.

https://github.com/thekevinbrown/webpack-typescript-TS2305-reproduction

Thanks for your help!

thekevinbrown commented 4 years ago

It looks like I can work around this by changing:

  "typings": "src/index.ts",

to

  "typings": "lib",

in the package.json files.

I should probably explain that we don't intend to publish these packages, so the reason for specifying the src directory as typings is so that we don't have to rebuild on every change to see things update across packages in VSCode as we work.

If there's a better way to achieve this I'm all ears, and I'm still interested as to why ts-loader doesn't work in this scenario while tsc does. I'll work around this in the meantime though.

stale[bot] commented 4 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

stale[bot] commented 4 years ago

Closing as stale. Please reopen if you'd like to work on this further.

thekevinbrown commented 4 years ago

I'd like to reopen this as there's been no movement.

appzuka commented 4 years ago

Hi,

I looked at your repo and think projectReferences might be the way to achieve what you are looking for. I forked your repo and added project references. Take a look at:

https://github.com/appzuka/webpack-typescript-TS2305-reproduction.git

Now the error does not occur when building using webpack but VS code knows about changes to the referenced project without the need to rebuild it.

thekevinbrown commented 4 years ago

Hi @appzuka,

Thanks for that. I guess the question I have is why does TypeScript work in this scenario but not ts-loader?

I'd really rather not maintain lists of all the other projects inside each project's tsconfig.json if possible.

appzuka commented 4 years ago

From what I can see in the documentation, the typings field in package.json is intended to point to a .d.ts file containing type definitions. Is there any documentation to say that this can point to a ts file?

Nevertheless, pointing to index.ts does seem to work when using tsc. It correctly gets the type definitions by looking at src/index.ts and all of its dependencies. This seems to me to be defeating the purpose of importing a pre-transpiled library with a .d.ts file for the definitions - you may be increasing your build time by doing this. If you are doing this you may as well point your main field to src/index.ts. Everything will then work as you wish at the cost of rebuilding your entire project on every build. I note that if you run

npx lerna run --scope '@reproduction/b' build

before building project a there are no errors, showing tsc is not using the pre-built package.

The error goes away if you use transpileOnly: true. If you then re-enable error checking with fork-ts-checker-webpack-plugin the error does not come back. The documentation for fork-ts-checker-webpack-plugin mentions that it uses Typescript's module resolution, not webpack's so there can be some differences. If you don't want to use project references this could be a solution for you.

You could use the decarationMap option to emit map files so that VSCode knows where the source code is from the .d.ts files. This seems to work in VSCode so that 'go to definition' takes you to the source code, not the .d.ts file. It is worth adding this option even if you use project references to get this feature. However, it does not seem to show an error in VSCode when a change is made to one of the package sources.

It is strange that it works with tsc but not with webpack and ts-loader. It seems that when package a is imported by tsc it looks in a/src/index.ts, as specified in package.json, and finds the type definitions. When the same package is imported by ts-loader it notices that the file specified in typings is not a .d.ts file and ignores it.

Although ts-loader is meant to be a drop-in replacement for tsc, the documentation does say there can be differences between ts and ts-loader if there is no tsconfig.json in the root of the project. That is not the case here but this is a complex setup with multiple tsconfig.json files.

If it is documented that typings can point to a .ts file then this may be a bug, otherwise, it may not be worth tracking down what the difference is.

In any case, project references seems to be the way to go here. The cost of listing your referenced projects is outweighed by the benefits of using a workflow which tsc, ts-loader and VSCode have all be designed to work with.

thekevinbrown commented 4 years ago

Fair enough, I had tried setting composite: true before and couldn't get it to work.

Having fully reviewed the docs and walking step by step I got it working this time, and incremental builds are a huge win for us. Thank you for taking the time to explain, it now works with ts-loader as well. Really appreciate it!

johnnyreilly commented 4 years ago

I'm only just spotting this now, but from the looks of it @thekevinbrown opened up an issue which the very generous @appzuka was able to solve by providing a guide to using project references! (support of which in ts-loader is down to the wonderful @andrewbranch and @sheetalkamat )

Thanks all! 🥰🌴

eric-burel commented 4 years ago

I am kind of hitting the similar issue on my project, reproduction here (not minimal however): https://github.com/VulcanJS/vulcan-npm/tree/bugfix/build-ts-loader

I don't really get how project reference scales to a huge number of packages. You have to write for each packages all valid references, in addition to having them in package.json? This seems weird to me, all the more that building the project is actually building each package. Project references looks more like it is meant for monolithic project that may have a few independant package, not a huge package base.

If you have example of existing open source project using webpack + ts-loader + lerna that could help greatly, I struggle to find one.

Edit: specifically, it does not work if I build the dependent module first. So if "foo" is using "bar", "foo" is building ok, but if I build "bar" first, then "foo" doesn't build anymore. I think when I build "foo" alone, it uses "bar/index.ts" instead of "bar/dist/index.js" which doesn't exist yet.

Edit 2: I think I am progressing, the missing export are all coming from .js files. And when I open the dist/index.d.ts file, it indeed indicates that it misses file definition from "myFile.js", which is not included in the dist folder

Edit 3: it seems that disallowing .js file and convert them to .ts fixes my issue. I still have problems with window in an isomorphic context but that's unrelated.