起因是这样的,自己做了一个网站,开发的时候好好的,部署到服务器上去后,打开的时候白屏了好长时间才展示内容, 这可不能忍,必须找出原因优化掉!
服务器配置
CPU:1核,内存:2GiB,带宽:1Mbps
这上来就找到原因了啊,这配置这么低,肯定慢啊,怎么办?
换!!!
然而贫穷像是一万多条无形的枷锁束缚住了我,让我换服务器的双手动弹不得。
此路不通,只能另寻他法解决了。
优化前首屏加载测试
测试结果分析
- 从截图可以看到,首屏加载耗时19.15秒,主要是chunk-vendors.2daba5b2.js这个文件加载耗时最长,为17.6秒,大小为1.9M,其他文件均在4秒内加载完毕。通常页面加载的一个文件大小超过300k,已经算比较大了。第二个比较耗时的文件是chunk-vendors.62bee483.css,这个应该是样式文件。其他的文件加载耗时都不超过1秒,所以后面优化先从那两个文件下手。
- 重新编译项目,看下项目生成的文件
可以看到前面提到的两个文件比较大,后面列出了每个文件使用gz压缩后的大小,但是浏览器实际并没有加载压缩后的文件,而是原始文件。再打开打包文件夹,发现实际生成的js文件夹中除了js文件,还有js.map文件,js.map文件通常用于开发环境调试用,方便我们查找错误,在生成环境是不需要用到的,而且都比较大,这也是一个优化的点。
分析项目依赖情况
运行vue ui,编译查看chunk-vendors中的结构发现,主要是element-ui依赖比较大,其次是vue和mavon-editor
整个项目的情况如下
那么如何优化呢
开启nginx压缩配置
修改nginx配置,启用gzip压缩
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
测试页面加载时间缩短到5.2秒,chunk-vendors.js传输大小为556k,加载时间为4秒,其他文件加载时间基本不超过200毫秒
生产配置不生成js.map
修改项目根目录中vue.config.js配置文件,设置productionSourceMap: false
module.exports = {
runtimeCompiler: true,
productionSourceMap: false
}
打包测试文件夹大小由9.1M减小到2.26M
配置gzip压缩插件
执行npm i compression-webpack-plugin@5.0.1 -D安装插件,在vue.config.js中修改打包配置
const CompressionPlugin = require("compression-webpack-plugin");
const productionGzipExt = /\.(js|css|json|txt|html|ico|svg)(\?.*)?$/i;
module.exports = {
runtimeCompiler: true,
productionSourceMap: false,
configureWebpack: () => {
if (process.env.NODE_ENV === "production") {
return {
plugins: [
new CompressionPlugin({
filename: "[path].gz[query]",
algorithm: "gzip",
test: productionGzipExt,
threshold: 1024, // 大于1024字节的资源才处理
minRatio: 0.8, // 压缩率要高于80%
deleteOriginalAssets: false, // 删除原始资源,如果不支持gzip压缩的浏览器无法正常加载则关闭此项
}),
],
};
}
},
};
插件需要指定版本,最新版本的会报错这个和nginx压缩配置感觉重复了,实际测试和nginx压缩配置的速度差不多,如果两个压缩都有,速度并没有提升
修改elementui组件按需引入
- 执行npm install babel-plugin-component -D安装 babel-plugin-component2. 修改.babelrc内容如下:
{
"presets": [["@babel/preset-env", { "modules": false}]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
- 在main.js中引入需要用到的组件,示例如下:
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "element-ui/lib/theme-chalk/index.css";
import mavonEditor from "mavon-editor";
import "mavon-editor/dist/css/index.css";
import axios from "axios";
import {
Avatar,
Button,
Container,
DatePicker,
Dialog,
Dropdown,
DropdownItem,
DropdownMenu,
Footer,
Form,
FormItem,
Header,
Image,
Input,
Main,
Message,
MessageBox,
Notification,
Option,
Select,
Table,
TableColumn,
TabPane,
Tabs,
Timeline,
TimelineItem,
} from "element-ui";
Vue.use(Button);
Vue.use(Dialog);
Vue.use(Dropdown);
Vue.use(DropdownMenu);
Vue.use(DropdownItem);
Vue.use(Input);
Vue.use(Select);
Vue.use(Table);
Vue.use(TableColumn);
Vue.use(DatePicker);
Vue.use(Form);
Vue.use(FormItem);
Vue.use(Tabs);
Vue.use(TabPane);
Vue.use(Header);
Vue.use(Main);
Vue.use(Footer);
Vue.use(Timeline);
Vue.use(TimelineItem);
Vue.use(Image);
Vue.use(Avatar);
Vue.use(Container);
Vue.use(Option);
Vue.use(mavonEditor);
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$axios = axios;
Vue.config.productionTip = false;
axios.interceptors.request.use(
(config) => {
config.url = "/api/" + config.url;
config.headers.token = sessionStorage.getItem("identityId");
return config;
},
(error) => {
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
if (response.data && response.data.exceptionCode) {
const exceptionType = response.data.exceptionType;
Notification({ title: response.data.exceptionMessage, type: exceptionType.toLowerCase() });
return Promise.reject(response.data);
}
return response;
},
(error) => {
return Promise.reject(error);
}
);
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
修改按需引入后elementui依赖大小约为1.3M
修改组件局部引入为异步组件
在一个组件中引入其他组件时使用异步的方式引入,如
export default {
components: {
register: () => import('./views/Register.vue'),
login: () => import('./views/Login.vue')
}
};
完成后此时chunk-vendors.js这个文件已经从优化前的1.9M缩小到890k
页面加载约3秒可以显示出来,其他资源在页面显示后继续后台加载,全部加载完总耗时约5秒,请求数68次
组件按组分块
使用命名chunk语法webpackChunkName: "块名"将某个路由下的组件打包在同一个异步块中,如
import Vue from "vue";
import VueRouter from "vue-router";
Vue.use(VueRouter);
const routes = [
{
path: "/",
redirect: 'home'
},
{
path: '/home',
component: () => import(/* webpackChunkName: "home-page" */ '../views/Home.vue')
},
{
path: '/documents',
component: () => import(/* webpackChunkName: "home-page" */ '../views/documents/DocumentList.vue')
},
{
path: '/documentcontent',
component: () => import(/* webpackChunkName: "home-page" */ '../views/documents/DocumentContent.vue')
},
{
path: '/write',
component: () => import(/* webpackChunkName: "home-page" */ '../views/WriteMarkdown.vue')
},
{
path: '/about',
component: () => import(/* webpackChunkName: "home-page" */ '../views/About.vue')
},
{
path: '/management',
component: () => import(/* webpackChunkName: "management" */ '../views/management/Management.vue'),
children: [
{ path: '', component: () => import(/* webpackChunkName: "management" */ '../views/management/ManagementOptions.vue') },
{ path: 'developplan', component: () => import(/* webpackChunkName: "management" */ '../views/management/DevelopmentPlan.vue') },
{ path: 'tags', component: () => import(/* webpackChunkName: "management" */ '../views/management/TagsManage.vue') },
{ path: 'documents', component: () => import(/* webpackChunkName: "management" */ '../views/management/DocumentsManage.vue') }
]
},
{
path: '/games',
component: () => import(/* webpackChunkName: "games" */ '../views/games/Games.vue'),
children: [
{ path: '', component: () => import(/* webpackChunkName: "games" */ '../views/games/GameList.vue') },
{ path: 'minesweeper', component: () => import(/* webpackChunkName: "games" */ '../views/games/minesweeper/MineSweeper.vue') }
]
},
{
path: '/tools',
component: () => import(/* webpackChunkName: "tools" */ '../views/tools/ToolsView.vue'),
children: [
{ path: '', component: () => import(/* webpackChunkName: "tools" */ '../views/tools/ToolsList.vue') },
{ path: 'imageconverter', component: () => import(/* webpackChunkName: "tools" */ '../views/tools/ImageConverter.vue') }
]
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes,
});
export default router;
打包编译后文件比之前要减少了一部分,并且合并后的文件资源也不大,完全可以接受
页面加载耗时基本没变,但是请求数减少到51次
总结
- nginx压缩对性能的提升最大,通过压缩文件缩短资源加载时间
- gzip压缩插件会将文件压缩成gz格式,暂时不知道怎么用
- elementui按需引入会减小依赖资源的大小,
chunk-vendors.js
文件体积会减小 - 使用异步组件可以在后台加载渲染不到的资源,优先加载渲染需要的资源,缩短页面响应时间,但同时会增加打包后的文件数量,导致页面请求数量增加。
- 组件按路由分组,打包的时候会将相同组名的资源打包到一个文件中,可以减小请求数