移动端适配与性能

  • better-scroll

    • 核心解决的是移动端的web 页面的高性能的滚动插件吧,用于的是进行新开发的组件,解决移动端原生滚动卡顿的文艺吧,使用better-scroll的pull-up(上拉加载)、pull-down(下拉刷新)插件,实现“下拉刷新列表、上拉加载更多”;轮播图使用slide插件,实现流畅轮播

https://better-scroll.github.io/

原理图

import { useRef, useState, useEffect } from 'react'
import BScroll from 'better-scroll'
import './App.css'

function App() {
  // 1. 获取wrapper ref(必须用useRef,保证引用不变)
  const scrollRef = useRef(null)
  // 2. 保存BS实例(用useRef存,避免状态更新重渲染)
  const scrollInstance = useRef(null)
  // 3. 业务数据
  const [list, setList] = useState(Array.from({ length: 20 }, (_, i) => `初始列表-${i + 1}`))
  const [pullDownText, setPullDownText] = useState('下拉刷新')
  const [pullUpText, setPullUpText] = useState('上拉加载更多')
  const [isLoading, setIsLoading] = useState(false)

  // 初始化BS:仅在ref.current挂载后执行一次(空依赖+判断ref)
  useEffect(() => {
    if (!scrollRef.current) return
    // 初始化BS实例
    scrollInstance.current = new BScroll(scrollRef.current, {
      scrollY: true,
      click: true,
      bounce: true,
      // 下拉刷新配置
      pullDownRefresh: { threshold: 50, stop: 20 },
      // 上拉加载配置
      pullUpLoad: { threshold: -20 }
    })

    // 监听下拉刷新
    scrollInstance.current.on('pullingDown', handlePullDown)
    // 监听上拉加载
    scrollInstance.current.on('pullingUp', handlePullUp)
    // 监听滚动
    scrollInstance.current.on('scroll', (pos) => {
      console.log('React滚动位置:', pos.y)
    })

    // 清理函数:组件卸载时销毁BS实例
    return () => {
      scrollInstance.current && scrollInstance.current.destroy()
    }
  }, []) // 空依赖,仅执行一次

  // 窗口大小变化时刷新BS
  useEffect(() => {
    const handleResize = () => {
      scrollInstance.current && scrollInstance.current.refresh()
    }
    window.addEventListener('resize', handleResize)
    return () => {
      window.removeEventListener('resize', handleResize)
    }
  }, [])

  // 数据更新后刷新BS(依赖list变化)
  useEffect(() => {
    if (!scrollInstance.current) return
    // nextTick兜底:确保DOM渲染完成
    setTimeout(() => {
      scrollInstance.current.refresh()
    }, 0)
  }, [list])

  // 下拉刷新逻辑
  const handlePullDown = async () => {
    setPullDownText('正在刷新...')
    setIsLoading(true)
    await new Promise(resolve => setTimeout(resolve, 2000))
    // 刷新数据
    setList(prev => [`下拉刷新-${new Date().getTime()}`, ...prev])
    setPullDownText('下拉刷新')
    setIsLoading(false)
    // 结束下拉状态
    scrollInstance.current.finishPullDown()
  }

  // 上拉加载逻辑
  const handlePullUp = async () => {
    if (isLoading) return
    setPullUpText('正在加载...')
    setIsLoading(true)
    await new Promise(resolve => setTimeout(resolve, 2000))
    // 加载更多数据
    const newData = Array.from({ length: 10 }, (_, i) => `上拉加载-${i + 1}-${new Date().getTime()}`)
    setList(prev => [...prev, ...newData])
    setPullUpText('上拉加载更多')
    setIsLoading(false)
    // 结束上拉状态
    scrollInstance.current.finishPullUp()
  }

  return (
    <div className="scroll-wrapper" ref={scrollRef}>
      <div className="scroll-content">
        <div className="pull-down">{pullDownText}</div>
        <ul>
          {list.map(item => (
            <li key={item}>{item}</li>
          ))}
        </ul>
        <div className="pull-up">{pullUpText}</div>
      </div>
    </div>
  )
}

export default App
<template>
  <!-- BS核心容器:wrapper + content -->
  <div class="scroll-wrapper" ref="scrollRef">
    <div class="scroll-content">
      <!-- 下拉刷新提示 -->
      <div class="pull-down">
        {{ pullDownText }}
      </div>
      <!-- 滚动列表 -->
      <ul>
        <li v-for="item in list" :key="item">{{ item }}</li>
      </ul>
      <!-- 上拉加载提示 -->
      <div class="pull-up">
        {{ pullUpText }}
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
// 引入BetterScroll核心类
import BScroll from 'better-scroll'

// 1. 获取DOMref
const scrollRef = ref(null)
// 2. 保存BS实例(全局可操作)
let scroll = null
// 3. 业务数据
const list = ref(Array.from({ length: 20 }, (_, i) => `初始列表-${i + 1}`))
const pullDownText = ref('下拉刷新')
const pullUpText = ref('上拉加载更多')
// 加载状态(防止重复请求)
const isLoading = ref(false)

// 初始化BS
onMounted(() => {
  // 确保DOM渲染完成,nextTick兜底(可选,onMounted已保证DOM存在)
  nextTick(() => {
    initScroll()
  })
})

// 销毁BS实例(组件卸载时)
onUnmounted(() => {
  scroll && scroll.destroy()
})

// 核心:BS初始化配置
function initScroll() {
  scroll = new BScroll(scrollRef.value, {
    // 基础配置
    scrollY: true, // 垂直滚动(默认true,水平滚动设scrollX: true)
    click: true, // 开启原生点击(BS默认拦截click,需手动开启)
    bounce: true, // 回弹效果(默认true)
    // 下拉刷新配置:2.x 内置下拉刷新,无需额外插件
    pullDownRefresh: {
      threshold: 50, // 下拉阈值:下拉超过50px触发刷新
      stop: 20 // 刷新后回弹的停留位置(20px)
    },
    // 上拉加载配置:2.x 内置上拉加载
    pullUpLoad: {
      threshold: -20 // 上拉阈值:上拉超过-20px触发加载
    }
  })

  // 监听:下拉刷新事件
  scroll.on('pullingDown', handlePullDown)
  // 监听:上拉加载事件
  scroll.on('pullingUp', handlePullUp)
  // 可选:监听滚动事件(实时获取滚动位置)
  scroll.on('scroll', (pos) => {
    console.log('滚动位置:', pos.y) // pos.y是垂直滚动距离,pos.x是水平
  })
}

// 下拉刷新逻辑
async function handlePullDown() {
  pullDownText.value = '正在刷新...'
  isLoading.value = true
  // 模拟接口请求(2s)
  await new Promise(resolve => setTimeout(resolve, 2000))
  // 刷新数据
  list.value.unshift(`下拉刷新-${new Date().getTime()}`)
  pullDownText.value = '下拉刷新'
  isLoading.value = false
  // 关键:刷新完成后,调用finishPullDown 告诉BS结束下拉状态
  scroll.finishPullDown()
  // 可选:数据更新后刷新BS(DOM尺寸变化)
  scroll.refresh()
}

// 上拉加载逻辑
async function handlePullUp() {
  if (isLoading.value) return // 防止重复加载
  pullUpText.value = '正在加载...'
  isLoading.value = true
  // 模拟接口请求(2s)
  await new Promise(resolve => setTimeout(resolve, 2000))
  // 加载更多数据
  list.value.push(...Array.from({ length: 10 }, (_, i) => `上拉加载-${i + 1}-${new Date().getTime()}`))
  pullUpText.value = '上拉加载更多'
  isLoading.value = false
  // 关键:加载完成后,调用finishPullUp 告诉BS结束上拉状态
  scroll.finishPullUp()
  scroll.refresh()
}

// 可选:窗口大小变化时刷新BS(如旋转屏幕)
window.addEventListener('resize', () => {
  scroll && scroll.refresh()
})
</script>

<style scoped>
/* 核心样式:wrapper必须固定高度 + overflow:hidden */
.scroll-wrapper {
  width: 100%;
  height: 80vh; /* 固定高度,可根据需求调整 */
  overflow: hidden;
  background: #f5f5f5;
}

.scroll-content {
  padding: 0 16px;
}

ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

li {
  height: 60px;
  line-height: 60px;
  border-bottom: 1px solid #eee;
}

.pull-down, .pull-up {
  text-align: center;
  height: 40px;
  line-height: 40px;
  color: #999;
}
</style>
  • fastclick

    • 解决移动端300ms点击延迟的问题,是移动端H5的必备插件。解决问题:移动端浏览器默认有300ms的点击延迟(用于判断是否是双击缩放),导致按钮、列表项点击时反应迟缓,影响用户体验

https://awesometop.cn/posts/8d07c3f51d7640c6915e2e78f06d5552
  • lib-flexible/amfe-flexible

    • 移动端rem适配工具,配合postcss-plugin-px2rem,实现“一套代码适配所有移动端屏幕”(如iPhone、安卓手机的不同尺寸)。解决问题:移动端屏幕尺寸繁多(如375px、414px、750px),手动适配每个尺寸繁琐,rem适配可自动根据屏幕宽度计算rem值,实现自适应布局

  • vue-lazyload

    • Vue图片懒加载插件,版本兼容Vue2.7,核心作用是“首屏不加载所有图片,只加载可视区域的图片”,减少首屏加载时间。解决问题:用户中心有大量图片(如用户头像、权益图片、订单商品图),首屏加载所有图片会导致加载速度慢、流量消耗大

    • react 中的使用是:react-lazyload

    • npm install intersection-observer --save

    • npm install react-intersection-observer --save

import { useRef, useState, useEffect } from 'react'

/**
 * 图片懒加载自定义Hooks
 * @param {string} imgUrl - 图片真实地址
 * @param {object} options - 配置项(预加载、根容器)
 * @returns {ref, isLoading, isError, lazyUrl}
 */
const useLazyLoad = (imgUrl, options = {}) => {
  const targetRef = useRef(null) // 绑定到需要懒加载的元素
  const [isLoading, setIsLoading] = useState(true) // 加载中状态
  const [isError, setIsError] = useState(false) // 加载失败状态
  const [lazyUrl, setLazyUrl] = useState('') // 最终渲染的图片地址

  // 默认配置:根容器为视口,预加载100px
  const defaultOptions = {
    root: null, // 自定义滚动容器(DOM对象),null为窗口视口
    rootMargin: '100px 0px', // 预加载:上下各100px(核心,对标offset)
    threshold: 0, // 0表示元素刚进入视口就触发
  }

  const observerOptions = { ...defaultOptions, ...options }

  useEffect(() => {
    // 兼容低版本浏览器(可选,可引入polyfill)
    if (!window.IntersectionObserver) {
      setLazyUrl(imgUrl)
      setIsLoading(false)
      return
    }

    // 获取观察目标
    const target = targetRef.current
    if (!target) return

    // 创建Intersection Observer实例
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        // 元素进入视口
        if (entry.isIntersecting) {
          // 加载图片
          const img = new Image()
          img.src = imgUrl
          // 图片加载成功
          img.onload = () => {
            setLazyUrl(imgUrl)
            setIsLoading(false)
            observer.unobserve(target) // 加载完成后取消观察,优化性能
          }
          // 图片加载失败
          img.onerror = () => {
            setIsError(true)
            setIsLoading(false)
            observer.unobserve(target)
          }
        }
      })
    }, observerOptions)

    // 开始观察目标元素
    observer.observe(target)

    // 组件卸载/依赖更新时,销毁观察者(防止内存泄漏,React核心注意点)
    return () => {
      observer.unobserve(target)
      observer.disconnect()
    }
  }, [imgUrl, observerOptions]) // 依赖变化重新创建观察者

  return { targetRef, isLoading, isError, lazyUrl }
}

export default useLazyLoad
  • smoothscroll-ployfill

    • 平滑滚动兼容插件,解决低版本浏览器不支window.scrollTo平滑滚动的问题,提升滚动体验。解决问题:部分低版本浏览器(如iOS8、安卓7),使window.scrollTo实现滚动时,是瞬间跳转,而非平滑滚动,影响用户体验

核心思考是:

jsbridge 原生桥接

  • 统一 H5 与 iOS/Android 原生的桥接协议,避免两端因桥接方式不一致导致的兼容问题;

  • 封装原生通信的底层逻辑(如postMessage、自定义 URL Scheme、注入原生 JS 对象),H5 端无需关心原生实现细节;

  • 解决同步 / 异步调用的回调处理、错误捕获问题;

  • 支持 H5 主动调用原生(callHandler)、原生主动调用 H5(registerHandler)双向通信

import JSBridge from 'jsbridge'

// 初始化桥接(需等待原生WebView注入桥接对象,Hybrid核心:必须等原生就绪)
const initBridge = () => {
  return new Promise((resolve) => {
    if (window.JSBridge) {
      resolve(window.JSBridge)
    } else {
      // 监听原生桥接初始化完成事件
      document.addEventListener('JSBridgeReady', () => {
        resolve(window.JSBridge)
      }, false)
    }
  })
}

// H5调用原生方法:异步封装,统一错误处理
export const callNative = async (handlerName, data = {}) => {
  try {
    const bridge = await initBridge()
    return new Promise((resolve, reject) => {
      bridge.callHandler(handlerName, data, (response) => {
        // 与原生约定响应格式:{ code: 0, data: {}, msg: '' }
        if (response.code === 0) {
          resolve(response.data)
        } else {
          reject(new Error(response.msg || '原生调用失败'))
        }
      })
    })
  } catch (error) {
    console.error('桥接调用失败:', error)
    return Promise.reject(error)
  }
}

// 注册H5方法,供原生调用
export const registerH5Handler = (handlerName, callback) => {
  initBridge().then((bridge) => {
    bridge.registerHandler(handlerName, (data, responseCallback) => {
      try {
        // 执行H5业务逻辑
        const result = callback(data)
        // 向原生返回执行结果
        responseCallback({ code: 0, data: result, msg: 'success' })
      } catch (error) {
        responseCallback({ code: -1, data: null, msg: error.message })
      }
    })
  })
}

// 全局导出,方便组件使用
window.callNative = callNative
window.registerH5Handler = registerH5Handler

localforage

  • 解决原生localStorage三大痛点:容量小(仅 5MB)、同步操作(阻塞 WebView 主线程)、仅支持字符串存储(复杂对象需手动序列化);

  • 支持异步存储,不阻塞 WebView 的渲染和交互,性能更优;

  • 支持多种存储引擎(IndexedDB、WebSQL、localStorage),自动降级,兼容低版本 WebView;

  • 支持存储对象、数组、二进制数据,无需手动JSON.stringify/parse

  • 容量更大(IndexedDB/WebSQL 支持几十 MB 甚至上百 MB),满足 Hybrid 中离线数据、用户信息、缓存数据的存储需求

import { useState } from 'react'
import localforage from 'localforage'

// 初始化配置
localforage.config({
  name: 'HybridReactApp', // 存储库名称
  version: 1.0,
  storeName: 'hybridStore', // 存储表名称
  description: 'React Hybrid开发的本地存储'
})

/**
 * 本地存储自定义Hooks
 * @param {string} key - 存储键
 * @param {any} initialValue - 初始值
 * @returns {[value, setValue, removeValue]} - 存储值、设置方法、删除方法
 */
const useLocalForage = (key, initialValue) => {
  const [value, setValue] = useState(initialValue)

  // 初始化获取值
  useState(() => {
    const getValue = async () => {
      try {
        const storedValue = await localforage.getItem(key)
        if (storedValue !== null) {
          setValue(storedValue)
        }
      } catch (error) {
        console.error('获取本地存储失败:', error)
      }
    }
    getValue()
  }, [key])

  // 设置值
  const setStoredValue = async (newValue) => {
    try {
      await localforage.setItem(key, newValue)
      setValue(newValue)
    } catch (error) {
      console.error('设置本地存储失败:', error)
    }
  }

  // 删除值
  const removeStoredValue = async () => {
    try {
      await localforage.removeItem(key)
      setValue(initialValue)
    } catch (error) {
      console.error('删除本地存储失败:', error)
    }
  }

  return [value, setStoredValue, removeStoredValue]
}

export default useLocalForage
export { localforage }

生态总结

模块分类

包名

核心解决问题

优先级

适用场景

原生桥接与通信

jsbridge

H5 与原生双向通信、桥接封装

★★★★★

纯 H5+iOS/Android 原生 Hybrid

react-native-webview

RN + 原生 Hybrid 的 WebView 通信

★★★☆☆

React Native 参与的 Hybrid

WebView 兼容与适配

core-js@3

ES6+ API 补全、低版本 WebView 兼容

★★★★★

所有 Hybrid 项目(需兼容低版本 WebView)

@babel/preset-env + react

ES6+/JSX 语法转译

★★★★★

所有 React Hybrid 项目

css-vendor + autoprefixer

CSS3 属性前缀补全、CSS 兼容

★★★★★

所有 Hybrid 项目(需兼容低版本 WebView)

滚动与触摸体验

better-scroll

高性能滚动、下拉刷新、上拉加载、滚动穿透

★★★★☆

局部滚动容器、弹窗内滚动

fastclick

300ms 点击延迟、点击体验优化

★★★☆☆

兼容 Android 4.4+/iOS 9 + 低版本 WebView

react-gesture-handler

原生级复杂手势处理、手势与滚动冲突

★★★☆☆

需实现左滑 / 右滑 / 长按等复杂手势

视觉与渲染优化

react-lazyload

图片懒加载、布局抖动避免

★★★★☆

普通图片懒加载、快速替代 Vue-Lazyload

react-lazyload-image-component

增强型图片懒加载、Intersection Observer 实现

★★★★★

高性能图片懒加载、Retina 屏适配

amfe-flexible + postcss-pxtorem

设备自适应、rem 布局、px 自动转 rem

★★★★★

所有 Hybrid 项目(需适配不同设备)

性能与体验优化

react-loadable / React.lazy

组件级代码分割、首屏加载优化

★★★★★

所有 Hybrid 项目(需提升首屏加载速度)

localforage

增强型本地存储、替代 localStorage

★★★★★

所有 Hybrid 项目(需本地存储数据)

swr/useSWR

数据请求缓存、页面聚焦刷新、重复请求合并

★★★★☆

高频数据请求、需离线缓存的 Hybrid 项目

开发与调试工具

eruda

移动端 WebView 调试控制台、低版本调试

★★★★☆

非微信生态 Hybrid、低版本 WebView 调试

vConsole

微信生态移动端调试控制台、体积更小

★★★★☆

微信内置 WebView、微信小程序 / H5 Hybrid

react-devtools

React 组件调试、重渲染性能分析

★★★★☆

所有 React Hybrid 项目(需调试组件)

通用能力增强

qs

URL 参数解析 / 序列化、复杂参数处理

★★★★★

所有 Hybrid 项目(需传递 URL 参数)

js-cookie

Cookie 操作封装、Cookie 同步

★★★☆☆

需使用 Cookie 的 Hybrid 项目

react-helmet-async

动态设置页面头部、原生导航栏标题同步

★★★★☆

所有 Hybrid 项目(需统一页面标题)

网络请求工具

  • axios: ^0.18.0选型原因:主流的HTTP请求工具,版本稳定,兼容移动端浏览器,支持拦截器、请求取消、响应处理,比原生fetch更易用、更强大。解决问题:实现前端与后端的接口交互(GET/POST请求),处理请求参数、响应数据,拦截错误请求(如401未登录、500服务器错误)。实际运用:封装axios实例,配置请求拦截器(添加token、请求头)、响应拦截器(处理错误、解析数据),所有接口请求(如获取用户信息、查询订单)都通过该实例发送。

  • axios-cache-adapter: ^2.1.1选型原因:axios的缓存插件,版本兼容axios@0.18.0,核心作用是“缓存GET请求的响应数据”,减少重复请求,提升加载速度。解决问题:用户中心有很多高频GET请求(如用户信息、权益列表),每次进入页面都重复请求,会增加服务器压力、延长加载时间。实际运用:对高频GET请求(如获取用户信息)配置缓存,缓存有效期内,再次请求时直接使用缓存数据,无需请求后端接口。

  • crypto-js: ^4.1.1、js-md5: ^0.7.3、sha1: ^1.1.1选型原因:数据加密工具,分别支持AES、MD5、SHA1加密,版本稳定,用于敏感数据(如密码、token)的加密,保证数据安全。解决问题:用户中心的敏感数据(如用户密码、登录token),传输或存储时需要加密,避免被窃取。实际运用:用户登录时,使用MD5加密密码后再提交接口;敏感请求参数(如token),使用AES加密后传输。

  • urijs: ^1.19.5选型原因:URL处理工具,简化URL的解析、拼接、修改操作,避免手动处理URL导致的错误(如参数拼接错误、URL编码问题)。解决问题:用户中心有很多需要拼接URL参数的场景(如跳转页面时携带订单ID、用户ID),手动拼接易出错。实际运用:跳转页面时,使用urijs拼接URL参数(new URI('/order').addSearch('orderId', 123).toString()),解析URL参数(如获取当前URL中的用户ID)

工具类和其他依赖

  • dayjs: 1.11.9选型原因:轻量级日期处理工具,替代moment.js(体积大),版本稳定,API和moment.js类似,学习成本低,用于日期格式化、计算。解决问题:用户中心有很多日期处理场景(如订单创建时间格式化、会员到期时间计算),原生Date对象使用繁琐,易出错。实际运用:订单列表中,将订单创建时间(如1699999999999)格式化为“YYYY-MM-DD HH:mm:ss”;计算会员到期时间(如当前时间+30天)。

  • lodash: ^4.17.11选型原因:实用的JS工具库,提供大量现成的工具函数(如数组去重、对象深拷贝、防抖节流),版本稳定,配合babel-plugin-lodash可实现按需加载。解决问题:手动编写工具函数(如数组去重、对象深拷贝)耗时且易出错,lodash提供现成的函数,提升开发效率。实际运用:订单列表去重、用户信息对象深拷贝、按钮点击防抖(避免多次点击提交),都使用lodash的工具函数。

  • html2canvas: ^1.4.1选型原因:HTML转Canvas工具,版本稳定,适配移动端,核心作用是“将页面某部分转为图片”(如用户权益凭证、个人信息卡片)。解决问题:用户中心需要“保存权益凭证、分享个人信息卡片”的功能,手动生成图片繁琐,html2canvas可直接将HTML转为图片。实际运用:用户点击“保存权益凭证”时,使用html2canvas将权益凭证的HTML部分转为图片,供用户保存到手机。

  • qrcodejs2: 0.0.2选型原因:二维码生成工具,版本稳定,适配移动端,核心作用是“生成二维码”(如权益分享二维码、登录二维码)。解决问题:用户中心的分享功能(如分享权益给好友),需要生成二维码,手动生成二维码繁琐,qrcodejs2可快速生成。实际运用:用户点击“分享权益”时,生成包含权益链接的二维码,好友扫描二维码即可访问权益页面。

构建性能优化

  • hard-source-webpack-plugin: ^0.13.1 选型原因:webpack缓存插件,版本兼容项目的webpack版本,核心作用是“缓存webpack的编译结果”,二次打包时直接复用缓存,提升打包速度。 解决问题:项目代码量较大时,每次打包都需要重新编译所有模块,耗时较长,缓存插件可大幅缩短二次打包时间。 实际运用:无需手动使用,webpack构建时自动调用,二次打包(如修改少量代码后打包)时,速度提升50%以上。

  • thread-loader: ^3.0.4选型原因:webpack多线程编译插件,版本兼容,核心作用是“利用CPU多核能力,并行编译JS、CSS模块”,提升打包速度。 解决问题:单线程编译时,CPU利用率低,打包速度慢;多线程编译可充分利用CPU多核,缩短打包时间。 实际运用:webpack配置中引入thread-loader,编译JS、CSS时自动开启多线程,提升打包速度。

  • terser-webpack-plugin: ^4.2.3、optimize-css-assets-webpack-plugin: ^6.0.1 选型原因:JS、CSS压缩插件,版本兼容,核心作用是“打包时压缩JS、CSS代码”,减小线上包体积,提升加载速度。 解决问题:未压缩的JS、CSS代码体积大,会延长线上页面的加载时间,压缩插件可去除冗余代码(如注释、空格),减小体积。 实际运用:生产环境打包时,自动压缩JS、CSS代码,减小dist目录的体积,提升线上页面加载速度。

  • webpack-bundle-analyzer: ^4.4.2选型原因:webpack包体积分析工具,版本兼容项目webpack配置,核心作用是“可视化展示打包后的包体积结构”,帮助开发者定位体积过大的模块。 解决问题:打包后包体积过大时,无法快速定位是哪个依赖、哪个模块导致的,难以针对性优化;该工具可生成可视化图表,清晰展示每个模块、每个依赖的体积占比。 实际运用:打包完成后,通过该工具查看包体积图表,定位体积过大的依赖(如echarts体积较大,可考虑按需引入),针对性进行体积优化,避免线上包体积过大导致加载缓慢。

  • mini-css-extract-plugin: ^2.4.3 选型原因:CSS提取插件,版本兼容webpack,核心作用是“将打包后的CSS代码提取为独立的CSS文件”,而非内嵌在JS文件中。 解决问题:若CSS内嵌在JS文件中,会导致JS文件体积过大,延长JS加载时间,同时无法充分利用浏览器的并行加载能力;提取为独立CSS文件,可实现JS、CSS并行加载,提升首屏加载速度。 实际运用:生产环境打包时,自动将所有CSS代码提取为独立的.css文件,通过link标签引入页面,实现并行加载。

  • postcss-plugin-px2rem: ^0.8.1、postcss-px2vw: 0.0.1 选型原因:PostCSS适配插件,版本兼容postcss@7.0.14,配合lib-flexible使用,实现“px转rem/vw”,适配不同移动端屏幕。 解决问题:开发时按设计稿编写px单位,无需手动换算rem/vw,插件自动转换,避免适配错误,提升开发效率。 实际运用:开发时按750px设计稿编写px样式(如width: 750px),插件自动转为rem(适配lib-flexible)或vw,实现多屏幕自适应。