动态组件查找导致 Next.js 包大小爆炸,如何解决? [英] Next.js bundle size is exploding as a result of dynamic component lookup, how to solve?

查看:51
本文介绍了动态组件查找导致 Next.js 包大小爆炸,如何解决?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

tldr:

检查

选择 ComponentOne 会导致:

使用 recharts.js

选择 ComponentTwo 会导致:

使用victory.js

解决方案

问题

TLDR:Next 的 Webpack 配置将动态加载的组件分块为自己的块,这可能会创建重复或组合的块依赖项.

在您的示例中,我将组件 1 和 2 分别复制为组件 3 和 4.但是,对于组件 4(它是组件 2 的副本),我添加了一个额外的 moment-timezone 依赖项.结果是一个带有重复 victory-pie 依赖项的单独块(它还为 victorymoment-timezone 导入了整个库 包):

解释

即使两个 3rd 方图表包之间存在相当多的依赖共享(主要是共享 d3 依赖项),如果组件正在重用碰巧具有共享依赖项的 3rd 方库并且跨多个路由动态加载,Webpack 可能会尝试将这些 3rd 方块组合成一个组合块:而不是预期的两个或更多块:

但是,正如您将在上面的块屏幕截图中注意到的那样,即使没有跨多个路由重用/重新导入第 3 方包,您仍然具有重复的依赖项(例如,上面屏幕截图中的大桃子和石灰绿色块都包含重复的 d3-scaled3-timed3-path 等依赖块).

不幸的是,这是通过 next/dynamic 导入的组件的必要和预期行为(也适用于使用 Webpack 的动态 import 语句),因为它必须遍历每个动态导入的组件的整个依赖图并(可能)将它们添加为自己的块——换句话说,在动态加载组件的情况下,Webpack 不知道正在加载哪个组件在运行时,因此它必须创建一个完整的块才能根据请求加载(即使其他组件可能共享相同的依赖项,它也不会知道).不仅如此,因为它不知道在动态组件中导入/使用了什么,它不能摇树依赖!因此,当您添加更多动态加载的组件时,这会创建非常大且重复的块.

解决方案

不幸的是,真的没有解决办法.即使我尝试手动将这些依赖项分离并分组为它们自己的单独块(以减少冗余/构建大小),组件也不会再呈现.这是有道理的,当每个组件都以某种方式被分块成为自己独立的应用程序"时.在主应用程序中.

在这种情况下,最简单的解决方案是渲染静态图像来代替动态加载的 React 组件(如视频的缩略图).

其他想法

我查看了 Next 的 Webpack 配置并取得了一些进展.您可以创建自己的 webpack

这里有一些初步工作可用作 next.config.js 文件的基础:

next.config.js

module.exports = {webpack(配置,{ isServer }){/* 在构建时添加用于拆分块的客户端 webpack 优化规则 */如果(!isServer){config.optimization.splitChunks.cacheGroups = {...config.optimization.splitChunks.cacheGroups,胜利:{测试:/[\/]node_modules[\/](victory-pie|victory-core|victory-pie/es)[\/]/,名称:胜利",优先级:50,重用ExistingChunk:真,},重新图表:{测试:/[\/]node_modules[\/](recharts|recharts-scale)[\/]/,优先级:20,名称:recharts",重用ExistingChunk:真,},lodash:{测试:/[\/]node_modules[\/](lodash)[\/]/,名称:lodash",重用ExistingChunk:真,优先级:40,},};}/* 将新配置返回到下一个 */返回配置;},};

tldr:

Check the repo - common.js includes all dependencies, even though, only one is used on the respective page.

  • http://localhost:3000/components/ComponentOne
  • http://localhost:3000/components/ComponentTwo

Livedemo: click here

More Details:

I have an app (find attached a grossly simplified version) where based on a user input a different component is rendered. Finding the component to be rendered happens via a component-map. It makes sense that the common.js includes all dependencies for the switcher page, where both components have to be accessible (and thus their dependencies). But it does not make sense for the individual pages to include the respectively other dependency.

To summarize:

  • I want to be able to have a large group of components, that can are rendered based on a user's input. It is not feasible for my use-case to serialize and deserialize (as shown here) them as the components are wildly different and require different dependencies
  • I also want to render every component out to its own statically generated page, where I retrieve additional SEO information from a database. In this case, however, I only want to load the required dependencies for the particular component at hand.

http://localhost:3000

Selecting ComponentOne results in:

Uses recharts.js

Selecting ComponentTwo results in:

Uses victory.js

解决方案

PROBLEM

TLDR: Next's Webpack configuration is chunking dynamically loaded components as its own chunk, which may create duplicated or combined chunk dependencies.

With your example, I duplicated component 1 and 2 as component 3 and 4 respectively. However, with component 4 (which is a copy of component 2), I added an additional moment-timezone dependency. The result is a separated chunk with duplicated victory-pie dependencies (it also imported the entire library for both victory and moment-timezone packages):

EXPLANATION

Even though there is quite a lot of dependency sharing between the two 3rd party charting packages (mainly both share d3 dependencies), if the components are reusing 3rd party libraries that happen to have shared dependencies and be dynamically loaded across multiple routes, Webpack may attempt to combine these 3rd party chunks into one combined chunk: instead of the expected two or more chunks:

But, as you'll notice in the chunk screenshot right above, even if the 3rd party packages aren't being reused/reimported across multiple routes, you still have duplicated dependencies (for example, both large peach and lime-green chunks in the screenshot above contain duplicated d3-scale, d3-time, d3-path, and so on dependency chunks).

Unfortunately, this is a necessary and expected behavior of a component being imported via next/dynamic (also applies to using Webpack's dynamic import statements) because it must traverse the entire dependency graph for each dynamically imported component and (potentially) add them as their own chunk -- in other words, in the case of a dynamic loaded component, Webpack doesn't know what component is being loaded during runtime, so it must create an entire chunk for it to be able to be loaded upon request (even if other components may share the same dependencies, it won't know). Not only that, since it doesn't know what's being imported/used within the dynamic component, it can't tree-shake dependencies! This, as a result, creates incredibly large and duplicated chunks as you add more dynamically loaded components.

SOLUTION

Unfortunately, there's really no fix. Even when I tried to manually separate and group these dependencies as their own separate chunks (to reduce redundancy/build size), the component would no longer render. Which makes sense, when each component is chunked in a way to be its own separate "app" within the main app.

In this case, the simplest solution would to be render a static image in lieu of a dynamically loaded React component (like a thumbnail for a video).

OTHER THOUGHTS

I took a look into Next's Webpack configuration and was able to make some progress. You can create your own webpack splitChunks rules for Next to use, which will help reduce some chunk redundancy; but, even then, I was still getting duplicate chunks (derived mostly from d3 shared dependencies). You can try it out. Definitely not for the faint of heart as you'll be chasing a rabbit down a dark hole and you won’t achieve chunk distribution perfection. That said, it does help reduce the build size...

Here's some prelimary work to use as a foundation for your next.config.js file:

next.config.js

module.exports = {
  webpack(config, { isServer }) {
    /* adds client-side webpack optimization rules for splitting chunks during build-time */
    if (!isServer) {
      config.optimization.splitChunks.cacheGroups = {
        ...config.optimization.splitChunks.cacheGroups,
        victory: {
          test: /[\/]node_modules[\/](victory-pie|victory-core|victory-pie/es)[\/]/,
          name: "victory",
          priority: 50,
          reuseExistingChunk: true,
        },
        recharts: {
          test: /[\/]node_modules[\/](recharts|recharts-scale)[\/]/,
          priority: 20,
          name: "recharts",
          reuseExistingChunk: true,
        },
        lodash: {
          test: /[\/]node_modules[\/](lodash)[\/]/,
          name: "lodash",
          reuseExistingChunk: true,
          priority: 40,
        },
      };
    }
    /* return new config to next */
    return config;
  },
};

这篇关于动态组件查找导致 Next.js 包大小爆炸,如何解决?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆