rspack_resolver/
file_system.rs1use std::{
2 fs, io,
3 path::{Path, PathBuf},
4};
5
6use cfg_if::cfg_if;
7#[cfg(feature = "yarn_pnp")]
8use pnp::fs::{LruZipCache, VPath, VPathInfo, ZipCache};
9
10#[async_trait::async_trait]
12pub trait FileSystem {
13 async fn read(&self, path: &Path) -> io::Result<Vec<u8>>;
19 async fn read_to_string(&self, path: &Path) -> io::Result<String>;
30
31 async fn metadata(&self, path: &Path) -> io::Result<FileMetadata>;
41
42 async fn symlink_metadata(&self, path: &Path) -> io::Result<FileMetadata>;
53
54 async fn canonicalize(&self, path: &Path) -> io::Result<PathBuf>;
65}
66
67#[derive(Debug, Clone, Copy)]
69pub struct FileMetadata {
70 pub is_file: bool,
71 pub is_dir: bool,
72 pub is_symlink: bool,
73}
74
75impl FileMetadata {
76 pub fn new(is_file: bool, is_dir: bool, is_symlink: bool) -> Self {
77 Self {
78 is_file,
79 is_dir,
80 is_symlink,
81 }
82 }
83}
84
85#[cfg(feature = "yarn_pnp")]
86impl From<pnp::fs::FileType> for FileMetadata {
87 fn from(value: pnp::fs::FileType) -> Self {
88 Self::new(
89 value == pnp::fs::FileType::File,
90 value == pnp::fs::FileType::Directory,
91 false,
92 )
93 }
94}
95
96impl From<fs::Metadata> for FileMetadata {
97 fn from(metadata: fs::Metadata) -> Self {
98 Self::new(metadata.is_file(), metadata.is_dir(), metadata.is_symlink())
99 }
100}
101
102pub struct FileSystemOptions {
103 #[cfg(feature = "yarn_pnp")]
104 pub enable_pnp: bool,
105}
106
107impl Default for FileSystemOptions {
108 fn default() -> Self {
109 Self {
110 #[cfg(feature = "yarn_pnp")]
111 enable_pnp: true,
112 }
113 }
114}
115
116pub struct FileSystemOs {
118 options: FileSystemOptions,
119 #[cfg(feature = "yarn_pnp")]
120 pnp_lru: LruZipCache<Vec<u8>>,
121}
122
123impl Default for FileSystemOs {
124 fn default() -> Self {
125 Self {
126 options: FileSystemOptions::default(),
127 #[cfg(feature = "yarn_pnp")]
128 pnp_lru: LruZipCache::new(50, pnp::fs::open_zip_via_read_p),
129 }
130 }
131}
132
133#[cfg(not(target_arch = "wasm32"))]
134#[async_trait::async_trait]
135impl FileSystem for FileSystemOs {
136 async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
137 cfg_if! {
138 if #[cfg(feature = "yarn_pnp")] {
139 if self.options.enable_pnp {
140 return match VPath::from(path)? {
141 VPath::Zip(info) => self.pnp_lru.read(info.physical_base_path(), info.zip_path),
142 VPath::Virtual(info) => tokio::fs::read(info.physical_base_path()).await,
143 VPath::Native(path) => tokio::fs::read(&path).await,
144 }
145 }
146 }}
147
148 tokio::fs::read(path).await
149 }
150
151 async fn read_to_string(&self, path: &Path) -> io::Result<String> {
152 cfg_if! {
153 if #[cfg(feature = "yarn_pnp")] {
154 if self.options.enable_pnp {
155 return match VPath::from(path)? {
156 VPath::Zip(info) => self.pnp_lru.read_to_string(info.physical_base_path(), info.zip_path),
157 VPath::Virtual(info) => tokio::fs::read_to_string(info.physical_base_path()).await,
158 VPath::Native(path) => tokio::fs::read_to_string(&path).await,
159 }
160 }
161 }
162 }
163 tokio::fs::read_to_string(path).await
164 }
165
166 async fn metadata(&self, path: &Path) -> io::Result<FileMetadata> {
167 cfg_if! {
168 if #[cfg(feature = "yarn_pnp")] {
169 if self.options.enable_pnp {
170 return match VPath::from(path)? {
171 VPath::Zip(info) => self
172 .pnp_lru
173 .file_type(info.physical_base_path(), info.zip_path)
174 .map(FileMetadata::from),
175 VPath::Virtual(info) => {
176 tokio::fs::metadata(info.physical_base_path())
177 .await
178 .map(FileMetadata::from)
179 }
180 VPath::Native(path) => tokio::fs::metadata(path).await.map(FileMetadata::from),
181 }
182 }
183 }
184 }
185
186 tokio::fs::metadata(path).await.map(FileMetadata::from)
187 }
188
189 async fn symlink_metadata(&self, path: &Path) -> io::Result<FileMetadata> {
190 tokio::fs::symlink_metadata(path)
191 .await
192 .map(FileMetadata::from)
193 }
194
195 async fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
196 cfg_if! {
197 if #[cfg(feature = "yarn_pnp")] {
198 if self.options.enable_pnp {
199 return match VPath::from(path)? {
200 VPath::Zip(info) => {
201 dunce::canonicalize(info.physical_base_path().join(info.zip_path))
202 }
203 VPath::Virtual(info) => dunce::canonicalize(info.physical_base_path()),
204 VPath::Native(path) => dunce::canonicalize(path),
205 }
206 }
207 }
208 }
209
210 dunce::canonicalize(path)
211 }
212}
213
214#[cfg(target_arch = "wasm32")]
215#[async_trait::async_trait]
216impl FileSystem for FileSystemOs {
217 async fn read(&self, path: &Path) -> io::Result<Vec<u8>> {
218 std::fs::read(path)
219 }
220
221 async fn read_to_string(&self, path: &Path) -> io::Result<String> {
222 std::fs::read_to_string(path)
223 }
224
225 async fn metadata(&self, path: &Path) -> io::Result<FileMetadata> {
226 if let Ok(m) = std::fs::metadata(path).map(FileMetadata::from) {
229 return Ok(m);
230 }
231
232 self.symlink_metadata(path).await?;
233 let path = self.canonicalize(path).await?;
234 std::fs::metadata(path).map(FileMetadata::from)
235 }
236
237 async fn symlink_metadata(&self, path: &Path) -> io::Result<FileMetadata> {
238 std::fs::symlink_metadata(path).map(FileMetadata::from)
239 }
240
241 async fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
242 use std::path::Component;
243 let mut path_buf = path.to_path_buf();
244 let link = fs::read_link(&path_buf)?;
245 path_buf.pop();
246 for component in link.components() {
247 match component {
248 Component::ParentDir => {
249 path_buf.pop();
250 }
251 Component::Normal(seg) => {
252 path_buf.push(seg.to_string_lossy().trim_end_matches('\0'));
253 }
254 Component::RootDir => {
255 path_buf = PathBuf::from("/");
256 }
257 Component::CurDir | Component::Prefix(_) => {}
258 }
259
260 if fs::symlink_metadata(&path_buf).is_ok_and(|m| m.is_symlink()) {
262 let dir = self.canonicalize(&path_buf).await?;
263 path_buf = dir;
264 }
265 }
266 Ok(path_buf)
267 }
268}
269
270#[tokio::test]
271async fn metadata() {
272 let meta = FileMetadata {
273 is_file: true,
274 is_dir: true,
275 is_symlink: true,
276 };
277 assert_eq!(
278 format!("{meta:?}"),
279 "FileMetadata { is_file: true, is_dir: true, is_symlink: true }"
280 );
281 let _ = meta;
282}