Webpack 代码分割 动态导入的实践例子

本文介绍如何在React项目中使用Webpack的动态导入特性进行代码分割,提高应用加载速度。通过实例展示了如何封装动态导入逻辑,包括加载占位符、错误提示及延迟加载等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

代码分割,动态导入的场景

简单来说,就是例如我们的页面中,有一个组件内部实现逻辑非常庞大,引入了
许多其他的包,导致该页面整体体积过大,影响首屏渲染速度,此时我们期望优先加载
其他内容,最后再去加载这块组件内容,避免首屏渲染时间过长。也就是从chunks再拆分出
部分逻辑,单独生成一个script文件,在我们需要的时机进行请求该模块的script文件,再将
它显示出来,这样就是代码分割,动态引入的一个经典场景。

动态导入的实现原理

动态导入,本质上是来自于ECMA关于异步import语法的提案,该语法是让import支持异步引入。
例如import('./xxxfile').then(res=>{}),使得import语法promise异步化,res中就可以拿到
该模块的语法资源,再由开发者对其进行处理,例如,在webpack的实现中,引入一个react组件的res是这样的
一个JS对象:
image

其中default便是该模块默认到处的内容,也就是我们默认导出的组件了。

实践例子

1.模拟一下场景,我们有一个需要被拆分的 react 组件 DynamicTest.js

import React from 'react';
import styles from './DynamicTest.less';

function DynamicTest() {
  // 实际案例中,这里的实现逻辑可能非常复杂,我们想把它单独拎出来
  return <div className={styles.root}>动态加载组件内容</div>;
}

export default DynamicTest;

以及其组件样式文件 ./DynamicTest.less,
现在我们期望把这个文件和其样式文件单独从打包chunk中拆分出来。

原来的webpack输出:

chunk.js      
chunk.css

期望的webpack输出:

chunk.js
chunk.css          
DynamicTest.js               
DynamicTest.chunk.css        

2.对动态导入的逻辑进行封装

在对DynamicTest进行动态导入的时候,我们期望能实现以下功能 createLoadable:

  • 资源正在加载的时候,显示Loading占位符组件
  • 资源加载错误的时候,显示Error占位符组件
  • 资源加载完成的时候,显示DynamicTest组件
  • 允许设置一个delay,来延迟加载的时机

也就是以下的调用例子,createLoadable传入以上需求的配置,返回一个可动态导入的react组件:

const DynamicTest = createLoadable({
  // loader(func),返回动态导入的文件路径
  // 其中 /* webpackChunkName: "DynamicTest" */ 是webpack规定的,来命名打包后文件的名字
  loader: () => import(/* webpackChunkName: "DynamicTest" */ './DynamicTest'),
  // loading(reactComponent) react组件,props是loading(正在加载)以及err(发生错误)
  // 我们可以根据其props进行样式定制化
  loading: ({loading,err})=> loading ? 'loading' : err ? 'error': null,
  // delay(ms) 延迟加载的时间
  delay: 5000,
});

在我们的页面中,调用DynamicTest组件

export default () => { 
  return (
    <Wrapper>
      <DynamicTest/>
    </Wrapper>
  );
};
  1. createLoadable的实现逻辑
import React, { useState, useEffect } from 'react';

// loader组件,控制动态引入的状态
function Loader({ loader, loading, delay, loadedComponentProps }) {
  // import进来的模块内容
  const [loaded, setLoaded] = useState(null);
  // err引入发生的错误,isLoading是否正在加载中
  const [err, setErr] = useState(null);
  const [isLoading, setLoading] = useState(false);

  const load = () => {
    loader()
      .then(_loaded => {
        // log =>{__esModule: true, Symbol(Symbol.toStringTag): "Module", default: ƒ}
        setLoaded(_loaded);
      })
      .catch(_err => {
        setErr(_err);
      })
      .finally(() => {
        setLoading(false);
      });
  };
  useEffect(() => {
    setLoading(true);
    let h = null;
    if (delay) {
      h = setTimeout(load, delay);
    } else {
      load();
    }
    return () => {
      clearTimeout(h);
    };
  }, []);

  // 加载中或者错误
  // 返回占位符组件
  if (isLoading || err) {
    return React.createElement(loading, { isLoading, err });
  }

  // 加载成功
  // 返回加载成功后的组件
  if (loaded) {
    const loadedComponent =
      loaded && loaded.__esModule ? loaded.default : loaded;

    return React.createElement(loadedComponent, loadedComponentProps);
  }

  return null;
}

// 默认的占位符组件
const DefaultLoading = ({ loading, error }) => {
  // 组件正在异步加载的时候
  if (loading) return <div>Loading</div>;
  // 组件加载发生了错误的时候
  if (error) return <div>Unknown error occurred</div>;
  return null;
};

/**
 * @method createLoadable
 * @desc 通过import动态导入的语法,返回一个React组件,在合适的时机展示loading与err以及异步加载组件的内容
 * @param {loadr|fn}  () => import('./DynamicTest') 动态引入
 * @param {loading|component}  占位符组件,会被注入isLoading、err的props
 * @param {delay|ms}  延迟加载的时间
 * */
const createLoadable = ({ loader, loading = DefaultLoading, delay = 0 }) => {
  return props =>
    React.createElement(Loader, {
      loader,
      loading,
      delay,
      loadedComponentProps: props,
    });
};

export default createLoadable;

createLoadable的实现简而言之,就是创建了一个react组件loader,该组件帮助我们来管理import动态导入时的状态,
加载中、加载失败、以及加载完成,再去选择展示的组件,以及加上了延迟加载的功能需要。

预览

  1. 正在加载的时候
    image

  2. 加载完成,展示组件内容

  3. network中单独出来的chunk

在这里插入图片描述

其他:如何修改打包后的分片文件名

1.使用webpack,修改

output: {
 chunkFilename: '[name].bundle.js',
}

2.使用webpack-chain

config.output.chunkFilename(’[name].bundle.js’);

output中的chunkFilename其实就是entry中没有的其他chunk的命名规则。

总结

以上便是在react中如何使用webpack的动态导入特性的一个实践例子了,其实就是利用了webpack对import异步化语法的支持,
文件拆分的逻辑由webpack完成,我们通过编写一个组件来管理动态引入可能出现的几种状态,来显示我们期望的内容,甚至还可以
在导入失败的时候重新加载。

参考

y源码参考-

### Webpack 4 中的代码分割最佳实践 Webpack 4 对代码分割(Code Splitting)进行了显著改进,使得开发者能够更轻松地实现模块化加载和优化性能。以下是关于 Webpack 4 中代码分割的最佳实践: #### 自动化的代码分割功能 Webpack 4 提供了一个内置的功能来自动处理代码分割,无需额外插件即可完成大部分工作。通过 `optimization.splitChunks` 配置项可以定义如何拆分代码块[^1]。 ```javascript module.exports = { optimization: { splitChunks: { chunks: 'all', }, }, }; ``` 上述配置表示所有的 chunk(包括同步和异步引入的模块)都会被考虑用于分割。这种设置有助于减少重复依赖并提升缓存效率[^5]。 #### 动态导入支持 动态导入语法 (`import()`) 是 ES Module 的一部分,在 Webpack 4 中得到了全面的支持。它允许按需加载特定部分的应用程序逻辑,从而改善初始页面加载时间以及用户体验[^2]。 例如: ```javascript button.addEventListener('click', () => { import('./module.js').then((module) => { module.default(); }); }); ``` 此方法创建一个新的 JavaScript 文件作为单独的 chunk 并仅当事件触发时才下载该资源。 #### Manifest 文件的作用 为了进一步增强长期缓存策略的效果,建议生成 manifest 文件。Manifest 文件记录了应用入口点与其他 bundle 或 vendor 库之间的映射关系,并且由于其体积较小通常会被浏览器优先缓存下来[^3]。 可以通过调整如下选项启用 manifest 插件: ```javascript const { WebpackManifestPlugin } = require('webpack-manifest-plugin'); module.exports = { plugins: [ new WebpackManifestPlugin(), ], }; ``` 这样做的好处在于即使业务代码发生变化也不会影响到公共库或者框架层面上的内容更新频率。 #### 合理设定 Bundle 大小限制 尽管自动化工具可以帮助我们管理复杂的构建流程,但是仍然有必要关注最终产物的实际大小。过大的单个文件可能会导致网络传输瓶颈;而过多的小型片段则可能增加 HTTP 请求次数进而拖慢整体表现[^4]。 因此可以根据项目需求定制合适的阈值参数比如下面的例子所示: ```javascript performance: { hints: "warning", // 可选:"error" | false maxAssetSize: 200 * 1024, // 单位字节,默认为2MB assetFilter(assetFilename) { return !/\.map$/.test(assetFilename); } } ``` 以上配置会针对超出指定尺寸范围内的静态资产发出警告提示以便及时修正潜在问题。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值