Vite: 插件开发

news2024/11/24 20:28:47

概述

  • 说到自定义的能力,肯定很容易想到 插件机制 ,利用一个个插件来扩展构建工具自身的能力
  • 虽然 Vite 的插件机制是基于 Rollup 来设计的,但实际上 Vite 的插件机制也包含了自己独有的一部分,与Rollup 的各个插件 Hook 并非完全兼容
  • 因此我们重点关注 Vite 独有的部分以及和 Rollup 所区别的部分,而对于 Vite 和 Rollup 中相同的 Hook (如 resolveId 、load 、 transform )只是稍微提及,就不再展开赘述

一个简单的插件示例

  • 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


1 )通用 Hook

  • 在双引擎架构这一节中介绍过,Vite 开发阶段会模拟 Rollup 的行为
  • 其中 Vite 会调用一系列与 Rollup 兼容的钩子,这个钩子主要分为三个阶段:
    • 服务器启动阶段: options 和 buildStart 钩子会在服务启动时被调用
    • 请求响应阶段: 当浏览器发起请求时,Vite 内部依次调用 resolveId 、 load 和 transform 钩子
    • 服务器关闭阶段: Vite 会依次执行 buildEnd 和 closeBundle 钩子
  • 除了以上钩子,其他 Rollup 插件钩子(如 moduleParsed 、 renderChunk )均不会在Vite 开发阶段调用, 而生产环境下,由于 Vite 直接使用 Rollup,Vite 插件中所有 Rollup 的插件钩子都会生效

2 ) 独有 Hook

  • Vite 中特有的一些 Hook,这些 Hook 只会在 Vite 内部调用,而放到Rollup 中会被直接忽略

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 记录最终配置: configResolved

  • Vite 在解析完配置之后会调用 configResolved 钩子,这个钩子一般用来记录最终的配置
    信息,而不建议再修改配置,用法如下所示:
const exmaplePlugin = () => {
	 let config
	 return {
		 name: 'read-config',
		 configResolved(resolvedConfig) {
			 // 记录最终配置
			 config = resolvedConfig
		 },
		 // 在其他钩子中可以访问到配置
		 transform(code, id) {
		 	console.log(config)
		 }
	 }
}

2.3 获取 Dev Server 实例: 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.4 转换 HTML 内容: 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.5 热更新处理: 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();
      })
    }
    

2.6 总结一下

  • 以上就是 Vite 独有的五个钩子,我们来重新梳理一下:
    • config : 用来进一步修改配置
    • configResolved : 用来记录最终的配置信息
    • configureServer : 用来获取 Vite Dev Server 实例,添加中间件
    • transformIndexHtml : 用来转换 HTML 的内容
    • handleHotUpdate : 用来进行热更新模块的过滤,或者进行自定义的热更新处理

3 )插件 Hook 执行顺序

  • Vite 的通用钩子和独有钩子,估计你现在脑子里面一点乱: 这么多的钩子,到底谁先执行、谁后执行?

  • 来复盘一下上述的两类钩子,在 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 的执行顺序:

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

  • 服务启动阶段: config 、 configResolved 、 options 、 configureServer 、buildStart

  • 请求响应阶段: 如果是 html 文件,仅执行 transformIndexHtml 钩子;对于非 HTML
    文件,则依次执行 resolveId 、 load 和 transform 钩子

  • 热更新阶段: 执行 handleHotUpdate 钩子

  • 服务关闭阶段: 依次执行 buildEnd 和 closeBundle 钩子

插件应用位置

  • 梳理完 Vite 的各个钩子函数之后,接下来让我们来了解一下 Vite 插件的应用情景和应用顺序

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

    {
    	 // 'serve' 表示仅用于开发环境,'build'表示仅用于生产环境
    	 apply: 'serve'
    }
    
  • apply 参数还可以配置成一个函数,进行更灵活的控制

    apply(config, { command }) {
    	 // 只用于非 SSR 情况下的生产环境构建
    	 return command === 'build' && !config.build.ssr
    }
    
  • 同时,你也可以通过 enforce 属性来指定插件的执行顺序:

    {
    	 // 默认为`normal`,可取值还有`pre`和`post`
    	 enforce: 'pre'
    }
    
  • Vite 中插件的执行顺序如下图所示

  • Vite 会依次执行如下的插件:

    • Alias (路径别名)相关的插件
    • ⭐️ 带有 enforce: ‘pre’ 的用户插件
    • Vite 核心插件
    • ⭐️ 没有 enforce 值的用户插件,也叫 普通插件
    • Vite 生产环境构建用的插件
    • ⭐️ 带有 enforce: ‘post’ 的用户插件
    • Vite 后置构建插件(如压缩插件)

插件开发实战


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 这个虚拟模块,虽然这个模块不存在真实的文件系统中,但你打开浏览器后可以发现这个模块导出的函数是可以正常执行的: 弹出 55

  • 接着我们来尝试一下如何通过虚拟模块来读取内存中的变量,在 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;
    }
    
  • 这样就解决了类型报错的问题。接着你可以去浏览器观察一下输出的情况:

  • Vite 环境变量能正确地在浏览器中打印出来,说明在内存中计算出来的 virtual:env 模
    块的确被成功地加载了

  • 从中你可以看到,虚拟模块的内容完全能够被动态计算出来,因此它的灵活性和可定制程度非常高,实用性也很强,在 Vite 内部的插件被深度地使用,社区当中也有不少知名的插件(如 vite-plugin-windicss 、 vite-plugin-svg-icons 等)也使用了虚拟模块的技术

2 ) Svg 组件形式加载

  • 在一般的项目开发过程中,我们有时候希望能将 svg 当做一个组件来引入,这样我们可
    以很方便地修改 svg 的各种属性,相比于 img 标签的引入方式也更加优雅。

  • 但 Vite 本身并不支持将 svg 转换为组件的代码,需要我们通过插件来实现。接下来我们就来写一个 Vite 插件,实现在 React 项目能够通过组件方式来使用 svg 资源。首先安装一下需要的依赖:

    • $ pnpm 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 钩子中完成,流程如下

    • 根据 id 入参过滤出 svg 资源;
    • 读取 svg 文件内容;
    • 利用 @svgr/core 将 svg 转换为 React 组件代码;
    • 处理默认导出为 url 的情况;
    • 将组件的 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 = svgrResult.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;
    
  • 打开浏览器,可以看到组件已经正常显示

  • 调试技巧

    • 另外,在开发调试插件的过程,我推荐大家在本地装上 vite-plugin-inspect 插件
    • 并在Vite 中使用它
      // vite.config.ts
      import inspect from 'vite-plugin-inspect';
      // 返回的配置
      {
      	 plugins: [
      		 // 省略其它插件
      		 inspect()
      	 ]
      }
      
  • 这样当你再次启动项目时,会发现多出一个调试地址:

  • 你可以通过这个地址来查看项目中各个模块的编译结果

  • 点击特定的文件后,可以看到这个模块经过各个插件处理后的中间结果

  • 通过这个面板,我们可以很清楚地看到相应模块经过插件处理后变成了什么样子,让插件
    的调试更加方便

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

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

相关文章

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第50课-姿式识别控制机器人

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第50课-姿式识别控制机器人 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtns.network是一款主要由JavaScript编写的智体世界…

白话EAGLE2:解锁大模型的“打草稿”技术

实时了解业内动态&#xff0c;论文是最好的桥梁&#xff0c;专栏精选论文重点解读热点论文&#xff0c;围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;…

七天速通javaSE:第五天 数组基础

文章目录 前言一、认识数组二、数组的声明和创建1. 声明数组变量2. 创建数组3. 变量的初始化&#xff08;赋值&#xff09;3.1 静态初始化3.2 动态初始化 3. 示例 三、数组的使用1. 循环1.1 普通for循环1.2 For-Each 循环 2. 数组作为函数的参数和返回值 前言 本文将为大家介绍…

vue+fineReport 使用前端搜索+报表显示数据

--fineReprot 将需要搜索的参数添加到模版参数 sql&#xff1a; --前端传递参数 注&#xff1a;因为每次点击搜索的结果需要不一样&#xff0c;还要传递一个时间戳的参数&#xff1a; let timesamp new Date().getTime()

【模板】项目建设方案(Word原件)

1 引言 1.1 编写目的 1.2 项目概述 1.3 名词解释 2 项目背景 3 业务分析 3.1 业务需求 3.2 业务需求分析与解决思路 3.3 数据需求分析【可选】 4 项目建设总体规划【可选】 4.1 系统定位【可选】 4.2 系统建设规划 5 建设目标 5.1 总体目标 5.2 分阶段目标【可选】 5.2.1 业务目…

ICMAN触摸芯片之滑轮滑条演示

ICMAN触摸芯片之滑轮滑条演示 ICMAN触摸芯片实现的滑轮滑条方案—— 按键处理逻辑快速&#xff0c;物理结构定位&#xff0c;定位精准&#xff0c;实现简单&#xff0c; 属于按键滑轮滑条&#xff0c;由硬件实现的专用触摸芯片通过滑轮滑条结构设计来实现。 常见的硬件应用&a…

不使用canvs也能创建出点状背景

div{ height: 100%; touch-action: none; background: radial-gradient(circle, #e6e6e6 1px, transparent 1px); /* 创建一个点状背景 */ background-size: 15px 15px; /* 控制点的大小和间距 */ padding: 20px; /* 添加内边距使内容不靠边 */ position: relative; /* 让内部内…

DigiRL:让 AI 自己学会控制手机

类似于苹果此前发布的Ferret-UI 的安卓开源平替。主要用于在 Android 设备上识别 UI 和执行指令&#xff0c;不同的是它利用了离线到在线强化学习&#xff08;Offline-to-Online RL&#xff09;&#xff0c;能够快速适应应用更新或 UI 变化。

EDA 虚拟机 Synopsys Sentaurus TCAD 2018.06-SP2 CentOS7.9

下载地址&#xff08;制作不易&#xff0c;下载使用需付费&#xff0c;不能接受的请勿下载&#xff09;&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1358rH_Ner1TYdc_TgoXrew?pwdyq3p 提取码&#xff1a;yq3p

Meet AI4S 直播预告丨房价分析新思路:神经网络直击复杂地理环境中的空间异质性

近年来&#xff0c;房地产市场起起落落&#xff0c;房价已经成为了扰动居民幸福感的重要影响因素。大多数家庭都需要面对「买不买房、何时买房、在哪儿买房、买什么房」的艰难抉择&#xff0c;每一个问题的答案都在某种程度上与房价的波动息息相关。 近年来&#xff0c;我国各…

2023年第十四届蓝桥杯JavaB组省赛真题及全部解析(上)

目录 前言&#xff1a; 一、试题 A&#xff1a;阶乘求和&#xff08;填空&#xff09; 二、试题 B&#xff1a;幸运数字&#xff08;填空&#xff09; 三、试题 C&#xff1a;数组分割 四、试题 D&#xff1a;矩阵总面积 五、试题 E&#xff1a;蜗牛 六、试题 F&#xff…

ConcurrentLinkedQueue详解(详细图文+动画演示)

目录 ConcurrentLinkedQueue详解1、ConcurrentLinkedQueue简介2、ConcurrentLinkedQueue继承体系3、ConcurrentLinkedQueue的构造函数4、ConcurrentLinkedQueue的数据结构ConcurrentLinkedQueue类的属性注释ConcurrentLinkedQueue真正存储元素的类Node<E>ConcurrentLinke…

element ui 的 el-date-picker 日期选择组件设置可选日期范围

有时候&#xff0c;在使用日历控件的时候&#xff0c;我们需要进行定制&#xff0c;控制用户只能在指定日期范围内进行日期选择&#xff0c;在这里&#xff0c;我使用了 element ui 的 el-date-picker 日期选择控件&#xff0c;控制只能选择当前月及往前的2个月&#xff0c;效果…

基于单片机和组态王的温度监控系统的设计

摘 要 : 介绍了以 MSP430 单片机为核心 , 建立基于 DS18B20 和组态王的温度采集和监控系统。主要研究了单片机和组态王的通用通讯协议。按照 KingView 提供的通信协议 , 设计组态王与单片机的通信程序 , 实现了组态王与M SP430 单片机的直接串行通讯。在中药提取装置的…

优思学院|「按计划推动型」与「需求拉动型」的生产模式

针对生产架构做对比分类的用语&#xff0c;主要有按计划推进型与需求拉动型。 「按计划推动型」与「需求拉动型」两者乃是生产架构上常使用、成对比的两个用语。不过&#xff0c;有时不只用来指单纯的生产现场架构&#xff0c;也有人把它应用在更广泛的生产架构设计上。 按计划…

智能网络构建:探索大模型在网络领域的应用

网络领域以其高度复杂性和快速迭代为特点&#xff0c;完成从网络设计、配置、诊断到安全的网络任务需要广泛的专业知识。这些任务的固有复杂性&#xff0c;加上网络技术和协议不断变化的格局&#xff0c;为传统基于机器学习的方法带来了显著的障碍。这些方法在泛化和自动化网络…

吴恩达+ OpenAi共同编写<面向开发者的LLM入门教程>Github 50k+ star!

今天给大家推荐一本由吴恩达和OpenAI团队共同编写的关于大型语言模型&#xff08;LLM&#xff09;的权威教程<面向开发者的LLM入门教程>&#xff01;&#xff0c;在Github上已经高达50k star了&#xff0c;这含金量不用多说&#xff0c;在这里给大家强烈推荐一波&#xf…

孟德献刀 | 第4集 | 宁教我负天下人,休教天下人负我 | 你自比鸿鹄,安知他人是燕雀呀 | 三国演义 | 逐鹿群雄

&#x1f64b;大家好&#xff01;我是毛毛张! &#x1f308;个人首页&#xff1a; 神马都会亿点点的毛毛张 &#x1f4cc;这篇博客是毛毛张分享三国演义文学剧本中的经典台词和语句&#xff0c;本篇分享的是《三国演义》第Ⅰ部分《群雄逐鹿》的第4️⃣集《孟德献刀》&#x…