网站建设维护概括总结,黄页88网站,东莞网站推广教程,广州注册公司价格前言
现代前端对速度的追求已经进入二进制工具时代#xff0c;Rust 开发成为每个人的必修课。
一般我们将常见的前端 Rust 开发分为以下几类#xff0c;难度由上至下递增#xff1a; 开发 wasm 。 开发 swc 插件。 开发代码处理工具。
我们将默认读者具备最简单的 Rus…前言
现代前端对速度的追求已经进入二进制工具时代Rust 开发成为每个人的必修课。
一般我们将常见的前端 Rust 开发分为以下几类难度由上至下递增 开发 wasm 。 开发 swc 插件。 开发代码处理工具。
我们将默认读者具备最简单的 Rust 知识进行快速入门介绍。
正文
开发 wasm
意义
开发 wasm 的意义在于利用浏览器运行 wasm 的优势在 wasm 中进行大量复杂的计算、音视频、图像处理等当你有此类需求可以优先考虑使用 Rust 开发 wasm 分发至浏览器。
初始化
我们使用 wasm-pack 构建 wasm 参考 wasm-pack Quickstart 得到一个模板起始项目。
实战 case
使用 tsify 支持输出结构体的 TypeScript 类型实现一个简单的加法运算
# Cargo.toml 确保你含有这些依赖
[dependencies]
serde { version 1.0.163, features [derive] }
tsify 0.4.5use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use tsify::Tsify;#[derive(Tsify, Serialize, Deserialize)]
#[tsify(into_wasm_abi, from_wasm_abi)]
pub struct Rect {pub width: u32,pub height: u32
}#[wasm_bindgen]
pub fn plus(mut rect: Rect) - Rect {rect.width rect.height;rect
}构建 # devwasm-pack build --verbose --out-dir pkg --out-name index --dev# releasewasm-pack build --verbose --out-dir pkg --out-name index --release这将在当前目录的 pkg/* 下生成 wasm 产物与 index.js 等胶水代码导入 index.js 便即开即用非常方便。
运行 wasm
为了支持直接导入 .wasm 文件我们需要 webpack 5 的 asyncWebAssembly 特性支持此处以在 Umi 4 项目中调试为例创建一个 Umi 4 Simple 模板项目 pnpm create umi wasm-demo参考 FAQ 怎么用 WebAssembly 配置开启 wasm 支持
// .umirc.tsexport default {chainWebpack(config) {config.set(experiments, {...config.get(experiments),asyncWebAssembly: true})const REG /\.wasm$/config.module.rule(asset).exclude.add(REG).end();config.module.rule(wasm).test(REG).exclude.add(/node_modules/).end().type(webassembly/async).end()},
}之后便可在项目中直接导入刚刚打包好在 pkg/* 的 wasm 即开即用
import * as wasm from ./path/to/pkg
const ret wasm.plus({width: 1,height: 2,
})
// { width: 3, height: 2 }
console.log(ret: , ret);注 由于 wasm 文件可能较大当你需要优化时可将使用 .wasm 的组件手动 React.lazy(() import(./Component)) 拆包之后在 useEffect 中懒加载 await import(./path/to/pkg) 。 对于非 Umi 4 的 webpack 5 项目请自行开启 experiments.asyncWebAssembly 即可一键支持 wasm 导入。
缺点
由于当下浏览器和 PC 设备性能已足够强大更多场合下运行 wasm 进行数据计算传递数据花费的时间将 远远超出使用 JavaScript 进行同逻辑计算的时间 。
所以除音视频场景外你很可能不需要 wasm 而是优先考虑使用 Worker 等优化策略。
开发 swc 插件
意义
现代前端高效构建往往将 babel 替代为 swc 化为了替代 babel 插件实现代码转换开发 swc 插件成为了一门必修课。
初始化
参考 swc Create a project 我们用 swc 脚手架初始化得到一个插件的模板起始项目。
实战 case
我们编写一个最简单的功能将所有的 react 导入转换为 preact
# Cargo.toml 确保你含有这些依赖
[dependencies]
serde 1.0.163
serde_json 1.0.96
swc_core { version 0.76.39, features [ecma_plugin_transform] }use swc_core::ecma::{ast::{Program, ImportDecl, ImportSpecifier},visit::{as_folder, FoldWith, VisitMut, VisitMutWith},
};
use swc_core::plugin::{plugin_transform, proxies::TransformPluginProgramMetadata};
use serde::{Deserialize, Serialize};#[derive(Deserialize, Serialize)]
pub struct TransformPluginConfig {from: String,to: String,
}pub struct TransformVisitor {config: TransformPluginConfig,
}impl VisitMut for TransformVisitor {fn visit_mut_import_decl(mut self, n: mut ImportDecl) {n.visit_mut_children_with(self);if n.specifiers.len() 1 {if let ImportSpecifier::Default(_) n.specifiers[0] {if n.src.value self.config.from {n.src Box::new(self.config.to.clone().into());}}}}
}#[plugin_transform]
pub fn process_transform(program: Program, metadata: TransformPluginProgramMetadata) - Program {let config serde_json::from_str(metadata.get_transform_plugin_config().unwrap()).expect(invalid config);program.fold_with(mut as_folder(TransformVisitor { config }))
}在编写过程中以下文档可供参考 Swc Implementing a plugin Swc Rust docs
构建 # devcargo build --target wasm32-wasi# releasecargo build --target wasm32-wasi --release通过构建你可以在当前目录下得到 wasm 形式的 swc 插件产物。
运行 swc 插件
import { transformSync } from swc/coreconst transform async () {const { code } transformSync(
import React from react,{jsc: {experimental: {plugins: [[require.resolve(./target/wasm32-wasi/debug/my_first_plugin.wasm),{from: react,to: preact,},],],},parser: {syntax: typescript,dynamicImport: true,tsx: true,},target: es2015,minify: {compress: false,mangle: false,},transform: {react: {runtime: automatic,throwIfNamespace: true,development: true,useBuiltins: true,},},},module: {type: es6,ignoreDynamic: true,},minify: false,isModule: true,sourceMaps: false,filename: index.tsx,})// import React from preactconsole.log(code: , code)
}transform()缺点
为了避免多平台差异我们分发了 wasm32-wasi 目标的 wasm 包好处是只需构建一次即可全平台通用缺点是产物较大同时 wasm 运行速度不如 .node 但现代前端已无需担心只在本地编译阶段使用的包大小如 Nextjs 单包依赖已达 40 M 以上TypeScript 20 M 你可以无需关心产物体积问题。
至此我们介绍了如何借助 swc 插件实现 babel 插件的替代在下文中我们将继续深入真正构建多平台分发的二进制包同时不会做过多细节介绍推荐只学习到此处为止。
开发代码处理工具
意义
目前最主流的前端 Rust 开发即是借助 Swc 来解析 JavaScript 、TypeScript 代码从而实现代码信息提取、转换、编译等我们会将 Rust 编译为 Node addon .node 文件以获得远比 wasm 更快的运行速度。
初始化
使用 napi-rs 构建 参考 napi Create project 得到一个模板起始项目。
实战 case
此处同样我们实现一个将所有 react 导入转换为 preact 的需求所需要的依赖与模板代码如下
# Cargo.toml 确保你含有这些依赖
[dependencies]
napi { version 2.12.2, default-features false, features [napi4, error_anyhow] }
napi-derive 2.12.2
swc_common { version 0.31.12, features [sourcemap] }
swc_ecmascript { version 0.228.27, features [parser, visit, codegen] }#[macro_use]
extern crate napi_derive;use std::path::{Path, PathBuf};
use std::str;use swc_common::comments::SingleThreadedComments;
use swc_common::{sync::Lrc, FileName, Globals, SourceMap};
use swc_ecmascript::ast;
use swc_ecmascript::codegen::text_writer::JsWriter;
use swc_ecmascript::parser::lexer::Lexer;
use swc_ecmascript::parser::{EsConfig, Parser, StringInput, Syntax, TsConfig};
use swc_ecmascript::visit::{VisitMut, VisitMutWith};#[napi]
pub fn transform(code: String, options: ImportChange) - String {let is_jsx true;let is_typescript true;let filename_path_buf PathBuf::from(filename.tsx);let syntax if is_typescript {Syntax::Typescript(TsConfig {tsx: is_jsx,decorators: true,..Default::default()})} else {Syntax::Es(EsConfig {jsx: is_jsx,export_default_from: true,..Default::default()})};let source_map Lrc::new(SourceMap::default());let source_file source_map.new_source_file(FileName::Real(filename_path_buf.clone()),code.clone().into(),);let comments SingleThreadedComments::default();let lexer Lexer::new(syntax,Default::default(),StringInput::from(*source_file),Some(comments),);let mut parser Parser::new_from(lexer);let mut module parser.parse_module().expect(failed to parse module);swc_common::GLOBALS.set(Globals::new(), || {let mut visitor options;module.visit_mut_with(mut visitor);});let (code, _map) emit_source_code(Lrc::clone(source_map),Some(comments),module,None,false,).unwrap();code
}#[napi(object)]
pub struct ImportChange {pub from: String,pub to: String,
}impl ImportChange {pub fn new(from: String, to: String) - Self {Self { from, to }}
}impl VisitMut for ImportChange {fn visit_mut_module_decl(mut self, decl: mut ast::ModuleDecl) {if let ast::ModuleDecl::Import(import_decl) decl {if import_decl.src.value self.from {import_decl.src Box::new(self.to.clone().into());}}}
}pub fn emit_source_code(source_map: LrcSourceMap,comments: OptionSingleThreadedComments,program: ast::Module,root_dir: OptionPath,source_maps: bool,
) - Result(String, OptionString), napi::Error {let mut src_map_buf Vec::new();let mut buf Vec::new();{let writer Box::new(JsWriter::new(Lrc::clone(source_map),\n,mut buf,if source_maps {Some(mut src_map_buf)} else {None},));let config swc_ecmascript::codegen::Config {minify: false,target: ast::EsVersion::latest(),ascii_only: false,omit_last_semi: false,};let mut emitter swc_ecmascript::codegen::Emitter {cfg: config,comments: Some(comments),cm: Lrc::clone(source_map),wr: writer,};emitter.emit_module(program)?;}let mut map_buf vec![];let emit_source_maps if source_maps {let mut s source_map.build_source_map(src_map_buf);if let Some(root_dir) root_dir {s.set_source_root(Some(root_dir.to_str().unwrap()));}s.to_writer(mut map_buf).is_ok()} else {false};if emit_source_maps {Ok((unsafe { str::from_utf8_unchecked(buf).to_string() },unsafe { Some(str::from_utf8_unchecked(map_buf).to_string()) },))} else {Ok((unsafe { str::from_utf8_unchecked(buf).to_string() }, None))}
}构建 # devnapi build --platform# releasenapi build --release一般情况下我们通常会分发至以下 9 个平台 napi: {triples: {defaults: false,additional: [x86_64-apple-darwin,aarch64-apple-darwin,x86_64-pc-windows-msvc,aarch64-pc-windows-msvc,x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-unknown-linux-musl,aarch64-unknown-linux-musl,armv7-unknown-linux-gnueabihf]}}通常本地只能编译自己平台的 .node 二进制文件所以需要依赖 Github Actions CI 等云环境进行多平台的构建并且在 CICD 中构造好 npm 包使用 Npm Token 发布此部分内容往往是大量的模板代码与调试过程请自行研究学习。
运行二进制包
import { transform } from ./indexconsole.log(// import React from preacttransform(import React from react,{ from: react, to: preact })
)直接导入 napi 生成的 index.js 胶水代码即可使用 .node 二进制包。
缺点 通过构建 .node 分发至不同平台是目前最高运行效率、最小下载体积的方法但相对应需要手动管理所有 Rust 代码且多平台构建也强依赖云环境这提出了一些较高的要求。 随着对 napi / Rust 异步、并发编程 / Swc 的理解精进你可以写出更高运行效率的代码得到更快的执行速度。但最简单的代码依然够用因为现代计算机性能已经足够快1s 还是 10s 的争论没有意义。 在开发过程中你可能会遇到各种 Rust 构建相关的问题请自行研究并解决。
总结
本文对 Rust 浅尝辄止如希望更有所作为你可以通过不断精进 Rust 组织出更优雅的代码结构实现更高的执行效率。
前端 AST 人尽皆知如同开发 Babel 插件一样开发 Rust Swc 插件已然成为现代前端的必修课文本推荐只入门至 Swc 插件为止已经能应对绝大多数场景。
另外对于性能上无需过多追求由于计算机的性能已经过剩不管是 wasm 还是 .node 速度都是很快的秒级之争没有意义。
以上。