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 build" doesn't recognise node subpath export patterns in npm packages #46267

Open
1 task done
cseas opened this issue Feb 22, 2023 · 11 comments
Open
1 task done
Labels
bug Issue was opened via the bug report template. TypeScript Related to types with Next.js.

Comments

@cseas
Copy link
Contributor

cseas commented Feb 22, 2023

I'm trying to publish a component library that Next.js users can use individual components from without having to worry about bundle size overhead. Since Next.js doesn't tree shake the root import, as a library author, I have to provide deep imports to the individual components as a workaround. But the build command fails for deep imports.

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.2.0: Fri Nov 11 02:03:51 PST 2022; root:xnu-8792.61.2~4/RELEASE_ARM64_T6000
Binaries:
Node: 18.13.0
npm: 8.19.3
Yarn: 1.22.19
pnpm: N/A
Relevant packages:
next: 13.1.7-canary.26
eslint-config-next: N/A
react: 18.2.0
react-dom: 18.2.0

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

App directory (appDir: true), CLI (create-next-app), Package manager (npm, pnpm, Yarn), TypeScript

Link to the code that reproduces this issue

https://codesandbox.io/p/sandbox/purple-dream-eum7fq

To Reproduce

  • Visit the provided Codesandbox link
  • Check the import in app/page.tsx, the editor recognises the import correctly.
  • The Default tab running next dev runs the application without errors.
  • The build tab running next build throws an error: Type error: Cannot find module

In general, use a subpath import (e.g. import { something } from "my-package/something";) from an npm package that has defined this under exports field in its package.json:

  "exports": {
    "./*": {
      "import": "./dist/subpath/*.js"
    }
  },

Describe the Bug

The next build command is not able to recognise subpath imports from a package that exports them using Node's wildcard exports syntax. This error only occurs on build, the next dev command throws no errors, neither does the IDE.

Expected Behavior

next build should not throw an error for subpath imports from an npm package exporting them using wildcard syntax.

Which browser are you using? (if relevant)

Microsoft Edge Version 110.0.1587.50 (Official build) (arm64)

How are you deploying your application? (if relevant)

Vercel

@cseas cseas added the bug Issue was opened via the bug report template. label Feb 22, 2023
@github-actions github-actions bot added the TypeScript Related to types with Next.js. label Feb 22, 2023
@balazsorban44
Copy link
Member

This is unrelated to Next.js, actually.

next build runs the TypeScript compiler without emitting files for type checking.

(You can verify this by adding typescript: { ignoreBuildErrors: true }, which will pass next build)

I checked your package and I found the types condition on the bottom https://github.com/hazel-ui/hazel-ui/blob/8d21e1bbaafe09e1c2506fe5dbdd3147d962b30d/package.json#L14

According to the TS docs, it should be the first one:

https://www.typescriptlang.org/docs/handbook/esm-node.html#packagejson-exports-imports-and-self-referencing

Also, for the barrel files there is a built-in transform now, that should transform imports for tree-shaking! See https://nextjs.org/docs/advanced-features/compiler#modularize-imports

@cseas
Copy link
Contributor Author

cseas commented Feb 24, 2023

This is unrelated to Next.js, actually.

To confirm this, I tried replicating the same config and imports in a custom webpack setup. No errors on IDE, dev server, or build (with type checks). I'm only able to reproduce this issue in Next.js so doesn't seem a library packaging issue.


(You can verify this by adding typescript: { ignoreBuildErrors: true }, which will pass next build)

This is correct, thanks! But removing type checks from build isn't a viable solution. Since the IDE and a custom webpack setup don't reproduce this error for the same code, I'm guessing it's an issue with the typescript setup used by Next.js during build?


I checked your package and I found the types condition on the bottom

Thanks for the tip! The suggestion from the TypeScript team seems to come from this bug: microsoft/TypeScript#50762
But that doesn't seem to be the problem here, I moved the types field to be defined first and got the same error. I've anyway updated the codesandbox reproduction with a new release of the lib with types field mentioned first in exports.

Also note that the original error I reported is reproducible with [email protected].
next build throws a new error with the latest release [email protected]:

app/layout.tsx
Type error: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'. Consider writing a dynamic 'import("../../../app/layout")' call instead.
  To convert this file to an ECMAScript module, change its file extension to '.mts' or create a local package.json file with `{ "type": "module" }`.

This seems to be because the Next.js app directory is using CJS for some reason even though I have type: module in package.json. The TypeScript team's solution for this is to use moduleResolution: bundler but Next.js doesn't support that in tsconfig so we have to use moduleResolution: node16 as the best alternative.

Looks like this is related to #39375 and #32239 (reply in thread)


Also, for the barrel files there is a built-in transform now, that should transform imports for tree-shaking! See https://nextjs.org/docs/advanced-features/compiler#modularize-imports

I checked the docs for Modularize Imports but they say that the imports are transformed to default ones. Does this mean this only works for libraries that provide default exports? The transform I'm looking for is to named exports.

// this
import { MyComponent } from "my-lib";
// becomes
import { MyComponent } from "my-lib/MyComponent";

Doesn't look like this config is the solution for tree shaking named exports. Should I open a separate feature request for supporting this transform?

@yakiisama

This comment was marked as off-topic.

@CobyPear
Copy link

CobyPear commented Apr 28, 2023

I'm having some issues with this as well. I can publish the package I've been trying to convert to use subpath exports and an app that consumes it if needed. I having all sorts of trouble importing a component. Sometimes it works in dev mode, but always fails a build. import-cost seems to be working on these paths, and I can see the files in node_modules so I know the files are properly bundled in the package.

Module not found: Package path ./dist/components/header is not exported from package /REDACTED (see exports field in REDACTED/package.json)

And the exports of that package.json in the error message:

	"exports": {
		"./components/*": {
			"types": "./dist/components/*.d.ts",
			"import": "./dist/components/*.js"
		},
		"./utils/*": {
			"types": "./dist/utils/*.d.ts",
			"import": "./dist/utils/*.js"
		},
		"./style.css": "./dist/src/style.css"
	},

@yakiisama
Copy link

I'm having some issues with this as well. I can publish the package I've been trying to convert to use subpath exports and an app that consumes it if needed. I having all sorts of trouble importing a component. Sometimes it works in dev mode, but always fails a build. import-cost seems to be working on these paths, and I can see the files in node_modules so I know the files are properly bundled in the package.我也有一些问题。我可以发布我一直试图转换为使用子路径导出的包和一个在需要时使用它的应用程序。我在导入组件时遇到了各种麻烦。有时它可以在开发模式下工作,但总是无法构建。 import-cost 似乎在这些路径上工作,我可以在 node_modules 中看到文件,所以我知道这些文件已正确捆绑在包中。

Module not found: Package path ./dist/components/header is not exported from package /REDACTED (see exports field in REDACTED/package.json)

And the exports of that package.json in the error message:以及错误消息中那个 package.json 的导出:

	"exports": {
		"./components/*": {
			"types": "./dist/components/*.d.ts",
			"import": "./dist/components/*.js"
		},
		"./utils/*": {
			"types": "./dist/utils/*.d.ts",
			"import": "./dist/utils/*.js"
		},
		"./style.css": "./dist/src/style.css"
	},

I have the same case as well

@rafalzawadzki
Copy link

Same here!

"exports": {
        "./lib": {
            "types": "./src/lib/index.ts",
            "import": "./out/lib/index.js"
        },
        "./types": {
            "types": "./src/types/index.ts",
            "import": "./out/types/index.js"
        },
    },

and then import { func } from "shared/lib"; does not work. Which is a bummer because I spent last two days migrating to a mono repo using Turbopack to find out Next has such problem

@CobyPear
Copy link

I have a reproduction repo up for this here: https://github.com/CobyPear/repro-subpath-exports

I am struggling to understand how to bundle a component library for use with the app directory given what seems to be a bug, since when importing a non-tree shaken file, the useEffect at the top of the file breaks server components, which makes sense.

The modularize imports section in the next.config did not help, or I misconfigured it. I'm not sure why I would need to do that anyway if I already modularized my exports in the library.

@CobyPear
Copy link

After some more digging into this and messing with the exports field, it seems like Next.js does pick up exports but only if the entire path is defined, and patterns don't seem to work properly.

// this works, but why do I need to be explicit with the entire path?
"./dist/components/header": {
	"types": "./dist/components/header.d.ts",
	"import": "./dist/components/header.mjs",
	"require": "./dist/components/header.js"
},
// this does not work
"./components/*": {
	"types": "./dist/components/*.d.ts",
	"import": "./dist/components/*.mjs",
	"require": "./dist/components/*.js"
},

@balazsorban44 sorry for the ping but I'm curios if there is some discrepancy with the Next.js compiler and the way it reads subpath exports vs how they are documented to work in the Node.js docs.

@Jared-Dahlke
Copy link

After some more digging into this and messing with the exports field, it seems like Next.js does pick up exports but only if the entire path is defined, and patterns don't seem to work properly.

// this works, but why do I need to be explicit with the entire path?
"./dist/components/header": {
	"types": "./dist/components/header.d.ts",
	"import": "./dist/components/header.mjs",
	"require": "./dist/components/header.js"
},
// this does not work
"./components/*": {
	"types": "./dist/components/*.d.ts",
	"import": "./dist/components/*.mjs",
	"require": "./dist/components/*.js"
},

@balazsorban44 sorry for the ping but I'm curios if there is some discrepancy with the Next.js compiler and the way it reads subpath exports vs how they are documented to work in the Node.js docs.

this works for me too, but wow this is not going to be good dx for my library users.

Is there a better way to do this, or have you figured out a way to at least mask this so consumer apps get a better dx / cleaner import?

@jamieomaguire
Copy link

@Jared-Dahlke I am having this exact issue for an icon library. Did you end up just using explicit paths? I was really hoping to avoid including /dist in the path but not seeing any way around it!

@taco-chen
Copy link

same here as @jamieomaguire

 "exports": {
    ".": "./dist/index.js",
    "./react/24/solid": {
      "types": "./dist/react/24/solid/index.d.ts",
      "import": "./dist/react/24/solid/index.js"
    }
  },
import { ActivitiesIcon } from 'icons/react/24/solid';
                             // ^^^^^^^^^^^^^^^^^^^^ 
                             // Cannot find module 'icons/react/24/solid' or 
                             // its corresponding type declarations.ts(2307)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Issue was opened via the bug report template. TypeScript Related to types with Next.js.
Projects
None yet
Development

No branches or pull requests

8 participants