赞
踩
目录
移除 preload(预载) 与 prefetch (预取)
vue 脚手架默认开启了 preload 与 prefetch,对于小项目可以提升体验感,但当我们项目很大时,首屏加载就会很慢很慢。
先简单了解一下 preload 与 prefetch。
preload 与 prefetch 都是一种资源预加载机制;
preload 是预先加载资源,但并不执行,只有需要时才执行它;
prefetch 是意图预获取一些资源,以备下一个导航/页面使用;
preload 的优先级高于 prefetch。
配置文件:vue.config.js
- chainWebpack: config => {
- // 移除 preload(预载) 插件
- config.plugins.delete('preload')
- // 移除 prefetch(预取) 插件
- config.plugins.delete('prefetch')
- }
安装:npm install webpack-bundle-analyzer --save-devg
- const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
-
-
- chainWebpack: config => {
- // 添加资源可视化工具
- config.plugins.push(
- new BundleAnalyzerPlugin()
- )
- }
它可以查看资源模块的体积分布,然后可以对应做优化处理。
安装:npm install compression-webpack-plugin --save-dev
- // gzip压缩插件
- const CompressionWebpackPlugin = require('compression-webpack-plugin')
-
- chainWebpack: config => {
- // 添加gzip压缩插件
- config.plugins.push(
- new CompressionWebpackPlugin(
- {
- filename: info => {
- return `${info.path}.gz${info.query}`
- },
- algorithm: 'gzip',
- threshold: 5120, // 只有大小大于该值的资源会被处理 10240
- // test: new RegExp('\\.(' + ['js'].join('|') + ')$'),
- test: /\.js$|\.html$|\.json$|\.css/,
- minRatio: 0.8, // 只有压缩率小于这个值的资源才会被处理
- deleteOriginalAssets: false // 删除原文件
- }
- )
- )
- }
开启gzip压缩,需要配置nginx,打开nginx.config文件,写入以下(在http块内或者在单个server块里添加)
- #开启gzip
- gzip on;#低于1kb的资源不压缩
- gzip_min_length 1k;#压缩级别1-9,越大压缩率越高,同时消耗cpu资源也越多
- gzip_comp_level 9;#需要压缩哪些响应类型的资源,多个空格隔开。不建议压缩图片.
- gzip_types text/plain application/javascript application/x-javascript text/javascript text/xml text/css;#配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
- gzip_disable "MSIE [1-6]\.";
- #是否添加“Vary: Accept-Encoding”响应头
- gzip_vary on;
开启gzip压缩,服务器为tomcat,修改server.xml文件
- <Connector
- port="8080"
- protocol="HTTP/1.1"
- connectionTimeout="20000"
-
- compression="on"
- compressableMimeType="text/html,text/xml,text/javascript,text/css,text/plain,application/javascript"
- useSendfile="false"/>
- // compression="on" 打开压缩功能
- // compressableMimeType="text/html,text/xml" 压缩类型
- // useSendfile="false" 设置该属性将会压缩所有文件,不限阙值,不然可能按照阙值部分压缩
启用gzip压缩打包之后,会变成下面这样,自动生成gz包。目前大部分主流浏览器客户端都是支持gzip的,就算小部分非主流浏览器不支持也不用担心,不支持gzip格式文件的会默认访问源文件的,所以不要配置清除源文件。
配置好之后,打开浏览器访问线上,F12查看控制台,如果该文件资源的响应头里显示有Content-Encoding: gzip,表示浏览器支持并且启用了Gzip压缩的资源
- chainWebpack: config => {
- config
- .when(process.env.NODE_ENV !== 'development',
- config => {
- config
- .plugin('ScriptExtHtmlWebpackPlugin')
- .after('html')
- .use('script-ext-html-webpack-plugin', [{
- // `runtime` must same as runtimeChunk name. default is `runtime`
- inline: /runtime\..*\.js$/
- }])
- .end()
- config
- .optimization.splitChunks({
- chunks: 'all',
- cacheGroups: {
- libs: {
- name: 'chunk-libs',
- test: /[\\/]node_modules[\\/]/,
- priority: 10,
- chunks: 'initial' // 当为initial时,只能分离初始化的模块,异步的模块无法分离
- },
- elementUI: {
- name: 'chunk-elementUI', // 将 elementUI 拆分为单个包
- priority: 20, // 权重需要大于 libs 和 app 否则会被打包到 libs 或 app 中
- test: /[\\/]node_modules[\\/]_?element-ui(.*)/ // 匹配文件
- },
- echarts: {
- name: 'chunk-echarts', // 将 echarts 拆分为单个包
- priority: 20,
- test: /[\\/]node_modules[\\/]_?echarts(.*)/
- },
- commons: {
- name: 'chunk-commons',
- test: resolve('src/components'), // 可以自定你的规则
- minChunks: 3, // 当某个模块满足minChunks引用次数时,才会被打包。
- priority: 5,
- reuseExistingChunk: true // 默认为false,关闭表示拆分出复用部分的模块,给双方引用
- }
- }
- });
- // 最小化代码
- config.optimization.minimize(true);
- // 如果我们要在一个 HTML 页面上使用多个入口时,还需设置 optimization.runtimeChunk: 'single',否则还会遇到这里所述的麻烦。
- // https://bundlers.tooling.report/code-splitting/multi-entry/
- // https:// webpack.js.org/configuration/optimization/#optimizationruntimechunk
- config.optimization.runtimeChunk('single')
- }
- );
- }
chunks:可用值为“all”、“async”、“initial”,默认值是“async”
all:拆分同步和异步文件
async:只对异步文件作处理
initial:只对入口文件进行拆分,不去处理异步文件里的模块
minSize:控制最小包的大小,大于这个值才会去拆分,
如果拆分的公共模块小于这个大小,那就复制成多份,
直接打包到引用该模块的包里
minChunks:模块的重复调用次数大于等于minChunks值时,就会满足这项拆包条件,
但只看入口模块导入的,不看动态加载模块中导入的(import(‘…’)),
即使设置的chunks为“all”。
maxInitialRequests:入口文件最大请求的文件数量(import等方式)
入口文件本身算一个请求
入口文件动态加载的模块不算在内
通过runtimeChunk拆分出来的runtime文件不算在内
只算js,css不算在内
如果同时有两个模块满足cacheGroups的拆分规则,
但maxInitialRequests只允许再拆分一个,那么会拆出体积
更大的那个模块。
maxAsyncRequests:用于限制异步模块内部的并行最大请求数
import文件本身算一个请求
只算js、css不算在内
如果同时有两个模块满足cacheGroups的规则需要拆分,
但maxAsyncRequests只允许拆分一个时,那么会拆出体积更大的那个模块。
name:主要用于分离chunks后的名字,可以是字符串或者函数,
相同name会合并成一个chunk
splitChunks.cacheGroups
可以继承或者重写splitChunks对象下的属性,但是test、priority和reuseExistingChunk
只能配置在cacheGroup对象中
每个添加的对象都有默认配置,如果想禁用此配置,可以将其设置为false
项目打包时会根据依赖关系自动打包压缩依赖文件,当依赖文件过大,会导致首屏加载变慢
使用CDN: 简单来说就是可以使用CDN引入依赖库,减少依赖包体积,释放服务器压力, 它由距离最近的缓存服务器直接响应,提高加载速度
进一步考虑:当我们在开发环境下,使用CDN引入会比我们直接引入依赖要慢,所以配置CDN需要只在生产环境
配置步骤:
- // 环境区分主要为开发环境与其他环境(其他:生产,uat,测试等等)
- const isNotDevelopMentEnv = process.env.NODE_ENV !== 'development'
- const cdnData = {
- css: [
- 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/theme-chalk/index.css'
- ],
- js: [
- 'https://cdn.bootcdn.net/ajax/libs/vue/2.6.10/vue.min.js',
- 'https://cdn.bootcdn.net/ajax/libs/axios/0.19.2/axios.min.js',
- 'https://cdn.bootcdn.net/ajax/libs/vuex/3.1.0/vuex.min.js',
- 'https://cdn.bootcdn.net/ajax/libs/vue-router/3.0.6/vue-router.min.js',
- 'https://cdn.bootcdn.net/ajax/libs/element-ui/2.13.0/index.js',
- 'https://cdn.bootcdn.net/ajax/libs/jquery/1.12.1/jquery.min.js',
- 'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/vee-validate.min.js',
- 'https://cdn.bootcdn.net/ajax/libs/vee-validate/2.0.0-rc.21/locale/zh_CN.js'
- ],
- /**
- * 属性名称 vue, 表示遇到 import xxx from 'vue'
- * 这类引入 'vue'的,不去 node_modules 中找,而是去找全局变量 Vue
- * 其他的为VueRouter、Vuex、axios、ELEMENT、echarts,注意全局变量是一个确定的值,不能修改为其他值,修改为其他大小写或者其他值会报错
- */
- externals: {
- 'vue': 'Vue',
- 'vuex': 'Vuex',
- 'vue-router': 'VueRouter',
- 'element-ui': 'ELEMENT',
- 'vuex': 'Vuex',
- 'axios': 'axios',
- 'vee-validate': 'VeeValidate',
- 'jQuery':"jquery",
- 'jquery': 'window.$'
- }
- }
-
- // 在configureWebpack中添加externals
- configureWebpack: {
- externals: isNotDevelopMentEnv ? cdnData.externals : {}
- }
-
- // 在chainWepack中添加如下
- if (isNotDevelopMentEnv) {
- config.plugin('html')
- .tap(args => {
- args[0].cdn = cdnData
- return args
- })
- }
修改 public/index.html(根据环境,开发环境不使用CDN,生产环境才开放)
- <html>
- <head>
- <!-- 样式文件优先加载 -->
- <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.css) { %>
- <link href="<%= htmlWebpackPlugin.options.cdn.css[i] %>" rel="stylesheet">
- <% } %>
- </head>
- <body>
- <div id="app"></div>
- <!-- js加载 -->
- <% for (var i in htmlWebpackPlugin.options.cdn && htmlWebpackPlugin.options.cdn.js) { %>
- <script src="<%= htmlWebpackPlugin.options.cdn.js[i] %>"></script>
- <% } %>
- </body>
- </html>
采用CDN引入后,不需要删除原有依赖引入,因为在本地还是使用这些依赖进行调试的,打包后因为有CDN所以不会把这些依赖引入所以不用担心,import引入的不需要变更。例如main.js中使用import ElementUI from 'element-ui', 以上的代码已经实现在开发环境会设置不适用CDN,会使用依赖包文件;当发布到生产环境,因为我们已经在vue.config.js的externals中指代了element-ui,所以这个语句也是有效的可以直接使用CDN elementUI
易出错点:
Router is not defined
解决方案: 将Router 改为 'VueRouter'
Uncaught TypeError: Illegal constructor
解决方案:修改externals 中‘'element-ui’的value为:ELEMENT
安装:npm install useless-files-webpack-plugin --save-dev
- // 导入插件
- const UselessFile = require('useless-files-webpack-plugin')
-
-
- chainWebpack: config => {
- config.plugin('uselessFile')
- .use(
- new UselessFile({
- root: './src', // 项目目录
- out: './fileList.json', // 输出文件列表
- clean: false, // 是否删除文件,
- exclude: [/node_modules/] // 排除文件列表
- })
- )
- }
单页面应用的首屏加载较慢主要是因为单页面应用在首屏时,无论是否需要都会加载所有的模块,可通过按需加载、路由懒加载来优化。
动态加载,通过import来实现路由的动态加载,这个时候对于路由的加载是动态的,用到再加载。
- {、
- path:"/home",
- name:"home",
- component: () => import(/* webpackChunkName: "home" */ "@/views/home.vue"))
- }
按需加载,通过对路由文件的配置,来对相关模块划分区间,如登录界面可以和首页、主页面划分一块,在进入首屏时,只对首屏所在的区块进行加载。通过require.ensure()来将多个相同类的组件打包成一个文件。如示例代码,打包时,将两个组件打包成一个js文件,文件名为home
- {
- path: '/login', //path路径
- name: 'login', //组件名
- component: r => require.ensure([], () => r(require('../components/login')), 'home') //good类型的组件
- },
- {
- path: '/home', //path路径
- name: 'home', //组件名
- component: r => require.ensure([], () => r(require('../components/home')), 'home') //good类型的组件
- }
通常路由权限实现可以通过路由守卫 router.beforeEach,配合使用 router.addRoutes 动态添加路由
- import router from './router'
- import store from './store'
-
- router.beforeEach(async(to, from, next) => {
- const hasToken = getToken()
-
- if (hasToken) {
- if (to.path === '/login') {
- next({ path: '/' })
- } else {
- // 判断用户是否通过 getInfo 获取了自己的角色权限
- const hasRoles = store.getters.roles && store.getters.roles.length > 0
- if (hasRoles) {
- next()
- } else {
- try {
- // 获取用户角色
- const { roles } = await store.dispatch('user/getInfo')
-
- // 获取用户具有权限的路由
- const accessRoutes = await store.dispatch('permission/getUserMenus', roles)
- // 动态添加可访问的路由
- router.addRoutes(accessRoutes)
-
- // 确保addRoutes完整的hack方法
- // 设置 replace: true,因此导航不会留下历史记录
- next({ ...to, replace: true })
- } catch (error) {
- // 移除token,进入登录页面重新登录
- await store.dispatch('user/resetToken')
- console.error(error)
- next(`/login?redirect=${to.path}`)
- }
- }
- }
- }
- })
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。