动态导入:我错过了什么吗?

rpi*_*var 1 javascript dynamic-import reactjs webpack

我有一个使用 Webpack 作为捆绑器的 React 项目,我将我的捆绑包分成两个块 - 主代码库main.js和供应商捆绑包vendor.js

构建这些捆绑包后,main.js最终大小为 45kb 和vendor.js651kb。

一个特定的供应商库大小为 225kb,似乎是供应商导入中最严重的违规者。

我正在文件顶部的页面组件中导入该库:

import React from 'react';
import { ModuleA, ModuleB } from 'heavyPackage'; // 225kb import

...

const Page = ({ setThing }) => {

...

};
Run Code Online (Sandbox Code Playgroud)

为了尝试将这个繁重的导入加载到单独的包中,我尝试使用动态导入来导入这些模块。

在组件内部Page,直到调用特定函数才真正使用模块,因此我尝试在该范围内而不是在文件顶部导入模块:

import React from 'react';

...

const Page = ({ setThing }) => {

  ...

  const handleSignIn = async () => {
    const scopedPackage = await import('heavyPackage');
    const { moduleA, moduleB } = scopedPackage;

    // use moduleA & moduleB normally here
  };

};
Run Code Online (Sandbox Code Playgroud)

出于某种原因,我认为 Webpack 会智能地接受我在这里尝试做的事情,并将这个沉重的包分离成自己的块,仅在需要时下载,但生成的包是相同的 - 一个是main.js45kb,一个是 45kb。vendor.js那是 651kb。我的思路是否正确,可能是我的 Webpack 配置已关闭,还是我以错误的方式考虑动态导入?

编辑我已将 Webpack 配置为使用splitChunks. 以下是我的配置方法:

  optimization: {
    chunkIds: "named",
    splitChunks: {
      cacheGroups: {
        commons: {
          chunks: "initial",
          maxInitialRequests: 5,
          minChunks: 2,
          minSize: 0,
        },
        vendor: {
          chunks: "initial",
          enforce: true,
          name: "vendor",
          priority: 10,
          test: /node_modules/,
        },
      },
    },
  },
Run Code Online (Sandbox Code Playgroud)

Mat*_*tta 7

React 18 更新:不再需要下面的代码来分割块/动态加载组件。相反,您可以将React.lazy 与 Suspense 结合使用,这会获得类似的结果(这只适用于React Components,因此任何node_module导入都需要在此动态加载的组件中导入):

const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded

<Suspense fallback={<Spinner />}>
 <ProfilePage />
</Suspense>
Run Code Online (Sandbox Code Playgroud)

@Ernesto 的答案提供了一种使用插件进行代码分割的方法react-loadablebabel-dynamic-import但是,如果您的 Webpack 版本是 v4+(并且将自定义 Webpack 配置设置为所有人的 SplitChunks),那么您只需要使用魔术注释和自定义反应组件。

来自文档

通过在导入中添加 [magic] 注释,我们可以执行诸如命名块或选择不同模式等操作。有关这些神奇注释的完整列表,请参阅下面的代码,然后解释这些注释的作用。

// 单个目标

const ProfilePage = React.lazy(() => import('./ProfilePage')); // Lazy-loaded

<Suspense fallback={<Spinner />}>
 <ProfilePage />
</Suspense>
Run Code Online (Sandbox Code Playgroud)

// 多个可能的目标

import(
 /* webpackChunkName: "my-chunk-name" */
 /* webpackMode: "lazy" */
 'module'
);
Run Code Online (Sandbox Code Playgroud)

因此,您可以创建一个可重用的LazyLoad组件,如下所示:

import(
 /* webpackInclude: /\.json$/ */
 /* webpackExclude: /\.noimport\.json$/ */
 /* webpackChunkName: "my-chunk-name" */
 /* webpackMode: "lazy" */
 /* webpackPrefetch: true */
 /* webpackPreload: true */
 `./locale/${language}`
);
Run Code Online (Sandbox Code Playgroud)

然后在您的路由中,使用目录LazyLoad中的文件名并将其传递给它pages(例如pages/"Home"/index.js):

import React, { Component } from "react";
import PropTypes from "prop-types";

class LazyLoad extends Component {
  state = {
    Component: null,
    err: "",
  };

  componentDidMount = () => this.importFile();

  componentWillUnmount = () => (this.cancelImport = true);

  cancelImport = false;

  importFile = async () => {
    try {
      const { default: file } = await import(
        /* webpackChunkName: "[request]" */
        /* webpackMode: "lazy" */
        `pages/${this.props.file}/index.js`
      );

      if (!this.cancelImport) this.setState({ Component: file });
    } catch (err) {
      if (!this.cancelImport) this.setState({ err: err.toString() });
      console.error(err.toString());
    }
  };

  render = () => {
    const { Component, err } = this.state;

    return Component ? (
      <Component {...this.props} />
    ) : err ? (
      <p style={{ color: "red" }}>{err}</p>
    ) : null;
  };
}

LazyLoad.propTypes = {
  file: PropTypes.string.isRequired,
};

export default file => props => <LazyLoad {...props} file={file} />;
Run Code Online (Sandbox Code Playgroud)

在这一点上,React.LazyReact-Loadable是自定义 Webpack 配置或不支持动态导入的 Webpack 版本的替代方案。


可以在此处找到工作演示。按照安装说明进行操作,然后您可以运行以查看按名称yarn build拆分的路由。