diff --git a/packages/backend-rs/src/config/constant.rs b/packages/backend-rs/src/config/constant.rs
index f2340893cd..7e6b50fa52 100644
--- a/packages/backend-rs/src/config/constant.rs
+++ b/packages/backend-rs/src/config/constant.rs
@@ -1,17 +1,17 @@
 //! This module is used in the TypeScript backend only.
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub const SECOND: i32 = 1000;
-#[crate::ts_export]
+#[macros::ts_export]
 pub const MINUTE: i32 = 60 * SECOND;
-#[crate::ts_export]
+#[macros::ts_export]
 pub const HOUR: i32 = 60 * MINUTE;
-#[crate::ts_export]
+#[macros::ts_export]
 pub const DAY: i32 = 24 * HOUR;
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub const USER_ONLINE_THRESHOLD: i32 = 10 * MINUTE;
-#[crate::ts_export]
+#[macros::ts_export]
 pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
 
 /// List of file types allowed to be viewed directly in the browser
@@ -21,7 +21,7 @@ pub const USER_ACTIVE_THRESHOLD: i32 = 3 * DAY;
 /// * <https://github.com/sindresorhus/file-type/blob/main/supported.js>
 /// * <https://github.com/sindresorhus/file-type/blob/main/core.js>
 /// * <https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers>
-#[crate::ts_export]
+#[macros::ts_export]
 pub const FILE_TYPE_BROWSERSAFE: [&str; 41] = [
     // Images
     "image/png",
diff --git a/packages/backend-rs/src/config/meta.rs b/packages/backend-rs/src/config/meta.rs
index cedb6cf0ad..969d9d35f5 100644
--- a/packages/backend-rs/src/config/meta.rs
+++ b/packages/backend-rs/src/config/meta.rs
@@ -11,12 +11,12 @@ fn set_cache(meta: &Meta) {
     let _ = CACHE.lock().map(|mut cache| *cache = Some(meta.clone()));
 }
 
-#[crate::export(js_name = "fetchMeta")]
+#[macros::export(js_name = "fetchMeta")]
 pub async fn local_server_info() -> Result<Meta, DbErr> {
     local_server_info_impl(true).await
 }
 
-#[crate::export(js_name = "updateMetaCache")]
+#[macros::export(js_name = "updateMetaCache")]
 pub async fn update() -> Result<(), DbErr> {
     local_server_info_impl(false).await?;
     Ok(())
@@ -49,7 +49,7 @@ async fn local_server_info_impl(use_cache: bool) -> Result<Meta, DbErr> {
     Ok(meta)
 }
 
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct PugArgs {
     pub img: Option<String>,
     pub title: String,
@@ -62,7 +62,7 @@ pub struct PugArgs {
     pub private_mode: Option<bool>,
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn meta_to_pug_args(meta: Meta) -> PugArgs {
     use rand::prelude::*;
     let mut rng = rand::thread_rng();
diff --git a/packages/backend-rs/src/config/server.rs b/packages/backend-rs/src/config/server.rs
index 8991caec99..81f2baedb0 100644
--- a/packages/backend-rs/src/config/server.rs
+++ b/packages/backend-rs/src/config/server.rs
@@ -8,7 +8,7 @@ pub const VERSION: &str = macros::read_version_from_package_json!();
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 struct ServerConfig {
     pub url: String,
     pub port: u16,
@@ -73,7 +73,7 @@ struct ServerConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct DbConfig {
     pub host: String,
     pub port: u16,
@@ -86,7 +86,7 @@ pub struct DbConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct RedisConfig {
     pub host: String,
     pub port: u16,
@@ -101,13 +101,13 @@ pub struct RedisConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct TlsConfig {
     pub host: String,
     pub reject_unauthorized: bool,
 }
 
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct WorkerConfig {
     pub web: u32,
     pub queue: u32,
@@ -115,7 +115,7 @@ pub struct WorkerConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct WorkerConfigInternal {
     pub web: Option<u32>,
     pub queue: Option<u32>,
@@ -123,7 +123,7 @@ pub struct WorkerConfigInternal {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct IdConfig {
     pub length: Option<u8>,
     pub fingerprint: Option<String>,
@@ -131,7 +131,7 @@ pub struct IdConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct SysLogConfig {
     pub host: String,
     pub port: u16,
@@ -139,7 +139,7 @@ pub struct SysLogConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct DeepLConfig {
     pub managed: Option<bool>,
     pub auth_key: Option<String>,
@@ -148,7 +148,7 @@ pub struct DeepLConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct LibreTranslateConfig {
     pub managed: Option<bool>,
     pub api_url: Option<String>,
@@ -157,7 +157,7 @@ pub struct LibreTranslateConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct EmailConfig {
     pub managed: Option<bool>,
     pub address: Option<String>,
@@ -170,7 +170,7 @@ pub struct EmailConfig {
 
 #[derive(Deserialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct ObjectStorageConfig {
     pub managed: Option<bool>,
     pub base_url: Option<String>,
@@ -186,7 +186,7 @@ pub struct ObjectStorageConfig {
     pub s3_force_path_style: Option<bool>,
 }
 
-#[crate::export(object, use_nullable = false)]
+#[macros::export(object, use_nullable = false)]
 pub struct Config {
     // ServerConfig (from default.yml)
     pub url: String,
@@ -263,7 +263,7 @@ fn read_config_file() -> ServerConfig {
     data
 }
 
-#[crate::export]
+#[macros::export]
 pub fn load_config() -> Config {
     let server_config = read_config_file();
     let version = VERSION.to_owned();
diff --git a/packages/backend-rs/src/federation/acct.rs b/packages/backend-rs/src/federation/acct.rs
index b07cb71448..816e75baf2 100644
--- a/packages/backend-rs/src/federation/acct.rs
+++ b/packages/backend-rs/src/federation/acct.rs
@@ -1,7 +1,7 @@
 use std::{fmt, str::FromStr};
 
 #[cfg_attr(test, derive(Debug, PartialEq))]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Acct {
     pub username: String,
     pub host: Option<String>,
@@ -51,12 +51,12 @@ impl From<Acct> for String {
     }
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn string_to_acct(acct: &str) -> Acct {
     Acct::from_str(acct).unwrap()
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn acct_to_string(acct: &Acct) -> String {
     acct.to_string()
 }
diff --git a/packages/backend-rs/src/federation/nodeinfo/fetch.rs b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
index 1004bc6f56..4f11821054 100644
--- a/packages/backend-rs/src/federation/nodeinfo/fetch.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/fetch.rs
@@ -91,7 +91,7 @@ async fn fetch_nodeinfo_impl(nodeinfo_link: &str) -> Result<Nodeinfo20, Error> {
 type Nodeinfo = Nodeinfo20;
 
 /// Fetches and returns the NodeInfo (version 2.0) of a remote server.
-#[crate::export]
+#[macros::export]
 pub async fn fetch_nodeinfo(host: &str) -> Result<Nodeinfo, Error> {
     tracing::info!("fetching from {}", host);
     let links = fetch_nodeinfo_links(host).await?;
diff --git a/packages/backend-rs/src/federation/nodeinfo/generate.rs b/packages/backend-rs/src/federation/nodeinfo/generate.rs
index b37652637c..4d0a007823 100644
--- a/packages/backend-rs/src/federation/nodeinfo/generate.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/generate.rs
@@ -161,17 +161,17 @@ pub enum Error {
     Json(#[from] serde_json::Error),
 }
 
-#[crate::ts_export(js_name = "nodeinfo_2_1")]
+#[macros::ts_export(js_name = "nodeinfo_2_1")]
 pub async fn nodeinfo_2_1_as_json() -> Result<serde_json::Value, Error> {
     Ok(serde_json::to_value(nodeinfo_2_1().await?)?)
 }
 
-#[crate::ts_export(js_name = "nodeinfo_2_0")]
+#[macros::ts_export(js_name = "nodeinfo_2_0")]
 pub async fn nodeinfo_2_0_as_json() -> Result<serde_json::Value, Error> {
     Ok(serde_json::to_value(nodeinfo_2_0().await?)?)
 }
 
-#[crate::ts_export(js_name = "updateNodeinfoCache")]
+#[macros::ts_export(js_name = "updateNodeinfoCache")]
 pub async fn update_cache() -> Result<(), DbErr> {
     nodeinfo_2_1_impl(false).await?;
     Ok(())
diff --git a/packages/backend-rs/src/federation/nodeinfo/schema.rs b/packages/backend-rs/src/federation/nodeinfo/schema.rs
index 82f1dfe728..4abb02562e 100644
--- a/packages/backend-rs/src/federation/nodeinfo/schema.rs
+++ b/packages/backend-rs/src/federation/nodeinfo/schema.rs
@@ -34,7 +34,7 @@ pub struct Nodeinfo21 {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object, js_name = "Nodeinfo")]
+#[macros::export(object, js_name = "Nodeinfo")]
 pub struct Nodeinfo20 {
     /// The schema version, must be 2.0.
     pub version: String,
@@ -71,7 +71,7 @@ pub struct Software21 {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Software20 {
     /// The canonical name of this server software.
     pub name: String,
@@ -82,7 +82,7 @@ pub struct Software20 {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Deserialize, Serialize)]
 #[serde(rename_all = "lowercase")]
-#[crate::derive_clone_and_export]
+#[macros::derive_clone_and_export]
 pub enum Protocol {
     Activitypub,
     Buddycloud,
@@ -100,7 +100,7 @@ pub enum Protocol {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Clone, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Services {
     /// The third party sites this server can retrieve messages from for combined display with regular traffic.
     pub inbound: Vec<Inbound>,
@@ -112,7 +112,7 @@ pub struct Services {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Deserialize, Serialize)]
 #[serde(rename_all = "lowercase")]
-#[crate::derive_clone_and_export]
+#[macros::derive_clone_and_export]
 pub enum Inbound {
     #[serde(rename = "atom1.0")]
     Atom1,
@@ -131,7 +131,7 @@ pub enum Inbound {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Deserialize, Serialize)]
 #[serde(rename_all = "lowercase")]
-#[crate::derive_clone_and_export]
+#[macros::derive_clone_and_export]
 pub enum Outbound {
     #[serde(rename = "atom1.0")]
     Atom1,
@@ -169,7 +169,7 @@ pub enum Outbound {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Clone, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Usage {
     pub users: Users,
     pub local_posts: Option<u32>,
@@ -180,7 +180,7 @@ pub struct Usage {
 #[cfg_attr(test, derive(Debug, PartialEq))]
 #[derive(Clone, Deserialize, Serialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Users {
     pub total: Option<u32>,
     pub active_halfyear: Option<u32>,
diff --git a/packages/backend-rs/src/init/greet.rs b/packages/backend-rs/src/init/greet.rs
index 323548e59c..01f795ed00 100644
--- a/packages/backend-rs/src/init/greet.rs
+++ b/packages/backend-rs/src/init/greet.rs
@@ -12,7 +12,7 @@ const GREETING_MESSAGE: &str = "\
 ";
 
 /// Prints the greeting message and the Firefish version to stdout.
-#[crate::export]
+#[macros::export]
 pub fn greet() {
     println!("{}", GREETING_MESSAGE);
 
diff --git a/packages/backend-rs/src/init/log.rs b/packages/backend-rs/src/init/log.rs
index beb49decdc..47466735e3 100644
--- a/packages/backend-rs/src/init/log.rs
+++ b/packages/backend-rs/src/init/log.rs
@@ -3,7 +3,7 @@ use tracing::Level;
 use tracing_subscriber::FmtSubscriber;
 
 /// Initializes the [tracing] logger.
-#[crate::export(js_name = "initializeRustLogger")]
+#[macros::export(js_name = "initializeRustLogger")]
 pub fn initialize_logger() {
     let mut builder = FmtSubscriber::builder();
 
diff --git a/packages/backend-rs/src/init/system_info.rs b/packages/backend-rs/src/init/system_info.rs
index c4847cdd0d..7fde335e85 100644
--- a/packages/backend-rs/src/init/system_info.rs
+++ b/packages/backend-rs/src/init/system_info.rs
@@ -20,7 +20,7 @@ pub fn system_info() -> &'static std::sync::Mutex<System> {
 }
 
 /// Prints the server hardware information as the server info log.
-#[crate::export]
+#[macros::export]
 pub fn show_server_info() -> Result<(), SysinfoPoisonError> {
     let system_info = system_info().lock()?;
 
diff --git a/packages/backend-rs/src/lib.rs b/packages/backend-rs/src/lib.rs
index dbb3f734a1..1b9933fb8a 100644
--- a/packages/backend-rs/src/lib.rs
+++ b/packages/backend-rs/src/lib.rs
@@ -1,5 +1,3 @@
-use macros::{derive_clone_and_export, export, ts_export};
-
 pub mod config;
 pub mod database;
 pub mod federation;
diff --git a/packages/backend-rs/src/misc/check_server_block.rs b/packages/backend-rs/src/misc/check_server_block.rs
index 885507a9ae..a6355f5e94 100644
--- a/packages/backend-rs/src/misc/check_server_block.rs
+++ b/packages/backend-rs/src/misc/check_server_block.rs
@@ -18,7 +18,7 @@
 /// # Ok(())
 /// # }
 /// ```
-#[crate::ts_export]
+#[macros::ts_export]
 pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> {
     Ok(crate::config::local_server_info()
         .await?
@@ -45,7 +45,7 @@ pub async fn is_blocked_server(host: &str) -> Result<bool, sea_orm::DbErr> {
 /// # Ok(())
 /// # }
 /// ```
-#[crate::ts_export]
+#[macros::ts_export]
 pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> {
     Ok(crate::config::local_server_info()
         .await?
@@ -73,7 +73,7 @@ pub async fn is_silenced_server(host: &str) -> Result<bool, sea_orm::DbErr> {
 /// # Ok(())
 /// # }
 /// ```
-#[crate::ts_export]
+#[macros::ts_export]
 pub async fn is_allowed_server(host: &str) -> Result<bool, sea_orm::DbErr> {
     let meta = crate::config::local_server_info().await?;
 
diff --git a/packages/backend-rs/src/misc/check_word_mute.rs b/packages/backend-rs/src/misc/check_word_mute.rs
index 0faf946c8d..fdf4e1a8a0 100644
--- a/packages/backend-rs/src/misc/check_word_mute.rs
+++ b/packages/backend-rs/src/misc/check_word_mute.rs
@@ -3,7 +3,7 @@ use once_cell::sync::Lazy;
 use regex::Regex;
 use sea_orm::DbErr;
 
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct PartialNoteToCheckWordMute {
     pub file_ids: Vec<String>,
     pub text: Option<String>,
@@ -49,7 +49,7 @@ fn check_word_mute_impl(
 /// * `note` : [PartialNoteToCheckWordMute] object
 /// * `muted_words` : list of muted keyword lists (each array item is a space-separated keyword list that represents an AND condition)
 /// * `muted_patterns` : list of JavaScript-style (e.g., `/foo/i`) regular expressions
-#[crate::export]
+#[macros::export]
 pub async fn check_word_mute(
     note: PartialNoteToCheckWordMute,
     muted_words: &[String],
diff --git a/packages/backend-rs/src/misc/convert_host.rs b/packages/backend-rs/src/misc/convert_host.rs
index 2850d4cb29..0de4d1912d 100644
--- a/packages/backend-rs/src/misc/convert_host.rs
+++ b/packages/backend-rs/src/misc/convert_host.rs
@@ -13,7 +13,7 @@ pub enum Error {
     NoHostname,
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result<String, Error> {
     Ok(match host {
         Some(host) => format!("{}@{}", username, to_puny(host)?),
@@ -21,7 +21,7 @@ pub fn get_full_ap_account(username: &str, host: Option<&str>) -> Result<String,
     })
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn is_self_host(host: Option<&str>) -> Result<bool, Error> {
     Ok(match host {
         Some(host) => extract_host(&crate::config::CONFIG.url)? == to_puny(host)?,
@@ -29,12 +29,12 @@ pub fn is_self_host(host: Option<&str>) -> Result<bool, Error> {
     })
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn is_same_origin(uri: &str) -> Result<bool, Error> {
     Ok(url::Url::parse(uri)?.origin().ascii_serialization() == crate::config::CONFIG.url)
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn extract_host(uri: &str) -> Result<String, Error> {
     url::Url::parse(uri)?
         .host_str()
@@ -42,7 +42,7 @@ pub fn extract_host(uri: &str) -> Result<String, Error> {
         .and_then(|v| Ok(to_puny(v)?))
 }
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn to_puny(host: &str) -> Result<String, idna::Errors> {
     idna::domain_to_ascii(host)
 }
diff --git a/packages/backend-rs/src/misc/emoji.rs b/packages/backend-rs/src/misc/emoji.rs
index a19fc6229d..47c2e0debe 100644
--- a/packages/backend-rs/src/misc/emoji.rs
+++ b/packages/backend-rs/src/misc/emoji.rs
@@ -1,6 +1,6 @@
 //! This module is used in the TypeScript backend only.
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub fn is_unicode_emoji(s: &str) -> bool {
     emojis::get(s).is_some()
 }
diff --git a/packages/backend-rs/src/misc/escape_sql.rs b/packages/backend-rs/src/misc/escape_sql.rs
index 5ff7e51729..747ceb51c7 100644
--- a/packages/backend-rs/src/misc/escape_sql.rs
+++ b/packages/backend-rs/src/misc/escape_sql.rs
@@ -1,11 +1,11 @@
 /// Escapes `%` and `\` in the given string.
-#[crate::export]
+#[macros::export]
 pub fn sql_like_escape(src: &str) -> String {
     src.replace('%', r"\%").replace('_', r"\_")
 }
 
 /// Returns `true` if `src` does not contain suspicious characters like `%`.
-#[crate::export]
+#[macros::export]
 pub fn safe_for_sql(src: &str) -> bool {
     !src.contains([
         '\0', '\x08', '\x09', '\x1a', '\n', '\r', '"', '\'', '\\', '%',
diff --git a/packages/backend-rs/src/misc/format_milliseconds.rs b/packages/backend-rs/src/misc/format_milliseconds.rs
index b30af15847..148e5791b5 100644
--- a/packages/backend-rs/src/misc/format_milliseconds.rs
+++ b/packages/backend-rs/src/misc/format_milliseconds.rs
@@ -1,5 +1,5 @@
 /// Converts milliseconds to a human readable string.
-#[crate::export]
+#[macros::export]
 pub fn format_milliseconds(milliseconds: u32) -> String {
     let mut seconds = milliseconds / 1000;
     let mut minutes = seconds / 60;
diff --git a/packages/backend-rs/src/misc/get_image_size.rs b/packages/backend-rs/src/misc/get_image_size.rs
index 58f9783fe0..1964c9cbd9 100644
--- a/packages/backend-rs/src/misc/get_image_size.rs
+++ b/packages/backend-rs/src/misc/get_image_size.rs
@@ -44,13 +44,13 @@ const BROWSER_SAFE_IMAGE_TYPES: [ImageFormat; 8] = [
 static MTX_GUARD: Mutex<()> = Mutex::const_new(());
 
 #[cfg_attr(test, derive(Debug, PartialEq))]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct ImageSize {
     pub width: u32,
     pub height: u32,
 }
 
-#[crate::export]
+#[macros::export]
 pub async fn get_image_size_from_url(url: &str) -> Result<ImageSize, Error> {
     let attempted: bool;
 
diff --git a/packages/backend-rs/src/misc/is_quote.rs b/packages/backend-rs/src/misc/is_quote.rs
index 42e792f956..3ee15a5873 100644
--- a/packages/backend-rs/src/misc/is_quote.rs
+++ b/packages/backend-rs/src/misc/is_quote.rs
@@ -4,7 +4,7 @@ use crate::model::entity::note;
 // https://github.com/napi-rs/napi-rs/issues/2060
 type Note = note::Model;
 
-#[crate::export]
+#[macros::export]
 pub fn is_quote(note: Note) -> bool {
     note.renote_id.is_some() && (note.text.is_some() || note.has_poll || !note.file_ids.is_empty())
 }
diff --git a/packages/backend-rs/src/misc/is_safe_url.rs b/packages/backend-rs/src/misc/is_safe_url.rs
index 1e5c5244ce..603710310f 100644
--- a/packages/backend-rs/src/misc/is_safe_url.rs
+++ b/packages/backend-rs/src/misc/is_safe_url.rs
@@ -1,4 +1,4 @@
-#[crate::export]
+#[macros::export]
 pub fn is_safe_url(url: &str) -> bool {
     if let Ok(url) = url.parse::<url::Url>() {
         if url.host_str().unwrap_or_default() == "unix"
diff --git a/packages/backend-rs/src/misc/latest_version.rs b/packages/backend-rs/src/misc/latest_version.rs
index 58f50d0b0b..1f330f4d2e 100644
--- a/packages/backend-rs/src/misc/latest_version.rs
+++ b/packages/backend-rs/src/misc/latest_version.rs
@@ -46,7 +46,7 @@ async fn get_latest_version() -> Result<String, Error> {
 }
 
 /// Returns the latest Firefish version.
-#[crate::export]
+#[macros::export]
 pub async fn latest_version() -> Result<String, Error> {
     let version: Option<String> =
         cache::get_one(cache::Category::FetchUrl, UPSTREAM_PACKAGE_JSON_URL).await?;
diff --git a/packages/backend-rs/src/misc/mastodon_id.rs b/packages/backend-rs/src/misc/mastodon_id.rs
index bb837c42f7..65fbd4d057 100644
--- a/packages/backend-rs/src/misc/mastodon_id.rs
+++ b/packages/backend-rs/src/misc/mastodon_id.rs
@@ -1,10 +1,10 @@
-#[crate::export]
+#[macros::export]
 pub fn to_mastodon_id(firefish_id: &str) -> Option<String> {
     let decoded: [u8; 16] = basen::BASE36.decode_var_len(firefish_id)?;
     Some(basen::BASE10.encode_var_len(&decoded))
 }
 
-#[crate::export]
+#[macros::export]
 pub fn from_mastodon_id(mastodon_id: &str) -> Option<String> {
     let decoded: [u8; 16] = basen::BASE10.decode_var_len(mastodon_id)?;
     Some(basen::BASE36.encode_var_len(&decoded))
diff --git a/packages/backend-rs/src/misc/note/summarize.rs b/packages/backend-rs/src/misc/note/summarize.rs
index 83bbb0246d..9649fc9345 100644
--- a/packages/backend-rs/src/misc/note/summarize.rs
+++ b/packages/backend-rs/src/misc/note/summarize.rs
@@ -1,4 +1,4 @@
-#[crate::export(js_name = "getNoteSummary")]
+#[macros::export(js_name = "getNoteSummary")]
 pub fn summarize_impl(
     file_ids: &[String],
     text: Option<String>,
diff --git a/packages/backend-rs/src/misc/nyaify.rs b/packages/backend-rs/src/misc/nyaify.rs
index 5e343f5838..c58795f798 100644
--- a/packages/backend-rs/src/misc/nyaify.rs
+++ b/packages/backend-rs/src/misc/nyaify.rs
@@ -20,7 +20,7 @@ use regex::{Captures, Regex};
 /// # use backend_rs::misc::nyaify::nyaify;
 /// assert_eq!(nyaify("I'll take a nap.", Some("en")), "I'll take a nyap.");
 /// ```
-#[crate::export]
+#[macros::export]
 pub fn nyaify(text: &str, lang: Option<&str>) -> String {
     let mut to_return = text.to_owned();
 
diff --git a/packages/backend-rs/src/misc/password.rs b/packages/backend-rs/src/misc/password.rs
index 507313c778..bc2025f275 100644
--- a/packages/backend-rs/src/misc/password.rs
+++ b/packages/backend-rs/src/misc/password.rs
@@ -7,7 +7,7 @@ use argon2::{
 };
 
 /// Hashes the given password using [argon2] algorithm.
-#[crate::export]
+#[macros::export]
 pub fn hash_password(password: &str) -> Result<String, password_hash::errors::Error> {
     let salt = SaltString::generate(&mut OsRng);
     Ok(Argon2::default()
@@ -26,7 +26,7 @@ pub enum Error {
 }
 
 /// Checks whether the given password and hash match.
-#[crate::export]
+#[macros::export]
 pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
     if is_old_password_algorithm(hash) {
         Ok(bcrypt::verify(password, hash)?)
@@ -40,7 +40,7 @@ pub fn verify_password(password: &str, hash: &str) -> Result<bool, Error> {
 
 /// Returns whether the [bcrypt] algorithm is used for the password hash.
 #[inline]
-#[crate::export]
+#[macros::export]
 pub fn is_old_password_algorithm(hash: &str) -> bool {
     // bcrypt hashes start with $2[ab]$
     hash.starts_with("$2")
diff --git a/packages/backend-rs/src/misc/reaction.rs b/packages/backend-rs/src/misc/reaction.rs
index 662e6ac766..01441b90a3 100644
--- a/packages/backend-rs/src/misc/reaction.rs
+++ b/packages/backend-rs/src/misc/reaction.rs
@@ -5,14 +5,14 @@ use sea_orm::prelude::*;
 use std::collections::HashMap;
 
 #[cfg_attr(test, derive(PartialEq, Debug))]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct DecodedReaction {
     pub reaction: String,
     pub name: Option<String>,
     pub host: Option<String>,
 }
 
-#[crate::export]
+#[macros::export]
 pub fn decode_reaction(reaction: &str) -> DecodedReaction {
     // Misskey allows you to include "+" and "-" in emoji shortcodes
     // MFM spec: https://github.com/misskey-dev/mfm.js/blob/6aaf68089023c6adebe44123eebbc4dcd75955e0/docs/syntax.md?plain=1#L583
@@ -38,7 +38,7 @@ pub fn decode_reaction(reaction: &str) -> DecodedReaction {
     }
 }
 
-#[crate::export]
+#[macros::export]
 pub fn count_reactions(reactions: &HashMap<String, u32>) -> HashMap<String, u32> {
     let mut res = HashMap::<String, u32>::new();
 
@@ -62,7 +62,7 @@ pub enum Error {
     Db(#[from] DbErr),
 }
 
-#[crate::export]
+#[macros::export]
 pub async fn to_db_reaction(reaction: Option<&str>, host: Option<&str>) -> Result<String, Error> {
     if let Some(reaction) = reaction {
         // FIXME: Is it okay to do this only here?
diff --git a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
index a36c34c664..725f6ac481 100644
--- a/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
+++ b/packages/backend-rs/src/misc/remove_old_attestation_challenges.rs
@@ -5,7 +5,7 @@ use chrono::{Duration, Utc};
 use sea_orm::prelude::*;
 
 /// Delete all entries in the [attestation_challenge] table created at more than 5 minutes ago
-#[crate::export]
+#[macros::export]
 pub async fn remove_old_attestation_challenges() -> Result<(), DbErr> {
     let res = attestation_challenge::Entity::delete_many()
         .filter(attestation_challenge::Column::CreatedAt.lt(Utc::now() - Duration::minutes(5)))
diff --git a/packages/backend-rs/src/misc/system_info.rs b/packages/backend-rs/src/misc/system_info.rs
index 2c4f6cdcf2..4b31ceaaee 100644
--- a/packages/backend-rs/src/misc/system_info.rs
+++ b/packages/backend-rs/src/misc/system_info.rs
@@ -5,14 +5,14 @@ use sysinfo::{Disks, MemoryRefreshKind};
 
 // TODO: i64 -> u64 (we can't export u64 to Node.js)
 
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Cpu {
     pub model: String,
     // TODO: u16 -> usize (we can't export usize to Node.js)
     pub cores: u16,
 }
 
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Memory {
     /// Total memory amount in bytes
     pub total: i64,
@@ -22,7 +22,7 @@ pub struct Memory {
     pub available: i64,
 }
 
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct Storage {
     /// Total storage space in bytes
     pub total: i64,
@@ -30,7 +30,7 @@ pub struct Storage {
     pub used: i64,
 }
 
-#[crate::export]
+#[macros::export]
 pub fn cpu_info() -> Result<Cpu, SysinfoPoisonError> {
     let system_info = system_info().lock()?;
 
@@ -46,7 +46,7 @@ pub fn cpu_info() -> Result<Cpu, SysinfoPoisonError> {
     })
 }
 
-#[crate::export]
+#[macros::export]
 pub fn cpu_usage() -> Result<f32, SysinfoPoisonError> {
     let mut system_info = system_info().lock()?;
     system_info.refresh_cpu_usage();
@@ -57,7 +57,7 @@ pub fn cpu_usage() -> Result<f32, SysinfoPoisonError> {
     Ok(total_cpu_usage / (cpu_threads as f32))
 }
 
-#[crate::export]
+#[macros::export]
 pub fn memory_usage() -> Result<Memory, SysinfoPoisonError> {
     let mut system_info = system_info().lock()?;
 
@@ -70,7 +70,7 @@ pub fn memory_usage() -> Result<Memory, SysinfoPoisonError> {
     })
 }
 
-#[crate::export]
+#[macros::export]
 pub fn storage_usage() -> Option<Storage> {
     // Get the first disk that is actualy used (has available space & has at least 1 GB total space).
     let disks = Disks::new_with_refreshed_list();
diff --git a/packages/backend-rs/src/service/antenna/process_new_note.rs b/packages/backend-rs/src/service/antenna/process_new_note.rs
index 7e86fbd31f..13fc7fdcbb 100644
--- a/packages/backend-rs/src/service/antenna/process_new_note.rs
+++ b/packages/backend-rs/src/service/antenna/process_new_note.rs
@@ -37,7 +37,7 @@ pub enum Error {
 // https://github.com/napi-rs/napi-rs/issues/2060
 type Note = note::Model;
 
-#[crate::export]
+#[macros::export]
 pub async fn update_antennas_on_new_note(
     note: &Note,
     note_author: &Acct,
diff --git a/packages/backend-rs/src/service/antenna/update.rs b/packages/backend-rs/src/service/antenna/update.rs
index 350b9ba9db..169e574379 100644
--- a/packages/backend-rs/src/service/antenna/update.rs
+++ b/packages/backend-rs/src/service/antenna/update.rs
@@ -1,6 +1,6 @@
 //! This module is (currently) used in the TypeScript backend only.
 
-#[crate::ts_export]
+#[macros::ts_export]
 pub async fn update_antenna_cache() -> Result<(), sea_orm::DbErr> {
     super::cache::update().await?;
     Ok(())
diff --git a/packages/backend-rs/src/service/note/watch.rs b/packages/backend-rs/src/service/note/watch.rs
index d63ce00e85..144f4e2360 100644
--- a/packages/backend-rs/src/service/note/watch.rs
+++ b/packages/backend-rs/src/service/note/watch.rs
@@ -1,7 +1,7 @@
 use crate::{database::db_conn, model::entity::note_watching, util::id::gen_id_at};
 use sea_orm::{prelude::*, ActiveValue};
 
-#[crate::export]
+#[macros::export]
 pub async fn watch_note(
     watcher_id: &str,
     note_author_id: &str,
@@ -24,7 +24,7 @@ pub async fn watch_note(
     Ok(())
 }
 
-#[crate::export]
+#[macros::export]
 pub async fn unwatch_note(watcher_id: &str, note_id: &str) -> Result<(), DbErr> {
     let db = db_conn().await?;
 
diff --git a/packages/backend-rs/src/service/push_notification.rs b/packages/backend-rs/src/service/push_notification.rs
index 8bde9987d3..29de12fca5 100644
--- a/packages/backend-rs/src/service/push_notification.rs
+++ b/packages/backend-rs/src/service/push_notification.rs
@@ -31,7 +31,7 @@ fn get_client() -> Result<IsahcWebPushClient, Error> {
         .cloned()?)
 }
 
-#[crate::export]
+#[macros::export]
 pub enum PushNotificationKind {
     Generic,
     Chat,
@@ -129,7 +129,7 @@ async fn handle_web_push_failure(
     Ok(())
 }
 
-#[crate::export]
+#[macros::export]
 pub async fn send_push_notification(
     receiver_user_id: &str,
     kind: PushNotificationKind,
diff --git a/packages/backend-rs/src/service/stream.rs b/packages/backend-rs/src/service/stream.rs
index 36d7f841fe..412ed3a3a7 100644
--- a/packages/backend-rs/src/service/stream.rs
+++ b/packages/backend-rs/src/service/stream.rs
@@ -54,7 +54,7 @@ pub enum Stream {
     },
 }
 
-#[crate::export]
+#[macros::export]
 pub enum ChatEvent {
     Message,
     Read,
diff --git a/packages/backend-rs/src/service/stream/channel.rs b/packages/backend-rs/src/service/stream/channel.rs
index 028aab69c7..b1bb865fa1 100644
--- a/packages/backend-rs/src/service/stream/channel.rs
+++ b/packages/backend-rs/src/service/stream/channel.rs
@@ -1,6 +1,6 @@
 use crate::service::stream::{publish_to_stream, Error, Stream};
 
-#[crate::export(js_name = "publishToChannelStream")]
+#[macros::export(js_name = "publishToChannelStream")]
 pub async fn publish(channel_id: String, user_id: String) -> Result<(), Error> {
     publish_to_stream(
         &Stream::Channel { channel_id },
diff --git a/packages/backend-rs/src/service/stream/chat.rs b/packages/backend-rs/src/service/stream/chat.rs
index 2f60ff4015..88532c7e86 100644
--- a/packages/backend-rs/src/service/stream/chat.rs
+++ b/packages/backend-rs/src/service/stream/chat.rs
@@ -3,7 +3,7 @@ use crate::service::stream::{publish_to_stream, ChatEvent, Error, Stream};
 // We want to merge `kind` and `object` into a single enum
 // https://github.com/napi-rs/napi-rs/issues/2036
 
-#[crate::export(js_name = "publishToChatStream")]
+#[macros::export(js_name = "publishToChatStream")]
 pub async fn publish(
     sender_user_id: String,
     receiver_user_id: String,
diff --git a/packages/backend-rs/src/service/stream/chat_index.rs b/packages/backend-rs/src/service/stream/chat_index.rs
index e686db8149..25b2913f2c 100644
--- a/packages/backend-rs/src/service/stream/chat_index.rs
+++ b/packages/backend-rs/src/service/stream/chat_index.rs
@@ -1,6 +1,6 @@
 use crate::service::stream::{publish_to_stream, Error, Stream};
 
-#[crate::export]
+#[macros::export]
 pub enum ChatIndexEvent {
     Message,
     Read,
@@ -9,7 +9,7 @@ pub enum ChatIndexEvent {
 // We want to merge `kind` and `object` into a single enum
 // https://github.com/napi-rs/napi-rs/issues/2036
 
-#[crate::export(js_name = "publishToChatIndexStream")]
+#[macros::export(js_name = "publishToChatIndexStream")]
 pub async fn publish(
     user_id: String,
     kind: ChatIndexEvent,
diff --git a/packages/backend-rs/src/service/stream/custom_emoji.rs b/packages/backend-rs/src/service/stream/custom_emoji.rs
index 164d89b957..890d550d6a 100644
--- a/packages/backend-rs/src/service/stream/custom_emoji.rs
+++ b/packages/backend-rs/src/service/stream/custom_emoji.rs
@@ -4,7 +4,7 @@ use serde::Serialize;
 // TODO: define schema type in other place
 #[derive(Serialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct PackedEmoji {
     pub id: String,
     pub aliases: Vec<String>,
@@ -17,7 +17,7 @@ pub struct PackedEmoji {
     pub height: Option<i32>,
 }
 
-#[crate::export(js_name = "publishToBroadcastStream")]
+#[macros::export(js_name = "publishToBroadcastStream")]
 pub async fn publish(emoji: &PackedEmoji) -> Result<(), Error> {
     publish_to_stream(
         &Stream::CustomEmoji,
diff --git a/packages/backend-rs/src/service/stream/drive.rs b/packages/backend-rs/src/service/stream/drive.rs
index c3b49d0db3..21ddf1f4b2 100644
--- a/packages/backend-rs/src/service/stream/drive.rs
+++ b/packages/backend-rs/src/service/stream/drive.rs
@@ -1,13 +1,13 @@
 use crate::service::stream::{publish_to_stream, Error, Stream};
 
-#[crate::export]
+#[macros::export]
 pub enum DriveFileEvent {
     Create,
     Update,
     Delete,
 }
 
-#[crate::export]
+#[macros::export]
 pub enum DriveFolderEvent {
     Create,
     Update,
@@ -17,7 +17,7 @@ pub enum DriveFolderEvent {
 // We want to merge `kind` and `object` into a single enum and merge the 2 functions
 // https://github.com/napi-rs/napi-rs/issues/2036
 
-#[crate::export(js_name = "publishToDriveFileStream")]
+#[macros::export(js_name = "publishToDriveFileStream")]
 pub async fn publish_file(
     user_id: String,
     kind: DriveFileEvent,
@@ -37,7 +37,7 @@ pub async fn publish_file(
     .await
 }
 
-#[crate::export(js_name = "publishToDriveFolderStream")]
+#[macros::export(js_name = "publishToDriveFolderStream")]
 pub async fn publish_folder(
     user_id: String,
     kind: DriveFolderEvent,
diff --git a/packages/backend-rs/src/service/stream/group_chat.rs b/packages/backend-rs/src/service/stream/group_chat.rs
index 49ebc299d2..005a523f1c 100644
--- a/packages/backend-rs/src/service/stream/group_chat.rs
+++ b/packages/backend-rs/src/service/stream/group_chat.rs
@@ -3,7 +3,7 @@ use crate::service::stream::{publish_to_stream, ChatEvent, Error, Stream};
 // We want to merge `kind` and `object` into a single enum
 // https://github.com/napi-rs/napi-rs/issues/2036
 
-#[crate::export(js_name = "publishToGroupChatStream")]
+#[macros::export(js_name = "publishToGroupChatStream")]
 pub async fn publish(
     group_id: String,
     kind: ChatEvent,
diff --git a/packages/backend-rs/src/service/stream/moderation.rs b/packages/backend-rs/src/service/stream/moderation.rs
index 218441e15f..3ac261ff50 100644
--- a/packages/backend-rs/src/service/stream/moderation.rs
+++ b/packages/backend-rs/src/service/stream/moderation.rs
@@ -3,7 +3,7 @@ use serde::Serialize;
 
 #[derive(Serialize)]
 #[serde(rename_all = "camelCase")]
-#[crate::export(object)]
+#[macros::export(object)]
 pub struct AbuseUserReportLike {
     pub id: String,
     pub target_user_id: String,
@@ -11,7 +11,7 @@ pub struct AbuseUserReportLike {
     pub comment: String,
 }
 
-#[crate::export(js_name = "publishToModerationStream")]
+#[macros::export(js_name = "publishToModerationStream")]
 pub async fn publish(moderator_id: String, report: &AbuseUserReportLike) -> Result<(), Error> {
     publish_to_stream(
         &Stream::Moderation { moderator_id },
diff --git a/packages/backend-rs/src/service/stream/notes.rs b/packages/backend-rs/src/service/stream/notes.rs
index 6c2336e347..8832bc03e3 100644
--- a/packages/backend-rs/src/service/stream/notes.rs
+++ b/packages/backend-rs/src/service/stream/notes.rs
@@ -7,7 +7,7 @@ use crate::{
 // https://github.com/napi-rs/napi-rs/issues/2060
 type Note = note::Model;
 
-#[crate::export(js_name = "publishToNotesStream")]
+#[macros::export(js_name = "publishToNotesStream")]
 pub async fn publish(note: &Note) -> Result<(), Error> {
     publish_to_stream(&Stream::Notes, None, Some(serde_json::to_string(note)?)).await
 }
diff --git a/packages/backend-rs/src/util/id.rs b/packages/backend-rs/src/util/id.rs
index f61454d9c3..2d384db78c 100644
--- a/packages/backend-rs/src/util/id.rs
+++ b/packages/backend-rs/src/util/id.rs
@@ -52,7 +52,7 @@ pub struct InvalidIdError {
     id: String,
 }
 
-#[crate::export]
+#[macros::export]
 pub fn get_timestamp(id: &str) -> Result<i64, InvalidIdError> {
     let n: Option<u64> = BASE36.decode_var_len(&id[0..8]);
     if let Some(n) = n {
@@ -68,13 +68,13 @@ pub fn get_timestamp(id: &str) -> Result<i64, InvalidIdError> {
 /// in the same millisecond to reach 50% chance of collision.
 ///
 /// Ref: <https://github.com/paralleldrive/cuid2#parameterized-length>
-#[crate::export]
+#[macros::export]
 pub fn gen_id() -> String {
     create_id(&Utc::now().naive_utc())
 }
 
 /// Generate an ID using a specific datetime
-#[crate::export]
+#[macros::export]
 pub fn gen_id_at(date: DateTime<Utc>) -> String {
     create_id(&date.naive_utc())
 }
diff --git a/packages/backend-rs/src/util/random.rs b/packages/backend-rs/src/util/random.rs
index 738926576d..0c35657a47 100644
--- a/packages/backend-rs/src/util/random.rs
+++ b/packages/backend-rs/src/util/random.rs
@@ -3,7 +3,7 @@
 use rand::{distributions::Alphanumeric, thread_rng, Rng};
 
 /// Generates a random string based on [thread_rng] and [Alphanumeric].
-#[crate::export]
+#[macros::export]
 pub fn generate_secure_random_string(length: u16) -> String {
     thread_rng()
         .sample_iter(Alphanumeric)
@@ -12,7 +12,7 @@ pub fn generate_secure_random_string(length: u16) -> String {
         .collect()
 }
 
-#[crate::export]
+#[macros::export]
 pub fn generate_user_token() -> String {
     generate_secure_random_string(16)
 }