侧边栏壁纸
博主头像
小佑Blog| 小佑前端 | WEB前端博客 | WEB前端笔记 博主等级

未来属于那些相信梦想,并愿意为之付诸行动的人

  • 累计撰写 69 篇文章
  • 累计创建 92 个标签
  • 累计收到 67 条评论

目 录CONTENT

文章目录

Vite优化构建策略

@小佑前端
2024-04-02 / 0 评论 / 0 点赞 / 119 阅读 / 0 字 / 正在检测是否收录...

适合自己的构建方案远大于最好和更好的方案,
这可能是你在全网看到的最详细vite在开发环境构建策略教程了;

配置策略

配置路径别名

// vite.config.js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {join} from "path"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      "@": join(__dirname, "/src")
    }
  }
})

开发环境解决跨域

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {join} from "path"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [vue()],
  resolve: {
    alias: {
      "@": join(__dirname, "/src")
    }
  },
  server: {
    proxy: {
      // 代理所有 /api请求
      "/api": {
        target: "目标origin",
        // 改变请求的origin为target的值
        changeOrigin: true,
      }
    }
  }
})

配置环境变量

企业级项目,都会区分很多环境,供我们测试试用。不能让我们的测试数据去污染线上的数据。所以vite也提供了我们环境配置文件的方式,让我们很轻松的去通过一些环境选择对应的接口地址等等。

.env.[mode]的格式可以在不同模加载加载不同的内容。

环境加载优先级

一份用于指定模式的文件(例如 .env.production)会比通用形式的优先级更高(例如 .env)。

另外,Vite 执行时已经存在的环境变量有最高的优先级,不会被 .env 类文件覆盖。例如当运行 VITE_SOME_KEY=123 vite build 的时候。

.env 类文件会在 Vite 启动一开始时被加载,而改动会在重启服务器后生效。

我们可以在源码中通过import.meta.env.*的方式获取以VITE_开头的已加载的环境变量。

// .env.development
VITE_BASE_API = "/api"
// package.json
"scripts": {
    "dev": "VITE_BASE_API=/oop vite",
}

执行yarn dev后,我们可以发现,import.meta.env.VITE_BASE_API是命令行中指定的参数。
image-1691648051582

通用组件自动注册

vue的Globvite的Glob 导入功能:该功能可以帮助我们在文件系统中导入多个模块

const modules = import.meta.glob('./dir/*.js')
// 以上将会被转译为下面的样子:
const modules = {
  './dir/foo.js': () => import('./dir/foo.js'),
  './dir/bar.js': () => import('./dir/bar.js')
}

然后再通过vue提供的注册异步组件的方式进行引入,vue的defineAsyncComponent方法:该方法可以创建一个按需加载的异步组件 基于以上两个方法,实现组件自动注册。

// import SvgIcon from './svg-icon/index.vue'
// import HmPopup from './popup/index.vue'

import { defineAsyncComponent } from 'vue'

// const components = [SvgIcon, HmPopup]

export default {
  install(app) {
    // components.forEach((element) => {
    //   app.component(element.name, element)
    // })
    // 获取当前路径下所有文件夹下的index.vue
    const components = import.meta.glob('./*/index.vue')
    // 遍历获取到的组件模块
    for (let [key, component] of Object.entries(components)) {
      const componentName = 'hm-' + key.replace('./', '').split('/')[0]
      // 通过 defineAsyncComponent 异步导入指定路径下的组件
      app.component(componentName, defineAsyncComponent(component))
    }
  }
}

其实如果组件都提供了name属性,我们可以直接手动引入各组件模块,然后实现半自动注册。组件提供name的好处是,在vue-devtools中调试时方便查找各个组件。
在vue官网中,在 3.2.34 或以上的版本中,使用 <script setup> 的单文件组件会自动根据文件名生成对应的 name 选项,即使是在配合 <KeepAlive> 使用时也无需再手动声明。 但是对于我们文件名都为index.vue的开发者来说,就没办法了。

去除debugger、console

// vite.config.js
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue()
  ],
  build: {
    target: 'es2020',
    minify: 'terser',
    terserOptions: {
      compress: {
        // 生产环境时移除console
        drop_console: true,
        drop_debugger: true
      }
    }
  }
})

使用svg图标作为icon图标

首先我们需要封装一个通用的svg组件,来使用svg图标。

<template>
  <svg aria-hidden="true">
    <use :xlink:href="symbolId" :fill="color" :fillClass="fillClass" />
  </svg>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps({
  // 图标名称
  name: {
    type: String,
    required: true
  },
  // 颜色
  color: {
    type: String
  },
  // 类名
  fillClass: {
    type: String
  }
})

// 生成图标唯一id #icon-xxx
const symbolId = computed(() => `#icon-${props.name}`)
</script>

然后全局注册该svg通用组件,这里我们使用插件的方式

import SvgIcon from "./svg-icon/index.vue"

export default {
  install(app) {
    app.component("SvgIcon", SvgIcon)
  }
}

main.js中直接通过use注册后,即可使用。

    <svg-icon name="back"></svg-icon>

image-1691648896785
但是这样项目中并不能知道svg图标的路径,我们需要使用vite-plugin-svg-icons插件来指定查找路径。
在vite.config.js中配置svg相关内容

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import {join} from "path"
import {createSvgIconsPlugin} from "vite-plugin-svg-icons"

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue(),
    createSvgIconsPlugin({
      // 指定需要缓存的图标文件夹
      iconDirs: [join(__dirname, "/src/assets/icons")],
      // 指定symbolId格式,就是svg.use使用的href
      symbolId: "icon-[name]"
    })
  ],
})

在main.js中导入并注册svg-icons,他会把指定文件夹下的svg图片都注册在首页。

// 注册 svg-icons
import "virtual:svg-icons-register"

image-1691649012249

base公共基础路径

开发或生产环境服务的公共基础路径。可以是以下几种值:

  • 绝对 URL 路径,例如 /boot/
  • 完整的 URL,例如 https://boot.com/
  • 空字符串或 ./(用于嵌入形式的开发)
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path';

export default defineConfig({
    base: './'    // 开发或生产环境服务的公共基础路径
})

省略的扩展名

导入时想要省略的扩展名列表。默认为['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json']

// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path';

export default defineConfig({
    resolve: {
        extensions: ['.js', '.ts', '.jsx', '.tsx', '.json'],
    }
})

注意:不建议忽略自定义导入类型的扩展名(如:.vue),因为他会影响 IDE 和类型的支持。

指定服务器监听地址

指定服务器监听的的IP地址,默认值为localhost,只监听本地的127.0.0.1.当我们需要开发移动端项目时,需要在手机浏览器上访问当前项目,这个时候就需要将host的值设置为true或0.0.0.0,这样服务器就好监听所有地址,包括局域网和公网地址。

// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path';

export default defineConfig({
    server: {
        host: true    // 监听所有地址
    }
})

image-1691649383484
当手机和电脑处于同一网络环境下,我们就可以通过下面的那个地址进行访问了。

处理css

PostCSS是用来处理css的,可以通过添加各种插件来处理css。像浏览器样式兼容问题、浏览器适配问题等等,都可以通过使用PostCSS来解决。Vite 对PostCSS 有良好的支持,我们只需要安装相应的插件即可。如移动端适配可使用postcss-px-to-viewport对不同设备进行布局适配。

npm install postcss-px-to-viewport -D
// vite.config.ts
import { defineConfig } from 'vite'
import postcssPxToViewport from 'postcss-px-to-viewport'
import { resolve } from 'path';

export default defineConfig({
    css: {
        postcss: {
            plugins: {
                // viewport 布局适配
                postcssPxToViewport({
                    viewportWidth: 375
                })
            }
        }
    }
})

这样我们书写的 px 单位就会转为 vw 或 vh,很轻松就解决了手机不同设备的适配问题。

打包文件输出目录

指定打包文件的输出目录。默认为dist,当dist被占用或公司有统一的命名规范时,可以进行设置调整。

// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path';

export default defineConfig({
    build: {
        outDir: 'build'    // 打包文件的输出目录
    }
})

静态文件存放目录

指定静态文件的存放目录。默认为 assets, 可根据需要自行调整。

// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path';

export default defineConfig({
    build: {
        assetsDir: 'static'    // 静态文件的存放目录
    }
})

优化策略

Bundle 分析

借助插件 rollup-plugin-visualizer,来进行 bundle 分析

pnpm i rollup-plugin-visualizer -D
import { visualizer } from 'rollup-plugin-visualizer';
export default defineConfig({
  plugins: [vue(), visualizer({
    emitFile: false,
    file: "stats.html", //分析图生成的文件名
    open:true //如果存在本地服务端口,将在打包后自动展示
  })],
})

打包之后会在项目根目录生成 stats.html 文件打开

图片压缩

node version: >= 12.0.0
vite version: >=2.0.0

yarn add vite-plugin-imagemin -D
​ import viteImagemin from "vite-plugin-imagemin"

​ plugins: [vue(), viteImagemin()]

gzip 压缩

当前端资源过大时,服务器请求资源会比较慢。前端可以将资源通过Gzip压缩使文件体积减少大概60%左右,压缩后的文件,通过后端简单处理,浏览器可以将其正常解析出来。
如果浏览器的请求头中包含content-encoding: gzip,即证明浏览器支持该属性。
image-1691651830141

	npm i vite-plugin-compression -D

修改 vite.config.js 配置

 import { defineConfig } from 'vite
import viteCompression from 'vite-plugin-compression'
export default defineConfig({
  plugins:[
    viteCompression(
      {
        algorithm: 'gzip',
        threshold: 10240,
        verbose: false,
        deleteOriginFile: true
      }
    )
  ]
})

打包后就会生成 gzip 文件了,但是服务端 nginx 还需要配置一下才能生效

http {
    gzip_static on;
    gzip_proxied any;
}

配置项

可配置项名称 数据类型 默认值 释义
verbose boolean true 是否在控制台中输出压缩结果
filter RegExp or (file: string) => boolean /.(js mjs
disable boolean false 是否禁用
threshold number 1025 如果体积大于阈值,则进行压缩,单位为b
algorithm string gzip 压缩算法,可选[‘gzip’,‘brotliCompress’,‘deflate’,‘deflateRaw’]
ext string .gz 生成的压缩包的后缀
compressionOptions object - 对应压缩算法的参数
deleteOriginFile boolean - 压缩后是否删除源文件

分包策略

默认情况下,浏览器重复请求相同名称的静态资源时,会直接使用缓存的资源。利用这个机制我们可以将不会经常更新的代码单独打包成一个 JS 文件,这样就可以减少 HTTP 请求,同时降低服务器压力。以 lodash 为例:

npm i lodash

安装 lodash ,然后在 main.js 中写入以下代码:

// src/main.js
import { cloneDeep } from 'lodash'

const obj = cloneDeep({})

打包结果:
image-1691653571751
项目代码和依赖模块打包成了一个 JS 文件。接着我们来配置分包,修改底层的 Rollup 配置:

// vite.config.js
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: id => {
          // 将 node_modules 中的代码单独打包成一个 JS 文件
          if(id.includes('node_modules')) {
            return 'vendor'
          }
        }
      }
    }
  }
})

打包结果如下:
image-1691653616682
可以看到依赖模块已经单独生成一个JS文件了。这样我们即使修改了main.js中的代码重新打包,依赖文件vendor.528a7280.js也不会发生变化的,对于这个文件,浏览器也不会再次发起请求。如果依赖模块很多的话,性能是不是有很大的提升呢?

cdn 加速

内容分发网络(Content Delivery Network,简称 CDN)就是让用户从最近的服务器请求资源,提升网络请求的响应速度。通常我们请求依赖模块使用 CDN ,而请求项目代码依然使用自己的服务器。还是以 lodash 为例:

// src/main.js
import _ from 'lodash'

const obj = _.cloneDeep({})

使用 CDN 也比较简单,一个插件就可以搞定:

npm i vite-plugin-cdn-import -D
// vite.config.js
import { defineConfig } from 'vite'
import viteCDNPlugin from 'vite-plugin-cdn-import'

export default defineConfig({
  plugins: [
    viteCDNPlugin({
      // 需要 CDN 加速的模块
      modules: [
        {
          name: 'lodash',
          var: '_',
          path: `https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js`
        }
      ]
    })
  ]
})

构建成功后,Vite 会自动帮我们将 cdn 资源通过 script 标签插入到 html 中:
image-1691653867436

这样请求 lodash 资源就会产生加速 buff ,而且项目体积也会大大减小!

共享公共块

manualChunks 是 rollup 提供的一个配置项,也是最最最基础的优化配置项。
它可以将一些模块作为共享公共块单独打包,这样可以减少重复代码,使得打包出来的构建产物更小。
我们可以将 vue 和 element-ui 等模块抽离出去作为共享公共块,具体配置如下:
vue.config.js

// ...
defineConfig({
  build: {
    rollupOptions: {
      output: {
        manualChunks: {
          vue: ['vue'],
          echarts: ['echarts'],
          'element-ui': ['element-ui'],
        },
      },
    },
  },
});

按需引入Echarts

echarts 的按需引入方式与其版本有关,我们这里使用的是 5.x 版本,参考官网的引入方式可修改为如下所示:

import * as echarts from 'echarts/core';
import { CanvasRenderer } from 'echarts/renderers';
// 这里我们只引入了折线图、柱状图、饼图,如果你需要其他的图表,可以自行引入
import { LineChart, BarChart, PieChart } from 'echarts/charts';
// 同理,如果你需要其他的组件,也应当自行引入
import { TooltipComponent, TitleComponent, GridComponent, LegendComponent } from 'echarts/components';

echarts.use([
  LineChart,
  BarChart,
  PieChart,
  TooltipComponent,
  CanvasRenderer,
  TitleComponent,
  GridComponent,
  LegendComponent,
]);

// 最终我们导出的是一个 echarts 的实例,这样我们就可以在项目中使用了
export default echarts;

后续会持续更新。。。。。。

0

评论区