最近项目打包的时候发现打包后的文件夹占用内层巨大,项目的加载速度也是有可见的延迟,于是想尽办法优化包,最终从几十M压到了几M,趁此机会总结一波优化方法,仅供参考。
# 前端优化 ## 优化打包速度、加载速度、首屏体验,降低包size 1. 图片压缩 - 大尺寸图片大小尽量控制在 250kb 以下 小尺寸图片尽量控制在 50kb 以下 - 在线压缩工具:http://www.bejson.com/ui/compress_img/ 2. 字体包压缩 - 字体包通常很大 但是项目文件中使用该字体的往往就几个字 - 安装 font-spider 可以把需要的字符抽取出来生成字体文件 + `npm install font-spider -g` + 新建一个文件夹用于生成新的字体文件 + 将需要使用字体的字符写入html任意标签中 并且引用原字体文件 + `font-spider ./demo/*.html` 使用命令解析这个html文件 就会生成新的字体包 3. 防止编译文件中出现map文件 - 打包后产生后缀名为.map的文件是由于配置了sourcemap选项生成的,打包后的文件不容易找到出bug对应的源代码的位置,sourcemap就是来帮我们解决这个问题的,有了map就可以像未压缩的代码一样,准确的输出是哪一行哪一列有错。 - 去config/index.js中改一个参数就行 `productionSourceMap: false` 4. 使用CDN - 在 webpack.base.conf.js 配置 需要避免打包的依赖 - 在 index.html 引入相应的cdn路径 - 解决 打包时间长、打包后的体积过大、服务器网络不稳定或者宽带不高 ```javascript externals: { 'vue': 'Vue', 'vue-router': 'VueRouter', 'element-ui': 'ELEMENT', 'echarts': 'echarts', 'vuex': 'Vuex' }, ``` 5. 使用gzip压缩 - 去config/index.js中改一个参数 `productionGzip: true` - 安装 `npm install --save-dev compression-webpack-plugin@1.1.11` - 再打包就会生成 .gz 的压缩包文件(需服务器配合使用) 6. 清扫代码 - 使用Webpack的UglifyJsPlugin插件,压缩代码、删除console.log等调试语句、删除单行/多行/文档注释、删除sourceMap、copyright等 - 再 webpack.prod.conf.js 中设置 ```javascript new UglifyJsPlugin({ uglifyOptions: { comments: false, show_copyright: false, compress: { warnings: false, drop_debugger: true, drop_console: true } }, sourceMap: false, parallel: true }), ``` 7. 路由使用懒加载 - 当定位到相应路由时才加载组件页面 - 极大优化了 __首屏加载__ 过慢的问题 - 在路由配置中使用`component: (resolve) => require(['./XXX.vue'], resolve)` 8. momentJS - moment带有很多的语言包 将中文包过滤出来 在 webpack.prod.conf.js 的 plugins中加入 `new webpack.ContextReplacementPlugin(/moment[/\]locale$/, /zh-cn/),` 大约会减少200kb - 如果只用了moment的极少功能 如 format() 可以自己实现简易版的函数代替 ```javascript // 简易版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); }; ``` 9. UI框架 - 必须按需加载 无法按需加载的采用所需功能的替代方案 10. 首屏加载视觉优化 - 在入口 index.html 中加入骨架屏或者loading动画或者可跳转的导航栏 - 可以消除白屏 争取更多加载时间 ## 主流框架性能优化 1. 使用 immutable.js 优化深层树渲染 避免不必要的渲染 + React - 关键是对 shouldComponentUpdate 的控制 - diff是比较虚拟节点树 对不同的节点进行更新再整体覆盖真实节点执行render函数 虽然避免了大量的DOM操作但是要渲染整个节点树 而immutable对象因为是不可变的 当顶层render执行时 那些没有改变的节点就不会触发他们自身的render函数 从而大幅提升性能 - React.PureComponent 在只有一层 state 和 props 时 会自动进行浅比较 从而控制shouldComponentUpdate - 深层 state 和 props 就要用到 immutable.js 如果对象树中一个节点发生变化 只修改这个节点和受它影响的父节点,其它节点则进行共享 - 参考文章 https://github.com/camsong/blog/issues/3 ```javascript 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; } ``` ## 其他建议 1. 使用 webpack 模块打包器 从多方面进行优化 2. 尽可能减少请求次数 避免不必要的重复的请求 3. 预加载即将呈现的内容 推迟加载当前不需要的内容 4. 减少 DOM元素数量 `document.getElementsByTagName('*').length` 可获取页面元素数量 5. CSS 避免使用 @import 6. 移动端 避免空的图像来源