从度娘到处搜刮的一些前端优化的手段,主要是一些对首屏加载的优化已经减小打包文件大小的措施。
目录
优化首屏加载体验
在js等文件尚未加载完成之前在页面展示一些信息
- 在入口 index.html 中加入骨架屏或者loading动画或者可跳转的导航栏,可以消除白屏 争取更多加载时间
- 框架中的渲染机制是在js、css等资源加载完成之后再渲染App到根节点上,在 index.html 中加入的HTML代码是不对被打包处理的,会在浏览器请求页面之后直接显示在浏览器中
路由使用懒加载
- 当定位到相应路由时才加载组件页面,极大缩短了资源加载时间,优化了 首屏加载 过慢的问题
- vue:比较简单,直接在创建路由的时候使用
const XXX = () => import('./XXX.vue')
引入组件,再放入路由配置选项component: XXX
中- 注意:必须先将组件引入变量中再放入component,直接使用以下方式无效果。
component: () => import('./XXX.vue')
- 注意:必须先将组件引入变量中再放入component,直接使用以下方式无效果。
- react
- 利用高阶组件和import()实现:基于webpack4.x和react-router5.x实现路由react路由懒加载
//lazyRouter.jsx import React from 'react' function LazyRouter(componentfn) { class LazyloadComponent extends React.Component { constructor(props) { super(props); this.state = { component: null } } async componentDidMount() { //注意参考文档中使用的componentWillMount将被废除 const { default: component } = await componentfn(); this.setState({ component }) } render() { const C = this.state.component; return C ? <C {...this.props} /> : null; } } return LazyloadComponent } export default LazyRouter //使用 const Home = LazyRouter(() => import('./pages/Home')) //... <Route path="/home" component={Home}>
- lazyload-loader 插件
module: { rules: [ { test: /\.(js|jsx)$/,, use: [ 'babel-loader', 'lazyload-loader' //注意位置 ] }] } //...... // 使用lazy! 前缀 代表需要懒加载的Router import Shop from 'lazy!./src/view/Shop'; // Router 正常使用 <Route path="/shop" component={Shop} />
减小webpack打包后的文件大小
图片压缩
- 大尺寸图片大小尽量控制在 250kb 以下 小尺寸图片尽量控制在 50kb 以下
- 在线压缩工具
- 对于那种小图标 尽量使用 iconfont 代替图片
字体包压缩
- 字体包通常很大 但是项目文件中使用该字体的往往就几个字
- 安装 font-spider 可以把需要的字符抽取出来生成字体文件
npm install font-spider -g
- 新建一个文件夹用于生成新的字体文件
- 将需要使用字体的字符写入html任意标签中 并且引用原字体文件
font-spider ./demo/*.html
使用命令解析这个html文件 就会生成新的字体包
防止编译文件中出现map文件
- 打包后产生后缀名为.map的文件是由于配置了sourcemap选项生成的,打包后的文件不容易找到出bug对应的源代码的位置,sourcemap就是来帮我们解决这个问题的,有了map就可以像未压缩的代码一样,准确的输出是哪一行哪一列有错。
- 去config/index.js中改一个参数就行
productionSourceMap: false
使用CDN
- 解决 打包时间长、打包后的体积过大、服务器网络不稳定或者宽带不高引起的页面加载过慢的问题
- 在 index.html 引入相应的cdn路径,在 webpack.base.conf.js 配置 需要避免被打包的依赖
externals: {
'vue': 'Vue',
'vue-router': 'VueRouter',
'element-ui': 'ELEMENT',
'echarts': 'echarts',
'vuex': 'Vuex'
},
清扫代码
- 使用Webpack的UglifyJsPlugin插件,压缩代码、删除console.log等调试语句、删除单行/多行/文档注释、删除sourceMap、copyright等
- 在 webpack.prod.conf.js 中设置
new UglifyJsPlugin({
uglifyOptions: {
comments: false,
show_copyright: false,
compress: {
warnings: false,
drop_debugger: true,
drop_console: true
}
},
sourceMap: false,
parallel: true
}),
使用gzip压缩
- 去config/index.js中改一个参数
productionGzip: true
- 安装
npm install --save-dev compression-webpack-plugin@1.1.11
- 再打包就会生成 .gz 的压缩包文件(需服务器配合使用)
UI框架
- 必须按需加载 无法按需加载的采用所需功能的替代方案
momentJS
- moment带有很多的语言包 将中文包过滤出来 在 webpack.prod.conf.js 的 plugins中加入
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /zh-cn/),
大约会减少200kb - 如果只用了moment的极少功能 如 format() 可以自己实现简易版的函数代替
// 简易版moment代替moment.js
class Moment {
date
constructor(arg = new Date().getTime()) {
this.date = new Date(arg);
}
padStart(num) {
num = String(num);
if (num.length < 2) {
return '0' + num;
} else {
return num;
}
}
unix() {
return Math.round(this.date.getTime() / 1000);
}
static unix(timestamp) {
return new Moment(timestamp * 1000);
}
format(formatStr) {
const date = this.date;
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const week = date.getDay();
const hour = date.getHours();
const minute = date.getMinutes();
const second = date.getSeconds();
const weeks = ['一', '二', '三', '四', '五', '六', '日'];
return formatStr.replace(/Y{2,4}|M{1,2}|D{1,2}|d{1,4}|h{1,2}|m{1,2}|s{1,2}/g, (match) => {
switch (match) {
case 'YY':
return String(year).slice(-2);
case 'YYY':
case 'YYYY':
return String(year);
case 'M':
return String(month);
case 'MM':
return this.padStart(month);
case 'D':
return String(day);
case 'DD':
return this.padStart(day);
case 'd':
return String(week);
case 'dd':
return weeks[week];
case 'ddd':
return '周' + weeks[week];
case 'dddd':
return '星期' + weeks[week];
case 'h':
return String(hour);
case 'hh':
return this.padStart(hour);
case 'm':
return String(minute);
case 'mm':
return this.padStart(minute);
case 's':
return String(second);
case 'ss':
return this.padStart(second);
default:
return match;
}
});
}
}
export const moment = (arg) => {
return new Moment(arg);
};
echarts 压缩
- 仅保留自己项目中需要的控件 能大幅缩减包大小
- 在线定制
主流框架性能优化
使用 immutable.js 优化深层树渲染 避免不必要的渲染
- 参考
- 关键是对 shouldComponentUpdate 的控制
- diff是比较虚拟节点树 对不同的节点进行更新再整体覆盖真实节点执行render函数 虽然避免了大量的DOM操作但是要渲染整个节点树 而immutable对象因为是不可变的 当顶层render执行时 那些没有改变的节点就不会触发他们自身的render函数 从而大幅提升性能
- React.PureComponent 在只有一层 state 和 props 时 会自动进行浅比较 从而控制shouldComponentUpdate
- 深层 state 和 props 就要用到 immutable.js 如果对象树中一个节点发生变化 只修改这个节点和受它影响的父节点,其它节点则进行共享
import { is } from 'immutable';
shouldComponentUpdate: (nextProps = {}, nextState = {}) => {
const thisProps = this.props || {}, thisState = this.state || {};
if (Object.keys(thisProps).length !== Object.keys(nextProps).length ||
Object.keys(thisState).length !== Object.keys(nextState).length) {
return true;
}
for (const key in nextProps) {
if (!is(thisProps[key], nextProps[key])) {
return true;
}
}
for (const key in nextState) {
if (thisState[key] !== nextState[key] && !is(thisState[key], nextState[key])) {
return true;
}
}
return false;
}
其他手段
- 使用 webpack 模块打包器 从多方面进行优化
- 尽可能减少请求次数 避免不必要的重复的请求
- 预加载即将呈现的内容 推迟加载当前不需要的内容
- 减少 DOM元素数量
document.getElementsByTagName('*').length
可获取页面元素数量 - CSS 避免使用 @import
- 移动端 避免空的图像来源