当前位置: 首页 > news >正文

前端性能优化实用方案(一):减少50%首屏资源体积的Webpack配置

1 首屏加载慢?试试这几个减少资源体积的方法

做前端的都知道,首屏加载速度直接关系到用户会不会继续用你的产品。有数据显示,页面加载时间每增加1秒,转化率就会下降7%。

这个数字听起来很抽象,但想想你自己平时的使用习惯就明白了——打开一个网页,如果3秒还没加载出来,你是不是就想关掉了?我在实际项目中验证过这些方法,效果很明显。

本文重点讲如何通过减少首屏资源体积来提升页面加载性能。

1.1 减少首屏资源体积的核心思路

首屏资源体积是影响加载速度的关键因素。我们可以从几个维度来优化,每个维度都能带来不同程度的提升。

1.2 打包工具的压缩配置

现代打包工具的压缩能力很强,但很多人都没有充分利用。合理配置可以让bundle体积减少30-50%。

Webpack压缩配置

const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');module.exports = {mode: 'production',optimization: {minimize: true,minimizer: [new TerserPlugin({terserOptions: {compress: {drop_console: true, // 移除consoledrop_debugger: true, // 移除debuggerpure_funcs: ['console.log'] // 移除指定函数},mangle: true, // 混淆变量名},extractComments: false, // 不提取注释到单独文件})],splitChunks: {chunks: 'all',cacheGroups: {vendor: {test: /[\\/]node_modules[\\/]/,name: 'vendors',chunks: 'all',}}}},plugins: [// Gzip压缩new CompressionPlugin({algorithm: 'gzip',test: /\.(js|css|html|svg)$/,threshold: 8192,minRatio: 0.8})]
};

Vite压缩配置

Vite的配置相对简单一些,但效果同样出色:

import { defineConfig } from 'vite';
import { resolve } from 'path';
import viteCompression from 'vite-plugin-compression';export default defineConfig({build: {minify: 'terser',terserOptions: {compress: {drop_console: true,drop_debugger: true,},},rollupOptions: {output: {manualChunks: {vendor: ['vue', 'vue-router'],utils: ['lodash', 'axios']}}}},plugins: [viteCompression({algorithm: 'gzip',ext: '.gz'})]
});

这套配置在我的项目中通常能减少30-50%的bundle体积。特别是开启Gzip后,文本文件的压缩率能达到70%以上。

1.3 异步加载:让首屏只加载必要的内容

异步加载的核心思想是:首屏只加载用户立即需要看到的内容,其他的延后加载。

路由懒加载

这是最基础也是最有效的优化方式:

import { createRouter, createWebHistory } from 'vue-router';// 传统同步加载(不推荐)
// import Home from '../views/Home.vue';
// import About from '../views/About.vue';const routes = [{path: '/',name: 'Home',// 路由懒加载component: () => import('../views/Home.vue')},{path: '/about',name: 'About',// 使用webpack魔法注释指定chunk名称component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')},{path: '/dashboard',name: 'Dashboard',// 预加载:在空闲时间预先加载component: () => import(/* webpackChunkName: "dashboard", webpackPrefetch: true */ '../views/Dashboard.vue')}
];export default createRouter({history: createWebHistory(),routes
});

组件懒加载

对于页面内的非关键组件,也可以采用懒加载:

<template><div><h2>首屏内容</h2><!-- 首屏可见内容 --><div class="above-fold"><p>重要内容立即显示</p></div><!-- 懒加载组件 --><Suspense><template #default><AsyncChart v-if="showChart" :data="chartData" /></template><template #fallback><div class="loading">图表加载中...</div></template></Suspense></div>
</template><script setup>
import { ref, onMounted } from 'vue';// 异步组件
const AsyncChart = defineAsyncComponent({loader: () => import('./Chart.vue'),delay: 200,timeout: 3000,errorComponent: () => import('./ErrorComponent.vue'),loadingComponent: () => import('./LoadingComponent.vue')
});const showChart = ref(false);
const chartData = ref([]);// 延迟加载非关键内容
onMounted(() => {// 首屏渲染完成后再加载图表setTimeout(() => {showChart.value = true;loadChartData();}, 100);
});const loadChartData = async () => {// 异步加载数据const { default: chartModule } = await import('../utils/chartData.js');chartData.value = chartModule.getData();
};
</script>

图片懒加载

图片往往是页面中最大的资源,懒加载效果特别明显:

// 原生Intersection Observer实现
class LazyLoader {constructor() {this.observer = new IntersectionObserver(this.handleIntersection.bind(this),{rootMargin: '50px 0px', // 提前50px开始加载threshold: 0.1});}observe(element) {this.observer.observe(element);}handleIntersection(entries) {entries.forEach(entry => {if (entry.isIntersecting) {const img = entry.target;const src = img.dataset.src;if (src) {img.src = src;img.removeAttribute('data-src');this.observer.unobserve(img);}}});}
}// 使用示例
const lazyLoader = new LazyLoader();// 为所有懒加载图片添加观察
document.querySelectorAll('img[data-src]').forEach(img => {lazyLoader.observe(img);
});

1.4 升级到体积更小的新版本

很多第三方库在新版本中都会优化体积,支持更好的tree-shaking。定期检查依赖版本是个好习惯。

xlsx库优化案例

这是一个典型的例子,新版本的xlsx库体积减少了60%:

// 旧版本(不推荐)- xlsx@0.12.x
// import XLSX from 'xlsx'; // 整个库约600KB// 新版本(推荐)- xlsx@0.18.x+
import { read, write, utils } from 'xlsx'; // 按需引入,减少约60%体积// 进一步优化:只引入需要的功能
import { read } from 'xlsx/dist/xlsx.mini.min.js'; // 最小化版本class ExcelHandler {// 读取Excel文件static async readFile(file) {return new Promise((resolve, reject) => {const reader = new FileReader();reader.onload = (e) => {try {const data = new Uint8Array(e.target.result);const workbook = read(data, { type: 'array' });const worksheet = workbook.Sheets[workbook.SheetNames[0]];const jsonData = utils.sheet_to_json(worksheet);resolve(jsonData);} catch (error) {reject(error);}};reader.readAsArrayBuffer(file);});}// 导出Excel文件static exportToExcel(data, filename = 'export.xlsx') {const worksheet = utils.json_to_sheet(data);const workbook = utils.book_new();utils.book_append_sheet(workbook, worksheet, 'Sheet1');// 使用动态导入减少初始bundle体积import('xlsx').then(XLSX => {XLSX.writeFile(workbook, filename);});}
}export default ExcelHandler;

日期库优化

moment.js虽然功能强大,但体积太大了。day.js是个很好的替代方案:

// 替换moment.js(约67KB)为day.js(约2KB)
// import moment from 'moment'; // 不推荐import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
import utc from 'dayjs/plugin/utc';// 按需加载插件
dayjs.extend(relativeTime);
dayjs.extend(utc);export const formatDate = (date, format = 'YYYY-MM-DD') => {return dayjs(date).format(format);
};export const getRelativeTime = (date) => {return dayjs(date).fromNow();
};// 体积对比:
// moment.js: ~67KB (gzipped: ~22KB)
// day.js: ~2KB (gzipped: ~1KB)
// 减少约97%的体积

这个替换在我的项目中节省了65KB的体积,而且API几乎一样,迁移成本很低。

1.5 能自己实现就不用第三方库

对于一些简单功能,自己实现往往比引入整个库更划算。

自实现常用工具函数

lodash很好用,但如果你只用几个函数,完全可以自己实现:

// 替代lodash的常用函数// 防抖函数 - 替代lodash.debounce
export const debounce = (func, wait, immediate = false) => {let timeout;return function executedFunction(...args) {const later = () => {timeout = null;if (!immediate) func.apply(this, args);};const callNow = immediate && !timeout;clearTimeout(timeout);timeout = setTimeout(later, wait);if (callNow) func.apply(this, args);};
};// 节流函数 - 替代lodash.throttle
export const throttle = (func, limit) => {let inThrottle;return function(...args) {if (!inThrottle) {func.apply(this, args);inThrottle = true;setTimeout(() => inThrottle = false, limit);}};
};// 深拷贝 - 替代lodash.cloneDeep
export const deepClone = (obj) => {if (obj === null || typeof obj !== 'object') return obj;if (obj instanceof Date) return new Date(obj.getTime());if (obj instanceof Array) return obj.map(item => deepClone(item));if (typeof obj === 'object') {const clonedObj = {};for (const key in obj) {if (obj.hasOwnProperty(key)) {clonedObj[key] = deepClone(obj[key]);}}return clonedObj;}
};// 数组去重 - 替代lodash.uniq
export const unique = (arr) => [...new Set(arr)];// 对象属性获取 - 替代lodash.get
export const get = (obj, path, defaultValue = undefined) => {const keys = path.split('.');let result = obj;for (const key of keys) {if (result == null || typeof result !== 'object') {return defaultValue;}result = result[key];}return result !== undefined ? result : defaultValue;
};// 体积对比:
// lodash完整版: ~70KB (gzipped: ~25KB)
// 自实现常用函数: ~2KB (gzipped: ~1KB)
// 减少约96%的体积

简单动画替代方案

如果只是做一些简单的动画效果,没必要引入整个动画库:

// 替代复杂动画库的轻量级解决方案// 简单的缓动函数
const easing = {linear: t => t,easeInQuad: t => t * t,easeOutQuad: t => t * (2 - t),easeInOutQuad: t => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t
};// 通用动画函数
export const animate = ({from,to,duration = 300,easeType = 'easeOutQuad',onUpdate,onComplete
}) => {const startTime = performance.now();const easeFn = easing[easeType] || easing.linear;const step = (currentTime) => {const elapsed = currentTime - startTime;const progress = Math.min(elapsed / duration, 1);const easedProgress = easeFn(progress);const currentValue = from + (to - from) * easedProgress;onUpdate(currentValue);if (progress < 1) {requestAnimationFrame(step);} else {onComplete && onComplete();}};requestAnimationFrame(step);
};// 使用示例
// animate({
//   from: 0,
//   to: 100,
//   duration: 500,
//   onUpdate: (value) => {
//     element.style.opacity = value / 100;
//   }
// });

1.6 编写代码时就考虑体积

写代码的时候稍微注意一下,就能减少不少体积。

代码优化技巧

// 1. 使用更短的变量名(在压缩前)
// 不推荐
const userInformationData = getUserData();
const processedUserInformation = processUserData(userInformationData);// 推荐
const userData = getUserData();
const processed = processUserData(userData);// 2. 避免重复代码,提取公共函数
// 不推荐
const validateEmail = (email) => {const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;return emailRegex.test(email);
};const validatePhone = (phone) => {const phoneRegex = /^1[3-9]\d{9}$/;return phoneRegex.test(phone);
};// 推荐
const createValidator = (regex) => (value) => regex.test(value);
const validateEmail = createValidator(/^[^\s@]+@[^\s@]+\.[^\s@]+$/);
const validatePhone = createValidator(/^1[3-9]\d{9}$/);// 3. 使用对象解构减少重复访问
// 不推荐
const processUser = (user) => {console.log(user.name);console.log(user.email);console.log(user.age);return {displayName: user.name,contact: user.email,years: user.age};
};// 推荐
const processUser = ({ name, email, age }) => {console.log(name, email, age);return {displayName: name,contact: email,years: age};
};// 4. 使用模板字符串替代字符串拼接
// 不推荐
const createMessage = (name, action, time) => {return 'User ' + name + ' performed ' + action + ' at ' + time;
};// 推荐
const createMessage = (name, action, time) => `User ${name} performed ${action} at ${time}`;// 5. 合理使用三元运算符
// 不推荐
let status;
if (user.isActive) {status = 'active';
} else {status = 'inactive';
}// 推荐
const status = user.isActive ? 'active' : 'inactive';

Tree Shaking优化

确保你的代码支持Tree Shaking,这样打包工具就能自动去除未使用的代码:

// 确保代码支持Tree Shaking// 1. 使用ES6模块语法
// 推荐
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const multiply = (a, b) => a * b;// 不推荐
// module.exports = {
//   add: (a, b) => a + b,
//   subtract: (a, b) => a - b,
//   multiply: (a, b) => a * b
// };// 2. 避免导入整个模块
// 不推荐
import * as utils from './utils';
utils.add(1, 2);// 推荐
import { add } from './utils';
add(1, 2);// 3. 避免副作用
// 不推荐(有副作用,无法被tree shake)
console.log('Module loaded');
export const helper = () => {};// 推荐(纯函数,可以被tree shake)
export const helper = () => {};

1.7 去除大的base64体积

base64编码会让文件体积增加33%,对于大文件来说这个开销很大。

图片资源优化

// 1. 避免大图片的base64编码
// 不推荐
const largeImageBase64 = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQ...' // 几百KB的base64// 推荐:使用CDN或静态资源
const imageUrl = 'https://cdn.example.com/images/large-image.webp';// 2. 小图标可以考虑base64(<2KB)
const smallIcon = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQi...';// 3. 动态加载图片
const loadImage = (src) => {return new Promise((resolve, reject) => {const img = new Image();img.onload = () => resolve(img);img.onerror = reject;img.src = src;});
};// 4. 图片压缩和格式优化
const optimizeImage = async (file, quality = 0.8) => {return new Promise((resolve) => {const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');const img = new Image();img.onload = () => {// 计算压缩后的尺寸const maxWidth = 1920;const maxHeight = 1080;let { width, height } = img;if (width > maxWidth) {height = (height * maxWidth) / width;width = maxWidth;}if (height > maxHeight) {width = (width * maxHeight) / height;height = maxHeight;}canvas.width = width;canvas.height = height;// 绘制并压缩ctx.drawImage(img, 0, 0, width, height);canvas.toBlob(resolve, 'image/webp', quality);};img.src = URL.createObjectURL(file);});
};

Webpack配置优化

在webpack配置中,可以设置只有小于2KB的图片才转为base64:

module.exports = {module: {rules: [{test: /\.(png|jpe?g|gif|svg)$/i,type: 'asset',parser: {dataUrlCondition: {maxSize: 2 * 1024 // 只有小于2KB的图片才转为base64}},generator: {filename: 'images/[name].[hash:8][ext]'}},{test: /\.(woff2?|eot|ttf|otf)$/i,type: 'asset/resource',generator: {filename: 'fonts/[name].[hash:8][ext]'}}]}
};

小结

通过这六个方面的优化,我们可以大幅减少首屏资源体积:

  1. 打包工具压缩:减少30-50%的bundle体积
  2. 异步加载:减少首屏阻塞时间50-70%
  3. 库版本更新:减少60-90%的第三方库体积
  4. 自实现替代:减少90%以上的不必要依赖
  5. 代码优化:减少10-20%的代码体积
  6. base64优化:减少33%的图片传输体积

这些优化措施的综合效果通常可以将首屏资源体积减少60-80%,页面加载速度提升明显。

在实际项目中,我建议按照优先级来实施:先做异步加载和打包压缩,这两个效果最明显;然后考虑替换大体积的第三方库;最后再优化代码细节。

下一篇文章我们会讲首屏速度优化的其他方面,包括资源加载策略和缓存优化。

http://www.dtcms.com/a/389629.html

相关文章:

  • SQL 条件函数 IF、CASE WHEN 用法速查
  • 【深度学习新浪潮】如何估算大模型的训练和推理内存需求?
  • PyTorch查看模块/类的所有方法/属性
  • 8大Android开发框架效率翻倍
  • docker基础知识与具体实践
  • 【多模态】Simple o3 提高多模态模型准确率
  • hybrid的配置
  • 理解虚拟细胞:初学者指南
  • 哪种体量的公司或者哪些行业哪些项目需要上云服务器?
  • Linux安装问题:404 Not Found?配置源列表sources.list,修改为可用镜像源就可以了!
  • Vue3 中 props 与 $emit 的使用及 defineProps 与 defineEmits 的区别详解
  • vue的跨域配置
  • 计算机网络实验03:交换机VLAN配置
  • Vue中v-if与v-show的区别及应用场景解析
  • C++造轮子:手搓 List 容器
  • redis-list的基本介绍
  • ​​[硬件电路-247]:开关电源的工作原理、优缺点及应用场合
  • 【面试】Java中的垃圾回收算法详解
  • AI使用心得-完善中
  • rust编写web服务01-项目起步与环境准备
  • ORM框架及SQLAlchemy
  • 驱动开发---双机调试搭建支持win11(2025)
  • 驱动开发1:内核程序框架
  • 生产制造如何应对客户的订单变更
  • 深入浅出SpringMVC:从入门到实战指南
  • 深度学习入门:从感知机到多层感知机,用逻辑电路讲透神经网络的进化
  • macos m1 芯片无法安装kubeedge keadm 解决办法
  • 猎板 PCB:以全维度工艺突破,构建 PCB 制造技术壁垒
  • android12 SDK31 wifi开发(仅提供连接wifi的工具类)
  • Android播放视频适配黑边问题类型总结