I’ve been working with Typescript for some time. We use localization in our frontend web components and we have our own implementation for it.
So, we have a translations folder where we have all the locale files with translations as key value pairs.
We use the locale files to load the translation files based on the language selected by the user from the browser.
And we load the locales like this with a dynamic import:
{
'component': locale => import(`./translations/${locale}.ts`),
},
While everything works fine when we use the extension .js
but when we use .ts
, tsc wasn’t able to transpile this part of the code.
I would expect the build to be something like this:
{
'component': locale => import(`./translations/${locale}.js`),
},
Does anybody has a clue why that is?
It's not possible to use .ts
extension and have the Typescript compiler change that to .js
, as the Typescript compiler itself never modifies module specifiers (that is the path of a module). So, you'll have to provide a .js
extension if you're using tsc
, else, if you really need to use it, you'll need to have some bundler like Webpack or Vite bundle your code, and then transpile it to javascript.
ESM (ECMAScript Modules) available in Node version greater than 12.0.0
, requires one to specify the extension while importing an ESM module. The same is the case if you use them in a browser. So, we need to add a .js
extension.
The Typescript documentation for ESM modules itself specifies to make use of a .js
extension to get correct runtime file as a rule for using ESM imports. To quote from the Typescript ESM docs -
relative import paths need full extensions (e.g we have to write import
./foo.js
instead of import./foo
)
And the reason it is the case, is because Typescript compiler never changes the module specifier. To quote from the Typescript ESM docs -
When a
.ts
file is compiled as an ES module, ECMAScript import/export syntax is left alone in the.js
output
Which is the reason why it's not changed when you compile it.
There have been many issues created on GitHub for problems that arise out of this and asking for support -
Until there was this proposal and the option moduleResolution
was updated with a new option bundler
with this PR, which explicitly specifies -
Who should use this mode?
- ✅ Application authors who use a bundler on their TS or JS files before a runtime consumes that bundle
- ✅ Application authors who run in Bun
- ✅ Library authors who use a bundler to deploy a UMD bundle
- ⚠️ Library authors who use a tool like Rollup to deploy multiple builds in different module formats—defer to advice from your build tool
- 🚫 Anyone intending to produce modules with tsc that will run in Node or the browser without further bundling or processing
- 🚫 Anyone intending to produce modules with tsc that will run in Deno without further bundling or processing
Why? Because Typescript compiler itself never modifies the module specifiers, and one needs to be dependent on an external bundler to do that for you.
I'd say just use the .js
extension, unless you have a very good reason to not to use it (which I doubt there would be one). Otherwise, add the moduleResolution
with bundler
option and noEmit
with true
values in your tsconfig.json
, along with allowImportingTsExtensions
option to use the .ts
extension and configure a bundler to use with it.