
WebAssembly (WA *** ) 代码可以显著提高速度,基于它编写和发布 npm 软件包现在已经成为一个非常有趣的目标。本文将向您展示如何开发和发布 WA *** 软件包,以便您能在自己的工作中应用这项技术。
(通常缩写为 Wa *** )是一种突破性的二进制指令格式,它改变了 *** 开发的格局。它允许开发人员在浏览器中以接近原生的速度运行 JavaScript 之外的其他语言编写的代码。WebAssembly 最令人兴奋的应用之一是在 领域。
在深入探讨文章的其他部分之前,让我们先快速讨论一下使用 WebAssembly 的目的。我们的主要目标是通过构建一个搜索索引来发现使用 WebAssembly 实现 包的优势,该索引可以让我们高效地搜索字符串 *** 并返回相关匹配结果。这将是 Wade.js npm 软件包的简化版本,但使用的是 和 Wa *** 。最后,你将知道如何开发一个高性能的搜索包。
使用 WebAssembly 编写 npm 软件包的好处包括:
要继续学习,请确保您的开发环境包括:
在接下来的章节中,您将了解如何设置 Rust 开发环境、在 Rust 中实现搜索功能以及编译和发布 npm 软件包。
您可以按照此处的说明在本地计算机上。
安装好 Rust 后,就该获取 二进制文件了。这个小工具可以帮助你将 Rust 代码编译成 WebAssembly,并打包以实现无缝开发。
设置成功后,运行下面的 命令来设置一个新的 Rust 项目:
cargo new --lib refactored-couscous
通过添加 --lib,你可以指示 cargo 生成一个 Rust 库模板。你应该将文件夹名称 “refactored-couscous” 改为你喜欢的名称。cargo 生成的文件夹结构应与下面的目录相同:
refactored-couscous/ ├── Cargo.lock ├── Cargo.toml ├── LICENSE_APACHE ├── src/ │ └── lib.rs └── target/
接下来,让我们在代码执行过程中添加必要的依赖项。打开 Cargo.toml 文件,更新其依赖项部分:
# file: ./Cargo.toml [dependencies] wa *** -bindgen = "0.2" js-sys = "0.3"
wa *** -bindgen crate 对于促进 Rust 和 JavaScript 之间的高级交互至关重要。它提供了 #[wa *** _bindgen] 属性,你可以用它来注释 Rust 结构和函数,使 JavaScript 可以访问它们。
js-sys crate 为 Rust 中的所有 JavaScript 全局对象和函数提供绑定。它是 wa *** -bindgen 生态系统的一部分,旨在与 wa *** -bindgen 配合使用。
在完成设置后,让我们开始实现搜索包。
在深入研究代码之前,了解我们的主要目标至关重要。我们的目标是建立一个搜索索引,让我们能够高效地搜索字符串 *** ,并返回相关的匹配结果。
// file: src/lib.rs
use std::collections::HashMap;
use wa *** _bindgen::prelude::*;
use js_sys::Array;
#[wa *** _bindgen]
#[derive(Debug, Clone)]
pub struct Token {
word: String,
position: usize,
frequency: usize,
}
我们首先导入必要的库。然后声明包含三个字段的 Token 结构:word, position, 和 frequency。struct 将表示单个单词、单词在数据中的位置以及单词在给定字符串中的频率。
接下来,让我们定义搜索索引结构。搜索包的核心是 Index 结构,它将存储我们要搜索的所有字符串,并允许我们快速查找单词的出现。
// file: ./src/lib.rs
#[wa *** _bindgen]
#[derive(Debug)]
pub struct Index {
data: Vec<String>,
tokens: HashMap<String, Vec<Token>>,
}
在上述代码段中,data 字段是一个存储所有字符串的向量。tokens 字段是一个 hashmap,其中每个键是一个单词,其值是一个 Token 结构向量。通过这种结构,我们可以快速查找数据中出现的所有单词。
接下来,让我们为每个字符串实现标记化。标记化就是将字符串分解成单个词或 “tokens”。有了单个标记,我们就可以单独分析和处理每个单词。这种粒度使我们能够专注于特定的单词,从而更容易搜索、分析或处理文本。
// file: ./src/lib.rs
impl Token {
fn tokenize(s: &str) -> Vec<String> {
s.to_lowercase()
.split_whitespace()
.map(|word| word.to_string())
.collect()
}
}
tokenize 函数将字符串作为输入。它会将字符串转换为小写,以确保搜索不区分大小写。然后将字符串分割成单词,再将每个单词转换成字符串。最后,单词被收集到一个向量中并返回。
接下来,让我们初始化并填充搜索 Index。
// file: ./src/lib.rs
#[wa *** _bindgen]
impl Index {
#[wa *** _bindgen(constructor)]
pub fn new() -> Self {
Index {
data: Vec::new(),
tokens: HashMap::new(),
}
}
pub fn add(&mut self, s: &str) {
let position = self.data.len();
self.data.push(s.to_string());
let tokens = Token::tokenize(s);
for token in tokens {
let frequency = s.matches(&token).count();
self.tokens.entry(token.clone()).or_insert_with(Vec::new).push(Token {
word: token,
position,
frequency,
});
}
}
...
}
在上述代码段中,new 函数初始化了一个空 Index。然后,add 函数允许我们向索引中添加新字符串。为此,它会对字符串进行标记化处理,计算每个标记的频率,并相应地更新 tokens 哈希表。
#[wa *** _bindgen(constructor)] 属性表示当从 JavaScript 访问 Rust 结构时,相关函数应被视为 Rust 结构的构造函数。
接下来,让我们实现搜索功能。为了在索引中搜索匹配项,我们将如下定义搜索函数:
// file: ./src/lib.rs
#[wa *** _bindgen]
impl Index {
...
pub fn search(&self, query: &str) -> Array {
let tokens = Token::tokenize(query);
let mut results = Vec::new();
for token in tokens {
if let Some(matches) = self.tokens.get(&token) {
for match_ in matches {
results.push(self.data[match_.position].clone());
}
}
}
results.sort();
results.dedup();
// Convert Vec<String> to js_sys::Array
results.into_iter().map(JsValue::from).collect()
}
}
search 函数首先对查询进行标记化。对于查询中的每个标记,它都会检查 tokens 哈希表中是否有匹配项。如果发现匹配,就会将 data 向量中的相应字符串添加到搜索结果中。然后对结果进行排序,并删除重复的结果。最后,使用 js_sys::Array 将结果转换为 JavaScript 数组并返回。
有了这个实现,我们就有了一个使用 Rust 构建的强大搜索索引。在下一节中,我们将深入探讨如何将 Rust 代码编译到 WebAssembly 中,以便将其无缝集成到 JavaScript 环境中。
本节将深入探讨将 Rust 代码转编到 WebAssembly 的不同 *** ,这取决于特定的 JavaScript 环境。在本讨论中,我们将集中讨论两个主要的编译目标: *** (与基于浏览器的应用程序有关)和捆绑程序(与服务器端操作有关)。
要转译为 WebAssembly,需要在项目根目录下运行下面的 wa *** -pack 命令:
wa *** -pack build --target web
执行命令后,会启动一系列进程。首先是将 Rust 源代码转译为 WebAssembly。随后,在生成的 WebAssembly 上执行 wa *** -bindgen 工具,生成一个 JavaScript 封装器,以促进浏览器与 WebAssembly 模块的兼容性。这个过程还协调了 pkg 目录的形成,将 JavaScript 封装器和原始 WebAssembly 代码重新定位到这个位置。根据 Cargo.toml 提供的信息,它还会创建相应的 package.json。如果存在 README.md 或许可证文件,则将其复制到软件包中。最终,这一系列操作会在 pkg 目录中创建一个合并的软件包。
在继续第二个目标之前,让我们先简单了解一下生成的 pkg 目录:
./pkg ├── LICENSE_APACHE ├── package.json ├── refactored_couscous.d.ts ├── refactored_couscous.js ├── refactored_couscous_bg.js ├── refactored_couscous_bg.wa *** └── refactored_couscous_bg.wa *** .d.ts
refactored_couscous.d.ts 是 TypeScript 声明文件,通过详细说明包中函数和模块的类型,为 TypeScript 开发人员提供了类型安全。 refactored_couscous.js 是由 wa *** -bindgen 创建的 JavaScript 封装器,它将 WebAssembly 模块与 JavaScript 领域连接起来,实现无缝集成。作为补充,refactored_couscous_bg.js 是一个辅助文件,用于处理一些底层操作以及与 WebAssembly 模块的交互。软件包的核心在于 refactored_couscous_bg.wa *** ,这是一个从 Rust 派生的 WebAssembly 二进制文件,封装了软件包的主要逻辑。最后,refactored_couscous_bg.wa *** .d.ts 是另一个 TypeScript 声明文件,与之前的 .d.ts 文件类似,但根据 WebAssembly 模块的具体情况进行了定制。
下一条 wa *** -pack 命令会将 Rust 代码转换为 WebAssembly 模块,该模块专门为使用基于 npm 的捆绑程序(如 Webpack 或 Rollup)而定制:
wa *** -pack build --target bundler
-target bundler 标志表示输出结果应与这些捆绑工具兼容,从而使生成的 WebAssembly 模块更容易集成到现代 *** 开发工作流程中。该命令生成的 pkg 目录与 --target web 标志生成的文件数量相同,但文件内容略有不同。
既然我们已经知道了如何针对不同的 JavaScript 环境,那么让我们从 --target web 转置开始,在基于浏览器的应用程序中使用生成的模块。
在根目录下创建一个 index.html 文件,并用以下内容更新它:
<!-- file: ./index.html --> <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8" /> <title>Wade Search in WebAssembly</title> <style> /* for the styles, see the https://github.com/Ikeh-Akinyemi/refactored-couscous/blob/main/index.html */ </style> </head> <body> <input type="text" id="searchInput" placeholder="Search..." /> <button onclick="performSearch()">Search</button> <ul id="results"></ul> <script type="module"> import init, { Index } from "./pkg/refactored_couscous.js"; let index; async function setup() { await init(); index = new Index(); // Sample data for demonstration purposes index.add("Hello world"); index.add("Start your Rust journey here") index.add("Found my empress."); index.add("Talkin about systems") index.add("Wade in Rust"); } function performSearch() { const query = document.getElementById("searchInput").value; const results = index.search(query); displayResults(results); } window.performSearch = performSearch; function displayResults(results) { const resultsElement = document.getElementById("results"); resultsElement.innerHTML = ""; results.forEach((result) => { const li = document.createElement("li"); li.textContent = result; resultsElement.appendChild(li); }); } setup(); </script> </body> </html>
在上述代码段中, <script> 元素中的 从生成的 JavaScript 封装器(refactored_couscous.js)中导入了 init 函数和 Index 类。init 函数非常重要,因为它初始化了 WebAssembly 模块,确保其可随时使用。
页面加载时会调用 setup 函数。它首先使用 await init() 确保 WebAssembly 模块完全初始化。然后,创建 WebAssembly 模块中 Index 类的实例,用于存储和搜索数据。
当用户点击 “Search” 按钮时,将触发 performSearch 函数。它从文本字段中检索用户输入的内容,使用 Index 类的 search *** 查找匹配内容,然后使用 displayResults 函数显示结果。
displayResults 函数获取搜索结果,为每个结果创建一个列表项,并将其添加到网页上的 results 无序列表中。
在浏览器中加载 index.html 文件并进行单词搜索,如下图所示:

在本节中,我们将从 --target bundler 转译开始,用 使用生成的模块。然后,我们将把该包发布到 NPM 注册表,并在 Node.js 中安装和使用它。
使用 --target bundler 标志运行 wa *** -pack transpilation 命令。然后将位置更改为 pkg 目录,并运行下面的 npm 命令:
npm link
在 pkg 目录中运行 npm link 命令,就能像全局安装 NPM 软件包一样访问该软件包。这样,你就可以使用软件包,而无需将其实际发布到 NPM 注册表中。这对于测试和开发目的尤其有用。
接下来,在项目根目录下创建一个新文件夹,用于存放我们将要介绍的示例:
mkdir -p examples/webpack-impl
在 ./examples/webpack-impl 文件夹中创建 package.json 文件,并添加以下配置:
// file: ./examples/webpack-impl/package.json
{
"scripts": {
"serve": "webpack-dev-server"
},
"dependencies": {
"refactored-couscous": "^0.1.0"
},
"devDependencies": {
"webpack": "^4.25.1",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.1.10"
}
}
接下来,运行以下 npm 命令链接软件包并安装其他软件包:
npm link refactored-couscous && npm install
安装完成后,创建一个 index.html 文件并添加以下 HTML 代码:
<!-- file: ./examples/webpack-impl/index.html --> <!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8" /> <title>refactored-couscous example</title> <style> /* for the styles, see https://github.com/Ikeh-Akinyemi/refactored-couscous/blob/main/examples/webpack-impl/index.html*/ </style> </head> <body> <div id="loading" style="display: none"> <div class="2345HAO83c9085b6f9fe5d2 spinner"></div> Loading... </div> <input type="file" id="fileInput" /> <input type="text" id="urlInput" placeholder="Enter URL" /> <button id="buildIndexButton">Build Index</button> <input type="text" id="searchInput" placeholder="Search..." /> <button id="searchButton">Search</button> <ul id="results"></ul> <script src="./index.js"></script> </body> </html>
接下来,让我们创建一个 index.js 文件,并添加以下 JavaScript 代码:
// file: ./examples/webpack-impl/index.js
import("refactored-couscous").then((js) => {
function splitIntoSentences(text) {
return text.match(/[^\.!\?]+[\.!\?]+/g) || [];
}
const index = new js.Index();
...
});
本节首先动态导入 refactored-couscous 模块。模块导入后,回调函数将以导入的模块为参数( js )执行。
接下来,我们定义了一个名为 split into sentences 的实用函数,用于根据标点符号将给定文本分割成单个句子。然后,从导入模块中创建 Index 类的实例。
现在,让我们添加一个事件监听器,检查用户是否上传了要搜索的文件或 URL,然后使用资源的内容建立 Index。
// file: ./examples/webpack-impl/index.js
import("refactored-couscous").then((js) => {
...
document
.getElementById("buildIndexButton")
.addEventListener("click", async () => {
const fileInput = document.getElementById("fileInput");
const urlInput = document.getElementById("urlInput");
const loadingDiv = document.getElementById("loading");
loadingDiv.style.display = "block";
if (fileInput.files.length) {
const file = fileInput.files[0];
const content = await file.text();
const sentences = splitIntoSentences(content);
sentences.forEach((sentence) => {
if (sentence.trim()) {
console.log(sentence);
index.add(sentence.trim());
}
});
} else if (urlInput.value) {
try {
const response = await fetch(urlInput.value);
const content = await response.text();
const sentences = splitIntoSentences(content);
sentences.forEach((sentence) => {
if (sentence.trim()) {
index.add(sentence.trim());
}
});
} catch (error) {
console.error("Error fetching URL:", error);
}
}
loadingDiv.style.display = "none";
});
...
});
在上述代码段中,我们通过 fileInput 文件输入元素检查是否提供了文件。如果是,它将读取文件内容,使用实用功能将其分割成句子,并将每个句子添加到索引中。
如果提供的是 URL 而不是文件,则会从 URL 获取内容,将其拆分成句子并添加到索引中。
接下来,让我们实现搜索 Index 并显示结果:
// file: ./examples/webpack-impl/index.js
import("refactored-couscous").then((js) => {
...
document.getElementById("searchButton").addEventListener("click", () => {
const loadingDiv = document.getElementById("loading");
loadingDiv.style.display = "block";
const query = document.getElementById("searchInput").value;
const results = index.search(query);
console.log(results);
loadingDiv.style.display = "none";
displayResults(results);
});
function displayResults(results) {
const resultsList = document.getElementById("results");
resultsList.innerHTML = ""; // Clear previous results
results.forEach((result) => {
const listItem = document.createElement("li");
listItem.textContent = result;
resultsList.appendChild(listItem);
});
}
});
之后,让我们在 examples/webpack-impl 文件夹的根目录下创建一个 webpack.config.js 文件来配置 Webpack,并在其中填充以下内容:
// file: ./examples/webpack-impl/webpack.config.js
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "index.js",
},
mode: "development",
};
设置完成后,就可以执行项目了。确保您使用的是 v16,特别是考虑到 Node.js 环境的特殊性。
npm run serve
执行后,在浏览器中访问 http://localhost:8080/ 。您可以选择直接上传文件或输入指向 *** 内容的 URL。

显示的图像证实了我们软件包的功能。下一步是将此软件包发布到 NPM 注册表,让更多人可以访问它。为此,请切换到 pkg 目录,并在 package.json 配置中加入 type: "module" 。这将确保软件包与 兼容。
npm publish
这将把软件包发布到你在 上的账户,如下图所示:

发布后,我们可以使用 npm install refactored-couscous 命令安装软件包,并使用 E *** odule 系统将其导入 :
// file: ./examples/cli/src/index.js
import { Index } from 'refactored-couscous/refactored_couscous.js';
const index = new Index();
index.add("Hello world");
index.add("Rust is amazing");
index.add("Wade in Rust");
const results = index.search("rust");
console.log(results);
设置好代码后,就可以使用 Node.js 运行脚本了。确保启用 WebAssembly 模块的实验标志:
node --experimental-wa *** -modules ./src/index.js
执行脚本后,你会看到控制台中打印的搜索结果,显示哪些条目包含 “rust” 一词:
[ 'Rust is amazing', 'Wade in Rust' ]
这表明我们的软件包在对所提供的数据进行搜索时非常有效。
在本文中,我们使用 Rust 和 WebAssembly 成功重写了一个简化版的 Wade.js npm 软件包。我们深入研究了 Rust 类型系统的复杂性、WebAssembly 的性能优势,以及 Rust、WebAssembly 和 JavaScript 之间的无缝互操作性。我们还探讨了如何构建文本数据并编制索引、执行搜索,以及如何将 WebAssembly 模块集成到浏览器和服务器端 JavaScript 环境中。最后,我们迈出了关键的一步,将我们的软件包发布到 NPM 注册表,使其可以被更广泛地使用。这个练习不仅展示了 Rust 和 WebAssembly 的强大功能和灵活性,还为将来创建更复杂、更高效、更安全的 *** 应用程序奠定了基础。
GitHub 库的链接提供。
不久前,PHP 8.0大张旗鼓地发布了。它带来了许多新特性、性能增强和变化——其中最令人兴奋的是新的JIT编译器。 技术世界总是在向前发展,PHP也是如此。 ,包含了几个令人兴奋的特性。它定于今年晚些时候于2021年11月25日发布。 在本文中,我们将详细介绍PHP 8.1将带来哪些新的东...
谁在尝试访问某些网站时没有遇到过更新Java的请求? 虽然许多人通过交互式网站功能熟悉Java,但用户可能不太熟悉JavaScript——或者,实际上,他们可能错误地认为两者是相同的。 在本文中,我们将讨论JavaScript 是什么以及Java和JavaScript之间的区别。然后我们将概...
Linux面板环境安装,主要支持LNMP和LAMP、Tomcat、node.js。不过对于大部分站长来说,主要是LNMP和LAMP两个环境的安装。 LNMP和LAMP两个环境的最大区别是,前者采用Nginx作为Web服务器,后者则采用Apache作为Web服务器。(选择哪个作为您的Web服务器,可...
本章节主要是对宝塔面板的主界面的各个版本进行一个简单的说明。 宝塔面板主界面主要包括:服务器操作系统、服务器状态、站点信息、软件管理及网络流量几个部分。 Windows面板有部分功能未实现,其余部分与Linux面板同步。 系统操作...
宝塔面板另外一个特质是,你无需通过Linux命令行来查看服务器各项指标状况,即可以阿里云服务器类似的可视化图表,查看资源使用、负载、CPU占用及内容使用百分比等指标。 默认监控是关闭,有需要的,可以开启,监控数据默认保存30天,可以自行修改,默认监控数据保存在日志,可手动清理该日志。 监控管理,...
宝塔面板的安全管理可以设置SSH开关、禁用PING、放行端口、屏蔽IP等相关的操作。 SSH的设置 SSH的关闭与启动,点击下图的开关,即可开启或关闭SSH远程连接。 SSH端口的修改,SSH远程连接的默认端口为22,修改端口前,请先查看该端口是否被占用。 PING命令的...