rust 前端npm依赖工具rsup升级日志

rsup是使用 rust 编写的一个前端 npm 依赖包管理工具,可以获取到项目中依赖包的最新版本信息,并通过 web 服务的形式提供查看、升级操作等一一系列操作。

在前一篇文章中,记录初始的功能设计,自己的想法实现过程。在自己的使用过程功能中,也会发现一些存在的问题,有一些问题值得记录的再次标记,供大家参考。

rsup 工具安装

在上一篇文章中描写的安装rsup工具部分错误,因为我本地是 macos 系统,

rust 默认执行cargo build构建的是适合 macos 的可执行文件,对于 windows、linux 是不能直接用;还有一个问题,就是rsup-web静态服务资源是不会被编译进工具包的,我本地能用也仅仅是我本地有源代码,它指向静态资源路径的就是我电脑的绝对地址。

可以采取将静态资源链接打包进二进制文件中。

  1. 使用include_bytes!rust 内置的宏将静态文件的内容嵌入到二进制文件
  2. 使用第三方 crate,比如embed-resource或者rust-embed

但是为了方便控制 web 静态资源,比如可以单独更新。采取了静态文件和可执行文件分离的方式,提供下载器同时下载rsup可执行文件和rsup-webweb 静态资源。针对不同的系统定义默认的下载路径,然后通过配置文件读取 web 静态资源提供 web 服务。

rsup工具包包含了配置文件、可执行文件、web 服务文件等。根据不同的系统,提供了三种安装工具包包括 linux、macos、windows。

macos installer

ubuntu instanller

windows instanller

提供了安装脚本文件sh一键下载解压、安装。无需手动配置环境变量。

curl -fsSL https://github.com/ngd-b/rsup/blob/main/install.sh | sh

windows用户需要手动下载安装包,解压后执行installer.exe即可,并且需要手动配置环境变量。

installer子包下载资源

这是为了解决上述问题新增的一个安装器,更友好的交互方式进行安装。也方便后面对下载方式进行更友好的优化。

执行安装器需要使用管理员权限。windows右键以管理员身份执行 exe;类 linux 系统需要使用sudo执行。

在这里插入图片描述

提供了从 github 或者 gitee 下载资源两种方式。使用第三方库 cratedialoguer进行交互选择。 目前只提供了从github下载资源。

在这里插入图片描述

use clap::{Parser, ValueEnum};
use dialoguer::{theme::ColorfulTheme, Select};#[derive(Parser, Debug, Clone, ValueEnum)]
pub enum Origin {Github,Gitee,
}impl Origin {// ...pub fn as_str(&self) -> &'static str {match self {Origin::Github => "github",Origin::Gitee => "gitee",}}/// 将枚举pub fn choices() -> Vec<&'static str> {vec![Origin::Github.as_str(), Origin::Gitee.as_str()]}
}/// 提示用户选择下载源
/// @return 下载源
pub fn prompt_origin() -> Origin {let select = Select::with_theme(&ColorfulTheme::default()).with_prompt("Please select download source...").default(0).items(Origin::choices().as_slice()).interact().unwrap();match select {0 => Origin::Github,1 => Origin::Gitee,_ => unreachable!(),}
}

使用reqwest 下载资源,并将资源保存到默认路径。文件路径output的目录必须要提前创建,而fs::File::create(output)创建了资源文件,如果文件已经存在会直接覆盖。

use reqwest::Client;
use tokio::fs;/// 下载文件
///
async fn download_file(client: &Client, url: &str, output: &str) -> Result<(), Box<dyn Error>> {// 下载地址let res = client.get(url).send().await?;if res.status().is_success() {// 下载成功// 保存文件到指定目录// 文件路径let mut file = fs::File::create(output).await?;// 保存文件let bytes = res.bytes().await?;file.write_all(&bytes).await?;Ok(())} else {let error_message = format!("Request failed with status code: {}", res.status());Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other,error_message,)))}
}

文件下载完成后需要解压。所有的资源文件都是.tar.gz格式的,使用flate2解压文件,并且需要使用tar进行解包提取到指定目录。

use flate2::read::GzDecoder;
use tar::Archive;/// 解压文件
///
/// @param url 下载地址
/// @param target_dir 保存目录
async fn decompress_file(url: &str, target_dir: &str) -> Result<(), Box<dyn Error>> {let tar_gz = File::open(url)?;let decomppress = GzDecoder::new(tar_gz);let mut archive = Archive::new(decomppress);// 处理解压目录,不存在则创建目录if !Path::new(target_dir).exists() {fs::create_dir_all(target_dir).await?;}archive.unpack(target_dir)?;Ok(())
}

所需要的资源下载解压完成后,现在默认目录下(类 linux 系统下是/opt/rsup)有三个文件

  • rsup 可执行文件
  • config.toml 配置文件
  • web web 静态资源

可以直接去执行rsup可执行文件。但是当前目录下没有package.json文件,我们可以指定参数--dir去访问指定目录下的package.json。为了方便命令的使用,安装时经将命令添加到环境变量中。

针对不同的操作系统,环境变量的配置文件不一样。windows系统需要用户自行配置,macos系统下是.zshrc;其他类系统默认为.bashrc

use std::io::Write;
use std::{error::Error, fs::OpenOptions};/// 提示用户是否添加命令到环境变量
/// 默认添加
pub fn prompt_add_to_env(path: &str) -> Result<(), Box<dyn Error>> {// ... 省略部分代码let home_dir = std::env::var("HOME")?;// 确定系统使用的shelllet shell_file_name = match os {"macos" => ".zshrc",_ => ".bashrc",};// 环境变量配置目录let shell_config_path = format!("{}/{}", home_dir, shell_file_name);// 写入配置let mut file = OpenOptions::new().append(true).open(shell_config_path)?;writeln!(file, "\n# Add rsup to PATH\nexport PATH=\"{}:$PATH\"", path)?;
}

写入配置文件后,需要重新加载配置文件。执行source ~/.zshrc或者.bashrc,这样就可以全局使用rsup命令了。

config子包管理配置文件

配置文件的读取和写入使用config子包,提供配置文件读写操作。installer安装时会默认生成配置文件,在rsup执行时会读取配置文件。为了方便配置文件管理,新增config子包。

使用了 crate toml 对配置文件config.toml进行读写序列化和反序列化。

use std::{error::Error,fs::{self, File},io::{self, Write},path::Path,
};impl Config {/// 读取配置文件///pub async fn read_config() -> Result<(), Box<dyn Error>> {// 读取配置文件let config_dir = Config::get_url();let config_file_dir = format!("{}/config.toml", config_dir);// ... 省略部分代码let config_content = fs::read_to_string(&config_file_dir)?;let config: Config = toml::from_str(&config_content)?;Ok(())}/// 写入配置文件pub async fn write_config() -> Result<Config, Box<dyn Error>> {let config_dir = Config::get_url();// ... 省略部分代码// 配置文件let config_url = format!("{}/config.toml", config_dir);let mut file = File::create(config_url.clone())?;let mut config = Config::default();// 配置文件路径config.dir = config_dir.clone();// 静态文件目录config.web.static_dir = format!("{}/web", &config_dir);let config_content = toml::to_string(&config)?;file.write_all(config_content.as_bytes())?;Ok(config)}
}

在主入口main中执行读取配置文件,然后可以在各个子包中读取。为了方便使用,在config中提供了静态全局变量CONFIG,使用了第三方 crateonce_cell实现。

use once_cell::sync::OnceCell;// 全局共享配置
pub static CONFIG: OnceCell<Config> = OnceCell::new();impl Config {pub async fn read_config() -> Result<(), Box<dyn Error>> {// ... 省略部分代码// 保存配置数据共享CONFIG.set(config).unwrap();}/// 父级包获取配置pub fn get_config() -> &'static Config {CONFIG.get().unwrap()}
}

这样就可以在其他子包中直接使用config::Config::get_config()获取配置数据了。

配置文件中包含的配置项有:

name = "rsup"
version = "0.3.0"
dir = "/opt/rsup"[web]
port = 8888
static_dir = "/opt/rsup/web"[pkg]
npm_registry = "https://registry.npmmirror.com"

配置文件中的dir字段是安装目录,默认安装在/opt/rsup;web.port字段是 web 服务的端口号,默认8888;pkg.npm_registry字段是 npm 依赖源地址,默认为国内镜像。通常只建议修改pkg.npm_registry设置源地址,方便请求依赖包。

command子包提供命令行交互

提供了新的子包command,用于解析命令行参数。统一管理命令行参数,方便使用。并且提供了一些方法使用。

在使用rsup命令时,可以指定目录使用前端 npm 依赖管理web服务;也可以通过输入自命令进行交互式操作。

子命令包含了两部分:Config 配置命令;Update更新命令。新创建了command子包,在主包解析参数时进行逻辑判断,如果输入命令则执行对应的子命令;未输入子命令则默认执行 web 服务;

#[tokio::main]
async fn main() {let args = Cli::parse();match args.command {Some(Commands::Config { .. }) | Some(Commands::Update { .. }) => {run().await;}_ => {let package = Package::new();// 默认启动pkg解析服务let package_clone = package.clone();task::spawn(async move {pkg::run(args.pkg_args, package_clone).await;});web::run(package.clone()).await;}}
}

执行run()方法调用了子包command中的方法,并解析命令行参数,根据参数执行对应的操作。

pub async fn run() {let cli = Commands::parse();let _ = match cli {Commands::Config { config } => match config {ConfigOptions::List => ConfigOptions::list_config().await,ConfigOptions::Set { key, value } => ConfigOptions::set_config_value(&key, value).await,ConfigOptions::Get { key } => ConfigOptions::get_config_value(&key).await,ConfigOptions::Delete => todo!(),},Commands::Update { update } => {// 获取最新的包地址let (rsup_url, rsup_web_url) = utils::get_pkg_url(None);// 获取命令安装目录let config = external_config::Config::get_config().await;match update {UpdateOptions::Rsup => UpdateOptions::rsup_update(rsup_url, &config.dir).await,UpdateOptions::Web => {UpdateOptions::rsup_web_update(rsup_web_url, &config.dir).await}}}};
}

Config 配置命令

Config配置命令用来管理配置文件,提供交互式操作。我们之前在installer安装时,默认生成配置文件。通过config命令可以查看、修改、删除配置项。

config list 可以展示出配置文件config.toml,在我们安装好rsup命令后,执行rsup config list可以看到配置文件内容。

在这里插入图片描述

config set key value 可以修改配置文件中的值,例如:rsup config set web.port 9999 修改web服务端口号。

对于配置文件的访问、修改,主要是使用了子包config中的方法。为了方便修改,对于子包config的实现进行了调整,文章上面提到的实现为第一版实现,可以做对比差异。

初始实现的需要在core主入口中调用一次读取配置文件,然后在其他子包中通过config::Config::get_config()获取。这种方式在config子包中不方便直接修改配置文件,需要重新读取。

使用tokio::sync::RwLock 实现读写锁,它是线程安全的。使用once_cell::sync::Lazy 实现懒加载,在首次使用时才去读取配置文件。

pub static CONFIG: Lazy<RwLock<Config>> = Lazy::new(|| {// 这里调用初始化let config = Config::read_config().unwrap();RwLock::new(config)
});

在使用set设置配置项时,需要管理员权限,配置更新后会同步更新config.toml配置文件

Update更新命令

rsup工具包含自身和web服务两部分,提供了更新命令,可以更新rsup工具和web服务。

通过rsup update rsup更新工具,通过rsup update web更新web服务。

在这里插入图片描述

utils子包提供公共方法

为了方便子包之间的共用方法的服用,提供了utils子包,提供了一些公共方法。

遇到的问题

记录一下遇到的问题,方便后续查阅。

在使用本地config 模块与配置文件config发生命名冲突

通过extern 明确导入外部模块

// 引入外部crate
extern crate config as external_config;

发布包到crates-io时名称重复,本地引用修改名称

本地开发时使用的名称utils,为了发布到crates-io时,需要修改名称rsup_utils,避免名称重复。然后本地引用时使用package字段指定名称,这样不需要去调整代码里的引用。

[package]
utils = { version = "0.1.0", path = "../utils", package = "rsup_utils" }

下载文件时展示进度条

之前的文件下载时,控制台会陷入长时间的阻塞状态,没有任何反应,为了提供更好的交互体验,使用indicatif展示进度条。

要采用进度条,在下载文件时就要使用流式读取文件,以便更新进度条。

增加两个新的lib库,futures-util提供对于stream的扩展函数。

cargo add indicatif
cargo add futures-util

修改请求reqwest增加特性支持stream

[dependencies]
reqwest = { version = "0.12.9", features = ["stream"] }

修改之前的下载函数download_file,不再使用write_all一次性写入文件,通过分批次读取写入,并同步更新进度条。

/// 下载文件
///
async fn download_file(client: &Client, url: &str, output: &str) -> Result<(), Box<dyn Error>> {// 下载地址let res = client.get(url).send().await?;if res.status().is_success() {// 获取文件大小let content_size = res.content_length().ok_or("无法获取文件大小")?;// 下载成功// 保存文件到指定目录// 文件路径let mut file = fs::File::create(output).await?;// 创建进度条let pb = ProgressBar::new(content_size);pb.set_style(ProgressStyle::default_bar().template("{msg} [{elapsed_precise}] {bar:80} {percent}%")?.progress_chars("##-"),);// 创建流式响应体let mut downloaded = 0;let mut stream = res.bytes_stream();while let Some(item) = stream.next().await {let chunk = item?;file.write_all(&chunk).await?;let len = chunk.len() as u64;downloaded += len;pb.set_position(downloaded);}pb.finish_with_message("下载完成");// 保存文件// let bytes = res.bytes().await?;// file.write_all(&bytes).await?;Ok(())} else {let error_message = format!("Request failed with status code: {}", res.status());Err(Box::new(std::io::Error::new(std::io::ErrorKind::Other,error_message,)))}
}

解决web服务自动后刷新页面加载不到的问题

这是典型的SPA的问题,由于我们使用的是history路由模式,路由由前端控制。我们刷新页面比如http://localhost:8888/home时,会请求http://localhost:8888/home,但是web服务没有这个路由,所以会返回404,导致刷新页面加载不到。

为了处理这个问题,需要增加通配符路由处理跳转route("/{tail:.*}", web::get().to(index)){tail:.*}是一个路径参数,它可以匹配任何路径。

let server = HttpServer::new(move || {//...App::new().app_data(web::Data::new(ms.clone())).route("/", web::get().to(index)).wrap(cors).service(web::scope("/api").configure(api::api_config)).service(Files::new("/static", format!("{}/static/", &static_file_path)).prefer_utf8(true),).route("/ws", web::get().to(socket_index))// SPA fallback route.route("/{tail:.*}", web::get().to(index))
})

windos系统下不同的命令执行名称

windows系统下,我们执行npm -v时,实际内部执行的是npm.cmd -v,而在mac系统下,执行npm -v时,实际内部执行的是npm -v,所以需要根据系统类型,使用不同的命令。

// 判断系统,如果是windows,则使用npm.cmd
let npm_cmd = if cfg!(windows) { "npm.cmd" } else { "npm" };

如果安装时是.exe的话就不需要添加后缀了,直接使用即可。比如node

web服务API参数映射处理

在处理API请求参数时,通过枚举定义了参数类型,然后通过解析匹配到指定的数据结构。

async fn update_pkg(req: web::Json<ReqParams>,data: web::Data<Ms>,
) -> Result<impl Responder, Error> {match &*req {ReqParams::UpdatePkg(params) => {}err => {// ...}
}

如果定义的数据结构字段存在重叠,某个结构完全包含另一个结构的字段,在匹配时就需要将完全包含的结构放在前面,否则可能会匹配到错误的结构。

#[derive(Deserialize, Serialize, Debug)]
#[serde(untagged)]
pub enum ReqParams {UpdatePkg(UpdateParams),// 删除// 目前接受一个nameRemovePkg(RemoveParams),
}

UpdateParamsRemoveParams存在字段重叠,UpdateParams包含了RemoveParams的所有字段,要想匹配到UpdateParams,需要将RemoveParams放在前面。

最后

部署了rsup文档服务网站rsup|Npm Helper

往期rsup文章:

  1. 模式匹配、trait 特征行为、必包、宏
  2. 多线程任务执行
  3. 并发线程间的数据共享
  4. 包、模块,引用路径
  5. 开发一个命令行工具
  6. rust 命令行工具rsup管理前端npm依赖

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【备赛】点亮LED

LED部分的原理图 led前面有锁存器&#xff0c;这是为了防止led会受到lcd的干扰&#xff08;lcd也需要用到这些引脚&#xff09;。 每次想要对led操作&#xff0c;就需要先打开锁存器&#xff0c;再执行操作&#xff0c;最后关闭锁存器。 这里需要注意的是&#xff0c;引脚配置…

CSS 使用white-space属性换行

一、white-space属性的常见值 * 原本格式&#xff1a; 1、white-space:normal 默认值&#xff0c;空格和换行符会被忽略过滤掉&#xff1b;宽度不够时文本会自动换行 * 宽度足够时&#xff0c;normal 处理后的格式 * 宽度不够时&#xff0c; normal 处理后的格式 2、white-spa…

electron-builder打包时github包下载失败【解决办法】

各位朋友们&#xff0c;在使用electron开发时&#xff0c;选择了electron-builder作为编译打包工具时&#xff0c;是否经常遇到无法从github上下载依赖包问题&#xff0c;如下报错&#xff1a; Get "https://github.com/electron/electron/releases/download/v6.1.12/ele…

【WSL2】 Ubuntu20.04 GUI图形化界面 VcXsrv ROS noetic Vscode 主机代理 配置

【WSL2】 Ubuntu20.04 GUI图形化界面 VcXsrv ROS noetic Vscode 主机代理 配置 前言整体思路安装 WSL2Windows 环境升级为 WIN11 专业版启用window子系统及虚拟化 安装WSL2通过 Windows 命令提示符安装 WSL安装所需的 Linux 发行版&#xff08;如 Ubuntu 20.04&#xff09;查看…

2025学年安徽省职业院校技能大赛 “信息安全管理与评估”赛项 比赛样题任务书

2024-2025 学年广东省职业院校技能大赛 “信息安全管理与评估”赛项 技能测试试卷&#xff08;五&#xff09; 第一部分&#xff1a;网络平台搭建与设备安全防护任务书第二部分&#xff1a;网络安全事件响应、数字取证调查、应用程序安全任务书任务1 &#xff1a;内存取证&…

数据库导出

MySQL数据库 使用命令行导出 导出整个数据库&#xff1a;在命令行中输入mysqldump -u用户名 -p密码 数据库名 > 导出文件路径/文件名.sql。例如mysqldump -uroot -p123456 mydb > /home/user/mydb_backup.sql&#xff0c;回车后输入密码即可将名为mydb的数据库导出为SQL…

OpenCV给图像添加噪声

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 如果你已经有了一张干净的图像&#xff0c;并希望通过编程方式向其添加噪声&#xff0c;可以使用 OpenCV 来实现这一点。以下是一个简单的例子&a…

OSPF BIT 类型说明

注&#xff1a;本文为 “OSPF BIT 类型 | LSA 类型 ” 相关文章合辑。 机翻&#xff0c;未校。 15 OSPF BIT Types Explained 15 种 OSPF BIT 类型说明 Rashmi Bhardwaj Distribution of routing information within a single autonomous system in larger networks is per…

Linux网络之传输层协议(UDP,TCP协议)

目录 重新认识端口号 端口号划分 netstat pidof UDP协议 UDP的特点 面向数据报 UDP的缓冲区 全双工和半双工 TCP协议 TCP的特点 TCP报头分析 源端口&#xff0c;目标端口&#xff0c;数据偏移(报文首部长度) 序号 确认号 窗口 6个标志位 ACK SYN …

Spring Boot 热部署

文章目录 一&#xff0c;Spring Boot热部署概述二&#xff0c;对项目HelloWorld01进行热部署 1、添加开发工具依赖2、热部署配置3、热部署测试 一&#xff0c;Spring Boot热部署概述 在开发过程中&#xff0c;通常会对一段业务代码不断地修改测试&#xff0c;在修改之后往往…

【前端基础】Day 3 CSS-2

目录 1. Emmet语法 1.1 快速生成HTML结构语法 1.2 快速生成CSS样式语法 2. CSS的复合选择器 2.1 后代选择器 2.2 子选择器 2.3 并集选择器 2.4 伪类选择器 2.4.1 链接伪类选择器 2.4.2 focus伪类选择器 2.5 复合选择器总结 3. CSS的元素显示模式 3.1 什么是元素显示…

使用vscode导出Markdown的PDF无法显示数学公式的问题

我的硬件环境是M2的MacBook air&#xff0c;在vscode中使用了Markdown PDF来导出md文件对应的PDF。但不管导出html还是PDF文件&#xff0c;数学公式都是显示的源代码。 我看了许多教程&#xff0c;给的是这个方法&#xff1a;在md文件对应的html文件中加上以下代码&#xff1a…

去耦电容的作用详解

在霍尔元件的实际应用过程中&#xff0c;经常会用到去耦电容。去耦电容是电路中装设在元件的电源端的电容&#xff0c;其作用详解如下&#xff1a; 一、基本概念 去耦电容&#xff0c;也称退耦电容&#xff0c;是把输出信号的干扰作为滤除对象。它通常安装在集成电路&#xf…

[原创]openwebui解决searxng通过接口请求不成功问题

openwebui 对接 searxng 时 无法查询到联网信息&#xff0c;使用bing搜索&#xff0c;每次返回json是正常的 神秘代码&#xff1a; http://172.30.254.200:8080/search?q北京市天气&formatjson&languagezh&time_range&safesearch0&languagezh&locale…

【JavaSE-1】初识Java

1、Java 是什么? Java 是一种优秀的程序设计语言,人类和计算机之间的交流可以借助 Java 这种语言来进行交流,就像人与人之间可以用中文、英语,日语等进行交流一样。 Java 和 JavaScript 两者有关系吗? 一点都没有关系!!! 前端内容:HTML CSS JS,称为网页三剑客 2、JDK 下…

C++知识整理day10——多态(多态的定义和实现、虚函数重写/覆盖、override和final关键字、纯虚函数和抽象类、多态的原理)

文章目录 1.多态的概念2.多态的定义和实现2.1 多态的构成条件2.2 多态必须具备的两个条件&#xff08;很重要&#xff09;2.3 虚函数2.4 虚函数的重写/覆盖2.5 协议&#xff08;了解即可&#xff09;2.6 析构函数的重写2.6 override和final关键字2.7 重载/重写/隐藏的对比 3.纯…

BladeX框架接口请求跨域

前端使用代理请求接口&#xff0c;接口可以正常访问。如果换全路径请求就跨域。 除了后端要配置跨域 还需要修改配置文件对OPTIONS请求的限制

文件操作 -- IO [Java EE 初阶]

目录 文件 1. 认识文件 2. 树型结构组织和目录 3. 文件路径 (Path) 4. 文件系统上存储的文件又可以分为两大类 4.1 文本文件 4.2 二进制文件 文件系统操作 1.Java 中操作文件 2. File 概述 2.1 属性 2.2 构造方法 2.3 方法 2.4 部分举例 文件内容操作 1. 数据流…

菜鸟之路Day19一一多线程(一)

菜鸟之路Day19一一多线程&#xff08;一&#xff09; 作者&#xff1a;blue 时间&#xff1a;2025.2.24 文章目录 菜鸟之路Day19一一多线程&#xff08;一&#xff09;o.概述1.什么是多线程2.并发与并行3.多线程的实现方式3.1继承Thread类的方式进行实现3.2实现Runnable接口的…

《Effective Objective-C》阅读笔记(上)

目录 高质量iOS之熟悉OC 了解OC语言的起源 在类的头文件中尽量少引入其他头文件 多用字面语法&#xff0c;少用与之等价的方法 字面数值 字面量数组 字面量字典 局限性 多用类型常量&#xff0c;少用#define预处理指令 用枚举表示状态、选项、状态码 高质量iOS之对象…