mantine
mantine copied to clipboard
Cannot Modularize Imports for Next.js
What package has an issue
@mantine/core
Describe the bug
Hey there,
With Next.js >= 13.1, you can make use of their Modularize Imports feature.
Right now there is an open PR on material-ui mentioning it: https://github.com/mui/material-ui/pull/35457/files
Could mantine support that as well? I attempted it, and it fails compilation at import { useMantineTheme } from '@mantine/core';
What version of @mantine/hooks page do you have in package.json?
N/A
If possible, please include a link to a codesandbox with the reproduced problem
No response
Do you know how to fix the issue
None
Are you willing to participate in fixing this issue and create a pull request with the fix
None
Possible fix
No response
Why would you need that? Tree shaking should remove all unused code.
@rtivital my understanding is that with SWC (the new default in Next.js), there is no tree shaking in development mode: https://github.com/vercel/next.js/discussions/17977#discussioncomment-3939189
So, why do you need tree shaking in development mode?
@rtivital in order to increase page load times, since they can become very slow. @tabler/icons, the dependency for mantine, is also in the middle of it's v2 branch for the same support: https://github.com/tabler/tabler-icons/issues/359
Do you experience any performance issues?
@rtivital I am fiddling with a setup forked from https://github.com/mantinedev/mantine-next-template but with upgraded dependencies (including Next >= 13), and it is slow on startup and initial page loads. The latency could be solely coming from @tabler/icons, but I don't know if there's a mechanism to easily tell (I couldn't find one at least). But I would assume @mantine/core is suffering from the same issue, considering @mui added support for it.
@tabler/icons is not a dependency of @mantine/core, if you do not use @mantine/tiptap you can uninstall it.
Can provide some measurable metrics on performance?
@tabler/icons is not a dependency of @mantine/core, if you do not use @mantine/tiptap you can uninstall it.
Figured I might have used the wrong choice of words there, "recommended via documentation" is more accurate 😄
Can provide some measurable metrics on performance?
After digging through several threads on the issue, I don't see an obvious way to get performance metrics. Next.js only tells you X modules were loaded, but it doesn't tell you which modules were loaded.
I will run an experiment where I only import a single mantine component from a single page and see if I can get a convincing delta and report back.
@rtivital I made a demo app using create-react-app to profile this.
Here's the demo app:
function App() {
return (
<MantineProvider withGlobalStyles withNormalizeCSS>
<Button>Click me!</Button>
</MantineProvider>
);
}
export default App;
I will showcase the modules loaded by webpack in each import scenario.
Scenario 1: Recommended by docs
import { MantineProvider } from '@mantine/core';
import { Button } from '@mantine/core';
orphan modules 1.06 MiB [orphan] 454 modules
runtime modules 28.5 KiB 14 modules
Scenario 2: Reaching into the ESM directory manually
import { MantineProvider } from '@mantine/styles/esm/theme/MantineProvider';
import { Button } from '@mantine/core/esm/Button/Button';
orphan modules 12.1 KiB [orphan] 20 modules
runtime modules 28.5 KiB 14 modules
Notice 454 modules were loaded with the current approach, and only 20 modules were loaded with the desired approach.
While tree shaking will occur during the minification process, webpack still needs to traverse all of these modules that get imported. If @mantinedev's build can be restructured so that:
"module": "esm/index.js",is changed into"module": "index.js",- All top level imports can be retrieved using path imports
Then transformations like Modularize Imports or babel-plugin-import will work, and we could import as we currently are:
import { Button, MantineProvider } from '@mantine/core';
while still only pulling in 20 modules.
Just an update here that @tabler/tabler-icons just merged their support for this as well:
https://github.com/tabler/tabler-icons/issues/359
Do you experience any performance issues?
Yes, I have experienced performance issues with Mantine in development (with Next.js) because it bundles all of Mantine instead of only using what's imported. This causes significantly slower dev-mode page load times, even for very simple pages. There are no issues in production as Next.js does tree shaking there, but it doesn't do any tree shaking in development mode.
For comparison, antd does allow modullarizeImports for most components with something like this:
modularizeImports: {
antd: {
transform: "antd/lib/{{lowerCase member}}",
}
}
Disclaimer: I haven't worked with SWC yet, so please take the following with a pinch of salt.
I'm using esbuild and it treeshakes Mantine just fine. And it "only" treeshakes on module level so unused modules will be discarded but unused exports within modules not (if I understand it correctly).
And the build performance for dev/production is excellent.
So if I understand it correctly these Next.js transform rules require the package to follow some specific file convention that the user can configure. Therefore the internal package/export structure becomes part of the public API. Is this really good?
What exactly do you require? As I see it Mantine already has a logicial file structure that may be described with such a configuration. Where does it fall short? On every module that exports multiple things? On every export that doesn't match the filename? Or because it doesn't use default exports?
@cyantree
I'm using esbuild and it treeshakes Mantine just fine. And it "only" treeshakes on module level so unused modules will be discarded but unused exports within modules not (if I understand it correctly). And the build performance for dev/production is excellent.
Treeshaking vs module loading is different. Everyone agrees here that treeshaking is working, but all modules are being loaded during development. That is what we are looking to fix.
So if I understand it correctly these Next.js transform rules require the package to follow some specific file convention that the user can configure. Therefore the internal package/export structure becomes part of the public API. Is this really good?
We are already using the public API by using the components the library publicizes. The only request is that the public API becomes consistent in the way that the file layout is structured so that we don't need to load all modules.
What exactly do you require?
I posted this in the issue description:
Could mantine support that as well? I attempted it, and it fails compilation at import { useMantineTheme } from > '@mantine/core';
Thanks for the clarification @anthonyalayo .
We are already using the public API by using the components the library publicizes. The only request is that the public API becomes consistent in the way that the file layout is structured so that we don't need to load all modules.
IMHO that's two different things.
- You're using the public API described in the docs and made public by the root exports from each package. That's the expected way to use the library.
- You want to access the same things via sub imports that follow a specific schema. So:
import {Button} from '@mantine/core';may becomeimport Button from '@mantine/core/Button';import {useMantineTheme} from '@mantine/core';may becomeimport useMantineTheme from '@mantine/core/useMantineTheme';- As I read it using
default exportsisn't mandatory, so it might be possible to stick withnamed exportsif wanted.
Is my understanding correct? So to get this working and stable the file structure must become part of the public API - in my understanding it's currently not part of it. And it must be consistent with the export namings.
Another idea: Because next.config.js is a JS file and not a JSON (or might it be a JSON file?) it may also be possible to utilize some require statements and fetch the transformation configuration directly from the package:
module.exports = {
modularizeImports: {
...require('@mantine/core/nextJsModularizeImports.js'),
},
}
This opens up the possibility to keep the file structure out of the public API.
And what about using package.json exports?
https://webpack.js.org/guides/package-exports/
https://nodejs.org/api/packages.html#subpath-exports
Is this already usable? For me this looks like the better and more standardized approach.
We are really suffering from this issue.
I'll put my experience here too: today it is not much about "dev" speed and module loading, but also about "React Server Components" experience, because when I'll use @mantine/core, I'll get a lot of stuff where Next.js complains that I should use "use client"; even when it's not necessary:
Just because of emotion cache from Mantine I've to put "use client"; in a place where it does not make much sense...
Here is my config:
modularizeImports: {
"@tabler/icons-react": {
transform: "@tabler/icons-react/dist/esm/icons/{{member}}",
},
"@mantine/core": {
transform: "@mantine/core/esm/{{member}}/{{member}}.js",
skipDefaultConversion: true,
preventFullImport: true,
},
"@mantine/core/lib/AppShell": {
transform: "@mantine/core/esm/AppShell/{{member}}/{{member}}.js",
skipDefaultConversion: true,
preventFullImport: true,
},
"@mantine/core/lib/Grid": {
transform: "@mantine/core/esm/Grid/{{member}}/{{member}}.js",
skipDefaultConversion: true,
preventFullImport: true,
},
"@mantine/core/lib/Timeline": {
transform: "@mantine/core/esm/Timeline/{{member}}/{{member}}.js",
skipDefaultConversion: true,
preventFullImport: true,
},
"@mantine/core/lib/Burger": {
transform: "@mantine/core/esm/Burger/Burger.js",
skipDefaultConversion: true,
preventFullImport: true,
},
"@mantine/core/lib/Menu/MenuDivider/MenuDivider": {
transform: "@mantine/core/esm/Menu/MenuDivider/MenuDivider.js",
skipDefaultConversion: true,
preventFullImport: true,
},
"@mantine/core/lib/Menu/MenuItem/MenuItem": {
transform: "@mantine/core/esm/Menu/MenuItem/MenuItem.js",
skipDefaultConversion: true,
preventFullImport: true,
},
"@mantine/core/lib/Menu/MenuTarget/MenuTarget": {
transform: "@mantine/core/esm/Menu/MenuTarget/MenuTarget.js",
skipDefaultConversion: true,
},
"@mantine/core/lib/Menu/MenuDropdown/MenuDropdown": {
transform: "@mantine/core/esm/Menu/MenuDropdown/MenuDropdown.js",
skipDefaultConversion: true,
},
},
It worked, but I unfortunately didn't notice a great perfomance gain on dev mode.
We have the "slow getServerSideProps" issue and while for MaterialUI, working with modularizeImports/importing the default exports of the specific components seems to ease the pain, I struggle finding a solution Mantine.
@mantine/core/lib/[Component]can't be resolved (even tho the types seem to have that path)modularizeImportsisn't really working, because paths aren't consistent, some things (MantineProvider,useMantineTheme) don't seem to be available on a sub-paths at all (at least not for typescript).
Has anyone found a working solution for NextJS SSR + Mantine imports?
hey guys thats a realy big problem. and also tabler ui has same problem
hey guys thats a realy big problem. and also tabler ui has same problem
I think Next.js 13.5 will address it with a new config option in next.config.js file. The recent commits on the Next.js project indicates that there will be an optimizePackageImports experimental option, and it will work automatically.
I tried the experimental.optimizePackageImports - by just adding "@mantine/core" our ssr pages got SO much faster!
in my case optimizePackageImports does not help me. i have ben add @mantine/core and also @tabler/icons-react and have the same modules in dev mode. (and same build ms)
Compiled /in 9.2s (6432 modules) (4000+ modules only @tabler/icons-react) but i am use only 10 icons. that a very big problem.... modularizeImports have same problem.
How did you get those numbers?
This is no longer an issue starting from Next.js 13.5 with optimizePackageImports. I've validated that it works correctly with app router:
With optimizePackageImports:
✓ Compiled /page in 1141ms (412 modules)
✓ Compiled /not-found in 166ms (403 modules)
Without optimizePackageImports:
✓ Compiled /page in 4.4s (2125 modules)
✓ Compiled /not-found in 382ms (2107 modules)
This is no longer an issue starting from Next.js 13.5 with optimizePackageImports. I've validated that it works correctly with app router:
With
optimizePackageImports:✓ Compiled /page in 1141ms (412 modules) ✓ Compiled /not-found in 166ms (403 modules)Without
optimizePackageImports:✓ Compiled /page in 4.4s (2125 modules) ✓ Compiled /not-found in 382ms (2107 modules)
I saw that, it works great. You might want to add a section about this on the installation docs. It doesn't seem to have any downsides