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

next/image not working when using in esm projects #54777

Closed
1 task done
yanush opened this issue Aug 30, 2023 · 17 comments · Fixed by #59852
Closed
1 task done

next/image not working when using in esm projects #54777

yanush opened this issue Aug 30, 2023 · 17 comments · Fixed by #59852
Labels
bug Issue was opened via the bug report template. linear: next Confirmed issue that is tracked by the Next.js team. locked Module Resolution Module resolution (CJS / ESM, module resolving)

Comments

@yanush
Copy link

yanush commented Aug 30, 2023

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.5.0: Thu Jun  8 22:22:20 PDT 2023; root:xnu-8796.121.3~7/RELEASE_ARM64_T6000
    Binaries:
      Node: 18.17.1
      npm: 9.6.7
      Yarn: N/A
      pnpm: N/A
    Relevant Packages:
      next: 13.4.20-canary.12
      eslint-config-next: 13.4.19
      react: 18.2.0
      react-dom: 18.2.0
      typescript: 5.2.2
    Next.js Config:
      output: N/A

Which area(s) of Next.js are affected? (leave empty if unsure)

No response

Link to the code that reproduces this issue or a replay of the bug

https://github.com/yanush/nextjs_issue_20230830

To Reproduce

  • Create a new nextjs app:
    npx create-next-app@latest

Using the following options:

  • TypeScript: No

  • EsLint: Yes

  • Tailwind CSS: No

  • src/ directory: No

  • App Router: Yes

  • import Alias: No

  • Edit package.json and add:
    "type": "module",

Edit next.config.js file to match esm:

/** @type {import('next').NextConfig} */
const nextConfig = {}

export default nextConfig;
  • try to run project using npm run dev

Describe the Bug

I get these errors:
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.

Check your code at page.js:19.
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: object.

The errors referring to lines that contain Image Tags

Notice:

  • Using the legacy Image works fine (Changing page.js import to: import Image from 'next/legacy/image')

  • Changing the project to commonjs also seems to solve the issue

Both of these fixes are not ideal for me

Expected Behavior

Should be working fine with the new image and don't throw errors

Which browser are you using? (if relevant)

No response

How are you deploying your application? (if relevant)

No response

NEXT-1774

@yanush yanush added the bug Issue was opened via the bug report template. label Aug 30, 2023
@yanush yanush changed the title next/image not working when using in Esm projects next/image not working when using in esm projects Aug 30, 2023
@devjiwonchoi
Copy link
Contributor

devjiwonchoi commented Sep 1, 2023

type: module is not implemented yet, so for now modify next.config.js as next.config.mjs.
#29935

@yanush
Copy link
Author

yanush commented Sep 2, 2023

It was working just fine in previous versions of next.js 13.
Was there any recent change that made it not to work again?

@devjiwonchoi
Copy link
Contributor

@yanush Which specific version was working?

@yanush
Copy link
Author

yanush commented Sep 5, 2023

@devjiwonchoi version 13.3.4 with experimental appDir true was working fine, but I'm not sure whether this version was using the legacy image by default

@yanush
Copy link
Author

yanush commented Nov 7, 2023

Any updates on this matter?
I hate having to use the legacy image just to keep using esm projects

@karlhorky
Copy link
Contributor

karlhorky commented Nov 13, 2023

Workaround 1: next/dist/client/image-component import path

Import the <Image> component directly from next/dist/client/image-component:

-import Image from 'next/image';
+import { Image } from 'next/dist/client/image-component';

Workaround 2: .default import property

Assign Image to the .default property of the import

-import Image from 'next/image';
+import ImageModule from 'next/image';

+const Image = ImageModule.default;

export default function Page() {
  return <Image ... />

This works because Image is a CommonJS module, and is not completely unwrapped:

console.log('Image', Image);
// Image { unstable_getImgProps: [Getter], default: [Getter] }

Workaround 3: unstable_getImgProps

Use unstable_getImgProps instead of Image:

-import Image from 'next/image';
+import { unstable_getImgProps } from 'next/image';

export default function Page() {
-  return <Image ... />
+  const { props: imgProps } = unstable_getImgProps({
+    src: '/vercel.svg',
+    alt: 'Vercel Logo',
+    width: 100,
+    height: 24,
+    priority: true,
+  });
+  return <img {...imgProps} />

Workaround 4: Switch page to TypeScript

Change page.js to page.tsx - with TypeScript the <Image /> component does not fail.

@karlhorky
Copy link
Contributor

karlhorky commented Nov 13, 2023

So I upgraded the original reproduction to [email protected] (latest canary at time of writing) and can still confirm the bug when using a JavaScript page via page.js:

  1. "type": "module" in package.json
  2. app/page.js (JavaScript, not TypeScript)
  3. Usage of <Image /> from 'next/image'
  4. 💥 Error: Unsupported Server Component type: {...}

Seems like a JS compiler bug?

Updated reproduction PR here:

yanush/nextjs_issue_20230830#1

app/page.js (broken)

Screenshot 2023-11-13 at 17 36 34

app/fixed/page.tsx (fixed) - same code

Screenshot 2023-11-13 at 17 36 47

@karlhorky
Copy link
Contributor

karlhorky commented Nov 13, 2023

Ah interesting, since this unstable_getImgProps (introduced in #51205 by @styfle ) is also being returned, maybe this creates this problem with ESM:

import Image from 'next/image'; // in ESM project with "type": "module" in package.json

console.log('Image', Image);
// Image { unstable_getImgProps: [Getter], default: [Getter] }

@yanush
Copy link
Author

yanush commented Nov 13, 2023

@karlhorky, Thanks for all your suggestions and workaround attempts!

@karlhorky
Copy link
Contributor

karlhorky commented Nov 17, 2023

Just thinking, one way to get compatibility here without a larger CJS -> ESM transition would be to *also* export a named export for these cases.

Then the following would "just work" in ESM with "type": "module" projects:

import { Image } from 'next/image';

To be clear, for users who want to stay with the default export, the following would still work:

import Image from 'next/image';

cc @feedthejim @shuding would the team be open for such a change? (backwards compatible)

cc @remcohaszing because we were talking about lacking ESM support in Next.js and using named exports

@remcohaszing
Copy link
Contributor

While I personally prefer named exports, I also believe there should be only one way to use an export. Adding a named export is a workaround. There is a deeper problem, this issue is just a symptom. Also it affects pretty much all Next.js exports, not just next/image.

A library (defined as something you import, e.g. next), should not rely on esModuleInterop if it’s compiled to CommonJS.

What really needs to happen, is that code like this:

import React from 'react'

export function unstable_getImgProps() {
  // …
}

export interface ImageProps {
  // …
}

export default function Image(props: ImageProps): React.ReactElement {
  // …
}

needs to be transformed to export = syntax and a namespace.

import * as React from 'react'

namespace Image {
  export function unstable_getImgProps() {
    // …
  }

  export interface ImageProps {
    // …
  }

  // This is for strict backwards compatibility. It’s not needed for typical Next.js users.
  export { Image as default }
}

function Image(props: ImageProps): React.ReactElement {
  // …
}

export = Image

Or go a step further and enable verbatimModuleSyntax:

import React = require('react')

namespace Image {
  export function unstable_getImgProps() {
    // …
  }

  export interface ImageProps {
    // …
  }

  // This is for strict backwards compatibility. It’s not needed for typical Next.js users.
  export { Image as default }
}

function Image(props: ImageProps): React.ReactElement {
  // …
}

export = Image

P.s. If Vercel is invested in supporting ESM/TypeScript (which means fixing this), I am for hire to fix this on a freelance contract.

@karlhorky
Copy link
Contributor

karlhorky commented Nov 19, 2023

Would be amazing if full ESM support was fixed in Next.js 👍 Maybe Vercel would sponsor this (considerable) effort

Originally, "type": "module" support was added by @timneutkens in this PR:

Basic support for ESM with TypeScript 'NodeNext' or 'Node16' module resolution added by @loettz here:

But since then, there have been a number of issues, pull requests and discussions showing problems with ESM support (sometimes in combination with the TypeScript 'NodeNext' or 'Node16' module resolution), just a small sampling:

cc @kachkaev @cseas because you have also been active in some issues and discussions

@karlhorky
Copy link
Contributor

karlhorky commented Nov 19, 2023

If this work to make the next package ESM-ready for most cases were to go ahead, I would suggest also including the following tools as part of the verification:

Are The Types Wrong? by @andrewbranch

Screenshot 2023-12-24 at 17 31 38

publint by @bluwy

Screenshot 2023-11-19 at 12 06 51

@karlhorky
Copy link
Contributor

Oh and it would be great if new end to end tests were added to verify continued ESM support as time goes on and new features get added.

Ideas for end to end tests:

  1. A "type": "module" project mixing JavaScript and TypeScript with some commonly-used patterns such as importing next/image, next/link, including Tailwind CSS + PostCSS configuration
  2. Usage of some static analysis tooling on published package artifacts such as the command line packages publint and @arethetypeswrong/cli

@huozhi huozhi added the linear: next Confirmed issue that is tracked by the Next.js team. label Dec 1, 2023
karlhorky added a commit to upleveled/next-js-example-fall-2023-atvie that referenced this issue Dec 21, 2023
karlhorky added a commit to upleveled/next-js-example-fall-2023-atvie that referenced this issue Dec 21, 2023
colbyfayock added a commit to cloudinary-community/next-cloudinary that referenced this issue Dec 22, 2023
# Description

Attempting to fix import issue in Pages router with suggested workaround
for ESM projects:
vercel/next.js#54777 (comment)

<!-- Specify above which issue this fixes by referencing the issue
number (`#<ISSUE_NUMBER>`) or issue URL. -->
<!-- Example: Fixes
https://github.com/colbyfayock/next-cloudinary/issues/<ISSUE_NUMBER> -->

## Type of change

<!-- Please select all options that are applicable. -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to not work as expected)
- [ ] Fix or improve the documentation
- [ ] This change requires a documentation update


# Checklist

<!-- These must all be followed and checked. -->

- [ ] I have followed the contributing guidelines of this project as
mentioned in [CONTRIBUTING.md](/CONTRIBUTING.md)
- [ ] I have created an
[issue](https://github.com/colbyfayock/next-cloudinary/issues) ticket
for this PR
- [ ] I have checked to ensure there aren't other open [Pull
Requests](https://github.com/colbyfayock/next-cloudinary/pulls) for the
same update/change?
- [ ] I have performed a self-review of my own code
- [ ] I have run tests locally to ensure they all pass
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes needed to the documentation
github-actions bot pushed a commit to cloudinary-community/next-cloudinary that referenced this issue Dec 22, 2023
# [5.17.0-beta.1](v5.16.0...v5.17.0-beta.1) (2023-12-22)

### Features

* Updates next/image import to dist path ([#388](#388)) ([59ad1ff](59ad1ff)), closes [/github.com/vercel/next.js/issues/54777#issuecomment-1808505837](https://github.com//github.com/vercel/next.js/issues/54777/issues/issuecomment-1808505837)
huozhi added a commit that referenced this issue Dec 23, 2023
## What

When users specify `"type": "module"` in Next.js app, especially with
`create-next-app`, `Image` component is not working. An error
`Unsupported Server Component type: {...}` is thrown.

## Why

`next/image` API is mixing with a client component as default export and
a named export as server component. But the entry file of the API is
still CJS file, which will import the module as the object. So you'll
get `{ default, unstable_getImageProps }` when you do `import Image from
'next/image'` instead of `Image` component itself, where the CJS module
load all the exports as an object. This is expected behavior for ESM but
breaks the usage.

It only errors when you're using js extensions, if you're using
typescript, it still works. If you're using turbopack, it works in dev
mode.

This is also because webpack can't analyze the exports from CJS module
of that `next/image` entry file. Usually we can assign the default
export to the module itself, then attach other named exports onto it, so
the default export equals the `module.exports` itself. But for
`next/image` since the default export is an client component, doing that
will error with React as you cannot modify the react client reference.
Turbopack doesn't use the same way to analyze the default export, so it
doesn't have this problem.

## How

We create few ESM version of entry files of nextjs APIs, then pick up
them to let app router for bundling, instead of using the `next/<api
name>.js` CJS files. Those ESM entries still point to the `next/dist/..`
CJS files. In this way webpack and directly gets the exports from the
`next/dist/...` files and be aware of the module exports. No more CJS
module wrapping the ESM module, the default and named exports can
preserve correctly.

Fixes #54777
Closes NEXT-1774
Closes NEXT-1879
Closes NEXT-1923
@karlhorky
Copy link
Contributor

karlhorky commented Dec 25, 2023

@huozhi thanks for the fix in #59852

I can confirm that [email protected] resolves the Unsupported Server Component type error, at least with next/image 🙌 :


Broken with [email protected] (Unsupported Server Component type error)

CodeSandbox demo: https://codesandbox.io/p/devbox/esm-js-import-bug-w-next-image-14-0-5-canary-24-773jvq?file=%2Fpnpm-lock.yaml%3A10%2C62

Screenshot 2023-12-24 at 16 57 48


Working with [email protected]

CodeSandbox demo: https://codesandbox.io/p/devbox/esm-js-import-bug-w-next-image-14-0-5-canary-25-x58yz8?file=%2Fpnpm-lock.yaml%3A10%2C62

Screenshot 2023-12-24 at 17 06 05

@huozhi huozhi added the Module Resolution Module resolution (CJS / ESM, module resolving) label Jan 2, 2024
agustints pushed a commit to agustints/next.js that referenced this issue Jan 6, 2024
## What

When users specify `"type": "module"` in Next.js app, especially with
`create-next-app`, `Image` component is not working. An error
`Unsupported Server Component type: {...}` is thrown.

## Why

`next/image` API is mixing with a client component as default export and
a named export as server component. But the entry file of the API is
still CJS file, which will import the module as the object. So you'll
get `{ default, unstable_getImageProps }` when you do `import Image from
'next/image'` instead of `Image` component itself, where the CJS module
load all the exports as an object. This is expected behavior for ESM but
breaks the usage.

It only errors when you're using js extensions, if you're using
typescript, it still works. If you're using turbopack, it works in dev
mode.

This is also because webpack can't analyze the exports from CJS module
of that `next/image` entry file. Usually we can assign the default
export to the module itself, then attach other named exports onto it, so
the default export equals the `module.exports` itself. But for
`next/image` since the default export is an client component, doing that
will error with React as you cannot modify the react client reference.
Turbopack doesn't use the same way to analyze the default export, so it
doesn't have this problem.

## How

We create few ESM version of entry files of nextjs APIs, then pick up
them to let app router for bundling, instead of using the `next/<api
name>.js` CJS files. Those ESM entries still point to the `next/dist/..`
CJS files. In this way webpack and directly gets the exports from the
`next/dist/...` files and be aware of the module exports. No more CJS
module wrapping the ESM module, the default and named exports can
preserve correctly.

Fixes vercel#54777
Closes NEXT-1774
Closes NEXT-1879
Closes NEXT-1923
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for 2 weeks. 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 Jan 17, 2024
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. linear: next Confirmed issue that is tracked by the Next.js team. locked Module Resolution Module resolution (CJS / ESM, module resolving)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants