parcel
parcel copied to clipboard
Importing typescript configured for ESM not supported by Parcel
π bug report
- Because Typescript developers were super nice, they decided that if you need the generated code to be valid ESM,
import "foo"
should be written asimport "foo.js"
even if we are importingfoo.ts
. - Typescript resolution algorithm ignores the extension and imports the
.ts
file if it's present. Otherwise it tries to import the.js
file. - Parcel's resolution doesn't follow this convention. If there is no
foo.js
file but there isfoo.ts
, Parcel errors out sayingfoo.js
not found.
More info:
π Configuration (.babelrc, package.json, cli command)
-
.parcelrc
: None - Babel: None
-
package.json
:{ .., "type": "module", .. }
π€ Expected Behavior
In a TS file, import "foo.js"
should try to import foo.ts
. If not present, try to import foo.js
.
π― Current Behavior
import "foo.js"
fails if foo.js
is not present, regardless of the presence of foo.ts
@parcel/core: Failed to resolve '../lib/main.js' from './demo/index.ts'
π¦ Context
Here's my file structure:
lib/
main.ts
demo/
index.html
index.ts
I'm trying to import main.ts
in index.ts
.
lib
is configured to compile to an ESM package, that can work on both client and server side.
So the imports inside main.ts
must have .js
extension.
Otherwise, typescript will emit it as import "foo"
, and the import will fail at runtime because ESM specs require an extension.
π§ͺ Minimal Repro
https://github.com/itsfarseen/repro-parcel-ts
π Your Environment
Software | Version(s) |
---|---|
Parcel | 2.3.2 |
Node | v14.19.0 |
Yarn | 1.22.17 |
Operating System | Arch Linux |
A resolution is much needed here. This issue is currently blocking us from migrating our Open Source repos to esm.
Here is note about the reasoning: https://www.typescriptlang.org/docs/handbook/esm-node.html
The TypeScript approach is actually very sensible and also in line with future Types in JavaScript: https://devblogs.microsoft.com/typescript/a-proposal-for-type-syntax-in-javascript/
Typescript requiring full extension in imports is fine. But I feel like it's typescript's responsibility to rewrite that from ".ts" to ".js". Otherwise, every other tool will need to have workarounds just for the sake of typescript.
Every other JS tool will need to duplicate the logic to "replace .ts with .js", the logic that should have been in the Typescript compiler in the first place.
@itsfarseen No absolutely not. An import is an external source. Its part of module resolution to figure out wether it is there in JavaScript or TypeScript format. As to full paths -> that is a requirement of ES modules, so not in TypeScripts scope, especially not with moving to Types out of the box in JavaScript, and supporting TypeScript without actual type checking directly in V8.
@philkunz Let's consider the pros vs cons. Feel free to add to this list :)
If typescript decides to transpile import statements, just like other language construct it transpiles:
Pros:
- Bundlers can just look at the imports and invoke tsc/swc/esbuild if it ends in ".ts".
Otherwise they will have to, for every ".js" imports, check if a ".ts" is available. If yes, try importing that. Otherwise try importing ".js" file. Suppose if I build an alternative superset of JS called MangoScript, the bundler will have to look for ".ms" files also. This if-else chain increases linearly with the number of languages supported by the bundler. So time to import a file = number of languages supported * time to check file exists in the filesystem. - Every JS tool just needs to invoke some typescript loader if an import ends in ".ts". This includes things like Jest test runner. No need to do the "if .ts exists try loading that" logic. No need to add any TS specific logic in the tools other than "invoke TS loader if import ends in .ts".
Cons:
- Just a little bit of rewrite logic in Typescript.
@itsfarseen It is unnecessary transpilation. TypeScript is a superset of JavaScript. Why transpile something that does not need transpilation? In the future, ideally, you only have to write a .ts file in the module chain, if you want to use a feature, that conflicts with JavaScript syntax. Otherwise, JavaScript is TypeScript and vice versa. You are introducing complexity at the code level, that can be solved in tooling.
Anyway this issue is about getting TypeScript esm support to work in parcel, not about a discussion what TypeScript deems right.
@devongovett @mischnic Can you tip me off where to look exactly for the resolution stuff? Maybe I can submit a PR then. Looks like https://github.com/parcel-bundler/parcel/blob/699f0b24c38eabcdad0960c62c03bd2f2902b19e/packages/core/package-manager/src/NodeResolver.js is responsible for the Node stuff. However I'm unsure where to look for the .js not found stuff.
Or would this be solved in swc?
No, it's in
https://github.com/parcel-bundler/parcel/blob/699f0b24c38eabcdad0960c62c03bd2f2902b19e/packages/resolvers/default/src/DefaultResolver.js#L18-L41 which calls this class https://github.com/parcel-bundler/parcel/blob/699f0b24c38eabcdad0960c62c03bd2f2902b19e/packages/utils/node-resolver-core/src/NodeResolver.js#L90
Ah I see.
https://github.com/parcel-bundler/parcel/blob/699f0b24c38eabcdad0960c62c03bd2f2902b19e/packages/resolvers/default/src/DefaultResolver.js#L34-L41
would basically need to return the .ts file, when no .js is present. Thank you for the hint.
Any update on this issue?
Another note on the reasoning for this on the TypeScript repo is at TypeScript#27481.
This may be fixed in the future by TypeScript#37582 to allow .ts
import paths, however it has been two years since that issue was opened and no progress as of yet has been made. Some of the latest comments seem to be good signs, however.
I think the best approach here is to try to accommodate for this now rather than wait for TypeScript developers to add a flag or configuration option, which could take a long time.
Maybe Parcel could add a config option for resolving .js
to .ts
when importing from a .ts
file if you don't want it to be the default behavior, although you don't really have a choice if you want to use ESM with TypeScript as of now.
I've tried looking through NodeResolver.js but editing the code personally is still a bit daunting for me and I can't figure out where to start or what to modify.
I implemented a parcel plugin for [dirty] solving that problem
You can check it here https://github.com/LiberQuack/parcel-resolver-fix-ts-esm-shit
I moved on to esbuild. Esbuild just works with esm and is really fast.
Iβm having the same issue. @LiberQuackβs plugin lets me work around it. Thanks!
Current situation with Parcel v2 is
We need to import files using .js extension despite file is index.ts
, otherwise TypeScript is complaining:
But if we change it to .js extension, then Parcel won't be able to build the project:
How can we solve this with Parcel v2 currently? @devongovett @mischnic
I have developed a plugin which allows you to use .jsx / .js extensions inside a TypeScript module file. It should also work with all other Parcel specific things (like tilde, absolute chars etc):
I've made a tiny simple Vanilla (no dependencies) browser app in TypeScript that already runs fine in the browser after compiling TypeScript (to ESM modules). I use ".js" extensions for all TS module imports according to TypeScript documentation.
I wanted to add a bundler for minification etc, so I decided to try out Parcel for the first time. When I found that Parcel is not capable of bundling ESM compliant TypeScript source code, I gave up.
@b8kkyn parcel-resolver-typescript-esm did not work according to documentation (with the current version of parcel). For Webpack I use resolve-typescript-plugin which works fine.
I find it a bit strange that native support for ESM compliant TypeScript code is not given priority for bundlers.
Resolving .js
to .ts
has been supported since Parcel v2.9.0 when we rewrote the resolver. https://github.com/parcel-bundler/parcel/pull/8807
@devongovett I have the issue, latest Parcel and everything (yarn outdated
100% green):
We can see that Parcel is aware of the correct file ./consts.ts
Is there any special configuration to enable?
My config:
{
"extends": "@parcel/config-default",
"resolvers": [
"@parcel/resolver-glob",
"..."
]
}
Edit: I made a trivial, 5 lines resolver plugin that remaps .js to .ts and it works π