Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow imports with .js extension #465

Closed
cyrilletuzi opened this issue Feb 10, 2017 · 29 comments
Closed

Allow imports with .js extension #465

cyrilletuzi opened this issue Feb 10, 2017 · 29 comments

Comments

@cyrilletuzi
Copy link

Since TS 2.0, it's possible to :
import { Something } from './something.js';

Module identifiers allow for .js extension

Before TypeScript 2.0, a module identifier was always assumed to be extension-less; for instance, given an import as import d from "./moduleA.js", the compiler looked up the definition of "moduleA.js" in ./moduleA.js.ts or ./moduleA.js.d.ts. This made it hard to use bundling/loading tools like SystemJS that expect URI's in their module identifier.

With TypeScript 2.0, the compiler will look up definition of "moduleA.js" in ./moduleA.ts or ./moduleA.d.ts.

But using this possibility with ts-loader results in error :
Module not found: Error: Can't resolve './something.js'

It's important to be compatible with ES6 modules, which require the .js extension, as <script type="module"></script> is now in test in all browsers, and as for now TS doesn't provide an option to add the .js extension in transpiled files (see microsoft/TypeScript#13422).

It's possible in rollup/rollup-plugin-typescript so I assume it's possible with ts-loader too.

@johnnyreilly
Copy link
Member

Just include js in your extensions:

  resolve: {
    extensions: ['.ts', '.tsx', '.js']
  }

https://github.com/TypeStrong/ts-loader/blob/master/examples/webpack2-gulp-react-flux-babel-karma/webpack.config.js#L62

@cyrilletuzi
Copy link
Author

I defined the extensions, and it's not working. My config :

module.exports = {
    entry: './test.ts',
    output: {
        path: '.',
        filename: 'bundle.js'
    },
    resolve: {
        extensions: ['.ts', '.js']
    },
    module: {
        rules: [
            { test: /\.ts$/, loader: 'ts-loader' }
        ]
    }
}; 

@cyrilletuzi
Copy link
Author

It's a very simple test project :

export class MyClass {
    firstName;
    constructor() {
        this.firstName = 'hello';
    }
}
import { MyClass } from './myclass.js';
new MyClass();

@johnnyreilly
Copy link
Member

You need to register a loader for your js; something like this:

{
      test: /\.js$/,
      exclude: /node_modules/,
      use: [
        {
          loader: 'babel-loader',
          options: babelOptions
        }
      ]
    }

See here: https://github.com/TypeStrong/ts-loader/blob/master/examples/webpack2-gulp-react-flux-babel-karma/webpack.config.js#L49

@cyrilletuzi
Copy link
Author

Why should I use another transpiler/loader ? My code is in TypeScript and TypeScript itself know how to resolve this :
import { Something } from './something.js';
into :
import { Something } from './something.ts';

A ts-loader should follow TS features.

@cyrilletuzi
Copy link
Author

Maybe it wasn't clear, but something.js does not exist. The real file is something.ts.

@johnnyreilly
Copy link
Member

If I was you then I would do this:

import { Something } from './something';

i.e. go extensionless. That's pretty much how webpack is setup to work, TypeScript is happy with that and ts-loader works with that.

@cyrilletuzi
Copy link
Author

cyrilletuzi commented Feb 10, 2017

Yes, but it's not ES6 compatible. So if I do (after classic dev transpilation with tsc) :

<script type="module" src="test.js"></script>

Then the import of something.js fails as extension-less imports are not ES6 compliant.

@johnnyreilly
Copy link
Member

If you're using webpack, you're using webpack.

I think what you're suggesting is not related to webpack + TypeScript usage. I'm not sure this is the right forum for you. What you're asking for is not webpack related as far as I can tell.

@cyrilletuzi
Copy link
Author

So you suggest I bundle my app with webpack each time I do a little modification during development ? I'm sure you don't do this either, as it can take a few minutes with real apps.

My code need to be the same for dev and prod. So now, I have to choose between being able to use webpack for prod (if I use extension-less imports), or being able to load my scripts in development with the new native loader <script type="module"> (if I use imports with extension). But can't do both.

But that's not even the point. It's a TypeScript feature since 2.0. A ts-loader should follow TypeScript features.

@johnnyreilly
Copy link
Member

johnnyreilly commented Feb 10, 2017

So you suggest I bundle my app with webpack each time I do a little modification during development ? I'm sure you don't do this either, as it can take a few minutes with real apps.

Yup - in watch mode that's exactly what I do. After the initial compilation it takes a matter of seconds to recompile for the app I'm working on at present.

My code need to be the same for dev and prod.

That seems very strange indeed. Typically you run in debug with full sourcemaps and no minification, then in production you run without sourcemaps and with minification.

But that's not even the point. It's a TypeScript feature since 2.0. A ts-loader should follow TypeScript features.

If you mean allowJs then ts-loader does support this.

being able to load my scripts in development with the new native loader <script type="module">

If this is your huckleberry then why do you want to use webpack at all?

@cyrilletuzi
Copy link
Author

This issue has nothing to do with the allowJs option (which just allows to transpile native JavaScript files from ES6 to ES5). But I can't explain more, code has been reproduced, TypeScript doc too, so I'll just quit. ;)

@jbrantly
Copy link
Member

See https://webpack.js.org/configuration/resolve/#resolve-extensions for more information on this. In short, you need to add "*" to your resolve.extensions.

@cyrilletuzi
Copy link
Author

Not working either.

@jbrantly
Copy link
Member

I missed the part about TS automagically changing the extension in it's lookup from .js to .ts. The issue here is that webpack doesn't have the same magic. You would need to somehow configure the same change in webpack's module resolver. That kinda/sorta might be out of scope for ts-loader; not sure. You might be able to accomplish this with NormalModuleReplacementPlugin but not positive.

@cyrilletuzi
Copy link
Author

Can't ts-loader let typescript do this ?

@jbrantly
Copy link
Member

You're conflating two separate systems. TypeScript is one system that understands modules and implements its own module resolution algorithm and rules. webpack is a completely different system with its own module resolution algorithm and rules. ts-loader sits in-between the two and effectively passes source back back and forth between them. Just because TypeScript knows that when you say someFile.js you actually mean someFile.ts doesn't mean that webpack knows that. Also TypeScript doesn't rewrite the source in that case, so the transpiled source that webpack is receiving from TypeScript still has someFile.js in it.

ts-loader has quite a bit of control over the TypeScript system and is able to change the TypeScript module resolution algorithm to roughly match that of webpacks. The inverse is not necessarily true.

@jbrantly
Copy link
Member

Also, in case it's not clear, I believe the error Module not found: Error: Can't resolve './something.js' is a webpack error, not a TypeScript error. In other words TypeScript is working fine here. It's the webpack part that's failing because it doesn't know how to resolve .js to .ts in this special case.

yushijinhun added a commit to bs-community/skinview-utils that referenced this issue Jan 26, 2020
@asofmosq
Copy link

Hello, I'm using webpack 4.32.2, ts-loader 8.0.7, typescript 4.0.5 and I'm curious if it's at all possible to do an import with .js extension.

@trusktr
Copy link

trusktr commented Feb 22, 2021

If you're using webpack, you're using webpack.

Here is a really valid use case:

A lib author wants to write ESM-compatible TS code and output both ESM JS and a UMD bundle for legacy reasons or <script src=""> users.

In TypeScript, they use .js in import specifiers to output valid ESM code that requires no build tool for consumption (Browser ESM).

The lib author publishes the ESM files, and those work great for people that use Node ESM, Webpack 5+, or native Browser ESM.

The author also wants to release a single bundle for users that prefer <script src="">. Maybe they have to support old browsers. This is also a common pattern that people use on codepen (and Browser ESM didn't even fully work on codepen until literally just days ago so the pattern is widely used on codepen).

However, the lib author will not be able to build the desired bundle for the <script src users because of this problem with webpack+ts-loader.

It seems it is ts-loader's job to get this right. Otherwise whose role is it?


A current workaround is to first build output ESM files with TypeScript's tsc compiler, then make the bundle from the output .js files without use of ts-loader.

@breadadams
Copy link

Just came across this issue when looking up a problem of ts-loader not picking up plain javascript files, even though the .js extension was in the resolve.extensions array. If it's worth it to anyone, the solution (so far) seems to have been the following:

module.exports = (env) => ({
  resolve: {
    extensions: [".tsx", ".ts", ".js"]
  },
  module: {
    rules: [
      {
        test: /\.(js|tsx?)$/,
        use: "ts-loader",
        exclude: /node_modules/
      }
    ]
  },
  // other config...
});

@jasonswearingen
Copy link

Just came across this issue when looking up a problem of ts-loader not picking up plain javascript files, even though the .js extension was in the resolve.extensions array. If it's worth it to anyone, the solution (so far) seems to have been the following:

That doesn't work. same problems. Your "other config..." must be doing something important.....

My solution is to just have webpack work on the normal typescript output. ts-loader is never going to fix this bug as it was instant-closed the day it was filed, 4 years ago.

@jeswin
Copy link

jeswin commented Aug 21, 2021

Closed? Many TypeScript projects targeting ESM will face this issue.

FWIW, popular projects which have switched to ESM+TS are already using this syntax. eg: https://github.com/sindresorhus/got/blob/main/source/core/index.ts

@CleyFaye
Copy link

This issue by itself render ts-loader unusable by itself on any recent codebase, as the proposed solution is either "write code that works with webpack and nowhere else" or "implement an extra layer of transformation in webpack to finish the job".

For other finding this (unfortunately) closed issues, the project https://github.com/softwareventures/resolve-typescript-plugin provides the extra step required to make this work.

@johnnyreilly
Copy link
Member

the project https://github.com/softwareventures/resolve-typescript-plugin provides the extra step required to make this work.

This is a great shout. If someone would like to submit a docs PR to suggest that people with this need use @djcsdy plugin, we'd happily take it.

@joelachance
Copy link

It's unfortunate this issue is being ignored by this community-- ease of use is important to developers, especially when updating projects, and responses like the ones above are unfortunate this isn't being resolved in this loader. The community here should re-consider this ticket, it's absolutely valid and this is the correct forum.

@johnnyreilly
Copy link
Member

The community here should re-consider this ticket, it's absolutely valid and this is the correct forum.

@joelachance you're part of this community. If you'd like to work on this it would be very welcome ❤️

@djcsdy
Copy link

djcsdy commented Aug 25, 2022

@joelachance Webpack itself provides a solution to this problem now.

module.exports = {
  //...
  resolve: {
    extensionAlias: {
      '.js': ['.ts', '.js'],
      '.mjs': ['.mts', '.mjs'],
    },
  },
};

See: https://webpack.js.org/configuration/resolve/#resolveextensionalias

I also recommend:

module.exports = {
  //...
  resolve: {
    enforceExtension: true,
  },
};

to enforce use of file extensions for full compatibility with ES Modules.

If for some reason you can't use a recent version of Webpack that includes these options, use https://github.com/softwareventures/resolve-typescript-plugin

Full disclosure: I am the author of the above plugin.

@joelachance
Copy link

Thank you for the quick responses! @djcsdy @johnnyreilly

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests