webpack: Inconsistency with TypeScript in Module Resolution with Fully-Specified ESM Imports
Bug report
What is the current behavior?
When using extention .js as part of an import (fully-specified ESM import), webpack fails to create a bundle if the file imported have extention .tsx
This shouldnt be like this based on the comment of RyanCavanaugh in this issue https://github.com/microsoft/TypeScript/issues/41887#issuecomment-741968855
“TypeScript doesn’t modify JavaScript code you write, the import path you write should be the one you want to appear in the output .js file”
If the current behavior is a bug, please provide the steps to reproduce.
I created a repo showing this unexpected behavior
this repo was tested with both ts-loader
and babel-loader
https://github.com/Josehower/webpack-babel-test
- clone the repo and install dependencies
- use command
$yarn start
import { test } from './test.js';
console.log('Hello Webpack Project.');
test();
jose fernando hower@DESKTOP-FHTIMFT MINGW64 /D/upleveled-softwork/webpack-test (main)
$ yarn start
yarn run v1.22.5
$ webpack serve --config ./webpack.config.js --mode development
i 「wds」: Project is running at http://localhost:8080/
i 「wds」: webpack output is served from /
i 「wds」: Content not from webpack is served from D:\upleveled-softwork\webpack-test\dist
× 「wdm」: asset bundle.js 367 KiB [emitted] (name: main)
runtime modules 706 bytes 4 modules
cacheable modules 336 KiB
modules by path ./node_modules/webpack-dev-server/ 21.2 KiB
modules by path ./node_modules/webpack-dev-server/client/ 20.9 KiB 10 modules
modules by path ./node_modules/webpack-dev-server/node_modules/ 296 bytes 2 modules
modules by path ./node_modules/html-entities/lib/*.js 61 KiB 5 modules
modules by path ./node_modules/querystring/*.js 4.51 KiB 3 modules
modules by path ./node_modules/webpack/hot/ 1.42 KiB
./node_modules/webpack/hot/emitter.js 75 bytes [built] [code generated]
./node_modules/webpack/hot/log.js 1.34 KiB [built] [code generated]
modules by path ./node_modules/url/*.js 23.1 KiB
./node_modules/url/url.js 22.8 KiB [built] [code generated]
./node_modules/url/util.js 314 bytes [built] [code generated]
./node_modules/webpack/hot/ sync nonrecursive ^\.\/log$ 170 bytes [built] [code generated]
ERROR in ./src/index.tsx 1:0-33
Module not found: Error: Can't resolve './test.js' in 'D:\upleveled-softwork\webpack-test\src'
resolve './test.js' in 'D:\upleveled-softwork\webpack-test\src'
using description file: D:\upleveled-softwork\webpack-test\package.json (relative path: ./src)
Field 'browser' doesn't contain a valid alias configuration
using description file: D:\upleveled-softwork\webpack-test\package.json (relative path: ./src/test.js)
no extension
Field 'browser' doesn't contain a valid alias configuration
D:\upleveled-softwork\webpack-test\src\test.js doesn't exist
*
Field 'browser' doesn't contain a valid alias configuration
D:\upleveled-softwork\webpack-test\src\test.js* doesn't exist
.js
Field 'browser' doesn't contain a valid alias configuration
D:\upleveled-softwork\webpack-test\src\test.js.js doesn't exist
.ts
Field 'browser' doesn't contain a valid alias configuration
D:\upleveled-softwork\webpack-test\src\test.js.ts doesn't exist
.tsx
Field 'browser' doesn't contain a valid alias configuration
D:\upleveled-softwork\webpack-test\src\test.js.tsx doesn't exist
as directory
D:\upleveled-softwork\webpack-test\src\test.js doesn't exist
webpack 5.36.0 compiled with 1 error in 2410 ms
i 「wdm」: Failed to compile.
- use command
$yarn tsc --noEmit
jose fernando hower@DESKTOP-FHTIMFT MINGW64 /D/upleveled-softwork/webpack-test (main)
$ yarn tsc --noEmit
yarn run v1.22.5
$ D:\upleveled-softwork\webpack-test\node_modules\.bin\tsc --noEmit
✨ Done in 1.49s.
What is the expected behavior?
-
In the example repo
yarn start
should succesfully create a bundle. -
Webpack should be able to recognize the files imported and create a bundle even if the import statements with the extention .js point files that have the extention .tsx
Other relevant information:
OS: Windows 10 10.0.19041
CPU: (4) x64 Intel(R) Core(TM) i5-7400 CPU @ 3.00GHz
Binaries:
Node: 16.0.0 - ~\AppData\Local\Temp\yarn--1619542324765-0.4257021548777624\node.CMD
Yarn: 1.22.5 - ~\AppData\Local\Temp\yarn--1619542324765-0.4257021548777624\yarn.CMD
npm: 7.10.0 - C:\Program Files\nodejs\npm.CMD
Browsers:
Chrome: 90.0.4430.93
Edge: Spartan (44.19041.906.0), Chromium (90.0.818.46)
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 10
- Comments: 53 (49 by maintainers)
another workaround I found was using
NormalModuleReplacementPlugin
to replace the problematic extensionin our case, this did the trick
Updated with the RegExp suggested by @laverdet https://github.com/webpack/webpack/issues/13252#issuecomment-1185674918
I tweaked the RegExp here because otherwise imports to modules like sha.js will fail. Thanks!
I think we can close it, now we have
extensionAlias
- https://webpack.js.org/configuration/resolve/#resolveextensionaliasSomething like that could be solved in a custom resolver plugin. I think think this typescript-specific problem fits into the webpack core.
The resolver plugin could take an
extensions
argument which is a list of typescript extensions that should be tried when the request ends with.js
.Use
extensionAlias
in Next.js viaexperimental.extensionAlias
config introduced in https://github.com/vercel/next.js/pull/45423:next.config.js
Yeah, sometimes I think npm should not allow to use
.
in a package name, because it is pain to write regexp and code looks misleading:Do we load
./sha.js
or./sha.js/index.js
? weirdnot stale
The problem is that using the fully-specified
.js
extension fails when using TypeScript.ts
files (they are not resolved in parity with the behavior of TypeScript andtsc
)With TypeScript and
tsc
, the following works:With webpack, it fails and cannot resolve the
./abc.js
specifier (note the difference between the extensions -.js
and.ts
).See @RyanCavanaugh’s comment here: https://github.com/microsoft/TypeScript/issues/41887#issuecomment-741968855
Oh sorry, deleted my comment now! Indeed the type is there in
5.74.0
, somehow one of my webpack versions was locked to5.73.0
😬@iamWing the
/^\..+\.js$/
regex seems to work for./app.js
, and it is intended to filter outsha.js
:The regex on the tweaked version doesn’t seem right to me. Neither
_/app.js
orsha.js
matches the expression.My suggestion would be use
/.*\/+.+\.js$/
instead. Matching a/
character in the middle of the path can help identifying if we’re importing a local module or not, because if we’re importing a dependency installed we usually just writeimport something from 'something';
, but if we’re importing a local module, we writeimport something from './path/something.js'
.A better way to cover all cases would be tweaking the function in
NormalModuleReplacementPlugin
to exclude some patterns for the edge cases.e.g.
Maybe we can also develop a plugin to do what we’re doing with
NormalModuleReplacementPlugin
here but also check the dependency list frompackage.json
so that all packages listed there can be excluded automatically? I’ll do some research on this later.Edited for typo
technically we can add this to default rules but webpack doesn’t support ts out of the box… so probably you will need specify extension alias
Accidentally close, yes, you can specify
extensionAlias
in theresolve
option (https://webpack.js.org/configuration/resolve/), like you do it foralias
/etcOk, so what is the configuration that would be required? I’m sure that this will be a common question for those looking to take advantage of this.
What I can see from the PR is something like this:
Is
extensionAlias
a valid top-level key in webpack config? Or doesenhanced-resolve
need to be added via a plugin or something in the config as well?should be fixed by https://github.com/webpack/enhanced-resolve/pull/351
@karlhorky It is not implemented in
enhanced-resolve
(we should start with it), but it is easy https://github.com/TypeStrong/ts-loader/issues/1383#issuecomment-968075478, just want to verify @sokra and @vankop are supporting my idea@karlhorky I think we need a plugin in https://github.com/webpack/enhanced-resolve/ (I already implemented it in thread) and allow customize resolver, allow ts-loader can create custom resolver using loader API and supports it out of box
Simple workaround for your simple case:
Ideally
ts-loader
should respectresolve.extensions
(betterresolve.byDependency.typescript
) and when you usingimport something from './foo.js'
try to resolve./foo.js
as./foo.tsx
/./foo.ts
and then other variants (i.e../foo.js
and etc).To avoid collisions between
ts-loader
resolving and Node.js resolving we should support https://webpack.js.org/configuration/resolve/#resolvebydependency, so you will use:It will be implement using https://webpack.js.org/api/loaders/#thisgetresolve, we use the same logic for
sass
/less
/etc so@import
with CSS and SASS works good.Honestly it’s not hard to do.
Yep, I see…
We need improve it on ts-loader side, webpack works correctly here.
The TypeScript team doesn’t agree, check out the issue I linked above: https://github.com/microsoft/TypeScript/issues/41887#issuecomment-741968855
What is problem with webpack here? If you use
import
you need to write extension, or disablefully-specified ESM import
, don’t know what we should fix here…