Skip to content

ESM Imports #1110

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

Closed
EisenbergEffect opened this issue May 16, 2020 · 23 comments
Closed

ESM Imports #1110

EisenbergEffect opened this issue May 16, 2020 · 23 comments
Labels

Comments

@EisenbergEffect
Copy link

EisenbergEffect commented May 16, 2020

Expected Behaviour

Webpack is able to locate a .ts file and successfully build.

Actual Behaviour

ERROR in ./src/main.ts
Module not found: Error: Can't resolve './log.js'

Steps to Reproduce the Problem

You need two files, a main.ts and a log.ts.

log.ts

export function log() {
  console.log('test');
}

main.ts

import { log } from './log.js'

log();

I'll provide the webpack config below. The above two files produce the error described. Nothing else is needed. Your first thought might be "but you just need to remove the .js file extension to get it to work". However, that isn't possible. To explain, we're trying to emit standards compliant ES Modules that can be run directly in the browser but that can also be bundled with webpack. For the code to run in a browser, the import statements must have a .js file extension. TypeScript lets us put a .js file extension in the imports and will build it correctly on its own. However, when used with webpack, the build fails as described.

I don't know if this is a problem deep in the bowels of Webpack or if it's an issue with ts-loader. Sadly, it's a ship blocker for our library ;(

We'd appreciate help on this very much. I can't find any blog post, docs, etc. about this issue and no one I've talked to has a solution.

Thanks!

webpack.config.js

const path = require('path');

module.exports = function(env, { mode }) {
  const production = mode === 'production';
  return {
    mode: production ? 'production' : 'development',
    devtool: production ? 'source-maps' : 'inline-source-map',
    entry: {
      app: ['./src/main.ts']
    },
    output: {
      filename: 'bundle.js'
    },
    resolve: {
      extensions: ['.ts', '.js'],
      modules: ['src', 'node_modules']
    },
    devServer: {
      port: 9000,
      historyApiFallback: true,
      writeToDisk: true,
      open: !process.env.CI,
      lazy: false
    },
    module: {
      rules: [
        {
          test: /\.ts$/i,
          use: [
            {
              loader: 'ts-loader'
            }
          ],
          exclude: /node_modules/
        }
      ]
    }
  }
}
@EisenbergEffect EisenbergEffect changed the title ESM Module Imports ESM Imports May 16, 2020
@johnnyreilly
Copy link
Member

Hello Rob!

Thanks for the detailed report. That's always appreciated. First and foremost, this is very much something that we'd like to support in ts-loader. If it's possible - that's to be discovered I guess.

I don't have the time to look into this myself at present. (COVID-19 has sent my working life into overdrive and I'm doing much less OSS than I'd like.). However I'm certainly game to try and assist if you want to look into this. We very much accept PRs 🥰

Potential causes as I see them:

  • webpack - does webpack support importing standards compliant ES Modules with a suffix? I suspect the answer is yes but there may be quirks. Can you advise @sokra?

  • TypeScript - given you can build with TypeScript directly maybe this is not a problem. @andrewbranch do you have any insight on this?

  • ts-loader - may not be using all of the relevant APIs in webpack / TypeScript correctly

As I say, I'm very time poor right now but I'll do what I can in terms of providing advice and reviewing any PRs. More than that I cannot offer I'm afraid. I hope it helps 🌻🥰

@EisenbergEffect
Copy link
Author

Hey @johnnyreilly! Thanks for the quick response. I understand the situation, so no worries. I've got a workaround for my project that I think gets us to a pretty good place. It would be cool to get this working eventually, as I know there are others that are trying to target native modules and things just don't quite work right all the time. It may be much broader than just ts-loader and might be better to wait for TS support directly at some point, but in my quest to probe all possibilities, I thought I'd drop this issue here.

@sokra
Copy link
Contributor

sokra commented May 16, 2020

You can bundle standard esm modules, but here you only have standard esm imports, but not the standard esm js files matching them. They are probably only generated as compile step for publishing.

So either consume the compiled js files via webpack.
Or create a custom resolver plugin which tries to resolve to a .ts file in addition to the .js file. That should be pretty easy, maybe there already exists one.
Or omit the .js extension and add it after the TS compile step for esm publishing.

@andrewbranch
Copy link
Contributor

Or create a custom resolver plugin which tries to resolve to a .ts file in addition to the .js file. That should be pretty easy, maybe there already exists one.

As @EisenbergEffect pointed out, this is how TypeScript’s module resolution works, so this sounds like the best solution. Could ts-loader itself contribute this behavior, or would it need to be an actual plugin? I know ts-loader finds ways to hook into some of the plugin APIs (like compiler hooks) already.

@johnnyreilly
Copy link
Member

Could ts-loader itself contribute this behavior, or would it need to be an actual plugin?

Thanks for commenting @andrewbranch! On where this functionality might live I guess it's a choice and I don't feel super strongly either way. I figure that how often this functionality would be used is a good rationale.

Obviously right now this is super niche. But if this is the future of the web™️ then it being out-of-the-box functionality entirely makes sense.

@andrewbranch
Copy link
Contributor

My question is more whether it’s possible to affect Webpack’s module resolution from a loader. It’s definitely something we’d want since ts-loader aims to mirror tsc’s behavior as closely as possible.

@johnnyreilly
Copy link
Member

Yeah - I'm not sure. @sokra can you advise?

@sokra
Copy link
Contributor

sokra commented May 19, 2020

Loaders shouldn't do that, but you can put it next to the ts-loader in the module.rules item.

test: /\.ts$/,
use: "ts-loader",
resolve: { plugins: [
  new TsResolvePlugin()
] }

@stale
Copy link

stale bot commented Jul 18, 2020

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 stale bot added the wontfix label Jul 18, 2020
@stale
Copy link

stale bot commented Jul 25, 2020

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

@stale stale bot closed this as completed Jul 25, 2020
@nicojs
Copy link

nicojs commented Feb 16, 2021

I guess this is still an issue? @sokra does the TsResolvePlugin already exist?

@gaspard
Copy link

gaspard commented Mar 22, 2021

Hmmm... This is still an issue I think.

TypeScript resolves foo.js as foo.ts (or foo.tsz) fine but WebPack breaks.

This is not so good because it implies that we need to handle different syntax and import styles for ESM modules included in the app and the app itself...

Maybe the solution is to build the app as an ESM module and just import 'app' inside the the app loader. Not sure what this does to optimisations though.

@djcsdy
Copy link

djcsdy commented Apr 23, 2021

I also encountered this issue trying to set up webpack to resolve modules in accordance with ESM. My goal was to have webpack resolve modules as close as possible to node, ts-node, and browsers, for consistency and to reduce developer confusion. Setting fullySpecified: true got me 90% of the way there, but TypeScript was a sticking point as described here.

I've developed a simple webpack plugin (actually an enhanced-resolve plugin, but enhanced-resolve is part of webpack) to try to solve the problem. I'm not sure of my approach and I'm seeking community feedback, so if anyone interested in this problem, and especially anyone involved with webpack internals, could take a look and let me know what they think I'd really appreciate it. See this issue for submitting feedback.

To use the plugin, install resolve-typescript-plugin from npm and then configure webpack something like this:

const ResolveTypeScriptPlugin = require("resolve-typescript-plugin").default;

exports = {
    module: {
        rules: [
            {
                test: /\.tsx?$/,
                use: "ts-loader"
            }
        ]
    },
    resolve: {
        fullySpecfied: true,
        plugins: [new ResolveTypeScriptPlugin()]
    }
};

I hope it's ok to mention this here. My goal is to draw attention to my attempt at solving this problem to anyone who is interested. If you have any feedback for me about the plugin please respond in the plugin repository and not here, to avoid excess noise for the developers of ts-loader.

EDIT: Note that the actual code in the GitHub repository for the plugin is all in the alpha branch.

@johnnyreilly
Copy link
Member

Thanks for sharing - it looks like the https://github.com/softwareventures/resolve-typescript-plugin link doesn't work?

@djcsdy
Copy link

djcsdy commented Apr 23, 2021

D'oh, sorry about that. I set it private by mistake. The link should work now.

@johnnyreilly
Copy link
Member

I think there might still be something wrong...

https://github.com/softwareventures/resolve-typescript-plugin/blob/main/index.ts

There's no code in index.ts

@djcsdy
Copy link

djcsdy commented Apr 25, 2021

@johnnyreilly The code is in the alpha branch. Sorry for the confusion.

Edit: I've set the primary branch to alpha for now to make it more obvious where the code is.

@johnnyreilly
Copy link
Member

Have you considered a side career in the word search and crossword industry? 😉

@djcsdy
Copy link

djcsdy commented Jul 7, 2021

Heads up there is a proper release of the above plugin now, and the code is all on the main branch where you would expect to find it :-).

@johnnyreilly
Copy link
Member

@sokra you might be interested by https://github.com/softwareventures/resolve-typescript-plugin ?

@djcsdy
Copy link

djcsdy commented Aug 25, 2022

Heads up to anyone facing this issue that Webpack itself provides a proper solution 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

@johnnyreilly
Copy link
Member

Thanks for this @djcsdy - do you fancy submitting a README.md docs PR to include these points?

@HonmaMeikodesu
Copy link

Heads up to anyone facing this issue that Webpack itself provides a proper solution 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

Another 2 years has passed and this issue still seems to be a thing. This solution is perfect yet somehow 'hacky', I suppose. ESM works sweet both in Webpack and Typescript separately, but when it combines, it cracks unexpectedly. what's grimmer to think about is that this issue will remain what it be for a period of time since it's hard to tell who should polyfill for this, nether loader or webpack individually

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

No branches or pull requests

8 participants