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

Module not found: Fully Specified ESM Imports (with .js extension) in TypeScript #41961

Closed
1 task done
karlhorky opened this issue Oct 27, 2022 · 8 comments · Fixed by #44177
Closed
1 task done

Module not found: Fully Specified ESM Imports (with .js extension) in TypeScript #41961

karlhorky opened this issue Oct 27, 2022 · 8 comments · Fixed by #44177
Labels
bug Issue was opened via the bug report template.

Comments

@karlhorky
Copy link
Contributor

karlhorky commented Oct 27, 2022

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

 
    Operating System:
      Platform: darwin
      Arch: arm64
      Version: Darwin Kernel Version 22.1.0: Sun Oct  9 20:14:30 PDT 2022; root:xnu-8792.41.9~2/RELEASE_ARM64_T8103
    Binaries:
      Node: 18.11.0
      npm: 8.19.2
      Yarn: 1.22.19
      pnpm: 3.8.1
    Relevant packages:
      next: 13.0.0
      eslint-config-next: N/A
      react: 18.2.0
      react-dom: 18.2.0

What browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

Describe the Bug

Hi there, first of all, thanks so much for the hard work on Next.js 🙌 13 is looking great!

Importing a file using the fully-specified ESM-style imports with .js leads to a Module not found error:

pages/index.tsx

import Component from "../components/Component.js";

/** Add your relevant code here for the issue to reproduce */
export default function Home() {
  return <Component />
}

components/Component.tsx:

export default function Component() {
  return <div>asdf</div>;
}

Error message:

wait  - compiling /_error (client and server)...
error - ./pages/index.tsx:1:0
Module not found: Can't resolve '../components/Component.js'
> 1 | import Component from "../components/Component.js";
  2 | 
  3 | /** Add your relevant code here for the issue to reproduce */
  4 | export default function Home() {

This does not match the behavior of the TypeScript compiler, which will resolve these files. See microsoft/TypeScript#41887 (comment) for more details.

experimental.fullySpecified config option

This also happens while using the experimental.fullySpecified: true option in the Next.js config:

next.config.mjs

/** @type {import('next').NextConfig} */
const config = {
  experimental: {
    fullySpecified: true,
  },
  reactStrictMode: true,
}
export default config;

Workaround 1: resolve.extensionAlias configuration option

webpack does have a resolve.extensionAlias configuration as of 5.74.0:

/** @type {import('next').NextConfig} */
const config = {
  reactStrictMode: true,
  webpack: (
    webpackConfig,
    { webpack },
  ) => {
    webpackConfig.resolve.extensionAlias = {
      '.js': ['.ts', '.tsx', '.js', '.jsx'],
      '.mjs': ['.mts', '.mjs'],
      '.cjs': ['.cts', '.cjs'],
    };
    return webpackConfig;
  },
};

export default config;

But this should be zero-config.

Related issue in webpack: webpack/webpack#13252

Workaround 2: webpack.NormalModuleReplacementPlugin configuration option

Configure the NormalModuleReplacementPlugin inside your webpack config

next.config.mjs

/** @type {import('next').NextConfig} */
const config = {
  reactStrictMode: true,
  webpack: (
    webpackConfig,
    { webpack },
  ) => {
    webpackConfig.plugins.push(
      new webpack.NormalModuleReplacementPlugin(new RegExp(/\.js$/), function (
        /** @type {{ request: string }} */
        resource,
      ) {
        resource.request = resource.request.replace('.js', '');
      }),
    );
    return webpackConfig;
  },
};

export default config;

Also, this should be zero-config.

Expected Behavior

Fully-specified ESM-style imports with .js extensions should resolve to the respective TypeScript files out of the box (as TSC does it), without any further webpack / Turbopack configuration necessary.

Link to reproduction

https://stackblitz.com/edit/vercel-next-js-y6faeb?file=components%2FComponent.tsx,pages%2Findex.tsx,next.config.js

To Reproduce

  1. Use TypeScript
  2. Import another TypeScript / TSX file with fully-specified .js
  3. Observe error message
@remcohaszing
Copy link
Contributor

Workaround 2 works, but it has a typo.

  webpackConfig.resolve.extensionAlias = {
    '.js': ['.ts', '.tsx', '.js'],
-   '.mjs': ['.mts', '.js'],
+   '.mjs': ['.mts', '.mjs'],
    '.cjs': ['.cts', '.cjs'],
  };

I think .jsx should be supported too.

webpackConfig.resolve.extensionAlias = {
  '.js': ['.ts', '.tsx', '.jsx', '.js'],
  '.mjs': ['.mts', '.mjs'],
  '.cjs': ['.cts', '.cjs'],
};

@karlhorky
Copy link
Contributor Author

Edited, thanks. I did mention that Workaround 2 worked also in the description above. It would still be nice if this was zero-config for people though 👍

@remcohaszing
Copy link
Contributor

Edited, thanks. I did mention that Workaround 2 worked also in the description above.

Yes, but I believe this fixes the error you mentioned. :)

It would still be nice if this was zero-config for people though 👍

Definitely!

@karlhorky
Copy link
Contributor Author

Yes, but I believe this fixes the error you mentioned. :)

Indeed, missed this - it is working! I've updated my description above.

@jaydenseric
Copy link
Contributor

With this next.config.mjs:

/** @type {import('next').NextConfig} */
export default {
  webpack: (webpackConfig, { webpack }) => {
    webpackConfig.resolve.extensionAlias = {
      ".js": [".ts", ".tsx", ".js", ".jsx"],
      ".mjs": [".mts", ".mjs"],
      ".cjs": [".cts", ".cjs"],
    };
    return webpackConfig;
  },
};

You run into a build error if you deep import .mjs modules from dependencies in your Next.js project like this:

import withGraphQLReact from "next-graphql-react/withGraphQLReact.mjs";

> dev
> next dev

ready - started server on 0.0.0.0:3000, url: http://localhost:3000
info  - Loaded env from [redacted]/.env
error - ./pages/_app.tsx:3:0
Module not found: Package path ./withGraphQLReact.mts is not exported from package [redacted]/node_modules/next-graphql-react (see exports field in [redacted]/node_modules/next-graphql-react/package.json)
  1 | import type { AppProps } from "next/app.js";
  2 | import nextHead from "next/head.js";
> 3 | import withGraphQLReact from "next-graphql-react/withGraphQLReact.mjs";
  4 | 
  5 | const { default: Head } = nextHead;
  6 | 

https://nextjs.org/docs/messages/module-not-found

Can the workaround be tweaked somehow to account for this?

@karlhorky
Copy link
Contributor Author

Not sure exactly why that's happening, but you could try reporting that over in webpack/webpack#13252, since that's the related issue in the webpack repo.

If you find a solution, let us know and I'll update my post above.

Or you could try Workaround 2, which uses webpack.NormalModuleReplacementPlugin instead.

@jaydenseric
Copy link
Contributor

This workaround appears to work:

// @ts-check

import { existsSync } from "node:fs";
import { join, parse, resolve } from "node:path";

/** @type {import("next").NextConfig} */
export default {
  typescript: {
    // TODO: Investigate why TypeScript via Next.js builds has ESM/CJS default
    // import interop errors when vanilla TypeScript via VS Code and the package
    // script `type-check` is ok.
    ignoreBuildErrors: true,
  },
  webpack: (webpackConfig, { webpack }) => {
    // TODO: Remove this config once this Next.js issue that `.tsx` files can’t
    // be imported using the `.js` file extension is fixed:
    // https://github.com/vercel/next.js/issues/41961
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
    webpackConfig.plugins.push(
      // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
      new webpack.NormalModuleReplacementPlugin(/\.js$/, function (
        /** @type {{ context: string, request: string }} */
        resource
      ) {
        // Skip a non relative import (e.g. a bare import specifier).
        if (resource.request.startsWith(".")) {
          const path = resolve(resource.context, resource.request);

          if (
            // Skip the relative import if it reaches into `node_modules`.
            !path.includes("node_modules") &&
            !existsSync(path)
          ) {
            const { dir, name } = parse(path);
            const extensionlessPath = join(dir, name);

            for (const fallbackExtension of [".tsx", ".ts", ".js"]) {
              if (existsSync(extensionlessPath + fallbackExtension)) {
                resource.request = resource.request.replace(
                  /\.js$/,
                  fallbackExtension
                );
                break;
              }
            }
          }
        }
      })
    );

    // eslint-disable-next-line @typescript-eslint/no-unsafe-return
    return webpackConfig;
  },
};

@kodiakhq kodiakhq bot closed this as completed in #44177 Jan 28, 2023
kodiakhq bot pushed a commit that referenced this issue Jan 28, 2023
## ESM: support module option for tsconfig.json 

- fixes #37525
- fixes #41961

With [TypeScript 4.7 providing ECMAScript Module Support](https://devblogs.microsoft.com/typescript/announcing-typescript-4-7/#ecmascript-module-support-in-node-js), we can now set this in our tsconfig.json file for the [module](https://www.typescriptlang.org/tsconfig#module) option.

Webpack added "extensionAlias" to solve importing ts files with .js extension -> webpack/enhanced-resolve#351



Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 28, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue was opened via the bug report template.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants