Skip to content

Commit

Permalink
feat: add intermediate file system (#8631)
Browse files Browse the repository at this point in the history
  • Loading branch information
LingyuCoder authored Dec 6, 2024
1 parent 3836042 commit 9e1205c
Show file tree
Hide file tree
Showing 15 changed files with 815 additions and 50 deletions.
10 changes: 9 additions & 1 deletion crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ export declare class RawExternalItemFnCtx {
}

export declare class Rspack {
constructor(options: RawOptions, builtinPlugins: Array<BuiltinPlugin>, registerJsTaps: RegisterJsTaps, outputFilesystem: ThreadsafeNodeFS, resolverFactoryReference: JsResolverFactory)
constructor(options: RawOptions, builtinPlugins: Array<BuiltinPlugin>, registerJsTaps: RegisterJsTaps, outputFilesystem: ThreadsafeNodeFS, intermediateFilesystem: ThreadsafeNodeFS, resolverFactoryReference: JsResolverFactory)
setNonSkippableRegisters(kinds: Array<RegisterJsTapKind>): void
/** Build with the given option passed to the constructor */
build(callback: (err: null | Error) => void): void
Expand Down Expand Up @@ -2136,4 +2136,12 @@ export interface ThreadsafeNodeFS {
readFile: (name: string) => Promise<Buffer | string | void> | Buffer | string | void
stat: (name: string) => Promise<NodeFsStats | void> | NodeFsStats | void
lstat: (name: string) => Promise<NodeFsStats | void> | NodeFsStats | void
open: (name: string, flags: string) => Promise<number | void> | number | void
rename: (from: string, to: string) => Promise<void> | void
close: (fd: number) => Promise<void> | void
write: (fd: number, content: Buffer, position: number) => Promise<number | void> | number | void
writeAll: (fd: number, content: Buffer) => Promise<number | void> | number | void
read: (fd: number, length: number, position: number) => Promise<Buffer | void> | Buffer | void
readUntil: (fd: number, code: number, position: number) => Promise<Buffer | void> | Buffer | void
readToEnd: (fd: number, position: number) => Promise<Buffer | void> | Buffer | void
}
6 changes: 6 additions & 0 deletions crates/node_binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl Rspack {
builtin_plugins: Vec<BuiltinPlugin>,
register_js_taps: RegisterJsTaps,
output_filesystem: ThreadsafeNodeFS,
intermediate_filesystem: ThreadsafeNodeFS,
mut resolver_factory_reference: Reference<JsResolverFactory>,
) -> Result<Self> {
tracing::info!("raw_options: {:#?}", &options);
Expand Down Expand Up @@ -73,6 +74,11 @@ impl Rspack {
Some(Box::new(NodeFileSystem::new(output_filesystem).map_err(
|e| Error::from_reason(format!("Failed to create writable filesystem: {e}",)),
)?)),
Some(Box::new(
NodeFileSystem::new(intermediate_filesystem).map_err(|e| {
Error::from_reason(format!("Failed to create intermediate filesystem: {e}",))
})?,
)),
None,
Some(resolver_factory),
Some(loader_resolver_factory),
Expand Down
6 changes: 6 additions & 0 deletions crates/rspack_core/src/compiler/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ pub struct CompilerHooks {
pub struct Compiler {
pub options: Arc<CompilerOptions>,
pub output_filesystem: Box<dyn WritableFileSystem>,
pub intermediate_filesystem: Box<dyn WritableFileSystem>,
pub input_filesystem: Arc<dyn FileSystem>,
pub compilation: Compilation,
pub plugin_driver: SharedPluginDriver,
Expand All @@ -69,11 +70,13 @@ pub struct Compiler {

impl Compiler {
#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub fn new(
options: CompilerOptions,
plugins: Vec<BoxPlugin>,
buildtime_plugins: Vec<BoxPlugin>,
output_filesystem: Option<Box<dyn WritableFileSystem>>,
intermediate_filesystem: Option<Box<dyn WritableFileSystem>>,
// only supports passing input_filesystem in rust api, no support for js api
input_filesystem: Option<Arc<dyn FileSystem + Send + Sync>>,
// no need to pass resolve_factory in rust api
Expand Down Expand Up @@ -109,6 +112,8 @@ impl Compiler {
let old_cache = Arc::new(OldCache::new(options.clone()));
let module_executor = ModuleExecutor::default();
let output_filesystem = output_filesystem.unwrap_or_else(|| Box::new(NativeFileSystem {}));
let intermediate_filesystem =
intermediate_filesystem.unwrap_or_else(|| Box::new(NativeFileSystem {}));

Self {
options: options.clone(),
Expand All @@ -127,6 +132,7 @@ impl Compiler {
input_filesystem.clone(),
),
output_filesystem,
intermediate_filesystem,
plugin_driver,
buildtime_plugin_driver,
resolver_factory,
Expand Down
29 changes: 29 additions & 0 deletions crates/rspack_fs/src/intermediate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::fmt::Debug;

use rspack_paths::Utf8Path;

use super::Result;

#[async_trait::async_trait]
pub trait IntermediateFileSystemExtras: Debug + Send + Sync {
async fn rename(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()>;
async fn create_read_stream(&self, file: &Utf8Path) -> Result<Box<dyn ReadStream>>;
async fn create_write_stream(&self, file: &Utf8Path) -> Result<Box<dyn WriteStream>>;
}

#[async_trait::async_trait]
pub trait ReadStream: Debug + Sync + Send {
async fn read(&mut self, buf: &mut [u8]) -> Result<()>;
async fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize>;
async fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize>;
async fn skip(&mut self, offset: usize) -> Result<()>;
async fn close(&mut self) -> Result<()>;
}

#[async_trait::async_trait]
pub trait WriteStream: Debug + Sync + Send {
async fn write(&mut self, buf: &[u8]) -> Result<usize>;
async fn write_all(&mut self, buf: &[u8]) -> Result<()>;
async fn flush(&mut self) -> Result<()>;
async fn close(&mut self) -> Result<()>;
}
6 changes: 4 additions & 2 deletions crates/rspack_fs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,19 @@ pub use read::ReadableFileSystem;

mod write;
pub use write::WritableFileSystem;
mod intermediate;
pub use intermediate::{IntermediateFileSystemExtras, ReadStream, WriteStream};

mod file_metadata;
pub use file_metadata::FileMetadata;

mod macros;

mod native_fs;
pub use native_fs::NativeFileSystem;
pub use native_fs::{NativeFileSystem, NativeReadStream, NativeWriteStream};

mod memory_fs;
pub use memory_fs::MemoryFileSystem;
pub use memory_fs::{MemoryFileSystem, MemoryReadStream, MemoryWriteStream};

mod error;
pub use error::{Error, Result};
107 changes: 103 additions & 4 deletions crates/rspack_fs/src/memory_fs.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
use std::{
collections::{HashMap, HashSet},
sync::Mutex,
io::{BufRead, Cursor, Read, Seek},
sync::{Arc, Mutex},
time::{SystemTime, UNIX_EPOCH},
};

use futures::future::BoxFuture;
use rspack_paths::{AssertUtf8, Utf8Path, Utf8PathBuf};

use crate::{Error, FileMetadata, FileSystem, ReadableFileSystem, Result, WritableFileSystem};
use crate::{
Error, FileMetadata, FileSystem, IntermediateFileSystemExtras, ReadStream, ReadableFileSystem,
Result, WritableFileSystem, WriteStream,
};

fn current_time() -> u64 {
SystemTime::now()
Expand Down Expand Up @@ -67,9 +71,9 @@ impl FileType {
}
}

#[derive(Debug, Default)]
#[derive(Debug, Default, Clone)]
pub struct MemoryFileSystem {
files: Mutex<HashMap<Utf8PathBuf, FileType>>,
files: Arc<Mutex<HashMap<Utf8PathBuf, FileType>>>,
}
impl FileSystem for MemoryFileSystem {}

Expand Down Expand Up @@ -135,6 +139,17 @@ impl MemoryFileSystem {
}
Ok(res.into_iter().collect())
}

fn _rename_file(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> {
if !self.contains_file(from)? {
return Err(new_error("from dir not exist"));
}
let mut files = self.files.lock().expect("should get lock");
let file = files.remove(from).expect("should have file");
files.insert(to.into(), file);

Ok(())
}
}
#[async_trait::async_trait]
impl WritableFileSystem for MemoryFileSystem {
Expand Down Expand Up @@ -253,6 +268,90 @@ impl ReadableFileSystem for MemoryFileSystem {
}
}

#[async_trait::async_trait]
impl IntermediateFileSystemExtras for MemoryFileSystem {
async fn rename(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> {
self._rename_file(from, to)?;
Ok(())
}

async fn create_read_stream(&self, file: &Utf8Path) -> Result<Box<dyn ReadStream>> {
let contents = self.read(file)?;
let reader = MemoryReadStream::new(contents);
Ok(Box::new(reader))
}

async fn create_write_stream(&self, file: &Utf8Path) -> Result<Box<dyn WriteStream>> {
let writer = MemoryWriteStream::new(file, self.clone());
Ok(Box::new(writer))
}
}

#[derive(Debug)]
pub struct MemoryReadStream(Cursor<Vec<u8>>);

impl MemoryReadStream {
pub fn new(contents: Vec<u8>) -> Self {
Self(Cursor::new(contents))
}
}

#[async_trait::async_trait]
impl ReadStream for MemoryReadStream {
async fn read(&mut self, buf: &mut [u8]) -> Result<()> {
self.0.read_exact(buf).map_err(Error::from)
}

async fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> {
self.0.read_until(byte, buf).map_err(Error::from)
}
async fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
self.0.read_to_end(buf).map_err(Error::from)
}
async fn skip(&mut self, offset: usize) -> Result<()> {
self.0.seek_relative(offset as i64).map_err(Error::from)
}
async fn close(&mut self) -> Result<()> {
Ok(())
}
}

#[derive(Debug)]
pub struct MemoryWriteStream {
file: Utf8PathBuf,
contents: Vec<u8>,
fs: MemoryFileSystem,
}

impl MemoryWriteStream {
pub fn new(file: &Utf8Path, fs: MemoryFileSystem) -> Self {
Self {
file: file.to_path_buf(),
contents: vec![],
fs,
}
}
}

#[async_trait::async_trait]
impl WriteStream for MemoryWriteStream {
async fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.contents.extend(buf);
Ok(buf.len())
}
async fn write_all(&mut self, buf: &[u8]) -> Result<()> {
self.contents = buf.to_vec();
Ok(())
}
async fn flush(&mut self) -> Result<()> {
self.fs.write(&self.file, &self.contents).await?;
Ok(())
}
async fn close(&mut self) -> Result<()> {
Ok(())
}
}

#[cfg(test)]
mod tests {
use rspack_paths::Utf8Path;
Expand Down
83 changes: 81 additions & 2 deletions crates/rspack_fs/src/native_fs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
use std::fs;
use std::{
fs::{self, File},
io::{BufRead, BufReader, BufWriter, Read, Write},
};

use futures::future::BoxFuture;
use rspack_paths::{AssertUtf8, Utf8Path, Utf8PathBuf};

use crate::{Error, FileMetadata, FileSystem, ReadableFileSystem, Result, WritableFileSystem};
use crate::{
Error, FileMetadata, FileSystem, IntermediateFileSystemExtras, ReadStream, ReadableFileSystem,
Result, WritableFileSystem, WriteStream,
};

#[derive(Debug)]
pub struct NativeFileSystem;
Expand Down Expand Up @@ -85,3 +91,76 @@ impl ReadableFileSystem for NativeFileSystem {
Box::pin(fut)
}
}

#[async_trait::async_trait]
impl IntermediateFileSystemExtras for NativeFileSystem {
async fn rename(&self, from: &Utf8Path, to: &Utf8Path) -> Result<()> {
fs::rename(from, to).map_err(Error::from)
}

async fn create_read_stream(&self, file: &Utf8Path) -> Result<Box<dyn ReadStream>> {
let reader = NativeReadStream::try_new(file)?;
Ok(Box::new(reader))
}

async fn create_write_stream(&self, file: &Utf8Path) -> Result<Box<dyn WriteStream>> {
let writer = NativeWriteStream::try_new(file)?;
Ok(Box::new(writer))
}
}

#[derive(Debug)]
pub struct NativeReadStream(BufReader<File>);

impl NativeReadStream {
pub fn try_new(file: &Utf8Path) -> Result<Self> {
let file = File::open(file).map_err(Error::from)?;
Ok(Self(BufReader::new(file)))
}
}

#[async_trait::async_trait]
impl ReadStream for NativeReadStream {
async fn read(&mut self, buf: &mut [u8]) -> Result<()> {
self.0.read_exact(buf).map_err(Error::from)
}

async fn read_until(&mut self, byte: u8, buf: &mut Vec<u8>) -> Result<usize> {
self.0.read_until(byte, buf).map_err(Error::from)
}
async fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize> {
self.0.read_to_end(buf).map_err(Error::from)
}
async fn skip(&mut self, offset: usize) -> Result<()> {
self.0.seek_relative(offset as i64).map_err(Error::from)
}
async fn close(&mut self) -> Result<()> {
Ok(())
}
}

#[derive(Debug)]
pub struct NativeWriteStream(BufWriter<File>);

impl NativeWriteStream {
pub fn try_new(file: &Utf8Path) -> Result<Self> {
let file = File::open(file).map_err(Error::from)?;
Ok(Self(BufWriter::new(file)))
}
}

#[async_trait::async_trait]
impl WriteStream for NativeWriteStream {
async fn write(&mut self, buf: &[u8]) -> Result<usize> {
self.0.write(buf).map_err(Error::from)
}
async fn write_all(&mut self, buf: &[u8]) -> Result<()> {
self.0.write_all(buf).map_err(Error::from)
}
async fn flush(&mut self) -> Result<()> {
self.0.flush().map_err(Error::from)
}
async fn close(&mut self) -> Result<()> {
Ok(())
}
}
Loading

2 comments on commit 9e1205c

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Benchmark detail: Open

Name Base (2024-12-06 37e17cf) Current Change
10000_big_production-mode_disable-minimize + exec 37 s ± 441 ms 36.7 s ± 144 ms -0.69 %
10000_development-mode + exec 1.78 s ± 32 ms 1.77 s ± 17 ms -0.60 %
10000_development-mode_hmr + exec 637 ms ± 6.1 ms 642 ms ± 19 ms +0.86 %
10000_production-mode + exec 2.33 s ± 34 ms 2.32 s ± 35 ms -0.51 %
arco-pro_development-mode + exec 1.71 s ± 60 ms 1.73 s ± 66 ms +1.14 %
arco-pro_development-mode_hmr + exec 424 ms ± 1.5 ms 425 ms ± 3.7 ms +0.18 %
arco-pro_production-mode + exec 3.11 s ± 93 ms 3.13 s ± 81 ms +0.58 %
arco-pro_production-mode_generate-package-json-webpack-plugin + exec 3.17 s ± 49 ms 3.13 s ± 69 ms -1.08 %
threejs_development-mode_10x + exec 1.62 s ± 12 ms 1.62 s ± 24 ms +0.51 %
threejs_development-mode_10x_hmr + exec 785 ms ± 10 ms 782 ms ± 4.4 ms -0.38 %
threejs_production-mode_10x + exec 4.87 s ± 36 ms 4.88 s ± 45 ms +0.09 %
10000_big_production-mode_disable-minimize + rss memory 10019 MiB ± 41.2 MiB 10007 MiB ± 53.5 MiB -0.12 %
10000_development-mode + rss memory 811 MiB ± 21.7 MiB 815 MiB ± 26.7 MiB +0.44 %
10000_development-mode_hmr + rss memory 1944 MiB ± 427 MiB 1959 MiB ± 405 MiB +0.74 %
10000_production-mode + rss memory 701 MiB ± 23.4 MiB 679 MiB ± 25.7 MiB -3.15 %
arco-pro_development-mode + rss memory 709 MiB ± 28.4 MiB 722 MiB ± 42.8 MiB +1.85 %
arco-pro_development-mode_hmr + rss memory 896 MiB ± 57.1 MiB 921 MiB ± 112 MiB +2.76 %
arco-pro_production-mode + rss memory 822 MiB ± 35 MiB 807 MiB ± 56.9 MiB -1.82 %
arco-pro_production-mode_generate-package-json-webpack-plugin + rss memory 828 MiB ± 54 MiB 809 MiB ± 71.4 MiB -2.38 %
threejs_development-mode_10x + rss memory 789 MiB ± 51 MiB 794 MiB ± 40.9 MiB +0.58 %
threejs_development-mode_10x_hmr + rss memory 1748 MiB ± 170 MiB 1756 MiB ± 214 MiB +0.48 %
threejs_production-mode_10x + rss memory 1117 MiB ± 78.8 MiB 1128 MiB ± 71.5 MiB +1.01 %

@rspack-bot
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Ran ecosystem CI: Open

suite result
modernjs ✅ success
_selftest ✅ success
rsdoctor ✅ success
rspress ✅ success
rslib ✅ success
rsbuild ✅ success
examples ✅ success
devserver ✅ success
nuxt ✅ success

Please sign in to comment.