Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4dccc48
refactor: change JsResolver::resolve_sync to JsResolver::resolve, mak…
nilptr Apr 13, 2025
dd3016b
feat: pass inputFileSystem to rust side
nilptr Apr 13, 2025
e3b9913
feat: impl ReadableFileSystem for NodeFileSystem
nilptr Apr 13, 2025
25ad636
fix: update checkChunkModules error message
nilptr Apr 13, 2025
c12f40d
fix: new-incremental watch > build-chunk-graph > chunk-modify test case
nilptr Apr 13, 2025
55557d3
chore: add comment
nilptr Apr 13, 2025
e8f157c
refactor: ajust new-incremental watch > build-chunk-graph > chunk-mod…
nilptr Apr 13, 2025
f9503f1
chore: don't pass built-in InputFileSystem to rust side
nilptr Apr 13, 2025
2ac6452
fix: ci error
nilptr Apr 13, 2025
906481f
fix: ci error
nilptr Apr 21, 2025
8ed4d1c
fix: fs.stat failed when enable virtual module
stormslowly May 14, 2025
044356e
fix: in case atime will be from snapshot and be just a number
stormslowly May 14, 2025
daf4599
feat: add experiments#useInputFileSystem config option
stormslowly May 14, 2025
d34176c
fix: add missing zode type defintio
stormslowly May 14, 2025
2c8d2c2
chore: 🔥 remove debug log
stormslowly May 15, 2025
7a05244
chore: add instrument for node fs access
stormslowly May 19, 2025
63a8b5d
fix: ci test error - new experiments.useInputFileSystem option
nilptr May 26, 2025
a179141
chore: clean up __SKIP_BINDING__
nilptr May 26, 2025
fe59ee6
feat(experiments.useInputFileSystem): support RegExp
nilptr May 28, 2025
638ddeb
chore: fix clippy error
nilptr May 29, 2025
56075c0
Merge remote-tracking branch 'origin/main' into nilptr/feat/input-fil…
stormslowly Jun 3, 2025
032eca3
fix: typings
stormslowly Jun 4, 2025
05b579e
chore: udpate api md
stormslowly Jun 4, 2025
7faca30
chore: update api md
stormslowly Jun 4, 2025
c59aad1
fix: unit testing
stormslowly Jun 4, 2025
628145d
chore: ✏️ add doc
stormslowly Jun 4, 2025
9c7bfcb
chore: ✏️ polish doc
stormslowly Jun 4, 2025
c2054d2
fix: wrong signature
stormslowly Jun 4, 2025
bdc4a30
refactor: when useInputFileSystem is empty use native fs directly
stormslowly Jun 4, 2025
1989a0c
fix: tsc error
stormslowly Jun 4, 2025
67f6f07
test: input file system
stormslowly Jun 4, 2025
e59f9ae
test: add disk file into test case
stormslowly Jun 4, 2025
b9c8681
docs: add directly usage case
stormslowly Jun 4, 2025
f6cdb5f
docs: polish
stormslowly Jun 5, 2025
089e43b
chore: revert chunk test
stormslowly Jun 5, 2025
2f93d9e
test: delete extra test step
stormslowly Jun 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/cache/restore/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ inputs:

outputs:
cache-hit:
description: 'A boolean value to indicate an exact match was found for the primary key'
description: "A boolean value to indicate an exact match was found for the primary key"
value: ${{ steps.github-cache.outputs.cache-hit == 'true' || steps.local-cache.outputs.cache-hit == 'true' }}

runs:
Expand Down
4 changes: 3 additions & 1 deletion crates/node_binding/binding.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ export declare class JsCompilation {
}

export declare class JsCompiler {
constructor(compilerPath: string, options: RawOptions, builtinPlugins: Array<BuiltinPlugin>, registerJsTaps: RegisterJsTaps, outputFilesystem: ThreadsafeNodeFS, intermediateFilesystem: ThreadsafeNodeFS | undefined | null, resolverFactoryReference: JsResolverFactory)
constructor(compilerPath: string, options: RawOptions, builtinPlugins: Array<BuiltinPlugin>, registerJsTaps: RegisterJsTaps, outputFilesystem: ThreadsafeNodeFS, intermediateFilesystem: ThreadsafeNodeFS | undefined | null, inputFilesystem: ThreadsafeNodeFS | undefined | null, 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 @@ -1875,6 +1875,7 @@ incremental?: false | { [key: string]: boolean }
parallelCodeSplitting: boolean
rspackFuture?: RawRspackFuture
cache: boolean | { type: "persistent" } & RawExperimentCacheOptionsPersistent | { type: "memory" }
useInputFileSystem?: false | Array<RegExp>
}

export interface RawExperimentSnapshotOptions {
Expand Down Expand Up @@ -2712,6 +2713,7 @@ export interface ThreadsafeNodeFS {
readFile: (name: string) => Promise<Buffer | string | void>
stat: (name: string) => Promise<NodeFsStats | void>
lstat: (name: string) => Promise<NodeFsStats | void>
realpath: (name: string) => Promise<string | void>
open: (name: string, flags: string) => Promise<number | void>
rename: (from: string, to: string) => Promise<void>
close: (fd: number) => Promise<void>
Expand Down
71 changes: 71 additions & 0 deletions crates/node_binding/src/fs_node/hybrid.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
use async_trait::async_trait;
use rspack_fs::{FileMetadata, NativeFileSystem, ReadableFileSystem, Result};
use rspack_paths::{Utf8Path, Utf8PathBuf};
use rspack_regex::RspackRegex;

use super::NodeFileSystem;

#[derive(Debug)]
pub struct HybridFileSystem {
allowlist: Vec<RspackRegex>,
node_fs: NodeFileSystem,
native_fs: NativeFileSystem,
}

impl HybridFileSystem {
pub fn new(
allowlist: Vec<RspackRegex>,
node_fs: NodeFileSystem,
native_fs: NativeFileSystem,
) -> Self {
Self {
allowlist,
node_fs,
native_fs,
}
}

fn pick_fs_for_path(&self, path: &Utf8Path) -> &dyn ReadableFileSystem {
if self
.allowlist
.iter()
.any(|regexp| regexp.test(path.as_str()))
{
&self.node_fs
} else {
&self.native_fs
}
}
}

#[async_trait]
impl ReadableFileSystem for HybridFileSystem {
async fn read(&self, path: &Utf8Path) -> Result<Vec<u8>> {
self.pick_fs_for_path(path).read(path).await
}
fn read_sync(&self, path: &Utf8Path) -> Result<Vec<u8>> {
self.pick_fs_for_path(path).read_sync(path)
}

async fn metadata(&self, path: &Utf8Path) -> Result<FileMetadata> {
self.pick_fs_for_path(path).metadata(path).await
}
fn metadata_sync(&self, path: &Utf8Path) -> Result<FileMetadata> {
self.pick_fs_for_path(path).metadata_sync(path)
}

async fn symlink_metadata(&self, path: &Utf8Path) -> Result<FileMetadata> {
self.pick_fs_for_path(path).symlink_metadata(path).await
}

async fn canonicalize(&self, path: &Utf8Path) -> Result<Utf8PathBuf> {
self.pick_fs_for_path(path).canonicalize(path).await
}

async fn read_dir(&self, path: &Utf8Path) -> Result<Vec<String>> {
self.pick_fs_for_path(path).read_dir(path).await
}
fn read_dir_sync(&self, path: &Utf8Path) -> Result<Vec<String>> {
self.pick_fs_for_path(path).read_dir_sync(path)
}
}
3 changes: 3 additions & 0 deletions crates/node_binding/src/fs_node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ pub use write::NodeFileSystem;

mod node;
pub use node::ThreadsafeNodeFS;

mod hybrid;
pub use hybrid::HybridFileSystem;
2 changes: 2 additions & 0 deletions crates/node_binding/src/fs_node/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ pub struct ThreadsafeNodeFS {
pub stat: ThreadsafeFunction<String, Promise<Either<NodeFsStats, ()>>>,
#[napi(ts_type = "(name: string) => Promise<NodeFsStats | void>")]
pub lstat: ThreadsafeFunction<String, Promise<Either<NodeFsStats, ()>>>,
#[napi(ts_type = "(name: string) => Promise<string | void>")]
pub realpath: ThreadsafeFunction<String, Promise<Either<String, ()>>>,
#[napi(ts_type = "(name: string, flags: string) => Promise<number | void>")]
pub open: Open,
#[napi(ts_type = "(from: string, to: string) => Promise<void>")]
Expand Down
112 changes: 108 additions & 4 deletions crates/node_binding/src/fs_node/write.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
use std::sync::Arc;

use async_trait::async_trait;
use napi::{bindgen_prelude::Either3, Either};
use napi::{
bindgen_prelude::{block_on, Either3},
Either,
};
use rspack_fs::{
Error, FileMetadata, IntermediateFileSystem, IntermediateFileSystemExtras, ReadStream, Result,
RspackResultToFsResultExt, WritableFileSystem, WriteStream,
Error, FileMetadata, IntermediateFileSystem, IntermediateFileSystemExtras, ReadStream,
ReadableFileSystem, Result, RspackResultToFsResultExt, WritableFileSystem, WriteStream,
};
use rspack_paths::Utf8Path;
use rspack_paths::{Utf8Path, Utf8PathBuf};
use tracing::instrument;

use super::node::ThreadsafeNodeFS;

Expand Down Expand Up @@ -151,6 +155,106 @@ impl IntermediateFileSystemExtras for NodeFileSystem {

impl IntermediateFileSystem for NodeFileSystem {}

#[async_trait]
impl ReadableFileSystem for NodeFileSystem {
#[instrument(skip(self), level = "debug")]
async fn read(&self, path: &Utf8Path) -> Result<Vec<u8>> {
self
.0
.read_file
.call_with_promise(path.as_str().to_string())
.await
.to_fs_result()
// TODO: simplify the return value?
.map(|result| match result {
Either3::A(buf) => buf.into(),
Either3::B(str) => str.into(),
Either3::C(_) => vec![],
})
}
#[instrument(skip(self), level = "debug")]
fn read_sync(&self, path: &Utf8Path) -> Result<Vec<u8>> {
block_on(self.read(path))
}

#[instrument(skip(self), level = "debug")]
async fn metadata(&self, path: &Utf8Path) -> Result<FileMetadata> {
let res = self
.0
.stat
.call_with_promise(path.as_str().to_string())
.await
.to_fs_result()?;
match res {
Either::A(stats) => Ok(stats.into()),
Either::B(_) => Err(Error::new(
std::io::ErrorKind::Other,
"input file system call stat failed",
)),
}
}

#[instrument(skip(self), level = "debug")]
fn metadata_sync(&self, path: &Utf8Path) -> Result<FileMetadata> {
block_on(self.metadata(path))
}

#[instrument(skip(self), level = "debug")]
async fn symlink_metadata(&self, path: &Utf8Path) -> Result<FileMetadata> {
let res = self
.0
.lstat
.call_with_promise(path.as_str().to_string())
.await
.to_fs_result()?;
match res {
Either::A(stats) => Ok(stats.into()),
Either::B(_) => Err(Error::new(
std::io::ErrorKind::Other,
"input file system call lstat failed",
)),
}
}

#[instrument(skip(self), level = "debug")]
async fn canonicalize(&self, path: &Utf8Path) -> Result<Utf8PathBuf> {
let res = self
.0
.realpath
.call_with_promise(path.as_str().to_string())
.await
.to_fs_result()?;
match res {
Either::A(str) => Ok(Utf8PathBuf::from(str)),
Either::B(_) => Err(Error::new(
std::io::ErrorKind::Other,
"input file system call realpath failed",
)),
}
}

#[instrument(skip(self), level = "debug")]
async fn read_dir(&self, dir: &Utf8Path) -> Result<Vec<String>> {
let res = self
.0
.read_dir
.call_with_promise(dir.as_str().to_string())
.await
.to_fs_result()?;
match res {
Either::A(list) => Ok(list),
Either::B(_) => Err(Error::new(
std::io::ErrorKind::Other,
"input file system call read_dir failed",
)),
}
}
#[instrument(skip(self), level = "debug")]
fn read_dir_sync(&self, dir: &Utf8Path) -> Result<Vec<String>> {
block_on(ReadableFileSystem::read_dir(self, dir))
}
}

#[derive(Debug)]
pub struct NodeReadStream {
fd: i32,
Expand Down
34 changes: 31 additions & 3 deletions crates/node_binding/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ extern crate rspack_allocator;
use std::{cell::RefCell, sync::Arc};

use compiler::{Compiler, CompilerState, CompilerStateGuard};
use fs_node::HybridFileSystem;
use napi::{bindgen_prelude::*, CallContext};
use rspack_collections::UkeyMap;
use rspack_core::{
BoxDependency, Compilation, CompilerId, EntryOptions, ModuleIdentifier, PluginExt,
};
use rspack_error::Diagnostic;
use rspack_fs::IntermediateFileSystem;
use rspack_fs::{IntermediateFileSystem, NativeFileSystem, ReadableFileSystem};

use crate::fs_node::{NodeFileSystem, ThreadsafeNodeFS};

Expand Down Expand Up @@ -140,11 +141,12 @@ impl JsCompiler {
env: Env,
mut this: This,
compiler_path: String,
options: RawOptions,
mut options: RawOptions,
builtin_plugins: Vec<BuiltinPlugin>,
register_js_taps: RegisterJsTaps,
output_filesystem: ThreadsafeNodeFS,
intermediate_filesystem: Option<ThreadsafeNodeFS>,
input_filesystem: Option<ThreadsafeNodeFS>,
mut resolver_factory_reference: Reference<JsResolverFactory>,
) -> Result<Self> {
tracing::info!(name:"rspack_version", version = rspack_version!());
Expand All @@ -168,10 +170,36 @@ impl JsCompiler {
bp.append_to(env, &mut this, &mut plugins)?;
}

let use_input_fs = options.experiments.use_input_file_system.take();
let compiler_options: rspack_core::CompilerOptions = options.try_into().to_napi_result()?;

tracing::debug!(name:"normalized_options", options=?&compiler_options);

let input_file_system: Option<Arc<dyn ReadableFileSystem>> = input_filesystem.and_then(|fs| {
use_input_fs.and_then(|use_input_file_system| {
let node_fs = NodeFileSystem::new(fs).expect("Failed to create readable filesystem");

match use_input_file_system {
WithFalse::False => None,
WithFalse::True(allowlist) => {
if allowlist.is_empty() {
return None;
}
let binding: Arc<dyn ReadableFileSystem> = Arc::new(HybridFileSystem::new(
allowlist,
node_fs,
NativeFileSystem::new(compiler_options.resolve.pnp.unwrap_or(false)),
));
Some(binding)
}
}
})
});

if let Some(fs) = &input_file_system {
resolver_factory_reference.input_filesystem = fs.clone();
}

let resolver_factory =
(*resolver_factory_reference).get_resolver_factory(compiler_options.resolve.clone());
let loader_resolver_factory = (*resolver_factory_reference)
Expand All @@ -198,7 +226,7 @@ impl JsCompiler {
.to_napi_result_with_message(|e| format!("Failed to create writable filesystem: {e}"))?,
)),
intermediate_filesystem,
None,
input_file_system,
Some(resolver_factory),
Some(loader_resolver_factory),
);
Expand Down
3 changes: 3 additions & 0 deletions crates/node_binding/src/raw_options/raw_experiments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use raw_cache::{normalize_raw_experiment_cache_options, RawExperimentCacheOption
use raw_incremental::RawIncremental;
use raw_rspack_future::RawRspackFuture;
use rspack_core::{incremental::IncrementalOptions, Experiments};
use rspack_regex::RspackRegex;

use super::WithFalse;

Expand All @@ -23,6 +24,8 @@ pub struct RawExperiments {
ts_type = r#"boolean | { type: "persistent" } & RawExperimentCacheOptionsPersistent | { type: "memory" }"#
)]
pub cache: RawExperimentCacheOptions,
#[napi(ts_type = "false | Array<RegExp>")]
pub use_input_file_system: Option<WithFalse<Vec<RspackRegex>>>,
}

impl From<RawExperiments> for Experiments {
Expand Down
22 changes: 14 additions & 8 deletions crates/node_binding/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,21 @@ impl JsResolver {
path: String,
request: String,
) -> napi::Result<Either<JsResourceData, bool>> {
block_on(async move {
match self.resolver.resolve(Path::new(&path), &request).await {
Ok(rspack_core::ResolveResult::Resource(resource)) => {
Ok(Either::A(ResourceData::from(resource).into()))
}
Ok(rspack_core::ResolveResult::Ignored) => Ok(Either::B(false)),
Err(err) => Err(napi::Error::from_reason(format!("{err:?}"))),
block_on(self._resolve(path, request))
}

async fn _resolve(
&self,
path: String,
request: String,
) -> napi::Result<Either<JsResourceData, bool>> {
match self.resolver.resolve(Path::new(&path), &request).await {
Ok(rspack_core::ResolveResult::Resource(resource)) => {
Ok(Either::A(ResourceData::from(resource).into()))
}
})
Ok(rspack_core::ResolveResult::Ignored) => Ok(Either::B(false)),
Err(err) => Err(napi::Error::from_reason(format!("{err:?}"))),
}
}

#[napi(
Expand Down
2 changes: 1 addition & 1 deletion packages/rspack-test-tools/src/helper/util/checkStats.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exports.checkChunkModules = function checkChunkModules(
const chunkModules = chunk.modules.map(m => m.identifier);
if (strict && expectedModules.length !== chunkModules.length) {
throw new Error(
`expect chunk ${chunkId} has ${chunkModules.length} modules: ${chunkModules}\nbut received ${chunkModules.length} modules`
`expect chunk ${chunkId} has ${expectedModules.length} modules: ${expectedModules}\nbut received ${chunkModules.length} modules`
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Object {
},
},
topLevelAwait: true,
useInputFileSystem: false,
},
externals: undefined,
externalsPresets: Object {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
it("should load a file from the disk", ()=>{
expect(42).toBe(42);
})
Loading
Loading