构建功能-vite、rollup

一、vite基础

1. 简介

vite 是一个构建工具,相较于 webpackvite 采用了不同的运行方式:

开发阶段:

在开发阶段,Vite 的工作流程如下:

  1. 依赖预构建

Vite 会先将项目中的第三方依赖(如 node_modules 中的包)进行预构建。它使用了 esbuild 这个超快的构建工具进行依赖的打包,将其他规范的代码转换成esmodule规范,然后放到当前目录下的node_modules/.vite/deps,同时对esmodule规范的各个模块进行统一集成。

预构建的目的是通过 esbuild 将 CommonJS 和 UMD 格式的模块转换为 ESM 格式(主要是node_modules中的第三方依赖),这样浏览器就可以直接加载这些依赖。

解决了:

  1. 不同的第三方包会有不同的导出格式(vite没法约束第三方包)
  2. 对路径的处理可以直接使用node_modules/.vite/deps,方便路径重写
  3. 网络多包传输的性能问题(也是原生esmodule规范不敢支持node_modules的原因之一,这样会导致网络加载很多依赖),有了依赖预构建以后无论有多少额外的export和import,vite都会尽可能的将他们进行集成,最后只生成一个或几个模块
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
  optimizeDeps: {
    exclude: ['lodash-es'] // 当遇到lodash-es这个依赖时不进行依赖预构建
  }
})
  1. 按需加载

当你在开发时请求一个模块时,Vite 会根据请求动态地加载该模块,并通过 HTTP 服务返回给浏览器。因为是 ESM 格式,浏览器会根据模块的导入顺序按需加载依赖,而无需一次性加载所有内容。

  1. HMR(热模块替换)

Vite 提供了极快的热模块替换机制。它会在你修改代码后只替换那些真正变化的模块,而不是重新打包整个项目。这样一来,开发体验更为流畅。

构建(打包)阶段:

在构建阶段,Vite 则会采用传统的打包方式,将项目的所有资源打包成可以用于生产环境的静态文件。Vite 默认使用 Rollup 作为打包工具,因此你可以享受到 Rollup 提供的所有优化特性。

Vite 的构建过程主要包括以下几步:

  1. 依赖分析

Vite 会分析项目中的所有模块依赖关系,生成模块依赖图。这些依赖关系将帮助 Vite 决定哪些模块需要打包在一起,哪些模块可以分包。

  1. 代码拆分

基于依赖分析的结果,Vite 会对代码进行拆分,将共享的模块提取到单独的 chunk 中。这种方式可以实现更好的缓存利用和并行加载。

  1. 优化

Vite 会使用 Rollup 的各种优化插件对代码进行优化,比如 tree-shaking(移除未使用的代码)、代码压缩、资源内联等,以生成更小、更高效的最终产物。

  1. 资源处理

Vite 会处理静态资源(如图片、CSS、字体等),将它们打包并优化。对于大文件,Vite 会将其拆分成小块,以支持按需加载。

  1. 输出

最终,Vite 会生成一组静态文件,通常包括一个或多个 JavaScript 文件、CSS 文件以及其他资源文件。这些文件可以直接部署到生产环境中。

2. vite相比webpack的优势

起因:当我们开始构建越来越大型的应用时,需要处理的 JavaScript 代码量也呈指数级增长。包含数千个模块的大型项目相当普遍。基于 JavaScript 开发的工具就会开始遇到性能瓶颈:通常需要很长时间(甚至是几分钟!)才能启动开发服务器,即使使用模块热替换(HMR),文件修改后的效果也需要几秒钟才能在浏览器中反映出来。如此循环往复,迟钝的反馈会极大地影响开发者的开发效率和幸福感。(来自vite官网)

当项目越大,构建工具(webpack)所需要处理的js越多(跟webpack的工作流程有关),就会导致需要很长时间才能启动开发服务器

3. 基础用法

  1. 安装 vite
npm i vite -D
  1. 根路径下创建 index.html 文件
DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0"
    />
    <title>vitetitle>
  head>
  <script
    defer
    type="module"
    src="./index.js"
  >script>
  <body>body>
html>

需要使用模块化的方式引入 index.js 文件,否则会报错

vite 的源码目录就是根目录

  1. index.js 文件中引入 css 文件
import './style/index.css'

const h1 = document.createElement('h1')
h1.innerText = 'hello vite!!!'
document.body.appendChild(h1)

console.log('hello vite')
  1. 开发模式启动 vite
npx vite
  1. 命令

vite: 启动 vite 开发服务器

vite build: 打包 vite 项目

vite preview: 预览 vite 打包后的代码

4. vite配置文件语法提示

  1. 方法1:使用defineConfig
import { defineConfig } from "vite"

export default defineConfig({
    build: {
        rollupOptions: {
            input: {
                index: "index.js",
            }
        }
    }
})
  1. 方法2:使用/** @type {import('vite').UserConfig} */注释
/** @type {import('vite').UserConfig} */
const viteConfig = {
    build: {
        rollupOptions: {
            input: {
                index: "index.js",
            }
        }
    }
}

export default viteConfig

5. vite开发环境与生产环境区分

文件有:

  • vite.config.js
  • vite.base.config.js
  • vite.dev.config.js
  • vite.prod.config.js

后三个文件配置:根据不同环境分别配置

import { defineConfig } from "vite"
export default defineConfig({})

这里的defineConfig也可以使用函数,函数返回配置对象,函数解构的参数有commandmode

  • command:buildserve
  • mode:developmentproduction
import { defineConfig } from "vite"
import viteBaseConfig from "./vite.base.config";
import viteDevConfig from "./vite.dev.config";
import viteProdConfig from "./vite.prod.config";

export default defineConfig(({ command, mode }) => {
    // command: 'build' or 'serve'
    // mode: 'development' or 'production'
    if (command === 'serve') {
        // 开发环境
        return {
            ...viteBaseConfig,
            ...viteDevConfig,
        }
    } else {
        // 生产环境
        return {
            ...viteBaseConfig,
            ...viteProdConfig,
        }
    }
})

使用策略模式进行重构:

import { defineConfig } from 'vite'
import viteBaseConfig from './vite.base.config'
import viteDevConfig from './vite.dev.config'
import viteProdConfig from './vite.prod.config'

// 策略模式
const envResolver = {
  build: () => ({ ...viteBaseConfig, ...viteProdConfig }),
  serve: () => ({ ...viteBaseConfig, ...viteDevConfig })
}

export default defineConfig(({ command, mode }) => {
  // command: 'build' or 'serve'
  return envResolver[command]()
})

6. vite环境变量处理

6.1 什么是环境变量?

会根据当前的代码环境产生值的变化的变量

代码环境:

  • 开发环境
  • 测试环境
  • 生产环境
  • 预发布环境
  • 灰度环境

6.2 在vite中的环境变量处理

(1)、原理

vite内使用dotenv这个库,dotenv会自动读取.env文件,并解析这个文件中对应的环境变量,并将其注入到process对象下(node进程对象),会涉及到一些vite.config.js中的一些配置:

  • root:项目根目录(index.html 文件所在的位置)。可以是一个绝对路径,或者一个相对于该配置文件本身的相对路径。默认值:process.cwd()
  • envDir:用来配置当前环境变量的文件地址
.env                # 所有情况下都会加载
.env.local          # 所有情况下都会加载,但会被 git 忽略
.env.[mode]         # 只在指定模式下加载
.env.[mode].local   # 只在指定模式下加载,但会被 git 忽略

但是vite考虑到和其他配置冲突问题,它不会直接注入到process对象下,可以手动调用viteloadEnv来手动确认env文件。

// vite.config.js
import { defineConfig, loadEnv } from 'vite'
import viteBaseConfig from './vite.base.config'
import viteDevConfig from './vite.dev.config'
import viteProdConfig from './vite.prod.config'

// 策略模式
const envResolver = {
  build: () => ({ ...viteBaseConfig, ...viteProdConfig }),
  serve: () => ({ ...viteBaseConfig, ...viteDevConfig })
}

export default defineConfig(({ command, mode }) => {
  /**
   * 当前env的文件目录
   * - 参数1:当前模式,dev/build
   * - 参数2:env路径
   * - 参数3:env前缀
   */
  const env = loadEnv(mode, process.cwd(), '')
  console.log('env', env) // 这个时候.env.production中的环境变量就被加到env中了,但是process.env访问不到

  return envResolver[command]()
})

注意:loadEnv(mode, process.cwd(), ''),这里的mode是脚本中vite --mode develpment,省略默认为develpoment

当我们调用loadEnv的时候,会做如下事情:

  1. 直接找到.env文件不解释,并解析其中的环境变量,放进一个对象里
  2. 会将传进来的mode这个变量的值进行拼接:.env.development,并根据我们提供的目录(提供的目录是指第二个参数传递的process.cwd())去取对应的配置文件并进行解析,并放进一个对象
  3. 将后者对象合并前者
const baseEnvConfig = {...xxx}
const modeEnvConfig = {...xxx}
const lastEnvConfig = {...baseEnvConfig, ...modeEnvConfig}

**node环境中:**例如在vite.config.js中,通过const env = loadEnv(mode, process.cwd(), '')来读取

**在客户端中:**vite会将对应的环境变量注入到import.meta.env.xxx中,但是vite做了一层拦截,如果你的环境变量不是以VITE_开头的,就不会注入到客户端中(node中可以得到)。例如:import.meta.env.VITE_APP_KEY

  • .env:所有环境需要用到的环境变量
  • .env.development:开发环境需要用到的环境变量(默认情况下vite将开发环境取名为development,所有dotenv才能读到.env.development
  • .env.production:生产环境

补充知识:为什么vite.config.js可以书写成esm规范?

因为vite在读取这个vite.config.js的时候会率先node去解析文件语法,如果发现你是esm规范,会直接将你的esm规范进行替换成commonjs规范

(2)、使用
  1. node环境使用

具体参考上面原理

/**
 * 当前env的文件目录
 * - 参数1:当前模式,dev/build
 * - 参数2:env路径
 * - 参数3:env前缀
 */
const env = loadEnv(mode, process.cwd(), '')
console.log('env', env) // 这个时候.env.production中的环境变量就被加到env中了,但是process.env访问不到
  1. 客户端使用

注意:需要添加VITE_前缀

import.meta.env.VITE_APP_KEY
// .env.development
VITE_APP_KEY=123

如果想要更改这个前缀,可以使用envPrefix配置

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

export default defineConfig({
  envPrefix: "WIFI_", // 配置vite注入客户端环境变量校验的前缀
})

7. vite是如何让浏览器可以识别.vue文件(原理)

vite搭建了一个开发服务器,这时候的vue文件是已经经过编译后的js了,通过开发服务器设置浏览器解析文件的方式(设置Content-Type)来加载。

res.set("Content-Type", "text/javascript") // 让浏览器以js的方式解析vue文件
const koa = require('koa');
const fs = require('fs');
const path = require('path');
const app = new koa();

app.use(async (ctx) => {
    if (ctx.request.url === '/') {
        const htmlContent = await fs.promises.readFile(path.resolve(__dirname, 'index.html'));
        ctx.response.body = htmlContent;
        ctx.response.set('Content-Type', 'text/html');
    }
    if (ctx.request.url.endsWith('.js')) {
        const JSContent = await fs.readFileSync(path.resolve(__dirname, 'main.js'), 'utf-8');
        console.log(JSContent);
        ctx.response.body = JSContent;
        ctx.response.set('Content-Type', 'application/javascript');
    }
});

app.listen(3000, () => console.log('listening on port 3000'));

8. vite中对css以及css模块化的简单处理(原理)

vite天生就支持对css的处理

处理css原理:

  1. vite在main.js中读取到了index.css
  2. 直接去使用fs模块去读取index.css中的文件内容
  3. 创建style标签,将css文件内容直接copy进style标签中
  4. 将style标签插入到index.html的head中
  5. 将该css文件中的内容直接替换为js脚本(方便热更新或css模块化)

css模块化原理(css module):

  1. xxx.module.css(module是一种约定,表示需要开启css模块化)
  2. 会将类名进行一定规则的替换(例如:将header 替换成_heaedr_i122st_1
  3. 同时创建一个映射对象 { header: '_heaedr_i122st_1' }
  4. 将替换过后的内容塞进style标签中放入head标签
  5. 将xxx.module.css内容进行全部抹除,替换成js脚本
  6. 将创建的映射对象,在js中默认导出

原理都是:开发服务器设置Content-Type

9. vite配置文件中css配置流程(modules篇)

在vite.config.js中我们通过css属性中的module去控制整个vite对css的处理行为

  1. localsConvention:修改生成的配置对象的key的展示形式(驼峰还是中横线)

    • camelCase:驼峰和中横线都生成

    • camelCaseOnly:只展示驼峰命名

    • dashes:驼峰和中横线都生成,默认是驼峰

    • dashesOnly:只有中横线

  2. scopeBehaviour:配置当前的模块化行为是模块化还是全局化(类名带hash的就是开启了模块化)

    • local:模块化
    • global:全局化
  3. generateScopedName:生成模块化类名的格式

    • 方式1:字符串
    • 方式2:返回一个函数,函数的返回值为显示的类型
export default defineConfig({
  css: { // 对css进行配置
    modules: { // 对css模块化的默认行为进行覆盖
      // 方法1:
      generateScopedName: "[name]__[local]___[hash:base64:5]", // 生成模块化类名的格式,格式在postcss官网中
      // 方法2:
      generateScopedName: (name, filepath, css) => {
        console.log(name, filepath, css);
        return "123" // 所有的类名都为123了
      }
    }
  }
})
  1. hashPrefix:生成哈希值的前缀,会参与hash的运算
  2. globalModulePaths:全局css的存放路径,会作为全局的css,不会被模块化
globalModulePaths: ["/src/styles/global.css"]

示例:

import { defineConfig } from "vite"

export default defineConfig({
  css: { // 对css进行配置
    modules: { // 对css模块化的默认行为进行覆盖
      localsConvention: "camelCase", // 修改生成的配置对象的key的展示形式(驼峰还是中横线)
      scopeBehaviour: "local", // 配置当前的模块化行为是模块化还是全局化
      generateScopedName: "[name]__[local]___[hash:base64:5]", // 生成模块化类名的格式,格式在postcss官网中
      hashPrefix: "prefix", // 生成哈希值的前缀,会参与hash的运算
      globalModulePaths: ["/src/styles/global.css"] // 全局css的存放路径,会作为全局的css,不会被模块化
    }
  }
})

10. vite配置文件中css配置流程(preprocessorOptions篇)

preprocessorOptions:主要是用来配置css预处理的一些全局参数,key(预处理器名) + config形式配置

less、sass:css预处理器

postcss:css后处理器

import { defineConfig } from "vite"

export default defineConfig({
  css: { // 对css进行配置
    preprocessorOptions: { // 对css进行预处理,比如less,scss等
      less: { // 整个配置对象都会最终给到less的执行参数中去(例如:在没有构建功能的情况下,通过less的命令去构建,命令的参数就是配置的对象)
        math: "always",
        globalVars: { // 全局变量,可以在所有less文件中直接使用,在webpack给less的loader中配置
          mainColor: "red", // less中使用: color: @mainColor;
        }
      },
      sass: {
      }
    },
    devSourcemap: true // 开启css的sourceMap,默认为false
  },
})

11. 使用postcss

vite天生支持postcss,postcss主要做css样式兼容的、前缀补全,后处理器

  1. 安装
npm i postcss postcss-cli -D
  1. 书写描述文件

格式很多,以postcss.config.js为例

npm i postcss-preset-env -D # 安装预设
// postcss.config.js
const postcssPresetEnv = require('postcss-preset-env')
module.exports = {
    plugins: [
        postcssPresetEnv()
    ]
}

12. vite配置文件中css配置流程(postcss篇)

直接在css.postcss中进行配置,该属性直接配置的就是postcss配置,和postcss.config.js一样

import { defineConfig } from 'vite'
const postcssPresetEnv = require('postcss-preset-env')

export default defineConfig({
  css: {
    postcss: {
      plugins: [postcssPresetEnv()]
    }
  }
})

13. vite对静态资源的处理

import img from './assets/img.png' // url
import imgUrl from './assets/img.png?url' // url
import imgRaw from './assets/img.png?raw' // buffer
console.log(img, imgUrl); // /src/assets/img.png
console.log(imgRaw); // 图片的 buffer

14. vite别名配置(使用+原理)

14.1 基本使用

import { defineConfig } from "vite"

export default defineConfig({
    resolve: {
        alias: {
            "@": "./src",
          	"@assets": "./src/assets"
        },
    },
})

14.2 resolve.alias原理

alias别名最终做的事情就是做字符串替换

  1. index.js

在7中讲的vite开发服务器如下:

const koa = require('koa');
const fs = require('fs');
const path = require('path');
const app = new koa();

// 导入vite.config.js配置
const viteConfig = require('./vite.config');
// utils方法:做路径替换的
const aliasResolve = require('./aliasResolver');

app.use(async (ctx) => {
    if (ctx.request.url === '/') {
        const htmlContent = await fs.promises.readFile(path.resolve(__dirname, 'index.html'));
        ctx.response.body = htmlContent;
        ctx.response.set('Content-Type', 'text/html');
    }
    if (ctx.request.url.endsWith('.js')) {
        const JSContent = await fs.readFileSync(path.resolve(__dirname, '.', 'main.js'), 'utf-8');
        // 直接进行 alias 的替换
        const lastResult = aliasResolve(viteConfig.resolve.alias, JSContent);
        ctx.response.body = lastResult;
        ctx.response.set('Content-Type', 'application/javascript');
    }
});

app.listen(3000, () => console.log('listening on port 3000'));
  1. aliasResolver.js
module.exports = function(aliasConf, JSContent) {
    const entires = Object.entries(aliasConf);
    let lastContent = JSContent;
    entires.forEach(entire => {
        const [alia, path] = entire;
        // vite会做path路径的处理(这里直接写死src)
        const srcIndex = path.indexOf('/src');
        const realPath = path.slice(srcIndex, path.length);
        // alias别名最终做的事情就是做字符串替换
        lastContent = JSContent.replace(alia, realPath);
    })
    return lastContent;
}
  1. 在main.js中(作为前端html引入的js)
import '@/test.js' // 会被替换成 import '/src/test.js'
console.log('main');

15. vite配置文件中对静态资源在生产环境的一些配置

import { defineConfig } from "vite"

export default defineConfig({
  base: "./", // 配置静态资源路径
  build: {
    rollupOptions: { // 配置rollup的一些构建策略
      input: { // 输入配置
        index: "index.js",
      },
      output: { // 输出配置
        // hash:文件名和文件内容生成签名
        entryFileNames: "[name].js", // 入口文件名
        assetFileNames: "[hash]-[name].[ext]", // 资源文件名
      },
    },
    assetsInlineLimit: 4096, // 默认是4kb,这里修改为0,不进行图片转base64,大于4kb的图片不转base64
    outDir: "dist", // 输出目录名
    assetsDir: "static", // 静态资源目录名,如果output设置了entryFileNames和assetFileNames,则不会生效
  },
})

16. vite插件理念了解

  1. 插件是什么?

vite会在不同的生命周期中去调用不同的插件以达到不同的目的

vite的插件必须返回给vite一个配置对象

17. vite常用插件:vite-aliases

vite-aliases:可以帮我们自动生成别名:检测当前目录下的,包括src在内的所有文件夹,并帮助我们生成别名

  1. 安装依赖
npm i [email protected] -D
  1. 使用
import { defineConfig } from "vite"
import { ViteAliases } from 'vite-aliases'

export default defineConfig({
    plugins: [
        ViteAliases()
    ],
})

18. 自定义插件:vite-aliases(原理)

手写vite-aliases其实就是在执行配置文件之前,去修改配置文件的resolve.alias

可以通过vite的config钩子,去修改配置文件

config

  • 类型: (config: UserConfig, env: { mode: string, command: string }) => UserConfig | null | void

在解析 Vite 配置前调用。钩子接收原始用户配置(命令行选项指定的会与配置文件合并)和一个描述配置环境的变量,包含正在使用的 modecommand。它可以返回一个将被深度合并到现有配置中的部分配置对象,或者直接改变配置(如果默认的合并不能达到预期的结果)。

文档:https://cn.vite.dev/guide/api-plugin.html#config

const path = require('path')
const fs = require('fs')

// 将文件和目录区分开
function diffDirAndFile(dirFilesArr = [], basePath = '') {
  const result = {
    dirs: [],
    files: []
  }
  dirFilesArr.forEach((name) => {
    const currentFileStat = fs.statSync(
      path.resolve(__dirname, basePath + '/' + name)
    )
    if (currentFileStat.isDirectory()) {
      result.dirs.push(name)
    } else {
      result.files.push(name)
    }
  })

  return result
}

// 遍历目录,生成别名obj
function getTotalSrcDir() {
  const result = fs.readdirSync(path.resolve(__dirname, '../src'))
  const diffResult = diffDirAndFile(result, '../src')
  const aliasResolveObj = {}

  diffResult.dirs.forEach((dirName) => {
    const key = `@${dirName}`
    const absPath = path.resolve(__dirname, '../src', dirName)
    aliasResolveObj[key] = absPath
  })

  return aliasResolveObj
}

module.exports = () => ({
  config: (config, env) => {
    // config:目前的配置对象
    // env:mode: string(development/production), command: string(build/serve)
    let aliasResolveObj = getTotalSrcDir()
    // 追加配置
    return {
      resolve: {
        alias: aliasResolveObj
      }
    }
  }
})

19. vite常用插件:vite-plugin-html

作用:动态控制html内容,使用的是ejs语法

  1. 安装依赖
npm i vite-plugin-html -D
  1. 配置vite.config.js
import { defineConfig } from "vite"
import { createHtmlPlugin } from 'vite-plugin-html'
export default defineConfig({
    plugins: [
        createHtmlPlugin({
            inject: {
                data: {
                    title: 'Vite + React'
                }
            }
        })
    ],
})
  1. 配置模版(在index.html中配置<%= title %>
DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title><%= title %>title>
head>
<body>
    <script type="module" src="./index.js">script>
body>
html>

20. 自定义插件:vite-plugin-html(原理)

通过transformIndexHtml这个钩子来实现,作用是转换html的

  • 类型: IndexHtmlTransformHook | { order?: 'pre' | 'post', handler: 返回html内容 }

文档:https://cn.vite.dev/guide/api-plugin.html#transformindexhtml

  1. plugins/CreateHtmlPlugin.js
module.exports = (options) => ({
    // 写法1:
    transformIndexHtml: (html, ctx) => {
        return html.replace(/<%= title %>/g, options.inject.data.title);
    },
    // 写法2:
    transformIndexHtml: {
        order: 'pre', // 在构建 html 前执行
        handler(html, ctx) {
            return html.replace(/<%= title %>/g, options.inject.data.title);
        }
    }
})
  1. vite.config.js
import { defineConfig } from 'vite'
import CreateHtmlPlugin from './plugins/CreateHtmlPlugin'

export default defineConfig({
  plugins: [
    CreateHtmlPlugin({
      inject: {
        data: {
          title: '首页'
        }
      }
    })
  ]
})

21. vite常用插件:vite-plugin-mock

用于模拟数据

  1. 安装依赖
npm i vite-plugin-mock mockjs -D
  1. vite.config.js
import { defineConfig } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'

export default defineConfig({
  plugins: [
    viteMockServe({
      mockPath: './mock',
      enable: true, // 是否启用 mock 功能
      localEnabled: true // 是否开启开发环境
    })
  ]
})
  1. /mock/index.js
import mockJS from 'mockjs'

const userList = mockJS.mock({
    // "字段名|个数"
    'list|100': [{
        name: '@cname', // 生成中文名
        'id|+1': 1, // id自增
        email: '@email',
        age: '@integer(18, 30)',
        address: '@city',
        createtime: '@datetime'
    }]
})

export default [
    {
        method: 'get',
        url: '/api/users',
        response: () => {
            return {
                code: 200,
                data: userList
            }
        }
    }
]

22. 自定义插件:vite-plugin-mock(原理)

使用configureServer这个钩子:是用于配置开发服务器的钩子

  • 类型: (server: ViteDevServer) => (() => void) | void | Promise<(() => void) | void>

文档:https://cn.vite.dev/guide/api-plugin.html#configureserver

只实现了vite-plugin-mock,其他的使用和上面21一样

const fs = require('fs')
const path = require('path')

module.exports = (options) => {
  return {
    configureServer(server) {
      const mockStat = fs.statSync('mock')
      const isDirectory = mockStat.isDirectory()
      let mockResult = []
      if (isDirectory) {
        mockResult = require(path.resolve(process.cwd(), 'mock/index.js'))
      }
      // 服务器相关配置
      server.middlewares.use((req, res, next) => {
        const mockItem = mockResult.find(item => item.url === req.url)
        if (mockItem) {
          const responseData = mockItem.response(req)
          res.end(JSON.stringify(responseData))
        } else {
          next()
        }
      })
    }
  }
}

23. 插件总结

  1. vite自带的:(rollup没有)
  • config:在解析 Vite 配置前调用(在自定义插件vite-aliases中)
  • configResolved:存储最终解析的配置
  • configureServer:用于配置开发服务器的钩子(在自定义插件vite-plugin-mock中)
  • configurePreviewServer:与 configureServer相同,但用于预览服务器
  • transformIndexHtml:转换 index.html 的专用钩子。钩子接收当前的 HTML 字符串和转换上下文(在自定义插件vite-plugin-html中)
  • handleHotUpdate:执行自定义 HMR 更新处理
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [
    {
      // 在解析 Vite 配置前调用
      config(config) {},
      // 当整个配置文件被解析并合后调用
      configResolved(config) {},
      // 用于配置开发服务器的钩子
      configureServer(server) {},
      // 与 configureServer 相同,但用于预览服务器
      configurePreviewServer(server) {},
      // 转换 index.html 的专用钩子。钩子接收当前的 HTML 字符串和转换上下文
      transformIndexHtml(html) {},
      // 自定义热更新,覆盖vite的热更新
      handleHotUpdate({ server, modules, timestamp }) {},
    }
  ]
})
  1. rollup和vite共用的:

文档:https://cn.vite.dev/guide/api-plugin.html#universal-hooks

  • options:
  • buildStart:和fullRollupOptions功能一样
import { defineConfig } from 'vite'

export default defineConfig({
  build: {
    rollupOptions: {
      output: {}
    }
  },
  plugins: [
    {
      // 配置 Rollup 打包选项(和上面的 build.rollupOptions 中的配置一样)
      options(rollupOptions) {},
      // 和configResolved一样
      buildStart(fullRollupOptions) {}
    }
  ]
})

24. vite和ts结合

  1. 安装vite-plugin-checker,ts错误,dev代码就会报错

vite.config.js中使用:

import { defineConfig } from 'vite'
import checker from 'vite-plugin-checker'

export default defineConfig({
  plugins: [
    checker({
      typescript: true
    })
  ]
})
  1. 对打包进行ts校验
"build": "tsc --noEmit && vite build"
  1. 对环境变量的类型提示

在ts的声明文件中:

// 三斜线指令:会引入 vite/client
/// 

interface ImportMetaEnv {
  readonly VITE_APP_TITLE: string
  // 更多环境变量...
}

二、vite性能优化

1. 性能优化概括

性能优化包括:

  • 开发时的构建速度优化:npm run dev的一瞬间到呈现结果要占用多少时长
  • 页面性能指标:和怎样写代码有关
    • 首屏渲染时间:fcp(first content paint),有人也称为页面第一个元素渲染时长
      • 懒加载:与写代码有关
      • http优化:强缓存 和 协商缓存
        • 强缓存:服务端给响应头追加一些字段(expires),在expires截止失效时间之前,无论如何刷新页面,都不会重新请求
        • 协商缓存:当服务端标记上协商缓存后,客户端再下次请求需要发送请求到服务器,由服务器判断是否需要缓存获取
    • 页面中最大元素的时长:lcp(largest content paint)
  • js逻辑:
    • 要注意副作用的清除
    • 尽量使用按需导入,可以tree-shaking
  • 打包构建优化:vite(rollup)、webpack
    • 优化体积:压缩,tree-shaking,图片资源压缩,cdn加载,分包

2. 分包策略

来源于浏览器缓存策略,当文件的文件名没有发生变化,就不会从新去请求,所以打包后的文件名一般是hash名,对于第三方库的包,是不需要在每次打包都重新加载的,就可以将这些包分离出去。

分包就是把一些不会常规更新的文件,进行单独打包处理

使用rollupOptions.output.manualChunks(需要的是一个对象)进行代码拆分

import { defineConfig } from "vite"

export default defineConfig({
    build: {
        minify: false, // 关闭压缩
        rollupOptions: {
            output: {
                // manualChunks: {
                //     lodash: ['lodash'] // 输出的文件名:单独打包的包
                // },
                // 拆分代码
                manualChunks: (id) => {
                    if (id.includes('node_modules')) {
                        return 'vendor'
                    }
                }
            }
        }
    }
})

3. gzip压缩

gzip压缩:将所有的静态文件进行压缩,已达到减少体积的目的。但是浏览器也是需要解压时间的,如果不是太多的文件,不要使用gzip压缩

  1. 安装依赖
npm i vite-plugin-compression -D
  1. vite.config.js
import { defineConfig } from "vite"
import compression from 'vite-plugin-compression'

export default defineConfig({
    plugins: [
        compression()
    ]
})
  1. 结果
dist/index.html                   0.35 kB │ gzip:  0.25 kB
dist/assets/index-BRmDW9Pn.js     1.35 kB │ gzip:  0.58 kB
dist/assets/vendor-DH-rnSg2.js  224.45 kB │ gzip: 42.26 kB
✓ built in 338ms

✨ [vite-plugin-compression]:algorithm=gzip - compressed file successfully: 
dist//Users/xxxxxxx/vite/vite-build/assets/index-BRmDW9Pn.js.gz    1.31kb / gzip: 0.56kb
dist//Users/xxxxxxx/vite/vite-build/assets/vendor-DH-rnSg2.js.gz   219.37kb / gzip: 40.91kb

需要后端设置响应头:content encoding --> gzip

4. 动态导入

在路由懒加载时用的比较多,例如:webpack中

component: () => import(/* webpackChunkName: 'Home' */'@/views/Home.vue')

5. cdn加速

cdn:内容分发网络,将我们依赖的第三方模块全部写成cdn的形式

使用:vite-plugin-cdn-import

import { defineConfig } from "vite"
import compression from 'vite-plugin-compression'
import importCDNPlugin from 'vite-plugin-cdn-import'

export default defineConfig({
    plugins: [
        importCDNPlugin({
            modules: [
                {
                    name: 'lodash',
                    var: '_', // lodash全局导出的符号
                    path: 'https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js'
                }
            ]
        })
    ]
})

三、rollup

以esm标准为目标的构架功能(默认只支持esm,其他模块化的需要使用插件才行),最熟知的功能:tree shaking

1. 基本使用

"preview": "rollup -i index.js", // 输出到控制台
"build": "rollup -i index.js --file dist/bundle.js", // 输出到dist目录
"build:umd": "rollup -i index.js --file dist/bundle.umd.js --format umd", // 打包成umd格式
"build:iife": "rollup -i index.js --file dist/bundle.iife.js --format iife", // 打包成iife(自执行)格式
"build:config": "rollup --config rollup.config.js" // 以配置文件进行打包

2. 基本命令

-c, --config <filename>			以配置文件进行打包
-d, --dir <dirname>         输出到某个目录
-f, --format <format>       输出的文件类型(amd, cjs, es, iife, umd, system)2
-i, --input <filename>      输入文件
-o, --file <output>         输出文件						
-n, --name <name>           在umd中,全局变量名

3. rollup配置文件使用

  1. 基本写法
/** @type {import('rollup').RollupOptions} */

export default {
  input: 'index.js',
  output: {
    file: 'dist/bundle.esm.js',
    format: 'es'
  },
  plugins: []
}
  1. 多配置写法
/** @type {import('rollup').RollupOptions} */

export default [
  {
      input: 'index.js',
      output: {
         file: 'dist/bundle.umd.js',
         format: 'umd',
         name: 'myLib' // 在umd中,全局变量名
      }
   },
  {
    input: 'index.js',
    output: {
      file: 'dist/bundle.esm.js',
      format: 'es'
    }
  }
]

插件:

  • @rollup/plugin-json:允许你在 JavaScript 模块中直接导入 JSON 文件,就像导入其他模块一样。
  • @rollup/plugin-node-resolve:是 Rollup 构建工具的一个重要插件,主要用于解析 Node.js 风格的模块导入。它的主要作用包括:
    • 解析 Node.js 模块:允许 Rollup 解析 node_modules 目录中的模块,就像 Node.js 在运行时解析模块一样。
    • 支持多种模块格式:能够处理 CommonJS 和 ES 模块,确保这些模块在 Rollup 打包过程中能够正确解析和转换。
    • 处理路径别名:可以配置路径别名,简化模块导入路径。
  • rollup-plugin-terser:打包文件压缩
  • @rollup/plugin-commonjs:主要用于处理 CommonJS 模块格式。
// rollup.config.js
import commonjs from '@rollup/plugin-commonjs'
import json from '@rollup/plugin-json'
import rosolve from '@rollup/plugin-node-resolve'
import { terser } from 'rollup-plugin-terser'

/** @type {import('rollup').RollupOptions} */
export default {
  input: 'index.js',
  // external: ['react'],
  external: {
    react: 'React'
  },
  output: {
    file: 'dist/bundle.esm.js',
    format: 'es',
    // plugins: [terser()],
    banner: '/* author: wifi */'
  },
  plugins: [rosolve(), commonjs(), json()] // rollup插件的执行顺序是按数组顺序执行的
}

你可能感兴趣的:(前端,工程化,vite)