❝所有耀眼的成绩,都需要苦熬,熬得过,出众;熬不过,出局❞
大家好,我是「柒八九」。一个「专注于前端开发技术/Rust
及AI
应用知识分享」的Coder
❝此篇文章所涉及到的技术有
Rust
wasm-bindgen/js-sys/web-sys
Web Worker
WebAssembly
Webpack/Vite
配置WebAssembly
OffscreenCanvas
npx f_cli_f create xxx
)tailwindcss
等MuPDF.js/mammoth.js
❞
因为,行文字数所限,有些概念可能会一带而过亦或者提供对应的学习资料。请大家酌情观看。
在前一篇文章写一个类ChatGPT应用,前后端数据交互有哪几种我们介绍了,如果要进行一个类ChatGPT
应用的开发,可能会用到的前后端数据交互的方式。同时呢,我们也介绍了最近公司所做的项目,做一款「基于文档类问答的AI」功能。
而谈到文档相关的应用,从操作文档角度来看,无非就是文件上传
,文件解析
和文件展示
。而我们之前在文件上传 = 拖拽 + 多文件 + 文件夹介绍过更优雅的上传方式。而文件展示
如果大家想了解的话,我们可以单独写一篇文章。
而我们今天来聊聊关于文件解析
的相关操作。
大家肯定用过很多「云盘」类的应用。在我们对本地文件进行上传后,在展示的时候一般分为两种模式
如果大家观察过云盘针对大图模式
的文件资源的展示,就会发现每个文件的头图都是用一个<img/>
接收了一个从后端返回的固定图片资源。
而现在,我们针对大图模式
有几点改进
概要内容
(这块可以借助AI
对文本进行Summary
处理,这个我们后面会单独写一篇文章),而不是单单的把文件的首页信息(pdf/word/pptx
)转换成图片(像阿里云盘一样)概要信息
,而不是走接口从服务器获取,也就是这是一个纯前端的事情pdf/word/ppt
等的图标为什么做呢,有没有发现我们通过上述的改造和处理,我们直接在大图模式
下,通过文件头图信息就能大致知晓文件的内容(概要信息),其次如果展示的资源信息过多,每次从后端获取对应的图片资源也是一件极其耗费带宽的事情。
讲到这里,大家可能会疑惑,你上面说了那么多,那么这和Rust
有啥关系?
关系大着呢,从上面的需求点出发,我们可以看出,其实针对文档解析
的处理,都是在前端环境中操作的。同时,针对大体积的文件资源,对其解析处理是一件极其耗时的事情。有时针对特殊文件,可能前端还暂时无法处理。
既然,我们想要在前端执行这些耗时
且不易处理的任务,我们就需要请帮手,而在其他语言中有成熟的方案来处理我们遇到的这些问题。(由于种种原因,其他端的小伙伴无瑕处理这种情况)
那么,我们就可以选择一种方式,在前端环境中通过某种方式来糅合其他语言的操作来执行对应的任务。那思来想去,WebAssembly
是再合适不过的方式了。如果不了解它,可以看我们之前的文章 - 浏览器第四种语言-WebAssembly。
❝当然,其他语言(
C/TypeScript
)都可以通过编译工具转化成WebAssembly
,此片文章中也会涉及,只不过我们是直接使用别人构建好的WebAssembly
,而现行阶段,Rust
是对WebAssembly
最友好的语言。并且,我们也会用Rust
手搓一个WebAssembly
。这也是为什么这篇文章的主标题叫Rust赋能前端
而不是WebAssembly赋能前端
(我们在本文的第三部分,Word 解析
中详细介绍了用Rust
写WebAssembly
,如果不想看mupdf
的可以直接跳到第三节) ❞
好了,天不早了,干点正事哇。
❝
❞
由于,WebAssembly
是一个新兴技术,在一些常规的打包工具(vite/webpack
)中使用,我们需要额外处理。
使用WebAssembly
从来源大致可以两类
npm包/公司私包
(针对如何发私包可以参考之前的如何在gitlab上发布npm包)wasm
这两种情况我们接下来都会涉及。其实他们的处理方式都是一样的。下面我们就来讲讲Webpack/Vite
是如何配置它们的。
针对Webpack
中使用WebAssembly
,我们之前在Rust 编译为 WebAssembly 在前端项目中使用就介绍过。
其实,最关键的点就是需要wasm-pack-plugin[1]
其次,我们还想让WebAssembly
模块能够和其他ESM
一样,通过import
进行方法的导入处理,针对Webapck5
我们还可以通过配置experiments
的asyncWebAssembly[2]为true
来启动该项功能。
最后,为了兼容性,我们处理TextEncoder/TextDecoder
。
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
// 让这个示例在不包含`TextEncoder`或`TextDecoder`的Edge浏览器中正常工作。
new webpack.ProvidePlugin({
TextDecoder: ['text-encoding', 'TextDecoder'],
TextEncoder: ['text-encoding', 'TextEncoder']
})
],
mode: 'development',
experiments: {
asyncWebAssembly: true
}
};
从Vite
官网看,它只兼容了引入预编译的.wasm
,但是对 WebAssembly
的 ES 模块集成提案
尚未支持。而恰巧,我们今天所涉及到的.wasm
都是ESM
格式的。
按照官网的提示,我们可以借助vite-plugin-wasm[3]的帮助。
配置也很简单,按照下面的处理即可。
import wasm from "vite-plugin-wasm";
import topLevelAwait from "vite-plugin-top-level-await";
export default defineConfig({
plugins: [
wasm(),
topLevelAwait()
],
worker: {
plugins: [
wasm(),
topLevelAwait()
]
}
});
由于,我们公司的打包工具是Vite
,还记得我们之前介绍过的脚手架工具吗。
大家可以在自己电脑中执行,npx f_cli_f create file_to_img
来构建一个以Vite
为打包工具的前端项目。
然后,我们就可以将上面逻辑写到对应的文件中。
执行到这里,我们的前期的配置工作就算完成了。
如果使用过我们的f_cli_f
的人,会知道。我们在项目中内置了很多东西,可以算是开箱即用。
所以,我们保留之前的结构的基础上,在pages
中新建一个FileToImg
的目录结构,并且将其放置于main
路由下。
最后的页面结构如下
pdf/word/text
的常规文件的解析<input type="file"/>
PDF
的内容检索Text/Png/Svg/Html
等格式❝所用技术
❞
❝在前端进行
在解析PDF
时,我们选择mupdf[5],它是一套用C
编写的工具库,同时还支持在多种语言环境(java/.net/js/python
)中使用。其实,mupdf
不仅支持对pdf
的解析,然后还支持分割/构建等。具体的功能点可以参考对应的官网。我们这里只关心它的解析功能。
并且,该库还支持对多种文件格式进行处理。不仅是pdf
还有我们常见的TXT
/Image
等。
对于我们前端而言,MuPdf
提供了适配Node/JS/TS
的包 - mupdf.js[6]
也就是说,我们借助Mupdf
可以实现在前端环境利用其他语言处理文档的能力。这是通过Emscripten[7]实现将C
编译为可以在js环境执行的WebAssembly
。关于如何将C
代码编译为WebAssembly
我们之前在WebAssembly-C与JS互相操作
同时,我们还可以基于mupdf.js源码[8]来实现符合自己团队的WebAssembly
,因为mupdf
中的有些特性,我们可以不要,而从选择个别的api来将其打包成.wasm
。
然后我们可以将打包好的文件(也可以直接使用官方提供的)按照下面的步骤,按照到我们项目中。
我们可以从mupdf-js[9]的npm
地址查询对应的api地址。这些api我们会在下面的代码解释部分中涉及到。
在我们用f_cli_f
构建的前端项目中,使用yarn add mupdf-js
来安装mupdf
的JS
版本。
接下来,就到了我们在前端项目中使用它的时候了。下面,有些不重要的代码和实现逻辑,我们就不写了,因为有些代码看起来又臭又长,然后也没啥可聊的。
我们在FileToImg
的index.tsx
中的定义了如下的页面结构。
也就是分为两部分
文件类型
/上传文件
/搜索文件内容
和选择输出格式
因为,我们的f_cli_f
是可以自行选择是否按照tailwind
,所以在我们选择它后,我们就可以直接使用对应的语法构建页面结构了。该页面中,就是做了一下简单的布局配置,如果像了解更多关于Tailwind
可以翻看我们之前的文章 - Tailwind CSS那些事儿
我们选择用一个对象来维护页面中处理文件的各种状态和信息。
const initialState = {
fileType: FileType.PDF,
processing: false,
file: null as File | null,
mode: ConversionMode.TEXT,
searchQuery: '',
error: '',
output: [] as string[],
};
fileType
: 就是页面左侧的下来框中的信息(PDF/Word/Text
)processing
: 文件是否正在被解析,用于展示不同的状态file
: 存储本地上传的文件信息output
: 最后的解析结果然后通过useState
将其固定到组件内部。
const [processorState, setProcessorState] = useState(initialState);
因为是一个展示项目,我们针对文件上传用了最原始的方式,我们通过在类型为file
的input
上绑定了一个针对onChange
的事件回调- handleFileChange
。如果想用更优雅的方式,可以看我们之前的文章 - 文件上传 = 拖拽 + 多文件 + 文件夹
<input type="file" onChange={handleFileChange} disabled={processing} />
主要的代码如下:
我们来简单解释一下
e.target.files
获取到file
信息processorState
获取fileType
用于区分是哪种文件格式processFile
基于file
信息和mode
来解析文件,setProcessorState
用于更新其中的output
这里我们根据fileType
处理了两种文件类型(pdf/word
),其实它们主要流程都差不多,都是先调用setProcessorState
先处理processing
等信息,然后调用对应的解析函数(processFile/processWordFile
),等文件解析成功后,再次调用setProcessorState
来更新output
内容。
这块的主要逻辑就是依据processorState
中的output
来展示对应的解析结果。
这个其实也没啥可唠的。就是一个根据文件类型展示的逻辑。接下来,让我们聊聊比较好玩的东西哇 。
在handleFileChange
中我们不是调用了一个processFile
吗,这其实才刚刚触及到本节的核心点。
processFile
我们是在pdf.ts
中导出的。
从上图中,我们可以得到几点有用的消息
pdf-worker
中导入了PDFWorker
实例(vite中导入web worker有很多方式[10])processFile
返回了一个Promise
processFile
被触发时,就通过postMessage
向PDFWorker
发送了收集到的file
信息worker
处理完数据后,我们通过res(val.data)
返回给processFile
调用处在pdf-worker.ts
中就涉及到mupdf-js
的引入和实例化操作了。并且,我们还通过self.addEventListener('message', handleMessage);
来监听主线中传人的数据信息,并且基于这些信息执行不同的操作。
handleMessage
中通过判断e.data.type
来决定是解析文件呢,还是执行查询convertFile
loadPdf
,用于将pdf
文件资源转换成mupdf
能够识别的格式convertPdfDocument
convertPdfDocument
中通过pdf.countPages(doc)
来获取文档的总页数,并且每页都执行convertPdfPage
convertPdfPage
中基于mode
来处理相关的解析逻辑getPageText/drawPageAsHTML/drawPageAsSVG
最后执行文件解析的就是convertPdfPage
方法对mupdf
各种方法的调用
这里我们有一个页数为4的PDF
文档。
在我们通过mupdf
处理后,选择完对应的显示模式,就会有对应的解析结果
❝所用技术
Rust
WebAssembly
wasm-bindgen/js-sys/web-sys
Web worker
OffscreenCanvas
mammoth.js
❞
在前端方面,Word
的解析其实和PDF
是类似的,都是在input
的onChange
中执行processFile
。
而这个processFile
是对应的xx.ts
(pdf.ts/word.ts
)中定义的。
上面就是word.ts
的主要逻辑
web worker
并将其实例化processFile
被执行时,将file
和一些配置信息传人到worker
中worker
将文件解析成功后,通过Promse
的res
返回给processFile
的调用处上面的执行逻辑其实和处理pdf
差不多,但是呢,有一点还是有点区别的。因为,Word
的解析和构图是我们来维护的,所以我们就需要想办法,
Word
解析成文本信息(这块我们使用mammoth.js[11])Canvas
,也就是创建一个零时Canvas
,将对应的文案信息绘制到上面后,然后将canvas
转换为blob
或者base64
的图片格式。而对于我们的库的使用者来讲,它们在解析文档的时候,其应用环境是不确定的,如果是主线程还好,但是如果是在Web Worker
中调用我们的库,那么这就有一个弊端,我们的逻辑是需要自己维护一个canvas
也就是需要document.createElement('canvas')
,但是在worker
中是没有document
的。所以,我们就需要做一个兼容处理。
let offscreen = null;
if (typeof OffscreenCanvas !== 'undefined') {
offscreen = new OffscreenCanvas(800, 600);
} else {
offscreen = document.createElement('canvas');
if (offscreen.transferControlToOffscreen) {
offscreen = offscreen.transferControlToOffscreen();
} else {
return rej(new Error(' 当前浏览器环境中不支持OffscreenCanvas'));
}
}
上面就是word-worker.ts
的大部分逻辑。
其实主要的逻辑就是
mammoth.js
对word
进行解析处理word2img
的draw_text_as_png
。blob
对象,随后我们使用createObjectURL
对其处理,并返回这里针对convertFile
中参数再做一下解释
file
: 上传的文件信息,在这里就是word
config
: 这里我们简化了配置,只定义了两个字段,用于配置生成canvas的宽高,当然这里还有更多关于canvas
的配置可以传人其中canvas
: 这个就是我们之前说的OffscreenCanvas
,该类型的canvas
可以很好的适配调用环境(主线程/worker)最后,在代码的最前面有一行
import { draw_text_as_png, CanvasConfig } from '@/wasm/word2img';
其实呢,这一步就是在引入我们的WebAssembly
,我们是将其直接放置到前端项目中的wasm
文件夹下了。
至于为什么是@
开头,是因为我们在tsconfig.json
中配置了相关的路径隐射。
然后,我们再配合vite-tsconfig-paths
就可以实现此类别名的配置了。针对这些我们在前端项目里都有啥?有过写过,这里就不在过多解释了。
这节算是最重要的部分,完全可以单拎出来重新写一篇,但是呢为了行文的完整性,我们还是将它糅合到这篇文章内。
之前在Rust学习笔记有过这方面的介绍。所以我们这里就直接上手了。
在 Rust
中,使用 cargo new
命令可以创建一个新的项目。
src
目录下会有一个 main.rs
文件,这是程序的入口点,包含一个 fn main() {}
函数。src
目录下会有一个 lib.rs
文件,这是库的入口点,通常定义公共 API。❝总结来说,
cargo new xx
创建的是一个二进制项目,适用于开发可执行程序;cargo new xx --lib
创建的是一个库项目,适用于开发可以被其他项目依赖和使用的库。 ❞
因为,我们要用Rust
生成一个库(library
)项目,可以被其他项目依赖,
cargo new file2img --lib
随后,我们更新我们的Cargo.toml
文件
[package]
name = "file2img"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
js-sys = "0.3.69"
wasm-bindgen = { version = "0.2.92" }
wasm-bindgen-futures = "0.4"
console_error_panic_hook = "0.1.7"
[dependencies.web-sys]
version = "0.3.69"
features = [
'TextMetrics',
'OffscreenCanvas',
'OffscreenCanvasRenderingContext2d'
]
这里面比较重要的部分是
js-sys
在使用 Rust
进行 WebAssembly
开发时,web-sys
和 js-sys
是两个常用的 crate
,它们用于与 JavaScript
和 Web API
进行交互。
js-sys
「用途」:js-sys
提供了对 JavaScript
原生对象和函数的低级别绑定。这些绑定使得 Rust 代码可以直接与基本的 JavaScript
特性和全局对象(如 Math
、Date
、Promise
等)进行交互。
「功能」:包括但不限于与 JavaScript
基本类型、标准库对象和全局作用域函数的交互。
「示例」:
use wasm_bindgen::prelude::*;
use js_sys::Math;
#[wasm_bindgen]
pub fn get_random_number() -> f64 {
Math::random()
}
web-sys
「用途」:web-sys
提供了对 Web API
的高层次绑定。这些 API 包括 DOM 操作、HTML 元素处理、网络请求、WebSockets
、Canvas
以及其他浏览器提供的功能。
「功能」:包括与浏览器环境相关的各种接口和对象的交互,比如 Document
、Element
、Window
、Fetch
、WebGL
等。
「示例」:
use wasm_bindgen::prelude::*;
use web_sys::window;
#[wasm_bindgen]
pub fn alert_message(message: &str) {
if let Some(win) = window() {
win.alert_with_message(message).unwrap();
}
}
❝
js-sys
:专注于 JavaScript
的核心对象和函数,提供基础的 JavaScript
环境支持。web-sys
:专注于 Web API
,提供浏览器环境下的各种高级功能支持。❞
在实际使用中,通常会根据需要同时使用这两个 crate
。例如,web-sys
可能依赖 js-sys
提供的一些基础功能,而我们在开发 Web
应用时可能会同时需要操作 DOM
元素(使用 web-sys
)和调用 JavaScript
原生函数(使用 js-sys
)。
在这里呢,其实算是我的开发习惯,我们在使用Rust
构建WebAssembly
时,其实这个算是一种黑盒模式,无法在写完代码后,里面看到效果。虽然,我们可以写测试用例,但是无法更直观的看到效果。
所以,我们在刚才构建的Rust
项目中配置一个前端开发服务器。
npm init
(一路回车),此时的Rust
项目也是一个前端项目index.html
(方便构建/操作DOM
)index.js
(前端主入口)webpack.config.js
yarn
)通过上述步骤,我们就可以在index.js
导入编译后的WebAssembly
。然后,通过启动一个前端服务,直接看到效果,从而进行验证。
{
"name": "file2img",
"version": "1.0.0",
"description": "",
"main": "index.js",
"author": "Front789",
"license": "ISC",
"scripts": {
"build": "webpack",
"serve": "webpack serve"
},
"devDependencies": {
"@wasm-tool/wasm-pack-plugin": "1.5.0",
"html-webpack-plugin": "^5.3.2",
"text-encoding": "^0.7.0",
"webpack": "^5.49.0",
"webpack-cli": "^4.7.2",
"webpack-dev-server": "^4.15.1"
}
}
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'index.js',
},
plugins: [
new HtmlWebpackPlugin({
template: 'index.html'
}),
new WasmPackPlugin({
crateDirectory: path.resolve(__dirname, ".")
}),
// 让这个示例在不包含`TextEncoder`或`TextDecoder`的Edge浏览器中正常工作。
new webpack.ProvidePlugin({
TextDecoder: ['text-encoding', 'TextDecoder'],
TextEncoder: ['text-encoding', 'TextEncoder']
})
],
mode: 'development',
experiments: {
asyncWebAssembly: true
}
};
这个我们在第一节中,如何在Webpack
中使用WebAssembly
中解释过。
import { draw_text_as_png,CanvasConfig } from './pkg';
const img = document.createElement('img');
img.width = 800;
img.height = 600;
document.body.appendChild(img);
let offscreen = null;
if (typeof OffscreenCanvas !== 'undefined') {
offscreen = new OffscreenCanvas(800, 600);
} else {
offscreen = document.createElement('canvas');
if (offscreen.transferControlToOffscreen) {
offscreen = offscreen.transferControlToOffscreen();
} else {
new Error('OffscreenCanvas is not supported in your browser.');
}
}
const config = new CanvasConfig(800,600)
const base64Image = await draw_text_as_png(
'我是柒八九。一个专注于前端开发技术/Rust及AI应用知识分享的Coder',
config,
offscreen
);
console.log(base64Image, `sss`);
const url = URL.createObjectURL(base64Image);
img.src = url;
当然,在index.js
可以引入自己的测试代码。这个是没有硬性要求的。
本来这块信息不想讲的,但是还是没忍住,所以我们就简单解释一下哇。
在 Rust
中,#[wasm_bindgen]
是一个属性宏(attribute macro
),用于与 wasm-bindgen
工具一起工作。wasm-bindgen
是一个用于在 Rust
和 JavaScript
之间实现高效绑定的库,主要用于将 Rust
编译成 WebAssembly
,并且使其能够与 JavaScript
进行交互。
Rust
中定义的函数可以在 JavaScript
中调用。JavaScript
中定义的函数可以在 Rust
中调用。JavaScript
中正确使用 Rust
导出的函数和类型。使用 #[wasm_bindgen]
可以将 Rust
函数导出,使其可以在 JavaScript
中调用。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
这个例子中,greet
函数可以在编译为 WebAssembly
后,在 JavaScript
中调用:
import { greet } from './your_wasm_module';
console.log(greet("Front789")); // 输出: "Hello, Front789!"
通过 #[wasm_bindgen]
,可以声明外部的 JavaScript
函数,使得它们可以在 Rust
中调用。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
fn alert(s: &str);
}
#[wasm_bindgen]
pub fn call_alert(message: &str) {
alert(message);
}
这个例子中,alert
函数在 Rust 中被声明并可以调用,这实际上调用的是 JavaScript
中的 alert
函数。
#[wasm_bindgen]
也可以用于与 JavaScript
对象和类进行交互。
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
type Document;
#[wasm_bindgen(method, getter, js_name = "title")]
fn title(this: &Document) -> String;
#[wasm_bindgen(method, setter, js_name = "title")]
fn set_title(this: &Document, title: &str);
}
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = window)]
static document: Document;
}
#[wasm_bindgen]
pub fn change_title(new_title: &str) {
document.set_title(new_title);
}
这个例子展示了如何使用 #[wasm_bindgen]
与 JavaScript
的 document
对象进行交互,从而改变网页的标题。
const base64Image = await draw_text_as_png(
'我是柒八九。 一个专注于前端开发技术/Rust及AI应用知识分享的Coder',
config,
offscreenCanvas
);
我们在JS
环境中,使用上面方式来调用draw_text_as_png
。其接收三个参数
text
:需要被画到canvas上的文案信息config
:用于配置canvas的属性offscreenCanvas
:用于承接text
的canvas
实例那么,我们可以看看用Rust
是如何实现该方法签名的。
在这个函数体内部,有几点需要注意
config
是我们定义的结构体console_error_panic_hook
crate 实现错误信息的更好输出wrap_text
用于将文本信息画到canvas
上canvas
的base64
格式draw_text_as_png
中接收的第二个参数,是我们在Rust
中定义的结构体。用于配置canvas
的各个属性。
#[wasm_bindgen]
pub struct CanvasConfig {
width: u32,
height: u32,
}
#[wasm_bindgen]
impl CanvasConfig {
#[wasm_bindgen(constructor)]
pub fn new(width: u32, height: u32) -> CanvasConfig {
CanvasConfig { width, height }
}
}
当我们把用Rust
编写的WebAssembly
在前端环境调用时,如果有些边界情况没考虑到。比方之前我们说过的,当我们在Rust
代码中需要用到Window
等比较特殊的属性时,在一些特殊环境下(Web Worker
)是不存在的。当代码在这些特殊环境下执行时,那么Rust
就会发生panic
。
如果在Rust
中没做好错误捕获和提示,那么在浏览器控制台会发生错误,但是提供的错误信息很少,我们不好定位。例如会发生unreachable[14]错误。
为了解决这个小瑕疵,我们可以借助console_error_panic_hook[15],在函数初始化时,就启用panic hook
。
panic::set_hook(Box::new(console_error_panic_hook::hook));
大家从注释上可以看出来有3部分内容
其实内容也不是很复杂,但是还需要额外说明一下,在这里我们是按照空格
对文本进行分割,其实我们可以按字符分割,这样能准确一点。
其次就是我们见到的measure_text
就是我们在CanvasRenderingContext2D.measureText[16],而fill_text
对标CanvasRenderingContext2D.fillText[17]
使用wasm-pack
:
cargo install wasm-pack
wasm-pack build
Rust
代码编译成WebAssembly
并生成适合JavaScript
项目使用的包。它可以自动处理生成WebAssembly
模块所需的所有步骤,包括编译、优化、并生成绑定代码。使用cargo-web
:
cargo install cargo-web
cargo web build --target wasm32-unknown-unknown
Rust
代码到WebAssembly
并生成相关JavaScript
绑定的工具。它提供了一些额外的功能,例如处理stdweb
库。使用wasm-bindgen
:
cargo install wasm-bindgen-cli
cargo build --target wasm32-unknown-unknown
wasm-bindgen target/wasm32-unknown-unknown/debug/your_crate.wasm --out-dir ./out
wasm-bindgen
是一个用于在Rust
和JavaScript
之间生成互操作性代码的工具。你可以直接使用它来编译Rust
代码成WebAssembly
,并生成相应的JavaScript
绑定代码。使用cargo
直接编译:
cargo build --target wasm32-unknown-unknown
Rust
自带的Cargo
工具可以直接编译Rust
代码到WebAssembly
目标。我们需要指定目标三元组为wasm32-unknown-unknown
。这是我们这篇文章开头讲的内容,我们使用@wasm-tool/wasm-pack-plugin
配合Webpack
从而实现了Rust
编译成WebAssembly
并且它还支持在Rust
代码发生变更后,自动编译。
最后,我们在pkg
就可以的到对应Rust
编译成WebAssembly
的相关代码。针对其中每个文件的含义,可以参考我们之前的文章Rust 编译为 WebAssembly 在前端项目中使用
「分享是一种态度」。
「全文完,既然看到这里了,如果觉得不错,随手点个赞和“在看”吧。」
[1]
wasm-pack-plugin: https://github.com/wasm-tool/wasm-pack-plugin
[2]
asyncWebAssembly: https://webpack.js.org/configuration/experiments/
[3]
vite-plugin-wasm: https://github.com/Menci/vite-plugin-wasm
[4]
pdf-dist: https://github.com/mozilla/pdfjs-dist
[5]
mupdf: https://mupdf.com/
[6]
mupdf.js: https://mupdfjs.readthedocs.io/en/latest/
[7]
Emscripten: https://emscripten.org/docs/getting_started/downloads.html
[8]
mupdf.js源码: https://github.com/ArtifexSoftware/mupdf.js/tree/master
[9]
mupdf-js: https://www.npmjs.com/package/mupdf-js
[10]
vite中导入web worker有很多方式: https://v3.vitejs.dev/guide/features.html#web-workers
[11]
mammoth.js: https://github.com/mwilliamson/mammoth.js
[12]
wasm-bindgen: https://crates.io/crates/wasm-bindgen
[13]
web-sys: https://rustwasm.github.io/wasm-bindgen/web-sys/index.html
[14]
unreachable: https://developer.mozilla.org/en-US/docs/WebAssembly/Reference/Control_flow/unreachable
[15]
console_error_panic_hook: https://crates.io/crates/console_error_panic_hook
[16]
CanvasRenderingContext2D.measureText: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/measureText
[17]
CanvasRenderingContext2D.fillText: https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillText
[18]
wasm-pack: https://rustwasm.github.io/wasm-pack/book/introduction.html
[19]
cargo-web: https://crates.io/crates/cargo-web