深入浅出Vite:如何开发一个Vite插件

news2024/11/25 18:43:29

说到自定义的能力,大家肯定很容易想到插件机制,利用一个插件来扩展构建工具自身的能力。在学习了 Vite 的插件机制后,我们接下来利用已掌握的Vite插件开发的基本知识来实战Vite插件的开发工作。

一、插件示例

Vite 插件与 Rollup 插件结构类似,为一个name和各种插件 Hook 的对象:

{
  name: 'vite-plugin-xxx',   //插件名称
  load(code) {
    // 钩子逻辑
  },
}

在插件命名方式上,如果插件是一个 npm 包,在package.json中的包命名也推荐以vite-plugin开头。一般情况下因为要考虑到外部传参,我们不会直接写一个对象,而是实现一个返回插件对象的工厂函数,如下代码所示:

// myPlugin.js
export function myVitePlugin(options) {
  console.log(options)
  return {
    name: 'vite-plugin-xxx',
    load(id) {
      // 在钩子逻辑中可以通过闭包访问外部的 options 传参
    }
  }
}


// 使用方式
// vite.config.ts
import { myVitePlugin } from './myVitePlugin';
export default {
  plugins: [myVitePlugin({ /* 给插件传参 */ })]
}

二、插件 Hook

2.1 通用 Hook

前面介绍过,Vite 开发阶段会模拟 Rollup 的行为:

image.png
其中 Vite 会调用一系列与 Rollup 兼容的钩子,这个钩子主要分为三个阶段:

  • 服务器启动阶段: options和buildStart钩子会在服务启动时被调用。
  • 请求响应阶段: 当浏览器发起请求时,Vite 内部依次调用resolveId、load和transform钩子。
  • 服务器关闭阶段: Vite 会依次执行buildEnd和closeBundle钩子。

除了以上钩子,其他 Rollup 插件钩子(如moduleParsed、renderChunk)均不会在 Vite 开发阶段调用。而生产环境下,由于 Vite 直接使用 Rollup,Vite 插件中所有 Rollup 的插件钩子都会生效。

2.2 独有 Hook

接下来,给大家介绍 Vite 中特有的一些 Hook,这些 Hook 只会在 Vite 内部调用,而放到 Rollup 中会被直接忽略。

2.2.1 config

Vite 在读取完配置文件(即vite.config.ts)之后,会拿到用户导出的配置对象,然后执行 config 钩子。在这个钩子里面,可以对配置文件导出的对象进行自定义的操作,如下代码所示:

const editConfigPlugin = () => ({
  name: 'vite-plugin-modify-config',
  config: () => ({
    alias: {
      react: require.resolve('react')
    }
  })
})

官方推荐的用法是在 config 钩子中返回一个配置对象,这个配置对象会和 Vite 已有的配置进行深度的合并。不过你也可以通过钩子的入参拿到 config 对象进行自定义的修改,如下代码所示:

const mutateConfigPlugin = () => ({
  name: 'mutate-config',
  // command 为 `serve`(开发环境) 或者 `build`(生产环境)
  config(config, { command }) {
    // 生产环境中修改 root 参数
    if (command === 'build') {
      config.root = __dirname;
    }
  }
})

在一些比较深层的对象配置中,这种直接修改配置的方式会显得比较麻烦,比如 optimizeDeps.esbuildOptions.plugins就需要写很多的样板代码。

// 防止出现 undefined 的情况
config.optimizeDeps = config.optimizeDeps || {}
config.optimizeDeps.esbuildOptions = config.optimizeDeps.esbuildOptions || {}
config.optimizeDeps.esbuildOptions.plugins = config.optimizeDeps.esbuildOptions.plugins || []

因此,对于这种情况下可以直接返回一个配置对象,比如:

config() {
  return {
    optimizeDeps: {
      esbuildOptions: {
        plugins: []
      }
    }
  }
}

2.2.2 configResolved

Vite 在解析完配置之后会调用configResolved钩子,这个钩子一般用来记录最终的配置信息,而不建议再修改配置,用法如下。

const exmaplePlugin = () => {
  let config


  return {
    name: 'read-config',


    configResolved(resolvedConfig) {
      // 记录最终配置
      config = resolvedConfig
    },


    // 在其他钩子中可以访问到配置
    transform(code, id) {
      console.log(config)
    }
  }
}

2.2.3 configureServer

这个钩子仅在开发阶段会被调用,用于扩展 Vite 的 Dev Server,一般用于增加自定义 server 中间件,如下代码:

const myPlugin = () => ({
  name: 'configure-server',
  configureServer(server) {
    // 姿势 1: 在 Vite 内置中间件之前执行
    server.middlewares.use((req, res, next) => {
      // 自定义请求处理逻辑
    })
    // 姿势 2: 在 Vite 内置中间件之后执行 
    return () => {
      server.middlewares.use((req, res, next) => {
        // 自定义请求处理逻辑
      })
    }
  }
})

2.2.4 transformIndexHtml

transformIndexHtml钩子用来灵活控制 HTML 的内容,你可以拿到原始的 html 内容后进行任意的转换:

const htmlPlugin = () => {
  return {
    name: 'html-transform',
    transformIndexHtml(html) {
      return html.replace(
        /<title>(.*?)</title>/,
        `<title>换了个标题</title>`
      )
    }
  }
}
// 也可以返回如下的对象结构,一般用于添加某些标签
const htmlPlugin = () => {
  return {
    name: 'html-transform',
    transformIndexHtml(html) {
      return {
        html,
        // 注入标签
        tags: [
          {
            // 放到 body 末尾,可取值还有`head`|`head-prepend`|`body-prepend`,顾名思义
            injectTo: 'body',
            // 标签属性定义
            attrs: { type: 'module', src: './index.ts' },
            // 标签名
            tag: 'script',
          },
        ],
      }
    }
  }
}

2.2.5 handleHotUpdate

handleHotUpdate钩子主要用在服务区热更新,这个钩子会在 Vite 服务端处理热更新时被调用,你可以在这个钩子中拿到热更新相关的上下文信息,进行热更模块的过滤,或者进行自定义的热更处理。

const handleHmrPlugin = () => {
  return {
    async handleHotUpdate(ctx) {
      // 需要热更的文件
      console.log(ctx.file)
      // 需要热更的模块,如一个 Vue 单文件会涉及多个模块
      console.log(ctx.modules)
      // 时间戳
      console.log(ctx.timestamp)
      // Vite Dev Server 实例
      console.log(ctx.server)
      // 读取最新的文件内容
      console.log(await read())
      // 自行处理 HMR 事件
      ctx.server.ws.send({
        type: 'custom',
        event: 'special-update',
        data: { a: 1 }
      })
      return []
    }
  }
}


// 前端代码中加入
if (import.meta.hot) {
  import.meta.hot.on('special-update', (data) => {
    // 执行自定义更新
    // { a: 1 }
    console.log(data)
    window.location.reload();
  })
}

下面我们来总结一下 Vite独有的五个钩子:

  • config: 用来进一步修改配置。
  • configResolved: 用来记录最终的配置信息。
  • configureServer: 用来获取 Vite Dev Server 实例,添加中间件。
  • transformIndexHtml: 用来转换 HTML 的内容。
  • handleHotUpdate: 用来进行热更新模块的过滤,或者进行自定义的热更新处理。

2.3 Hook 执行顺序

现在我们学习了 Vite 的通用钩子和独有钩子,那这么多的钩子,到底谁先执行、谁后执行呢?接下来,就来复盘一下上述的两类钩子,并且通过一个具体的代码示例来汇总一下所有的钩子。

// test-hooks-plugin.ts
// 注: 请求响应阶段的钩子
// 如 resolveId, load, transform, transformIndexHtml在下文介绍
// 以下为服务启动和关闭的钩子
export default function testHookPlugin () {
  return {
    name: 'test-hooks-plugin', 
    // Vite 独有钩子
    config(config) {
      console.log('config');
    },
    // Vite 独有钩子
    configResolved(resolvedCofnig) {
      console.log('configResolved');
    },
    // 通用钩子
    options(opts) {
      console.log('options');
      return opts;
    },
    // Vite 独有钩子
    configureServer(server) {
      console.log('configureServer');
      setTimeout(() => {
        // 手动退出进程
        process.kill(process.pid, 'SIGTERM');
      }, 3000)
    },
    // 通用钩子
    buildStart() {
      console.log('buildStart');
    },
    // 通用钩子
    buildEnd() {
      console.log('buildEnd');
    },
    // 通用钩子
    closeBundle() {
      console.log('closeBundle');
    }
}

首先,我们在 Vite 的脚手架工程中新建 test-hooks-plugin.ts文件,代码如下。

// test-hooks-plugin.ts
// 注: 请求响应阶段的钩子
// 如 resolveId, load, transform, transformIndexHtml在下文介绍
// 以下为服务启动和关闭的钩子
export default function testHookPlugin () {
  return {
    name: 'test-hooks-plugin', 
    // Vite 独有钩子
    config(config) {
      console.log('config');
    },
    // Vite 独有钩子
    configResolved(resolvedCofnig) {
      console.log('configResolved');
    },
    // 通用钩子
    options(opts) {
      console.log('options');
      return opts;
    },
    // Vite 独有钩子
    configureServer(server) {
      console.log('configureServer');
      setTimeout(() => {
        // 手动退出进程
        process.kill(process.pid, 'SIGTERM');
      }, 3000)
    },
    // 通用钩子
    buildStart() {
      console.log('buildStart');
    },
    // 通用钩子
    buildEnd() {
      console.log('buildEnd');
    },
    // 通用钩子
    closeBundle() {
      console.log('closeBundle');
    }
}

接着,将插件加入到 Vite 配置文件中,然后启动项目,就可以观察到各个 Hook 的执行顺序,如下图所示。

image.png

由此,我们可以梳理出 Vite 插件的执行顺序:

image.png

由上图可知,Vite插件的执行分为启动阶段、响应阶段、热更新阶段和关闭阶段。

  • 服务启动阶段: config、configResolved、options、configureServer、buildStart
  • 请求响应阶段:如果是 html 文件,仅执行transformIndexHtml钩子;对于非 HTML 文件,则依次执行resolveId、load和transform钩子。相信大家学过 Rollup 的插件机制,已经对这三个钩子比较熟悉了。
  • 热更新阶段:执行handleHotUpdate钩子。
  • 服务关闭阶段:依次执行buildEnd和closeBundle钩子。

2.4 插件应用场景

默认情况下 Vite 插件同时被用于开发环境和生产环境,你可以通过apply属性来决定应用场景。

{
  // 'serve' 表示仅用于开发环境,'build'表示仅用于生产环境
  apply: 'serve'
}

同时,apply参数还可以配置成一个函数,进行更灵活的控制:

apply(config, { command }) {
  // 只用于非 SSR 情况下的生产环境构建
  return command === 'build' && !config.build.ssr
}

当然,你也可以通过enforce属性来指定插件的执行顺序:

{
  // 默认为`normal`,可取值还有`pre`和`post`
  enforce: 'pre'
}

Vite 中插件的执行顺序如下图所示:

image.png

三、Vite插件开发实战

3.1 案例 1: 虚拟模块加载

首先我们来实现一个虚拟模块的加载插件,可能你会有疑问: 什么是虚拟模块呢?

作为构建工具,一般需要处理两种形式的模块,一种存在于真实的磁盘文件系统中,另一种并不在磁盘而在内存当中,也就是虚拟模块。通过虚拟模块,我们既可以把自己手写的一些代码字符串作为单独的模块内容,又可以将内存中某些经过计算得出的变量作为模块内容进行加载,非常灵活和方便。接下来让我们通过一些具体的例子来实操一下,首先通过脚手架命令初始化一个react + ts项目。

npm init vite

然后,通过pnpm i安装依赖,接着新建plugins目录,开始插件的开发:

// plugins/virtual-module.ts
import { Plugin } from 'vite';


// 虚拟模块名称
const virtualFibModuleId = 'virtual:fib';
// Vite 中约定对于虚拟模块,解析后的路径需要加上`\0`前缀
const resolvedFibVirtualModuleId = '\0' + virtualFibModuleId;


export default function virtualFibModulePlugin(): Plugin {
  let config: ResolvedConfig | null = null;
  return {
    name: 'vite-plugin-virtual-module',
    resolveId(id) {
      if (id === virtualFibModuleId) { 
        return resolvedFibVirtualModuleId;
      }
    },
    load(id) {
      // 加载虚拟模块
      if (id === resolvedFibVirtualModuleId) {
        return 'export default function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }';
      }
    }
  }
}

接着,我们在项目中来引入这个插件:

// vite.config.ts
import virtual from './plugins/virtual-module.ts'


// 配置插件
{
  plugins: [react(), virtual()]
}

然后,在main.tsx中加入如下的代码。

import fib from 'virtual:fib';


alert(`结果: ${fib(10)}`)

这里我们使用了 virtual:fib 这个虚拟模块,虽然这个模块不存在真实的文件系统中,但你打开浏览器后可以发现这个模块导出的函数是可以正常执行的。

image.png
接着我们来尝试一下如何通过虚拟模块来读取内存中的变量,在virtual-module.ts中增加如下代码:

import { Plugin, ResolvedConfig } from 'vite';


const virtualFibModuleId = 'virtual:fib';
const resolvedFibVirtualModuleId = '\0' + virtualFibModuleId;


+ const virtualEnvModuleId = 'virtual:env';
+ const resolvedEnvVirtualModuleId = '\0' + virtualEnvModuleId;


export default function virtualFibModulePlugin(): Plugin {
+   let config: ResolvedConfig | null = null;
  return {
    name: 'vite-plugin-virtual-fib-module',
+     configResolved(c: ResolvedConfig) {
+       config = c;
+     },
    resolveId(id) {
      if (id === virtualFibModuleId) { 
        return resolvedFibVirtualModuleId;
      }
+       if (id === virtualEnvModuleId) { 
+        return resolvedEnvVirtualModuleId;
+      }
    },
    load(id) {
      if (id === resolvedFibVirtualModuleId) {
        return 'export default function fib(n) { return n <= 1 ? n : fib(n - 1) + fib(n - 2); }';
      }
+      if (id === resolvedEnvVirtualModuleId) {
+        return `export default ${JSON.stringify(config!.env)}`;
+      }
    }
  }
}

在新增的这些代码中,我们注册了一个新的虚拟模块virtual:env,紧接着我们去项目去使用:

// main.tsx
import env from 'virtual:env';
console.log(env)

virtual:env一般情况下会有类型问题,我们需要增加一个类型声明文件来声明这个模块:

// types/shim.d.ts
declare module 'virtual:*' {
  export default any;
}

这样就解决了类型报错的问题。接着你可以去浏览器观察一下输出的情况:

image.png

可以看到,Vite 环境变量能正确地在浏览器中打印出来,说明在内存中计算出来的virtual:env模块的确被成功地加载了。从中你可以看到,虚拟模块的内容完全能够被动态计算出来,因此它的灵活性和可定制程度非常高,实用性也很强,在 Vite 内部的插件被深度地使用,社区当中也有不少知名的插件(如 vite-plugin-windicss、vite-plugin-svg-icons等)也使用了虚拟模块的技术。

3.2 案例 2: Svg 组件形式加载

在一般的项目开发过程中,我们有时候希望能将 svg 当做一个组件来引入,这样我们可以很方便地修改 svg 的各种属性,相比于img标签的引入方式,svg方式也更加优雅。但 Vite 本身并不支持将 svg 转换为组件的代码,所以需要我们通过插件来实现。

接下来,我们就来写一个 Vite 插件来支持以组件方式来使用 svg 资源。首先,

安装一下需要的依赖:

npm i resolve @svgr/core -D

接着,在plugins目录新建 svgr.ts:

import { Plugin } from 'vite';
import * as fs from 'fs';
import * as resolve from 'resolve';


interface SvgrOptions {
  // svg 资源模块默认导出,url 或者组件
  defaultExport: 'url' | 'component';
}


export default function viteSvgrPlugin(options: SvgrOptions) {
  const { defaultExport='url' } = options;
  return {
    name: 'vite-plugin-svgr',
    async transform(code ,id) {
      // 转换逻辑: svg -> React 组件
    }
  }
}

让我们来梳理一下需求,用户通过传入defaultExport可以控制 svg 资源的默认导出:

  • 当 defaultExport为 component,默认当做组件来使用。
import Logo from './Logo.svg'


// 在组件中直接使用
<Logo />
  • 当defaultExports为url,默认当做 url 使用,如果需要用作组件,可以通过具名导入的方式来支持。
import logoUrl, { ReactComponent as Logo } from './logo.svg';


// url 使用
<img src={logoUrl} />
// 组件方式使用
<Logo />

明确了需求之后,接下来让我们来整理一下插件开发的整体思路,主要逻辑在 transform钩子中完成,流程如下:

  1. 根据 id 入参过滤出 svg 资源;
  2. 读取 svg 文件内容;
  3. 利用 @svgr/core 将 svg 转换为 React 组件代码;
  4. 处理默认导出为 url 的情况;
  5. 将组件的 jsx 代码转译为浏览器可运行的代码。

下面是一段参考代码。

import { Plugin } from 'vite';
import * as fs from 'fs';
import * as resolve from 'resolve';


interface SvgrOptions {
  defaultExport: 'url' | 'component';
}


export default function viteSvgrPlugin(options: SvgrOptions): Plugin {
  const { defaultExport='component' } = options;


  return {
    name: 'vite-plugin-svgr',
    async transform(code, id) {
      // 1. 根据 id 入参过滤出 svg 资源;
      if (!id.endsWith('.svg')) {
        return code;
      }
      const svgrTransform = require('@svgr/core').transform;
      // 解析 esbuild 的路径,后续转译 jsx 会用到,我们这里直接拿 vite 中的 esbuild 即可
      const esbuildPackagePath = resolve.sync('esbuild', { basedir: require.resolve('vite') });
      const esbuild = require(esbuildPackagePath);
      // 2. 读取 svg 文件内容;
      const svg = await fs.promises.readFile(id, 'utf8');
      // 3. 利用 `@svgr/core` 将 svg 转换为 React 组件代码
      const svgrResult = await svgrTransform(
        svg,
        {},
        { componentName: 'ReactComponent' }
      );
      // 4. 处理默认导出为 url 的情况
      let componentCode = svgrResult;
      if (defaultExport === 'url') {
        // 加上 Vite 默认的 `export default 资源路径`
        componentCode += code;
        componentCode = componentCode.replace('export default ReactComponent', 'export { ReactComponent }');
      }
      // 5. 利用 esbuild,将组件中的 jsx 代码转译为浏览器可运行的代码;
      const result = await esbuild.transform(componentCode, {
        loader: 'jsx',
      });
      return {
        code: result.code,
        map: null // TODO
      };
    },
  };
}

接下来,我们就可以在项目中使用这个插件:

// vite.config.ts
import svgr from './plugins/svgr';


// 返回的配置
{
  plugins: [
    // 省略其它插件
    svgr()
  ]
}

当然,我们也可以在项目中使用组件的方式引入 svg:

// App.tsx
import Logo from './logo.svg'


function App() {
  return (
    <>
      <Logo />
    </>
  )
}


export default App;

3.3 如何调试

在开发调试插件的过程,我推荐大家在本地装上vite-plugin-inspect插件,并在 Vite 中使用它:

// vite.config.ts
import inspect from 'vite-plugin-inspect';


// 返回的配置
{
  plugins: [
    // 省略其它插件
    inspect()
  ]
}

此时,当你再次启动项目时,会发现多出一个调试地址:

image.png

当然,你还可以通过这个地址来查看项目中各个模块的编译结果。

image.png

点击特定的文件后,你可以看到这个模块经过各个插件处理后的中间结果,如下图所示。

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/584700.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

利用pycocotools库计算MAP:生成coco格式 json文件数据集和计算map值

文章目录 1.划分val数据集2. xml to json3. coco格式json文件4. 生成coco格式json文件5.使用pycocotools计算map6. 讨论 在目标检测任务中&#xff0c;需要通过Map指标判断模型的精度。为了测试engine文件推理结果的精度&#xff0c;本文介绍了如何使用pycocotools库计算Map&am…

4年测试,裸辞后已失业3个月.....

我做测试4年&#xff0c;一线城市薪水拿到15K&#xff0c;中间还修了一个专升本&#xff0c;这个年限不说资深肯定也是配得上经验丰富的。今年行情不好人尽皆知&#xff0c;但我还是对我的薪水不是很满意&#xff0c;于是打算出去面试&#xff0c;希望可以搏一个高薪。 但真到面…

vscode使用git

文章目录 前言一、配置ssh-key二、GitHub上创建一个空的仓库三、链接GitHub&#xff0c;并提交本地文件 前言 从今天开始学习前端知识&#xff0c;学会先使用工具很重要&#xff0c;今天尝试了下用vscode链接GitHub&#xff0c;实现代码管理。 前提&#xff1a; 1、需要先下载…

科技云报道:2023年安全运营之风将吹向何方?

科技云报道原创。 在实战演练成为常态化的背景下&#xff0c;建立实战化安全运营能力是一个绕不开的话题。作为网络安全发展的时代产物&#xff0c;安全运营被认为是解决现有挑战的有利方法。 但随着有安全形势、政策导向、发展需求的变化&#xff0c;安全运营的理念也在不断演…

深度解析如何通过财务共享建设助推企业数智化转型

国务院国资委印发了《关于中央企业加快建设世界一流财务管理体系的指导意见》&#xff08;以下简称《意见》&#xff09;&#xff0c;文中明确指出了数智化转型的路径&#xff0c;即“积极探索依托财务共享实现财务数智化转型的有效路径&#xff0c;推进共享模式、流程和技术创…

Arnold图像置乱的MATLAB实现

这件事情的起因是这样的&#xff0c;我需要研究一下各种图像置乱的算法。然后在知乎上找到了一篇关于Arnold变化的文章&#xff0c;但是呢&#xff0c;这个人实际上是卖资料&#xff0c;代做大作业的。详细的代码根部不给你&#xff0c;则给我气坏了&#xff0c;必须要手动实现…

Java泛型 <T> T、 T、<T>的用法

我们聊聊Java泛型中的 T 是什么&#xff1f; T 在Java泛型中&#xff0c;被称作类型变量。那么什么又是类型变量&#xff1f; 类型变量在整个类的定义中用于指定方法的返回类型&#xff0c;同时也可以指定字段和局部变量的类型&#xff0c;我们可以用具体的类型来替换类型变量…

数据库【数据操作】

这篇文章呢是小编对正在学习的数据库的实验内容进行一个简单的记录&#xff0c;以便后期复习&#xff0c;希望小编的这些例子也可以帮助到正在和我一样学习数据库的友友们哦&#xff5e; 实验目的&#xff1a; &#xff08;1&#xff09;掌握使用T-SQL插入数据&#xff0c;修…

Arthas

Arthas 概述 Arthas 是Alibaba开源的Java诊断工具&#xff0c;深受开发者喜爱。 当你遇到以下类似问题而束手无策时&#xff0c;Arthas可以帮助你解决&#xff1a; 这个类从哪个 jar 包加载的&#xff1f;为什么会报各种类相关的 Exception&#xff1f;我改的代码为什么没有…

无纸化、自动化、智能化|WMS系统升级你的仓储管理模式

随着物流行业的不断发展&#xff0c;现代仓储管理已经从传统的手工操作逐渐转向无纸化、自动化、智能化管理。WMS系统作为一种全新的仓储管理模式&#xff0c;正在逐步被企业所接受和运用。 什么是WMS系统&#xff1f; WMS系统全称为Warehouse Management System&#xff0c;即…

汇编寄存器之内存访问

1.内存中字的存储: 在CPU中用一个16位寄存器来存储一个字, 高8位存高字节,低8位存低字节 如AX寄存器存在一个字,那么AH存高字节,AL存低字节 在内存中存储字时是用两个连续的字节来存储字的, 这个字的低字节存在低单元,高字节存在高单元. 如下表示: 内存单元编号 单元中…

开发工程师-常用算法基本思想 -分类-时间复杂度与空间复杂度概述

文章目录 插入排序选择排序交换排序归并排序各种排序算法时间复杂度、空间复杂度以及稳定性分类 插入排序 1.基本思想&#xff1a;将一个数据插入到一个有序的数据列表&#xff0c;得到一个新的有序列表 2.分类&#xff1a;直接插入排序、希尔排序 选择排序 1.工作原理&…

机器学习基础知识之多模型性能对比评价方法

文章目录 1、交叉验证t检验2、Friedman检验与Nemenyi后续检验 在进行预测或分类对比实验时&#xff0c;通常需要比较两个或两个以上的模型性能&#xff0c;因此&#xff0c;下面将介绍两个常用的多模型性能对比评价方法&#xff0c;一种是交叉验证t检验&#xff0c;该方法主要用…

英语单词365-9

英语单词365-9 9.1 manipulate_哔哩哔哩_bilibili

网络安全就业会不会容易被淘汰?

先说结论&#xff1a;不会 作为一名资深的网络安全工程师&#xff0c;我的观点是网络安全是一个长期持续的领域&#xff0c;而不是一个很容易被淘汰的职业。可能会随着技术的进步和新的威胁的出现而有所改变&#xff0c;但作为一个专门从事网络安全领域的人员&#xff0c;我们…

SQL面试必备:100道高频考题解析

前言 在众多IT职场中&#xff0c;SQL技术一直是一个非常重要的技能点。如果你正在准备SQL相关的面试&#xff0c;那么这份“SQL面试 100 问”绝对是你不能错过的宝藏&#xff01; 这份清单涵盖了100道高频考题&#xff0c;从基础知识到复杂应用都有所涉及&#xff0c;帮助你全…

数据结构·第3章【栈和队列】

栈 顺序栈 栈&#xff08;Stack&#xff09;是限定仅在表的一端进行插入或删除操作的线性表。通常称插入删除的一端为栈顶&#xff08;top&#xff09;&#xff0c;另一端称为栈底&#xff08;bottom&#xff09;。 typedef struct{DataType data[StackSize];int top; }Se…

缩减虚拟机堆空间的方式,缓解32位cpu上虚拟内存地址空间限制导致的内存分配失败崩溃

缩减虚拟机堆空间的方式&#xff0c;缓解32位cpu上虚拟内存地址空间限制导致的内存分配失败崩溃 前言Matrix使用说明效果验证 前言 瑞芯微平台应用开发&#xff0c;目前RK3288芯片应用还是比较广泛&#xff08;成本低&#xff09;&#xff0c;它是一个32位cpu&#xff0c;并且…

Halcon中的一些3D算子

一、记录一些Halcon里的关于3D的算子 1.read_object_model_3d 从文件读取一个3d模型 如下图&#xff0c;读的一个ply文件出来是个3d点云模型 2.visualize_object_model_3d 交互式展示3d模型 即上个算子读出来后&#xff0c;通过这个算子可以把3d模型显示出来旋转、平移&am…

SpringFramework 中CollectionUtils 工具类的使用

CollectionUtils是Spring框架中的一个工具类&#xff0c;提供了一系列对集合的操作方法。 import org.springframework.util.CollectionUtils;import java.util.*;public class CollectionUtilDemo {public static void main(String[] args) {//判断一个集合或Map是否为空&…