bundler: init working version

This commit is contained in:
nym21
2025-06-14 20:17:49 +02:00
parent 65685c23e1
commit e9f362cc87
58 changed files with 5049 additions and 2239 deletions
+3
View File
@@ -10,6 +10,7 @@ version.workspace = true
[features]
full = [
"bundler",
"core",
"computer",
"exit",
@@ -23,6 +24,7 @@ full = [
"store",
"vec",
]
bundler = ["brk_bundler"]
core = ["brk_core"]
computer = ["brk_computer"]
exit = ["brk_exit"]
@@ -37,6 +39,7 @@ store = ["brk_store"]
vec = ["brk_vec"]
[dependencies]
brk_bundler = { workspace = true, optional = true }
brk_cli = { workspace = true }
brk_core = { workspace = true, optional = true }
brk_computer = { workspace = true, optional = true }
+1
View File
@@ -0,0 +1 @@
fn main() {}
+7
View File
@@ -1,5 +1,12 @@
#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
#[cfg(feature = "bundler")]
#[doc(inline)]
pub use brk_bundle as bundle;
#[doc(inline)]
pub use brk_cli as cli;
#[cfg(feature = "core")]
#[doc(inline)]
pub use brk_core as core;
+16
View File
@@ -0,0 +1,16 @@
[package]
name = "brk_bundler"
description = "A crate that bundles the selected website for the server"
version.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
[dependencies]
log = { workspace = true }
minify-html-onepass = "0.16.4"
notify = "8.0.0"
rolldown = { git = "https://github.com/rolldown/rolldown", branch = "main" }
sugar_path = { version = "1.2.0", features = ["cached_current_dir"] }
tokio = { workspace = true }
+136
View File
@@ -0,0 +1,136 @@
use std::{fs, io, path::Path, sync::Arc};
use log::error;
use minify_html_onepass::Cfg;
use notify::{EventKind, RecursiveMode, Watcher};
use rolldown::{Bundler, BundlerOptions, RawMinifyOptions, SourceMapType};
use sugar_path::SugarPath;
use tokio::sync::Mutex;
const VERSION: &str = env!("CARGO_PKG_VERSION");
pub async fn bundle(websites_path: &Path, source_folder: &str, watch: bool) -> io::Result<()> {
let source_path = websites_path.join(source_folder);
let dist_path = websites_path.join("dist");
let _ = fs::remove_dir_all(&dist_path);
copy_dir_all(&source_path, &dist_path)?;
let source_scripts = format!("./{source_folder}/scripts");
let source_entry = format!("{source_scripts}/entry.js");
let absolute_websites_path = websites_path.absolutize();
let mut bundler = Bundler::new(BundlerOptions {
input: Some(vec![source_entry.into()]),
dir: Some("./dist/scripts".to_string()),
cwd: Some(absolute_websites_path),
minify: Some(RawMinifyOptions::Bool(true)),
sourcemap: Some(SourceMapType::File),
..Default::default()
});
bundler.write().await.unwrap();
let absolute_source_path = source_path.absolutize();
let absolute_source_path_clone = absolute_source_path.clone();
let absolute_dist_path = dist_path.absolutize();
let absolute_dist_path_clone = absolute_dist_path.clone();
let absolute_source_scripts_path = websites_path.join(source_scripts).absolutize();
let absolute_source_index_path = source_path.join("index.html").absolutize();
let absolute_source_index_path_clone = absolute_source_index_path.clone();
let absolute_dist_index_path = dist_path.join("index.html").absolutize();
let absolute_source_sw_path = source_path.join("service-worker.js").absolutize();
let absolute_source_sw_path_clone = absolute_source_sw_path.clone();
let absolute_dist_sw_path = dist_path.join("service-worker.js").absolutize();
let write_index = move || {
let mut contents = fs::read_to_string(&absolute_source_index_path).unwrap();
if let Ok(entry) = fs::read_to_string(absolute_dist_path_clone.join("scripts/entry.js")) {
let start = entry.find("main").unwrap();
let end = entry.find(".js").unwrap();
let main_hashed = &entry[start..end];
contents = contents.replace("/scripts/main.js", &format!("/scripts/{main_hashed}.js"));
}
if let Ok(contents) = minify_html_onepass::in_place_str(
contents.as_mut_str(),
&Cfg {
minify_js: false,
minify_css: false,
},
) {
let _ = fs::write(&absolute_dist_index_path, contents);
}
};
let write_sw = move || {
let contents = fs::read_to_string(&absolute_source_sw_path)
.unwrap()
.replace("__VERSION__", &format!("v{VERSION}"));
let _ = fs::write(&absolute_dist_sw_path, contents);
};
write_index();
write_sw();
if !watch {
return Ok(());
}
tokio::spawn(async move {
let mut watcher = notify::recommended_watcher(
move |res: Result<notify::Event, notify::Error>| match res {
Ok(event) => match event.kind {
EventKind::Create(_) => event.paths,
EventKind::Modify(_) => event.paths,
_ => vec![],
}
.into_iter()
.filter(|path| path.starts_with(&absolute_source_path))
.filter(|path| !path.starts_with(&absolute_source_scripts_path))
.for_each(|source_path| {
let suffix = source_path.strip_prefix(&absolute_source_path).unwrap();
let dist_path = absolute_dist_path.join(suffix);
if source_path == absolute_source_index_path_clone {
write_index();
} else if source_path == absolute_source_sw_path_clone {
write_sw();
} else {
let _ = fs::copy(&source_path, &dist_path);
}
}),
Err(e) => error!("watch error: {:?}", e),
},
)
.unwrap();
if watch {
watcher
.watch(&absolute_source_path_clone, RecursiveMode::Recursive)
.unwrap();
let watcher =
rolldown::Watcher::new(vec![Arc::new(Mutex::new(bundler))], None).unwrap();
watcher.start().await;
}
});
Ok(())
}
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
fs::create_dir_all(&dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
+15 -14
View File
@@ -63,8 +63,9 @@ pub fn run(config: RunConfig) -> color_eyre::Result<()> {
let server = Server::new(served_indexer, served_computer, config.website())?;
let watch = config.watch();
let opt = Some(tokio::spawn(async move {
server.serve().await.unwrap();
server.serve(watch).await.unwrap();
}));
sleep(Duration::from_secs(1));
@@ -178,6 +179,11 @@ pub struct RunConfig {
#[arg(long, value_name = "SECONDS")]
delay: Option<u64>,
/// DEV: Activate to watch the selected website's folder for changes, default: false, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "BOOL")]
watch: Option<bool>,
/// DEV: Activate checking address hashes for collisions when indexing, default: false, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "BOOL")]
@@ -255,6 +261,10 @@ impl RunConfig {
config_saved.check_collisions = Some(check_collisions);
}
if let Some(watch) = config_args.watch.take() {
config_saved.watch = Some(watch);
}
if config_args != RunConfig::default() {
dbg!(config_args);
panic!("Didn't consume the full config")
@@ -267,19 +277,6 @@ impl RunConfig {
config.write(&path)?;
// info!("Configuration {{");
// info!(" bitcoindir: {:?}", config.bitcoindir);
// info!(" brkdir: {:?}", config.brkdir);
// info!(" services: {:?}", config.services);
// info!(" website: {:?}", config.website);
// info!(" rpcconnect: {:?}", config.rpcconnect);
// info!(" rpcport: {:?}", config.rpcport);
// info!(" rpccookiefile: {:?}", config.rpccookiefile);
// info!(" rpcuser: {:?}", config.rpcuser);
// info!(" rpcpassword: {:?}", config.rpcpassword);
// info!(" delay: {:?}", config.delay);
// info!("}}");
Ok(config)
}
@@ -448,6 +445,10 @@ impl RunConfig {
pub fn check_collisions(&self) -> bool {
self.check_collisions.is_some_and(|b| b)
}
pub fn watch(&self) -> bool {
self.watch.is_some_and(|b| b)
}
}
#[derive(
+6 -2
View File
@@ -25,7 +25,7 @@ pub fn init(path: Option<&Path>) {
.unwrap()
});
Builder::from_env(Env::default().default_filter_or("info,fjall=off,lsm_tree=off"))
Builder::from_env(Env::default().default_filter_or("info,fjall=off,lsm_tree=off,rolldown=off"))
.format(move |buf, record| {
let date_time = Timestamp::now()
.to_zoned(tz::TimeZone::system())
@@ -80,5 +80,9 @@ fn write(
args: impl Display,
) -> Result<(), std::io::Error> {
writeln!(buf, "{} {} {} {}", date_time, dash, level, args)
// writeln!(buf, "{} {} {} {} {}", date_time, _target, level, dash, args)
// writeln!(
// buf,
// "{} {} {} {} {}",
// date_time, _target, level, dash, args
// )
}
+2 -1
View File
@@ -10,6 +10,7 @@ repository.workspace = true
[dependencies]
axum = { workspace = true }
bitcoincore-rpc = { workspace = true }
brk_bundler = { workspace = true }
brk_computer = { workspace = true }
brk_exit = { workspace = true }
brk_core = { workspace = true }
@@ -25,7 +26,7 @@ color-eyre = { workspace = true }
jiff = { workspace = true }
log = { workspace = true }
minreq = { workspace = true }
oxc = { version = "0.72.3", features = ["codegen", "minifier"] }
oxc = { version = "0.73.0", features = ["codegen", "minifier"] }
serde = { workspace = true }
tokio = { workspace = true }
tower-http = { version = "0.6.6", features = ["compression-full", "trace"] }
+1 -1
View File
@@ -51,7 +51,7 @@ pub fn main() -> color_eyre::Result<()> {
let server = Server::new(served_indexer, served_computer, Website::Default)?;
let server = tokio::spawn(async move {
server.serve().await.unwrap();
server.serve(true).await.unwrap();
});
if process {
+4 -8
View File
@@ -32,16 +32,12 @@ fn any_handler(
app_state: AppState,
path: Option<extract::Path<String>>,
) -> Response {
let website_path = app_state
.websites_path
.as_ref()
.expect("Should never reach here is websites_path is None")
.join(app_state.website.to_folder_name());
let dist_path = app_state.dist_path();
if let Some(path) = path.as_ref() {
let path = path.0.replace("..", "").replace("\\", "");
let mut path = website_path.join(&path);
let mut path = dist_path.join(&path);
if !path.exists() || path.is_dir() {
if path.extension().is_some() {
@@ -55,13 +51,13 @@ fn any_handler(
return response;
} else {
path = website_path.join("index.html");
path = dist_path.join("index.html");
}
}
path_to_response(&headers, &path)
} else {
path_to_response(&headers, &website_path.join("index.html"))
path_to_response(&headers, &dist_path.join("index.html"))
}
}
+15 -1
View File
@@ -19,6 +19,7 @@ use axum::{
routing::get,
serve,
};
use brk_bundler::bundle;
use brk_computer::Computer;
use brk_core::dot_brk_path;
use brk_indexer::Indexer;
@@ -45,6 +46,15 @@ pub struct AppState {
websites_path: Option<PathBuf>,
}
impl AppState {
pub fn dist_path(&self) -> PathBuf {
self.websites_path
.as_ref()
.expect("Should never reach here is websites_path is None")
.join("dist")
}
}
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
const DEV_PATH: &str = "../..";
@@ -103,9 +113,13 @@ impl Server {
}))
}
pub async fn serve(self) -> color_eyre::Result<()> {
pub async fn serve(self, watch: bool) -> color_eyre::Result<()> {
let state = self.0;
if let Some(websites_path) = state.websites_path.clone() {
bundle(&websites_path, state.website.to_folder_name(), watch).await?;
}
let compression_layer = CompressionLayer::new()
.br(true)
.deflate(true)
+1 -1
View File
@@ -167,7 +167,7 @@ impl HeaderMapExtended for HeaderMap {
fn insert_content_type(&mut self, path: &Path) {
match path.extension().unwrap().to_str().unwrap() {
"js" => self.insert_content_type_application_javascript(),
"json" => self.insert_content_type_application_json(),
"json" | "map" => self.insert_content_type_application_json(),
"html" => self.insert_content_type_text_html(),
"css" => self.insert_content_type_text_css(),
"toml" | "txt" => self.insert_content_type_text_plain(),