NextJS 应用程序上的 Lazy Hydrate + 代码分割

Raf*_*bre 4 reactjs webpack next.js

我知道如何进行延迟水合,并且知道如何进行代码分割,但是如何才能仅在组件水合时才下载分割的块?

我的代码看起来像这样

import React from 'react';
import dynamic from 'next/dynamic';
import ReactLazyHydrate from 'react-lazy-hydration';

const MyComponent = dynamic(() => import('components/my-component').then((mod) => mod.MyComponent));

export const PageComponent = () => {
  return (
    ...
    <ReactLazyHydrate whenVisible>
      <MyComponent/>
    </ReactLazyHydrate>
    ...
  );
};
Run Code Online (Sandbox Code Playgroud)

MyComponent呈现在折叠下方,这意味着它只会在用户滚动时才会水合。问题在于,页面加载时,JS chunksMyComponent将立即下载。

我能够通过仅在客户端上使用动态导入来破解它,但这会使组件在水合时消失一秒钟,因为在服务器上渲染的 html 不会被 React 使用。它将重新创建 DOM 元素,并且在 JS chunk 加载之前它将为空。当元素消失一秒钟时,它会增加页面 CLS,这就是我不能使用此 hack 的主要原因。这是这个黑客的代码

const MyComponent = typeof window === 'undefined'
    ? require('components/my-component').MyComponent
    : dynamic(() => import('components/my-component').then((mod) => mod.MyComponent));
Run Code Online (Sandbox Code Playgroud)

请注意,我想在服务器渲染上渲染组件的 HTML。这就是为什么我不想延迟加载它。我想要 Lazy Hydrate,这样我就可以在服务器上呈现组件的 HTML,但仅在可见时下载并执行它的 JS。

小智 8

更新:

在文档中:

// stops preloading of code-split chunks
class LazyHead extends Head {
  getDynamicChunks(files) {
    const dynamicScripts = super.getDynamicChunks(files);
    try {
      // get chunk manifest from loadable
      const loadableManifest = __non_webpack_require__(
        '../../react-loadable-manifest.json',
      );
      // search and filter modules based on marker ID
      const chunksToExclude = Object.values(loadableManifest).filter(
        manifestModule => manifestModule?.id?.startsWith?.('lazy') || false,
      );
      const excludeMap = chunksToExclude?.reduce?.((acc, chunks) => {
        if (chunks.files) {
          acc.push(...chunks.files);
        }
        return acc;
      }, []);
      const filteredChunks = dynamicScripts?.filter?.(
        script => !excludeMap?.includes(script?.key),
      );

      return filteredChunks;
    } catch (e) {
      // if it fails, return the dynamic scripts that were originally sent in
      return dynamicScripts;
    }
  }
}

const backupScript = NextScript.getInlineScriptSource;
NextScript.getInlineScriptSource = (props) => {
  // dont let next load all dynamic IDS, let webpack manage it
  if (props?.__NEXT_DATA__?.dynamicIds) {
    const filteredDynamicModuleIds = props?.__NEXT_DATA__?.dynamicIds?.filter?.(
      moduleID => !moduleID?.startsWith?.('lazy'),
    );
    if (filteredDynamicModuleIds) {
      // mutate dynamicIds from next data
      props.__NEXT_DATA__.dynamicIds = filteredDynamicModuleIds;
    }
  }
  return backupScript(props);
};
Run Code Online (Sandbox Code Playgroud)

在下一个配置中

const mapModuleIds = fn => (compiler) => {
  const { context } = compiler.options;

  compiler.hooks.compilation.tap('ChangeModuleIdsPlugin', (compilation) => {
    compilation.hooks.beforeModuleIds.tap('ChangeModuleIdsPlugin', (modules) => {
      const { chunkGraph } = compilation;
      for (const module of modules) {
        if (module.libIdent) {
          const origId = module.libIdent({ context });
          // eslint-disable-next-line
          if (!origId) continue;
          const namedModuleId = fn(origId, module);
          if (namedModuleId) {
              chunkGraph.setModuleId(module, namedModuleId);
          }
        }
      }
    });
  });
};

const withNamedLazyChunks = (nextConfig = {}) => Object.assign({}, nextConfig, {
  webpack: (config, options) => {
    config.plugins.push(
      mapModuleIds((id, module) => {
        if (
          id.includes('/global-brand-statement.js')
          || id.includes('signposting/signposting.js')
          || id.includes('reviews-container/index.js')
          || id.includes('why-we-made-this/why-we-made-this.js')
        ) {
          return `lazy-${module.debugId}`;
        }
        return false;
      }),
    );

    if (typeof nextConfig.webpack === 'function') {
      return nextConfig.webpack(config, options);
    }

    return config;
  },
});
Run Code Online (Sandbox Code Playgroud)

在文件中,使用 next/dynamic

    <LazyHydrate whenVisible style={null} className="col-xs-12">
      <GlobalBrandStatement data={globalBrandData} />
    </LazyHydrate>
Run Code Online (Sandbox Code Playgroud)