likes
comments
collection
share

前端构建工具vite进阶系列(四) -- 插件系统让vite变得更强大

作者站长头像
站长
· 阅读数 32

前言

上一章前端构建工具vite进阶系列(三) -- 静态资源与css模块化的处理讲解了vite对静态资源的开箱即用与css模块化配置,比起webpack那么多约束,vite可谓是抛弃了那么沉重的包袱,这一章我们再来一起探索以下vite的插件系统,这将会使其又上升一个台阶。

插件是什么

webpack来说,plugins的作用在于强化其构建过程中,所遇到的一些工程化的问题,比如代码压缩资源压缩等,所以vite作为新时代的构建工具,理应当也具有插件系统来解决构建项目的整个生命周期中所遇到的工程化的问题,说白了,插件就是为了解决某一类型的问题而出现的一个一种工具函数。比如lodash,他被称之为一个库,也可以认作是一个插件。所以vite会在不同的生命周期中调用不同的插件去达成不同的目的。

插件系统初体验

那么我们了解了插件是什么之后,我们来认识第一个插件:vite-aliases

  • 作用:根据项目路径配置,自动生成路径别名。

  • 好处:不用再自己配置resolve了。

  • 安装

npm i vite-aliases -D
  • vite.config.js中添加
// vite.config.js
import { ViteAliases } from 'vite-aliases'

export default defineConfig({
    ...
    plugins: [
        ViteAliases()
    ]
})
  • 插件只支持ESM,所以需要在package.json里面添加
{
    "type": "module"
}
  • 结果:
    • 项目中的目录结构
 - src
 - - assets
 - - components
 - - pages
 - - store
 - - utils
    
根据路径生成如下配置,解释:如果在项目中引入资源import xx from '@/',那么他会直接去src目录找。
[
  {
    find: '@',
    replacement: '${your_project_path}/src'
  },
  {
    find: '@assets',
    replacement: '${your_project_path}/src/assets'
  },
  {
    find: '@components',
    replacement: '${your_project_path}/src/components'
  },
  {
    find: '@pages',
    replacement: '${your_project_path}/src/pages'
  },
  {
    find: '@store',
    replacement: '${your_project_path}/src/store'
  },
  {
    find: '@utils',
    replacement: '${your_project_path}/src/utils'
  }
]
更多配置请查看https://github.com/subwaytime/vite-aliases#configuration

vite-aliases源码解读

首先讲一下为什么要去读这个插件源码,原因有两点:

  • 其一,学习源码有助于我们提高自身编码水平,吸收别人的编程思维。
  • 其二,学东西一定要知其然而知其所以然,稍微了解皮毛是肯定不行的。当然了,阅读这个插件源码,肯定是需要去了解他背后的一个实现逻辑,有助于我们自己开发一个插件。 目录:

前端构建工具vite进阶系列(四) -- 插件系统让vite变得更强大

package.json里面运行npm run build,就会在根目录生成dist文件夹,也就是github上的包。但是它使用tsup来进行打包的,不懂得同学可以戳tsup文档,我们打开vite-aliases的源码,目录:./vite-aliases/src/index.ts

export function ViteAliases(options: Partial<Options> = {}): PluginOption {
    let gen: Generator; // Generator类
    return {
        name: 'vite-aliases', // 插件名字
        enforce: 'pre', // 插件执行顺序
        config(config, { command }) { // vite特有的config钩子,config为默认配置,command为命令
            gen = new Generator(command, options);
            gen.init(); // 调用init
            config.resolve = {
                alias: config.resolve?.alias 
                ? [
                    ...toArray(config.resolve.alias as any),
                    ...gen.aliases
                  ] 
                : gen.aliases,
            };
        },
    };
};

这里很明显,他跟webpack一样, 请戳 >>> 重学webpack系列(四) -- webpack的plugins机制的解读,也是注入在vite执行过程中所暴露的生命周期钩子里面执行的,但是与webpack的区别就是,vite的插件必须返回一个对象vite会在解析vite.config.js的时候(config钩子就是在解析vite.config.js之前执行),遍历plugins数组,找到可以执行的东西。既然了解了vite插件怎么去注入到vite构建流程中,那么我们现在来看看插件的具体实现。

ViteAliases

import type { PluginOption } from 'vite';
import { Generator } from './generator';
import type { Options } from './types';
import { toArray } from './utils';
export function ViteAliases(options: Partial<Options> = {}): PluginOption {
    let gen: Generator; // Generator类
    return {
        name: 'vite-aliases', // 插件名字
        enforce: 'pre', // 插件执行顺序
        config(config, { command }) { // vite特有的config钩子,config为默认配置,command为命令
            gen = new Generator(command, options);
            gen.init(); // 调用init,读取root目录,生成数组对象
            config.resolve = {
                alias: config.resolve?.alias  // 有alias就合并
                ? [
                    ...toArray(config.resolve.alias as any),
                    ...gen.aliases
                  ] 
                : gen.aliases, // 没有就直接用生成的alias
            };
        },
    };
};
  • ViteAliases函数被默认导出,config参数为整个vite.config.js文件,第二参数env为{ mode: 'development', command: 'serve', ssrBuild: false },如果开发环境是development,这里只是解构出了command指令。
  • 再来理解config.resolve,这个不就是配置别名吗?alias属性就是别名控制,后面都属于业务逻辑了。
  • 所以自己编写一个插件的思路就是:
    • 导出一个函数,返回一个对象。
    • 选取合适的生命周期钩子(与配置相关),注入configenv
    • 可以通过apply来应用插件的执行环境。

插件执行顺序

一个 Vite 插件可以额外指定一个 enforce 属性(类似于 webpack 加载器)来调整它的应用顺序。enforce 的值可以是pre 或 post。解析后的插件将按照以下顺序排列:

  • Alias
  • 带有 enforce: 'pre' 的用户插件
  • Vite 核心插件
  • 没有 enforce 值的用户插件
  • Vite 构建用的插件
  • 带有 enforce: 'post' 的用户插件
  • Vite 后置构建插件(最小化,manifest,报告)

小技巧

这里在源码中看到了一个还不错的工具函数,与大家分享一下。

const fg = require('fast-glob')

/**
 * Return all folders from the project directory
 * @param options
 */

async function getDirectories(config) {
  const { dir = 'src', root = process.cwd(), deep = true, depth = 1 } = config

  const directories = await fg.sync(deep ? `${dir}/**/*` : `${dir}/*`, {
    ignore: ['node_modules'], // 忽略
    onlyDirectories: true, // 只读出文件夹
    cwd: root, // 需要检测的路径
    deep: depth, // 嵌套层级
    absolute: true // 相对、绝对路径
  })

  if (!directories.length) {
    // 给出警告,我简化成console.log
    console.log('No Directories could be found!')
  }
  return directories
}

const config = {
  dir: 'src',
  root: process.cwd(),
  deep: true,
  depth: 2
}
getDirectories(config).then((list) => {
  console.log(list)
})

html的处理

webpack中,我们用webpack-html-plugin去生成我们的html,在vite中这一步其实内部已经帮你做了,但是关于html文件的内容控制,vite页提供了一个插件:vite-plugin-html。如何去控制html内容的呢?这需要ejs来帮助我们。

ejs基础语法

<%= name %>
<%= age %>

<%= injectScript %>

<% if (user) { %>
  <h2><%= user.name %></h2>
<% } %>
  • vite-plugin-html使用
// index.html
<title>
  <%= title %>
</title>

// vite-base-config.js
import {createHtmlPlugin} from 'vite-plugin-html';

plugins:[
    createHtmlPlugin({
        inject:{ // 注入data
            data:{
                title:"hello world" // title变量
                injectScript: `<script src="./inject.js"></script>`,

            }
        }
    });
]
  • vite-plugin-html的原理简述:
    • 就是利用transformIndexHtml钩子函数对html的转换,之后进行ejs模板替换成注入的变量,这样的一个好处是扩展性强。

mock数据相关

在前后端排期的时候,接口文档永远比接口先出来,嗯?这不是废话吗?那在后端写接口的时候,前端在干啥啊,那当然是造组件,mock数据咯,于是他来了。

vite提供了vite-plugin-mock来帮助你在项目中启动一个mock服务器来模拟真实接口,等真实接口出来,直接替换,提高效率以便于联调。

  • vite-plugin-mock的使用。
// vite.base.config.js
import {viteMockServe} from 'vite-plugin-mock'

plugins:[
    viteMockServe()
]

//新建mock.js文件写入
module.exports = [
  {
    method:"post",
    url:"/api/user",
    response:()=>{
      return {
        code:200,
        message:"success",
        data:data
      }
    }
  },
  {
    method:"post",
    url:"/api/info",
    response:()=>{
      return {
        code:200,
        message:"success",
        data:info
      }
    }
  }
]

引入 mockjs包来mock数据,于是乎就有了下面的数据

前端构建工具vite进阶系列(四) -- 插件系统让vite变得更强大

于是乎有了这个插件,我们就可以很完美的、无缝衔接的去调真实环境的api啦。(其实这个插件本身默认只是适用于开发环境)👀 👀 👀

  • vite-plugin-mock的原理简述:
    • 就是利用configureServer钩子函数,通过各种MiddleWare处理,交给vite本地开发服务器,做接口数据的处理的。

总结

本文我们去使用了三个比较有代表性的插件,了解了三个生命周期钩子:configtransformIndexHtmlconfigureServer,更多钩子介绍,请戳 >>> vite生命周期函数

  • config(config, env) : configUserConfigenv为指令,所以我们可以在这个钩子中去做一些关于配置项合并的事情。
  • transformIndexHtml(html):html为传入的index.html,在这里我们可以去做一些关于还html内容的处理。
  • configureServer(server):server表示服务器的配置,这里我们可以用这个区做一些服务器相关的处理,比如数据mock

当然还有很多钩子,也只有一起去慢慢探索啦 ~ 🌶 ,下一章 >>> 前端构建工具vite进阶系列(五) -- vite的运行机制与工作原理