TL;博士 🦀 有一个丰富的工具包用于使用 Rust 开发 WebAssembly。好有趣! 🤝 WebAssembly 和 Next.js 配合得很好,但要注意已知问题。 🧑🔬 Xor 过滤器是一种数据结构,可提供出色的内存效率和快速查找值的存在。 🧑🍳 WebAssembly 的性能和代码大小无法保证。确保进行测量和基准测试。 我一直都知道我想要为我的 中的文章提供全文搜索功能,以便访问者快速访问他们感兴趣的内容。迁移到 Contentlayer 后,它似乎不再那么牵强了。于是我开始探索🚀 投资组合 受 启发:WebAssembly 全文搜索引擎 tinysearch 经过一番研究,我找到了一个名为 的搜索引擎。这是一个使用 和 (Wasm) 构建的静态搜索引擎。作者 Matthias Endler 写 关于 是如何产生的精彩博客文章。 tinysearch Rust WebAssembly 了一篇 tinysearch 我喜欢在构建时构建一个简约的搜索引擎并将其以优化的低级代码发送到浏览器的想法。所以我决定使用 作为蓝图,编写自己的搜索引擎来与我的 集成。 tinysearch Next.js 静态站点 的代码库。它写得很好。我的搜索引擎的实现是它的简化版本。核心逻辑是一样的。 我强烈推荐阅读 tinysearch 搜索功能是什么样的? 很简单: 用户在搜索输入中键入任何内容。 搜索引擎搜索所有内容中的关键字并找到最相关的文章。 UI 显示一个排名的搜索结果列表。 您可以试试 的搜索功能! 文章页面 一点点统计数据 在撰写本文时,有: 7 篇文章(更多内容🚀) 13,925 字 682KB 的数据文件(由 Contentlayer 生成) 为了使全文搜索适用于追求速度的静态站点,代码大小必须很小。 WebAssembly 全文搜索功能如何工作? 大多数 。他们能够与 JavaScript 一起运行本机 WebAssembly 代码和二进制文件。 现代浏览器现在都支持 WebAssembly 搜索功能的概念很简单。它接受一个查询字符串作为参数。在函数中,我们将查询标记为搜索词。然后,我们根据每篇文章包含的搜索词数为每篇文章打分。最后,我们根据相关性对文章进行排名。分数越高,它的相关性就越高。 流程如下所示: 对文章进行评分是计算量最大的地方。一种简单的方法是将每篇文章转换为包含文章中所有唯一单词的哈希集。我们可以通过简单地计算哈希集中有多少搜索词来计算分数。 您可以想象,这不是哈希集最节省内存的方法。有更好的数据结构来代替它: 。 xor 过滤器 什么是异或过滤器? 是相对较新的数据结构,它允许我们估计一个值是否存在。它速度快,内存效率高,因此非常适合全文搜索。 Xor 过滤器 xor 过滤器不是像散列集那样存储实际输入值,而是 存储输入值的指纹(L 位散列序列)。在查找过滤器中是否存在某个值时,它会检查该值的指纹是否存在。 以特定方式 但是,Xor 过滤器有几个取舍: Xor 过滤器是概率性的,有可能发生误报。 Xor 过滤器无法估计部分值的存在。所以在我的用例中,全文搜索将只能搜索完整的单词。 我是如何使用 Rust 构建 Xor 过滤器的? 因为我有 Contentlayer 生成的文章数据,所以我通过在构建 WebAssembly 之前向它们提供数据来构建 xor 过滤器。然后我序列化了异或过滤器并将它们存储在一个文件中。要在 WebAssembly 中使用过滤器,我需要做的就是从存储文件中读取并反序列化过滤器。 过滤器生成流程如下所示: crate 是实现异或过滤器的不错选择,因为它提供了序列化/反序列化以及一些提高内存效率和误报率的功能。它还为我的用例提供了一个非常方便的 结构来构造带有字符串切片的异或过滤器。用 Rust 编写的构造大致如下所示: xorf HashProxy use std::collections::hash_map::DefaultHasher; use xorf::{Filter, HashProxy, Xor8}; mod utils; fn build_filter(title: String, body: String) -> HashProxy<String, DefaultHasher, Xor8> { let title_tokens: HashSet<String> = utils::tokenize(&title); let body_tokens: HashSet<String> = utils::tokenize(&body); let tokens: Vec<String> = body_tokens.union(&title_tokens).cloned().collect(); HashProxy::from(&tokens) } 如果您对实际实现感兴趣,可以 。 在存储库中阅读更多内容 把它们放在一起 在 Next.js 中集成 WebAssembly 这是我在 Next.js 中集成 xor 过滤器生成脚本和 WebAssembly 的方法。 文件结构如下所示: my-portfolio ├── next.config.js ├── pages ├── scripts │ └── fulltext-search ├── components │ └── Search.tsx └── wasm └── fulltext-search 为了支持 WebAssembly,我更新了我的 Webpack 配置以将 WebAssembly 模块加载为异步模块。为了使它适用于静态站点生成,我需要 来在 目录中生成 WebAssembly 模块,以便在运行 脚本时静态页面可以成功预渲染。 一种解决方法 .next/server next build next.config.js webpack: function (config, { isServer }) { // it makes a WebAssembly modules async modules config.experiments = { asyncWebAssembly: true } // generate wasm module in ".next/server" for ssr & ssg if (isServer) { config.output.webassemblyModuleFilename = './../static/wasm/[modulehash].wasm' } else { config.output.webassemblyModuleFilename = 'static/wasm/[modulehash].wasm' } return config }, 这就是集成的全部内容✨ 在 React 组件中使用 WebAssembly 为了从 Rust 代码构建 WebAssembly 模块,我使用 。 wasm-pack 生成的 文件和 JavaScript 的胶水代码位于 中。我需要做的就是使用 动态导入它们。像这样: .wasm wasm/fulltext-search/pkg next/dynamic 组件/Search.tsx import React, { useState, useCallback, ChangeEvent, useEffect } from 'react' import dynamic from 'next/dynamic' type Title = string; type Url = string; type SearchResult = [Title, Url][]; const Search = dynamic({ loader: async () => { const wasm = await import('../../wasm/fulltext-search/pkg') return () => { const [term, setTerm] = useState('') const [results, setResults] = useState<SearchResult>([]) const onChange = useCallback((e: ChangeEvent<HTMLInputElement>) => { setTerm(e.target.value) }, []) useEffect(() => { const pending = wasm.search(term, 5) setResults(pending) }, [term]) return ( <div> <input value={term} onChange={onChange} placeholder="🔭 search..." /> {results.map(([title, url]) => ( <a key={url} href={url}>{title}</a> ))} </div> ) } }, }) export default Search 优化 WebAssembly 代码大小 未经任何优化,原始 Wasm 文件大小为 。我使用 找出代码大小。 114.56KB Twiggy Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 117314 ┊ 100.00% ┊ Σ [1670 Total Rows] 与 的原始数据文件相比,它比我预期的要小得多。我很高兴已经将它交付到生产环境中,但我很想知道使用 可以减少多少代码大小。 628KB Rust 和 WebAssembly 工作组的优化建议 第一个实验是切换 LTO 并尝试不同 。以下配置产生最小的 代码大小: opt-level .wasm 货运.toml [profile.release] + opt-level = 's' + lto = true Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 111319 ┊ 100.00% ┊ Σ [1604 Total Rows] 接下来,我用 替换了默认分配器。 wee_alloc wasm/全文搜索/src/lib.rs + #[global_allocator] + static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 100483 ┊ 100.00% ┊ Σ [1625 Total Rows] 然后我尝试了 Binaryen 中的 工具。 wasm wasm-opt wasm-opt -Oz -o wasm/fulltext-search/pkg/fulltext_search_core_bg.wasm wasm/fulltext-search/pkg/fulltext_search_core_bg.wasm Shallow Bytes │ Shallow % │ Item ───────────────┼───────────┼───────────────────── 100390 ┊ 100.00% ┊ Σ [1625 Total Rows] 这比原始代码大小减少了 。 14.4% 最后,我能够在以下位置发布全文搜索引擎: 98.04 KB 原始 45.92 KB 压缩包 还不错😎 它真的很活泼吗? 我用 分析了性能并收集了一些数据: web-sys 搜索次数:208 分钟:0.046 毫秒 最大值:0.814 毫秒 平均值:0.0994 毫秒✨ 标准差:0.0678 平均而言,执行全文搜索所需的时间不到 0.1 毫秒。 真是太爽了😎 最后的想法 经过一些实验,我能够使用 WebAssembly、Rust 和 xor 过滤器构建一个快速、轻量级的全文搜索。它与 Next.js 和静态站点生成很好地集成。 速度和大小带来了一些权衡,但它们对用户体验没有太大影响。如果您正在寻找更全面的搜索功能,这里有一些很酷的产品可供选择: SaaS 搜索引擎 阿尔及利亚 静态搜索引擎 小搜索 基于服务器的搜索引擎 美利搜索 类型感 浏览器内搜索引擎 弹性搜索 迷你搜索 Elasticlunr.js 参考 文章: 使用 Rust 和 WebAssembly 的小型静态全文搜索引擎 - Matthias Endler 文章: 使用 Bloom 过滤器编写全文搜索引擎 - Stavros Korokithakis 文章: 异或过滤器:比布隆过滤器更快、更小 - Daniel Lemire 文章: 缩小 .wasm 代码大小 - Rust 和 WebAssembly 工作组 文章: 使用 MDX 和 Contentlayer 构建更好的 Next.js 静态站点 - Daw-Chih Liou 文章: Next.js - 动态导入 网站: Xor 过滤器:比 Bloom 和 Cuckoo 过滤器更快、更小 网站: Daw-Chih Liou 的作品集 网站: 美利搜索 网站: Typesense 网站: 阿尔及利亚 网站: Elasticlunr.js 网站: 锈 网站: WebAssembly 网站: MDN WebAssembly 网站: Next.js 网站: 我可以使用 WebAssembly 吗? 讲座: 近似成员查询 - 斯坦福 维基: 全文搜索 GitHub: Next.js 和 WebAssembly 示例 GitHub: 最小化 Rust 二进制大小 GitHub: dawchihliou.github.io GitHub: Webpack 5 打破了 SSR 的动态 wasm 导入 :小搜索 Github GitHub: 美利搜索 GitHub: xorf GitHub: wasm包 GitHub: 二进制 GitHub: 树枝 GitHub: 剪辑 GitHub: 网络系统 GitHub: 一次细胞 GitHub: 二进制码 GitHub: wee_alloc GitHub: 塞尔德 GitHub: 无论如何 GitHub: 内容层 GitHub: 弹性搜索 GitHub: 迷你搜索 这篇文章也发表在 。 道智的网站上