【万字详解】如何在微信小程序的 Taro 框架中设置静态图片 assets/image 的 Base64 转换上限值
设置方法
mini
中提供了 imageUrlLoaderOption
和 postcss.url
。
其中:
config.limit
和 imageUrlLoaderOption.limit
服务于 Taro 的 MiniWebpackModule.js , 值的写法要 ()KB * 1024
。
config.maxSize
服务于 postcss-url 的 inline.js , 值的写法要 ()KB
。
关于为什么 limit
和 maxSize
的写法不同?
因为在源码层:
limit
在使用时是判断 limit
和 2 * 1024
的值: maxSize: options.limit || 2 * 1024
;
maxSize
在使用时是判断 maxSize
和 0
,再乘以 1024
: const maxSize = (options.maxSize || 0) * 1024;
;
所以在配置时要注意区分。
const config = {// ...mini: {// ...imageUrlLoaderOption: {limit: num * 1024,},postcss: {// ...url: {enable: true / false,config: {limit: num * 1024,maxSize: num,},},// ...},},// ...
};// ...
Base64 转换上限值分以下 12 种情况去配置:
url | config | imageUrlLoaderOption | 转成 Base64 的图片上限 |
url.enable 为 true | config.limit 和 config.maxSize 都存在 | 没有 imageUrlLoaderOption.limit | config.limit 和 maxSize的最大值 |
有 imageUrlLoaderOption.limit | config.limit、imageUrlLoaderOption.limit 、 maxSize的最大值 | ||
config.maxSize 不存在, config.limit 存在 | 没有 imageUrlLoaderOption.limit | 全部图片都被转成 Base64 | |
有 imageUrlLoaderOption.limit | 全部图片都被转成 Base64 | ||
config.limit 不存在, config.maxSize 存在 | 没有 imageUrlLoaderOption.limit | 当 maxSize > 10 ,以 maxSize 为主;否则小于 10KB 的图片被转成 Base64 | |
有 imageUrlLoaderOption.limit | imageUrlLoaderOption.limit 和 maxSize的最大值 | ||
config.limit 和 config.maxSize 都不存在 | 没有 imageUrlLoaderOption.limit | 全部图片都被转成 Base64 | |
有 imageUrlLoaderOption.limit | 全部图片都被转成 Base64 | ||
url.enable 为 false | - | 没有 imageUrlLoaderOption.limit | 2KB |
有 imageUrlLoaderOption.limit | imageUrlLoaderOption.limit | ||
不存在 url | - | 没有 imageUrlLoaderOption.limit | 全部图片都被转成 Base64 |
有 imageUrlLoaderOption.limit | 全部图片都被转成 Base64 |
最实用的配置:
不使用 postcss 插件,通过 Taro 提供的 imageUrlLoaderOption
来设置转换上限值
// ...
const config = {// ...mini: {// ...imageUrlLoaderOption: {limit: -1,},postcss: {// ...url: {enable: false,},// ...},},// ...
};
// ...
关于 limit
config.limit
和 imageUrlLoaderOption.limit
的替换关系如图所示
源码解析
Taro 部分
class MiniWebpackModule
是 Taro 框架中用于处理和封装 Webpack 构建模块的类。它负责配置、加载和编译与各类文件格式相关的模块,通常在 Taro 生成项目构建配置时发挥作用。它的目标是将开发者的源代码转换成适用于小程序、 H5 、 React Native 等平台的最终代码。
getModules
getModules
方法的核心作用是配置和返回不同类型模块的处理规则,包括对 Sass 、 Scss 、 Less 、 Stylus 等样式文件的处理,并将其与 CSS 、 PostCSS 等工具结合起来,最终生成适合各种小程序平台的构建规则。
parsePostCSSOptions()
解析 PostCSS 的配置选项,返回 postcssOption
、postcssUrlOption
和 cssModuleOption
等。
通过 getDefaultPostcssConfig
方法获取 PostCSS 的默认配置,返回给 this.__postcssOption
进行保存。
调用 getImageRule
方法对 image
进行配置,并将配置存入 rule
对象中。
getModules() {const { appPath, config, sourceRoot, fileType } = this.combination;const { buildAdapter, sassLoaderOption, lessLoaderOption, stylusLoaderOption, designWidth, deviceRatio } = config;const { postcssOption, postcssUrlOption, cssModuleOption } = this.parsePostCSSOptions();this.__postcssOption = (0, postcss_mini_1.getDefaultPostcssConfig)({designWidth,deviceRatio,postcssOption,alias: config.alias,});const rule = {image: this.getImageRule(postcssUrlOption)};return { rule };
}
parsePostCSSOptions
defaultUrlOption
确实默认启用 postcss-url,并设置 limit
为 10KB(10240 字节)。代码中 postcssOption.url
会通过 recursiveMerge
方法与 defaultUrlOption
合并,如果配置中提供了 postcss-url 的自定义配置,将会覆盖默认值。
postcssUrlOption
不会赋值,即 config/index.ts 中配置的 config
不会被使用或存储。
parsePostCSSOptions() {const { postcss: postcssOption = {} } = this.combination.config;const defaultUrlOption = {enable: true,config: {limit: 10 * 1024 // limit 10k base on document}};const urlOptions = (0, helper_1.recursiveMerge)({}, defaultUrlOption, postcssOption.url);let postcssUrlOption = {};if (urlOptions.enable) {postcssUrlOption = urlOptions.config;}return {postcssOption,postcssUrlOption};
}
getDefaultPostcssConfig
接收 postcssOption
,从中提取 url
配置,并通过 recursiveMerge
将其与 defaultUrlOption
合并为 urlOption
。如果 postcssOption.url
存在,就会用自定义配置来替代或合并默认配置。
const getDefaultPostcssConfig = function ({ postcssOption = {} }) {const { url } = postcssOption,options = __rest(postcssOption, ["url"]);const urlOption = (0, helper_1.recursiveMerge)({}, defaultUrlOption, url);return [["postcss-url", urlOption, require("postcss-url")],...Object.entries(options),];
};
getImageRule
当调用 getImageRule
时,postcssOptions
和 imageUrlLoaderOption
合并生成新的 options
,并传递给 WebpackModule.getImageRule()
,limit
的优先级以 imageUrlLoaderOption.limit
为主导。
getImageRule(postcssOptions) {const sourceRoot = this.combination.sourceRoot;const { imageUrlLoaderOption } = this.combination.config;const options = Object.assign({}, postcssOptions, imageUrlLoaderOption);return WebpackModule_1.WebpackModule.getImageRule(sourceRoot, options);
}
WebpackModule_1.WebpackModule.getImageRule
如果 postcss.url.config.limit
没有设置,系统应有逻辑保证 limit
的默认值为 2KB。
static getImageRule(sourceRoot, options) {return {test: helper_1.REG_IMAGE,type: 'asset',parser: {dataUrlCondition: {maxSize: options.limit || 2 * 1024 // 2kb}},generator: {emit: options.emitFile || options.emit,outputPath: options.outputPath,publicPath: options.publicPath,filename({ filename }) {if ((0, shared_1.isFunction)(options.name))return options.name(filename);return options.name || filename.replace(sourceRoot + '/', '');}}};
}
postcss-url 部分
plugin
options
是个对象,属性有 url
、 limit
、 maxSize
。 url
默认值是 inline
。
当 enable
设为 false
, options
为 {}
;设为 true
, options
为打印配置中所设的,可能有 url
、 limit
、 maxSize
。
styles.walkDecls((decl) =>{});
遍历 CSS 文件中的每个声明(decl)。对每个声明,调用 declProcessor
函数,并将结果(是个 Promise
) push
到 promises
数组中。返回的 Promise
通过 then
打印的结果,是一个数组,值为'url("OSS || Base64 || assets")'
。
const plugin = (options) => {options = options || {};return {postcssPlugin: "postcss-url",Once(styles, { result }) {const promises = [];const opts = result.opts;const from = opts.from ? path.dirname(opts.from) : ".";const to = opts.to ? path.dirname(opts.to) : from;styles.walkDecls((decl) =>promises.push(declProcessor(from, to, options, result, decl)));return Promise.all(promises);},};
};
declProcessor
获取 patten
,值存在 undefined
、/(url\(\s*['"]?)([^"')]+)(["']?\s*\))/g
还有其他。
如果 patten
是 undefined
,直接返回 Promise.resolve()
。这是为了在 pattern
不存在时直接短路,避免不必要的计算。
正常函数内部执行了
Promise.resolve()
后,后面的代码是可以继续执行的。
如果 patten
不是 undefined
,新建一个 promises
数组,用来存储所有异步操作的 Promise
。 decl.value.replace(pattern, (matched, before, url, after) => { ... })
使用 replace
函数遍历和处理 decl.value
中的每个匹配项。 replace
函数的第二个函数是个回调函数,最终的返回值是 matched
。
在回调函数中执行 replaceUrl
函数,返回一个为 Promise
的 newUrlPromise
,然后调用 then
方法获取 newUrlPromise
的值。
如果 newUrlPromise
的值是 undefined
,直接返回 matched
。这种情况会发生在没有转换成 Base64 编码的本地图片路径上。
declProcessor
最终返回的是 Promise.all(promises)
(是个 Promise
)。
const declProcessor = (from, to, options, result, decl) => {const dir = { from, to, file: getDirDeclFile(decl) };const pattern = getPattern(decl);if (!pattern) return Promise.resolve();const promises = [];decl.value = decl.value.replace(pattern, (matched, before, url, after) => {const newUrlPromise = replaceUrl(url, dir, options, result, decl);promises.push(newUrlPromise.then((newUrl) => {if (!newUrl) return matched;if (WITH_QUOTES.test(newUrl) && WITH_QUOTES.test(after)) {before = before.slice(0, -1);after = after.slice(1);}decl.value = decl.value.replace(matched, `${before}${newUrl}${after}`);}));return matched;});return Promise.all(promises);
};
replaceUrl
asset
是一个对象,里面有 url
、 originUrl
、 pathname
、 absolutePath
、 relativePath
、 search
、 hash
。
当传入的是 OSS 路径或者 data:font/woff;base64
时, matchedOptions
是 undefined
,直接返回 Promise.resolve()
。
当传入的是 asset/images 下的图片时, matchedOptions
是 options
的值。会判断 matchedOptions
是不是个数组。如果是数组,则对数组里面的值一一执行 process
函数;不是数组,直接执行 process
函数。
process
函数里的 getUrlProcessor
函数会根据 url
的值判断走哪种类型的编译方法。
process
函数里的 wrapUrlProcessor
函数实现了对 urlProcessor
的“增强”,使其在处理 URL 的过程中可以记录警告信息和依赖关系。
replaceUrl
最终返回一个 Promise
,在 resultPromise.then(...)
的链式调用中, return newUrl
; 实际上是将 newUrl
封装在一个新的 Promise
中作为最终返回值,并且 Promise
的解析值是 newUrl
,可以是经过编码的 URL
、文件路径或 undefined
。
const replaceUrl = (url, dir, options, result, decl) => {const asset = prepareAsset(url, dir, decl);const matchedOptions = matchOptions(asset, options);if (!matchedOptions) return Promise.resolve();const process = (option) => {const wrappedUrlProcessor = wrapUrlProcessor(getUrlProcessor(option.url),result,decl);return wrappedUrlProcessor(asset, dir, option);};let resultPromise = Promise.resolve();if (Array.isArray(matchedOptions)) {for (let i = 0; i < matchedOptions.length; i++) {resultPromise = resultPromise.then(() => process(matchedOptions[i])).then((newUrl) => {asset.url = newUrl;return newUrl;});}} else {resultPromise = process(matchedOptions);}return resultPromise.then((newUrl) => {asset.url = newUrl;return newUrl;});
};
getUrlProcessor
根据 url
的值判断走哪种 post-url 类型
function getUrlProcessor(optionUrl) {const mode = getUrlProcessorType(optionUrl);if (PROCESS_TYPES.indexOf(mode) === -1) {throw new Error(`Unknown mode for postcss-url: ${mode}`);}return require(`../type/${mode}`);
}
wrapUrlProcessor
wrapUrlProcessor
实现了对 urlProcessor
的“增强”,使其在处理 URL 的过程中可以记录警告信息和依赖关系。
const wrapUrlProcessor = (urlProcessor, result, decl) => {const warn = (message) => decl.warn(result, message);const addDependency = (file) =>result.messages.push({type: "dependency",file,parent: getPathDeclFile(decl),});return (asset, dir, option) =>urlProcessor(asset, dir, option, decl, warn, result, addDependency);
};
inline.js
根据 options
中的 maxSize
获取 maxSize
,所以配置表中传入的maxSize
不需要乘 1024
。
如果 maxSize
不是 0
,获取图片的 size
。如果图片的 size
大于 maxSize
,调用 processFallback
,按回退处理方式(如文件路径链接)返回;如果图片的 size
小于 maxSize
,调用 inlineProcess
编译成 Base64 。
module.exports = function (asset,dir,options,decl,warn,result,addDependency
) {return getFile(asset, options, dir, warn).then((file) => {if (!file) return;if (!file.mimeType) {warn(`Unable to find asset mime-type for ${file.path}`);return;}const maxSize = (options.maxSize || 0) * 1024;if (maxSize) {const size = Buffer.byteLength(file.contents);if (size >= maxSize) {return processFallback.apply(this, arguments);}}return inlineProcess(file, asset, warn, addDependency, options);});
};
processFallback
根据 options.fallback
的值进行调用,当前 options.fallback
是 undefined
,直接返回 Promise.resolve();
。
function processFallback(originUrl, dir, options) {if (typeof options.fallback === "function") {return options.fallback.apply(null, arguments);}switch (options.fallback) {case "copy":return processCopy.apply(null, arguments);case "rebase":return processRebase.apply(null, arguments);default:return Promise.resolve();}
}
inlineProcess
该方法实现了将文件进行 Base64 转换,如果是 SVG 文件,则使用 encodeURIComponent
,否则使用 base64
编码。
const inlineProcess = (file, asset, warn, addDependency, options) => {const isSvg = file.mimeType === "image/svg+xml";const defaultEncodeType = isSvg ? "encodeURIComponent" : "base64";const encodeType = options.encodeType || defaultEncodeType;// Warn for svg with hashes/fragmentsif (isSvg && asset.hash && !options.ignoreFragmentWarning) {// eslint-disable-next-line max-lenwarn(`Image type is svg and link contains #. Postcss-url cant handle svg fragments. SVG file fully inlined. ${file.path}`);}addDependency(file.path);const optimizeSvgEncode = isSvg && options.optimizeSvgEncode;const encodedStr = encodeFile(file, encodeType, optimizeSvgEncode);const resultValue =options.includeUriFragment && asset.hash? encodedStr + asset.hash: encodedStr;// wrap url by quotes if percent-encoded svgreturn isSvg && encodeType !== "base64" ? `"${resultValue}"` : resultValue;
};
相关文章:

【万字详解】如何在微信小程序的 Taro 框架中设置静态图片 assets/image 的 Base64 转换上限值
设置方法 mini 中提供了 imageUrlLoaderOption 和 postcss.url 。 其中: config.limit 和 imageUrlLoaderOption.limit 服务于 Taro 的 MiniWebpackModule.js , 值的写法要 ()KB * 1024。 config.maxSize 服务于 postcss-url 的…...
复合选择器,CSS特性,背景属性,显示模式(HTML)
目录 复合选择器,CSS特性,背景属性,显示模式知识点: 练习一: 练习二: 复合选择器,CSS特性,背景属性,显示模式知识点: 复合选择器:后代选择器 :父选择器 子选择器(中间用空格隔开) eg:对div中的span进行设置,会对后代中的所有span都进行设置 选中所有后代(后代选择器.html)…...

加密货币行业与2024年美国大选
加密货币行业经历了近十年的飞速发展,尤其是在比特币、以太坊等主要加密资产的兴起之后,越来越多的美国人开始将其视为一种财富积累或交易的工具。然而,尽管这一新兴行业的市场规模在持续扩大,但加密货币仍面临着重重监管难题&…...
Hive SQL中判断内容包含情况的全面指南
Hive SQL中判断内容包含情况的实用指南 在 Hive SQL 的数据处理与分析世界里,判断字段是否包含特定内容是一项非常重要的操作。今天,我将为大家详细介绍 Hive SQL 中实现这一功能的多种方法,并附上相应的表创建和数据插入语句。 一、准备工作 - 表创建与数据插入 首先,我…...

匿名管道 Linux
目录 管道 pipe创建一个管道 让子进程写入,父进程读取 如何把消息发送/写入给父进程 父进程该怎么读取呢 管道本质 结论:管道的特征: 测试管道大小 写端退了,测试结果 测试子进程一直写,父进程读一会就退出 …...
苍穹外卖WebSocket无法建立连接 (修改前端代码)
我在部署nginx 反向代理服务器时,把80端口改成了90端口(不与80端口的Tomcat冲突)。 但黑马的资料里定义了前端连接nginx的端口号默认为80,造成连接不上的问题,此时只需要修改前端的端口号,使其知道如何连接到修改后的后端端口。 …...
音频内容理解
音频内容理解是音频处理和理解领域的一个重要方向,它涉及到从环境声音中提取语义信息,并能够对这些声音进行解释和描述。以下是音频内容理解的几个关键应用: 1. 音频问答(Audio Question Answering, AQA) 在这个任务…...

MQTT实用示例集:Air201版
今天贴出的是Air201版关于MQTT实用示例集,希望大家喜欢。 本示例教你通过使用脚本代码,对Air201模组进行MQTT链接操作。 操作例程包括: MQTT单链接 MQTT多链接 MQTT SSL不带证书链接 MQTT SSL带证书链接 大家可根据自身需求,…...
Day23 opencv图像预处理
图像预处理 在计算机视觉和图像处理领域,图像预处理是一个重要的步骤,它能够提高后续处理(如特征提取、目标检测等)的准确性和效率。OpenCV 提供了许多图像预处理的函数和方法,常见的操作包括图像空间转换、图像大小调…...

优化模型训练过程中的显存使用率、GPU使用率
参考:https://blog.51cto.com/u_16099172/7398948 问题:用小数据集训练显存使用率、GPU使用率正常,但是用大数据集训练GPU使用率一直是0. 小数据: 大数据: 1、我理解GPU内存占用率显存使用率,由模型的大小…...

RocketMQ学习笔记
RocketMQ笔记 文章目录 一、引言⼆、RocketMQ介绍RocketMQ的由来 三、RocketMQ的基本概念1 技术架构2 部署架构 四、快速开始1.下载RocketMQ2.安装RocketMQ3.启动NameServer4.启动Broker5.使⽤发送和接收消息验证MQ6.关闭服务器 五、搭建RocketMQ集群1.RocketMQ集群模式2.搭建主…...

Linux第三讲:环境基础开发工具使用
Linux第三讲:环境基础开发工具使用 1.Linux软件包管理器yum1.1什么是软件包管理器1.2操作系统生态问题1.3什么是yum源 2.vim详解2.1什么是vim2.2vim的多模式讲解2.2.1命令模式的诸多指令2.2.1.1gg和nshiftg2.2.1.2shift$和shift^2.2.1.3上、下、左、右2.2.1.4w和b2.…...
日本TikTok直播的未来:专线网络助力创作者突破极限
近年来,随着短视频平台的崛起,尤其是TikTok(国际版抖音)成为全球范围内广受欢迎的社交娱乐平台,直播功能的加入无疑为内容创作者提供了更广阔的展示舞台。在日本,TikTok直播不仅使得年轻人能够实时与粉丝互…...
如何在家庭网络中设置静态IP地址:一份实用指南
在家庭网络环境中,IP地址扮演着至关重要的角色。大多数家庭用户依赖路由器的DHCP(动态主机配置协议)来自动分配IP地址,但在某些情况下,手动设置静态IP地址能为家庭网络带来更多的便利性与稳定性,尤其是在涉…...

qt QFile详解
1、概述 QFile类是Qt框架中用于读取和写入文本和二进制文件资源的I/O工具类。它继承自QFileDevice类,后者又继承自QIODevice类。QFile类提供了一个接口,允许开发者以二进制模式或文本模式对文件进行读写操作。默认情况下,QFile假定文件内容为…...

ESP8266 自定义固件烧录-Tcpsocket固件
一、固件介绍 固件为自定义开发的一个适配物联网项目的开源固件,支持网页配网、支持网页tcpsocket服务器配置、支持串口波特率设置。 方便、快捷、稳定! 二、烧录说明 固件及工具打包下载地址: https://download.csdn.net/download/flyai…...

内网项目,maven本地仓库离线打包,解决Cannot access central in offline mode?
背景: 内网项目打包,解决Cannot access central in offline mode? 1、修改maven配置文件: localRepository改为本地仓库位置 <localRepository>D:\WorkSpace\WorkSoft\maven-repository\iwhalecloud-repository\business</loca…...

stack和queue --->容器适配器
不支持迭代器,迭代器无法满足他们的性质 边出边判断 实现 #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include<stack> #include<queue> using namespace std; int main() {stack<int> st;st.push(1);st.push(2);st.push(3);…...
ffmpeg视频解码
一、视频解码流程 使用ffmpeg解码视频帧主要可分为两大步骤:初始化解码器和解码视频帧,以下代码以mjpeg为例 1. 初始化解码器 初始化解码器主要有以下步骤: (1)查找解码器 // 查找MJPEG解码器pCodec avcodec_fin…...

前端入门一之CSS知识详解
前言 CSS是前端三件套之一,在MarkDown中也完美兼容这些语法;这篇文章是本人大一学习前端的笔记;欢迎点赞 收藏 关注,本人将会持续更新。 文章目录 Emmet语法:CSS基本语法:css语法结构只有3种:…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
uniapp 对接腾讯云IM群组成员管理(增删改查)
UniApp 实战:腾讯云IM群组成员管理(增删改查) 一、前言 在社交类App开发中,群组成员管理是核心功能之一。本文将基于UniApp框架,结合腾讯云IM SDK,详细讲解如何实现群组成员的增删改查全流程。 权限校验…...
Admin.Net中的消息通信SignalR解释
定义集线器接口 IOnlineUserHub public interface IOnlineUserHub {/// 在线用户列表Task OnlineUserList(OnlineUserList context);/// 强制下线Task ForceOffline(object context);/// 发布站内消息Task PublicNotice(SysNotice context);/// 接收消息Task ReceiveMessage(…...

高等数学(下)题型笔记(八)空间解析几何与向量代数
目录 0 前言 1 向量的点乘 1.1 基本公式 1.2 例题 2 向量的叉乘 2.1 基础知识 2.2 例题 3 空间平面方程 3.1 基础知识 3.2 例题 4 空间直线方程 4.1 基础知识 4.2 例题 5 旋转曲面及其方程 5.1 基础知识 5.2 例题 6 空间曲面的法线与切平面 6.1 基础知识 6.2…...

零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...

微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...

深度学习习题2
1.如果增加神经网络的宽度,精确度会增加到一个特定阈值后,便开始降低。造成这一现象的可能原因是什么? A、即使增加卷积核的数量,只有少部分的核会被用作预测 B、当卷积核数量增加时,神经网络的预测能力会降低 C、当卷…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测
uniapp 中配置 配置manifest 文档:manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号:4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

Rust 开发环境搭建
环境搭建 1、开发工具RustRover 或者vs code 2、Cygwin64 安装 https://cygwin.com/install.html 在工具终端执行: rustup toolchain install stable-x86_64-pc-windows-gnu rustup default stable-x86_64-pc-windows-gnu 2、Hello World fn main() { println…...