webpack进阶 -- 自定义Plugin,Loader封装打包优化

介绍

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。在 Webpack 处理应用程序时,它会在内部构建一个依赖图(dependency graph),这个依赖图对应映射到项目所需的每个模块,并生成一个或多个 bundle。在这个过程中,Webpack 可以通过使用 loaderplugin 来扩展其功能。

Loader

Loader 让 Webpack 能够去处理那些非 JavaScript 文件(Webpack 自身只理解 JavaScript)。Loader 可以将所有类型的文件转换为 Webpack 能够处理的有效模块,然后你就可以利用 Webpack 的打包能力,对它们进行处理。简单来说,loader 用于对模块的源代码进行转换。Loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于"任务运行者",并且可以将文件从不同的语言(如 TypeScript 转换为 JavaScript)转换为 JavaScript,或将内联图像转换为 data URL。Loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!

Plugin

Plugin 用于 bundle 文件的优化,资源管理和环境变量注入等一系列的任务。作用于整个构建过程,插件可以用来执行范围更广的任务,如打包优化、资源管理和环境变量的注入。简单来说,插件可以用于执行任何其他 loader 无法完成的任务。它们直接作用于整个构建过程,从打包优化和压缩,一直到重新定义环境中的变量等。

Loader 和 Plugin 的区别
  • Loader 用于转换某些类型的模块,它们作用于单个文件。
  • Plugin 影响整个构建过程,能够执行更广泛的任务,比如打包优化、资源管理和环境变量注入。
example

Loader 示例:使用 babel-loader 来转换 ES6+ 的代码到 ES5。

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['@babel/preset-env']
        }
      }
    }
  ]
}

Plugin 示例:使用 HtmlWebpackPlugin 来生成一个 HTML 文件,并自动注入打包后的脚本。

const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};

Webpack 的强大之处在于其高度的可配置性,通过合理地使用 loaderplugin,你可以极大地提升开发效率和项目的性能。

自定义Loader

concept

loader本质上是一个函数,loader的执行顺序在use数组里面是从下往上执行,里面有一个pitch方法,use数组中pitch方法的执行顺序是从上往下执行,因此我们如果想先执行某些功能,可以先在pitch方法中定义

在 Webpack 的 loader 文件中,this 关键字指的是 loader 上下文(loader context)。Webpack 为每个 loader 提供了一个上下文环境,这个环境包含了很多有用的方法和属性,允许开发者在 loader 中与 Webpack 的构建过程进行交互。
Loader 上下文中的一些常用属性和方法包括:
this.callback: 一个允许 loader 异步返回结果的函数。当你需要异步处理内容时,可以使用这个方法。
this.async: 一个函数,当调用时,会返回 this.callback 函数,用于异步模式。
this.resourcePath: 当前正在处理的文件的绝对路径。
this.query: loader 的选项对象。如果你在 webpack 配置中对某个 loader 传递了选项,这些选项会通过 this.query 访问到。
this.data: 在 pitch 阶段和普通阶段之间共享的数据对象。
this.loaders: 当前处理文件所使用的 loader 数组。
this.emitFile: 允许在输出目录中生成文件的方法。

example

假设你正在编写一个简单的 loader,它的作用是将文件内容转换为大写

module.exports = function(source) {
  // 使用 this.context 访问 loader 上下文
  const callback = this.async(); // 如果需要异步处理,获取异步回调函数
  if (!callback) {
    // 同步处理
    return source.toUpperCase();
  } else {
    // 异步处理
    setTimeout(() => {
      callback(null, source.toUpperCase());
    }, 1000);
  }
};

在这个例子中,我们使用了 this.async 来获取一个回调函数,用于异步返回处理结果。这是 this 在 loader 中的一个常见用法。

自定义babel-loader

创建校验规则 babelSchema.json

{
  "type": "object",
  "properties": {
    "presets": {
      "type": "array"
    }
  },
  "addtionalProperties": true
}

创建loader babelLoader.js

const { getOptions } = require('loader-utils');
const { validate } = require('schema-utils');
const babel = require('@babel/core');
const util = require('util');

const babelSchema = require('./babelSchema.json');

// babel.transform用来编译代码的方法
// 是一个普通异步方法
// util.promisify将普通异步方法转化成基于promise的异步方法
const transform = util.promisify(babel.transform);

module.exports = function (content, map, meta) {
  // 获取loader的options配置
  const options = getOptions(this) || {};
  // 校验babel的options的配置
  validate(babelSchema, options, {
    name: 'Babel Loader'
  });

  // 创建异步
  const callback = this.async();

  // 使用babel编译代码
  transform(content, options)
    .then(({code, map}) => callback(null, code, map, meta))
    .catch((e) => callback(e))

}

babelLoader使用 webpack.config.js

const path = require('path');

module.exports = {
    module: {
      rules: [{
        test: /\.js$/,
        loader: 'babelLoader',
        options: {
          presets: [
            '@babel/preset-env'
          ]
        }
      }]
    },
    // 配置loader解析规则:我们的loader去哪个文件夹下面寻找(这里表示的是同级目录的loaders文件夹下面寻找)
    resolveLoader: {
      modules: [
        'node_modules',
        path.resolve(__dirname, 'loaders')
      ]
    }
  }
warn

当编写 loader 时,应避免使用箭头函数定义 loader,因为箭头函数不绑定自己的 this,这会导致无法访问 loader 上下文。

自定义plugin

concept

Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。在Webpack的工作过程中,它提供了一系列的钩子(hooks)来让开发者在构建流程的不同阶段介入,从而可以自定义构建的行为。Webpack 使用了 Tapable 来实现这些钩子系统。Tapable 是一个类似于 Node.js 的 EventEmitter,用于自定义事件的发布和订阅,但它专为同步和异步的钩子设计。

Webpack 的编译器(Compiler)和编译(Compilation)对象提供了许多钩子,以下是一些常用的编译器钩子:

Compiler Hooks

Compiler 模块是 webpack 的主要引擎,它通过 CLI 或者 Node API 传递的所有选项创建出一个 compilation 实例。 它扩展(extends)自 Tapable 类,用来注册和调用插件。 大多数面向用户的插件会首先在 Compiler 上注册。

entryOption:在 webpack 选项中的 entry 配置项处理之后,执行插件。

afterPlugins:设置完初始插件之后,执行插件。

afterResolvers:设置完解析器之后,执行插件。

environment:环境准备好之后,执行插件。

afterEnvironment:环境完全准备好之后,执行插件。

beforeRun:在编译器开始读取记录之前,执行插件。

run:开始读取记录之前,执行插件。

emit:生成资源到 output 目录之前,执行插件。

afterEmit:生成资源到 output 目录之后,执行插件。

done:编译完成。

failed:编译失败。

这些钩子可以用于执行各种任务,比如清理/dist目录、生成自定义报告、自动部署等。

compilation Hooks

Compilation 模块会被 Compiler 用来创建新的 compilation 对象(或新的 build 对象)。 compilation 实例能够访问所有的模块和它们的依赖(大部分是循环依赖)。 它会对应用程序的依赖图中所有模块, 进行字面上的编译(literal compilation)。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。
Compilation 类扩展(extend)自 Tapable,Compilation 钩子是在每次构建过程中触发的,它们提供了对编译(compilation)对象的访问,这个对象包含了当前的模块资源、编译生成资源、变化的文件等信息。以下是一些常用的 Compilation 钩子:

buildModule: 在开始构建模块之前触发。

succeedModule: 当一个模块成功构建后触发。

failedModule: 当一个模块构建失败后触发。

finishModules: 当所有模块构建完成后触发。

seal: 在封装之前触发。封装(sealing)是优化和生成最终资源之前的一个步骤。

optimize: 在优化阶段开始时触发。

emit: 在生成资源到 output 目录之前触发,可以用来访问和修改输出资源。

afterEmit: 在生成资源到 output 目录之后触发。

done: 在编译完成后触发。

example

要在插件中使用这些钩子,你需要在插件类的 apply 方法中订阅它们。例如,如果你想在每个模块构建完成后做一些事情,你可以这样做
模块构建成功提示插件

class MyPlugin {
  apply(compiler) {
    compiler.hooks.compilation.tap('MyPlugin', (compilation) => {
      compilation.hooks.succeedModule.tap('MyPlugin', (module) => {
        console.log(`模块构建成功: ${module.identifier()}`);
      });
    });
  }
}

在这个例子中,MyPlugin 是一个自定义插件,它在每个模块构建成功后打印模块的标识符。Webpack 的插件系统和钩子机制非常强大,允许开发者高度自定义构建过程。理解和正确使用这些钩子对于开发高效、可维护的 webpack 插件至关重要。

打包检测非法字符插件

// 检测非法字符的插件
class CheckBetaEnvPlugin {
    constructor(options) {
      this.options = options || {};
    }
  
    apply(compiler) {
      compiler.hooks.emit.tapAsync('CheckBetaEnvPlugin', (compilation, callback) => {
        // 遍历所有即将输出的资源
        Object.keys(compilation.assets).forEach((filename) => {
          const source = compilation.assets[filename].source();
          // 检查资源内容是否包含beta环境字段
          if (source.includes(this.options.searchText)) {
            console.warn(`警告: 文件 ${filename} 包含beta环境字段: ${this.options.searchText}`);
          }
        });
        callback();
      });
    }
  }
  
  module.exports = CheckBetaEnvPlugin;
  
module.exports = {
    plugins: [
        new CheckBetaEnvPlugin({
            searchText: 'beta-api'
        })
    ]
}

它会在Webpack的emit阶段检查所有即将输出的文件,寻找非法字符(例如,我们这里假设beta-api是我们要检测的)

warn

使用钩子时,确保你的插件逻辑不会意外地影响到构建过程的性能和结果。钩子的使用和订阅需要遵循 webpack 的插件开发规范。钩子的异步版本通常以 Async 结尾,使用它们时需要特别注意异步逻辑的处理。

webpack打包优化

打包流程
  1. 初始化(Initialization)
    配置解析:Webpack 启动后会读取配置文件(默认是 webpack.config.js),合并命令行传入的参数,形成最终的配置对象。
    插件初始化:根据配置初始化插件,插件可以监听Webpack打包过程中的各个节点,执行相应的任务。
  2. 编译(Compilation)
    创建编译对象:根据配置初始化一个 compiler 对象,它包含了Webpack环境所有的配置信息,这个对象在Webpack启动时被一次性创建,并被用于后续的编译。
    确定入口:根据配置中的 entry 属性,找出所有的入口文件。
  3. 构建模块(Building)
    从入口开始解析:对每一个入口和其依赖进行递归解析。
    加载模块:根据模块类型,调用对应的 loader 对模块进行转换,比如将 ES6 转换为 ES5,将 LESS/SASS 转换为 CSS。
    模块转换:在加载模块的过程中,应用各种 Loader 对模块进行转换,最终转换为 JavaScript 代码。
  4. 完成模块编译并输出(Compilation & Output)
    构建模块关系图:根据入口文件和模块之间的依赖关系,构建出一个模块关系图。获取依赖关系是通过babel处理应用代码为抽象语法书(AST),不断递归获取依赖关系图。
    输出资源:根据入口和模块之间的依赖关系,组合成一个个包含多个模块的 Chunk,再将每个 Chunk 转换成一个单独的文件加入到输出列表,这个过程称为 chunking。
    写入文件系统:根据配置中的 output 属性,将文件内容写入到文件系统中。
  5. 完成(Finish)
    插件执行更多任务:在整个编译过程结束后,插件可以继续执行一些任务,比如优化、压缩等。
    完成打包:至此,Webpack 完成了整个打包过程。
    Webpack 的打包流程是高度可配置的,通过 Loader 和 Plugin,开发者可以灵活地控制每一步的具体行为,以适应不同项目的需求。
优化方式

1 减少解析的文件和目录(减少不必要的工作)缩小文件范围Loader

优化loader配置
test、include、exclude三个配置项来缩⼩loader的处理范围
推荐include

rules: [
      {
        test: /\.css$/,
        include: path.resolve(__dirname, "./src"),
        use: ["style-loader", "css-loader"],
      },
      {
        test: /\.less$/,
        include: path.resolve(__dirname, "./src"),
        use: [
          // "style-loader",
          MiniCssExtractPlugin.loader,
          {
            loader: "css-loader",
            options: {
              //css modules 开启
              modules: true,
            },
          },
          {
            loader: "postcss-loader",
          },
          "less-loader",
        ],
      },
      {
        test: /\.(png|jpe?g|gif)$/,
        include: path.resolve(__dirname, "./src"),
        use: {
          loader: "url-loader",
          options: {
            name: "[name]_[hash:6].[ext]",
            outputPath: "images/",
            //推荐使用url-loader 因为url-loader支持limit
            //推荐小体积的图片资源转成base64
            limit: 12 * 1024, //单位是字节 1024=1kb
          },
        },
      },
      {
        test: /\.js$/,
        include: path.resolve(__dirname, "./src"),
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],

2 借助MiniCssExtractPlugin完成抽离css

npm install mini-css-extract-plugin -D

const MiniCssExtractPlugin = require("mini-css-extract-plugin");
 {
 	test: /\.less$/,
 	use: [
 	// "style-loader", // 不再需要style-loader,⽤MiniCssExtractPlugin.loader代替
  	MiniCssExtractPlugin.loader,
  	"css-loader", // 编译css
  	"postcss-loader",
  	"less-loader" // 编译less
 	]
 },
plugins: [
  new MiniCssExtractPlugin({
   filename: "css/[name]_[contenthash:6].css",
   chunkFilename: "[id].css"
  })
 ]

3 Tree Shaking 配合代码分割优化(optimization.splitChunks)

Webpack 的代码分割(Code Splitting)是一种优化技术,旨在将代码拆分成多个小块(chunks),然后按需加载或并行加载这些小块,以减少初始加载时间和提高应用性能。

在webpack配置文件添加splitChunks:

//webpack.config.js
module.exports = {
  // ...
  optimization: {
    splitChunks: {
      chunks: 'all', // 对所有模块进行优化
      minSize: 20000, // 生成 chunk 的最小体积(以 bytes 为单位)
      minChunks: 1, // 在分割之前,模块被引用的最少次数
      maxAsyncRequests: 30, // 按需加载时的最大并行请求数
      maxInitialRequests: 30, // 入口点的最大并行请求数
      automaticNameDelimiter: '~', // 默认情况下,webpack 将使用块的来源和名称生成名称(例如 vendors~main.js)
      cacheGroups: { // 配置缓存组
        vendors: {
          test: /[\\/]node_modules[\\/]/, // 控制此缓存组选择的模块。匹配 node_modules 目录下的模块
          priority: -10 // 一个模块可以属于多个缓存组。优化将优先考虑具有更高优先级的缓存组。
        },
        default: {
          minChunks: 2, // 覆盖外部配置的 minChunks
          priority: -20,
          reuseExistingChunk: true // 如果当前块包含已从主束拆分的模块,则将重用它而不是生成新的块。
        }
      }
    }
  }
};

chunks: 指定哪些 chunks 将被选中进行优化。可以是 async(只对异步加载的模块进行分割)、initial(只对入口文件进行分割)、all(对所有模块进行分割)。
minSize: 生成的每个 chunk 的最小大小。
minChunks: 分割前必须共享模块的最小 chunks 数。它控制的是每个模块什么时候被抽离出去:当模块被不同entry引用的次数大于等于这个配置值时,才会被抽离出去。它的默认值是1。也就是任何模块都会被抽离出去(入口模块其实也会被webpack引入一次)。
maxAsyncRequests: 按需加载时的最大并行请求数。
maxInitialRequests: 入口点的最大并行请求数。
automaticNameDelimiter: 生成的文件名的分隔符。
cacheGroups: 缓存组可以继承和/或覆盖来自 splitChunks的任何选项。每个缓存组都有自己的配置,可以细粒度地控制如何分割代码。cacheGroups是splitChunks配置的核心,对代码的拆分规则全在cacheGroups缓存组里配置。
缓存组的每一个属性都是一个配置规则
priority:priority属性的值为数字,可以为负数。作用是当缓存组中设置有多个拆分规则,而某个模块同时符合好几个规则的时候,则需要通过优先级属性priority来决定使用哪个拆分规则。

4 压缩css,压缩Html等

npm install cssnano -D
npm i optimize-css-assets-webpack-plugin -D
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");

new OptimizeCSSAssetsPlugin({
 cssProcessor: require("cssnano"), //引⼊cssnano配置压缩选项
 cssProcessorOptions: {
 discardComments: { removeAll: true }
 }
})

其他优化方式

使⽤externals优化cdn静态资源

我们可以将⼀些JS⽂件存储在CDN上(减少Webpack打包出来的js体积),在index.html中通过标签引⼊,如果用标签引入但是我们希望在使⽤时,仍然可以通过import的⽅式去引⽤(如:import $ from ‘jquery’ ),并且希望webpack不会对其进⾏打包,此时就可以配置 externals 。

//webpack.config.js
module.exports = {
 //...
 externals: {
 //jquery通过script引⼊之后,全局中即有了 jQuery 变量
 'jquery': 'jQuery'
 }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

搜维尔科技:光学动作捕捉系统用于城市公共安全智慧感知实验室

用户名称:西安科技大学 主要产品:Optitrack Priime41 光学动作捕捉系统(8头) 在6米8米的空间内,通过8个Optitrack Priime41光学动作捕捉镜头,对人体动作进行捕捉,得到用户想要的人体三维空间坐…

synchronized关键字和ReentrantLock锁区别

synchronized关键字和ReentrantLock锁是Java中用于同步的两个重要机制,它们在很多方面有所不同: 1. **锁定范围**: synchronized关键字只能在方法的执行过程中提供锁定,而ReentrantLock可以锁定任何对象,包括方法、代码块和对象。…

认养小游戏功能介绍

认养小游戏通常模拟了真实的农业生产过程,让玩家能够在线上体验种植、养殖的乐趣。以下是一些常见的认养小游戏功能介绍: 选择认养的农产品:首先,玩家可以从游戏中提供的多种农产品中选择自己想要认养的种类,如蔬菜、…

容器化Jenkins远程发布java应用(方式二:自定义镜像仓库远程拉取构建)

1.创建maven项目 2.配置git、maven 3.阿里控制台>容器镜像服务>镜像仓库>创建镜像仓库 4.执行shell脚本(推送镜像到阿里云镜像仓库) 使用到登录阿里云仓库命令 #!/bin/bash # 服务名称 SERVER_NAMEplanetflix-app # 镜像tag IMAGE_TAG1.0.0-SN…

JavaWeb之过滤器(Filter)与监听器(Listener)

前言 过滤器(Filter) 1.什么是过滤器 2.过滤器的语法格式 3.使用场景 3.1.如何防止用户未登录就执行后续操作 3.2.设置编码方式--统一设置编码 3.3.加密解密(密码的加密和解密) 3.4.非法文字筛选 3.5.下载资源的限制 监听器(Listener) 1.什么是监听器 2.监听器分类…

5G工业路由器实现驾考科目三实时监控与远程控制

5G驾考路由器的应用主要体现在智能驾考系统中,其优势包括提高考试安全性、效率和规范性,同时杜绝违规行贿作弊的行为。 在驾考系统中,5G工业路由器是数据传输的桥梁设备。车载设备如摄像头、定位系统、硬盘录像机、传感器等,通过串…

DetCLIPv3:面向多功能生成开放词汇的目标检测

DetCLIPv3:面向多功能生成开放词汇的目标检测 摘要IntroductionRelated worksMethod DetCLIPv3: Towards Versatile Generative Open-vocabulary Object Detection 摘要 现有的开词汇目标检测器通常需要用户预设一组类别,这大大限制了它们的应用场景。在本文中&…

07 常用工具集

本课时主要介绍常用的工具,将会讲解三个知识点: JVM 相关工具的作用和适用场景; Git 常用命令和工作流; Linux 系统中常用分析工具。 常用工具汇总 常用工具汇总如下图所示。 说明:这里列出的都是一些相对独立的工…

Edge视频增强功能

edge://flags/#edge-video-super-resolution 搜索Video查找 Microsoft Video Super Resolution 设置为Enabled

Linux操作系统中管理磁盘的另外一种操作方式。即LVM——逻辑卷管理操作

在Linux操作系统中管理磁盘的一种方法名称——LVM,这种管理磁盘的优势。 1.使用LVM去管理磁盘可以在不影响原来数据的前提下去扩容磁盘空间或者是缩减磁盘空间。 在LVM中除了上层逻辑券可以扩容,下层的券组也可以扩容。 2.使用LVM管理的磁盘支持快照功…

TriCore: 从RTOS内核的角度看CSA

今天尝试从RTOS内核的角度来看看 TriCore 的 CSA。 CSA的细节信息可以参考前一篇文章 TriCore User Manual 笔记 1-CSDN博客 CSA 的全称是 Context Save Area,顾名思义就是专门用来保存上下文的一块存储区域。 既然是上下文使用,那必然要求低延迟&…

【Leetcode每日一题】 分治 - 交易逆序对的总数(难度⭐⭐⭐)(74)

1. 题目解析 题目链接:LCR 170. 交易逆序对的总数 这个问题的理解其实相当简单,只需看一下示例,基本就能明白其含义了。 2.算法原理 归并排序的基本思路 归并排序将数组从中间分成两部分,在排序的过程中,逆序对的来…

民航电子数据库:在console或服务器登录数据库

目录 前言登录切换数据库 前言 在不使用数据库管理工具的情况下,可以在console或服务器上操作数据库,这时就需要使用相关命令登录到数据库 登录 caeconsole nssl IP地址 端口 数据库名称 用户名 密码 切换数据库 use 数据库名称

springboot+vue+mybatis警情高发智能灯箱+PPT+论文+讲解+售后

时代在飞速进步,每个行业都在努力发展现在先进技术,通过这些先进的技术来提高自己的水平和优势,警情高发智能灯箱当然不能排除在外。警情高发智能灯箱是在实际应用和软件工程的开发原理之上,运用微信开发者、java语言以及SpringBo…

拦截器添加以及注册

自定义拦截器 自定义一个类 实现 HandlerInterceptor 接口 并重写里面的方法 preHandle、postHandle、afterCompletion preHandle:在执行具体的Controller方法之前调用 postHandle:controller执行完毕之后被调用 afterCompletion:方法需要…

关于FreeRTOS/Nuttx/Zephyr对于用户态程序实现的对比

前言: 现在很多单片机有MPU。比如STM32F4 F7 H7等以ARM CM3 CM4 CM为架构的单片机。那么现在很多RTOS都支持MPU配置,利用MPU实现用户态程序和内核态程序隔离。 从用户产品经理角度出发,作为RTOS的使用者,以一个简单的商业例子来…

区块链 | NFT 水印:Review on Watermarking Techniques(三)

🍍原文:Review on Watermarking Techniques Aiming Authentication of Digital Image Artistic Works Minted as NFTs into Blockchains 一个 NFT 的水印认证协议 可以引入第三方实体来实现对交易的认证,即通过使用 R S A \mathsf{RSA} RSA…

喜报|知从科技荣获“2023年度浦东新区创新创业奖”

4月11日,由上海市浦东新区人民政府举办的“2024年浦东新区经济突出贡献企业表彰活动”在上海国际会议中心隆重举行。知从科技凭借过去一年在行业内卓越的技术创新实力及对浦东新区发展作出的杰出贡献,入选创新创业20强企业,荣获“2023年度浦东…

Coze扣子开发指南:用免费API自己创建插件

虽然Coze扣子现在插件商店已经有几百个插件了,但相对于海量人群的众多差异化需求,还是远远不够的。如果插件商店没有合适的插件,其实完成可以自己创建,过程也很简单,不需要编写任何代码。 首先打开个人空间&#xff0…

python:做柱状图

import matplotlib.pyplot as plt # 数据 categories [A, B, C, D] values [23, 45, 56, 78] # 创建柱状图 plt.bar(categories, values) # 添加标题和标签 plt.title(柱状图示例) plt.xlabel(类别) plt.ylabel(数值) # 显示图形 plt.show() D:\software\新建文件夹\python\L…
最新文章