@rspack/binding 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/Cargo.lock +4322 -0
- package/Cargo.toml +44 -0
- package/binding.js +221 -0
- package/build.rs +5 -0
- package/napirs.rc.json +20 -0
- package/package.json +21 -0
- package/src/adapter/common.rs +11 -0
- package/src/adapter/mod.rs +124 -0
- package/src/adapter/utils.rs +62 -0
- package/src/lib.rs +272 -0
- package/src/utils.rs +88 -0
- package/tests/answer.js +1 -0
- package/tests/binding.spec.ts +19 -0
- package/tests/index.js +2 -0
package/src/lib.rs
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
use std::fmt::Debug;
|
|
2
|
+
// use std::path::Path;
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
|
|
5
|
+
use napi::bindgen_prelude::*;
|
|
6
|
+
use napi::{Env, Result};
|
|
7
|
+
use napi_derive::napi;
|
|
8
|
+
// use nodejs_resolver::Resolver;
|
|
9
|
+
use tokio::sync::Mutex;
|
|
10
|
+
mod adapter;
|
|
11
|
+
// mod options;
|
|
12
|
+
mod utils;
|
|
13
|
+
use adapter::create_node_adapter_from_plugin_callbacks;
|
|
14
|
+
|
|
15
|
+
use utils::get_named_property_value_string;
|
|
16
|
+
|
|
17
|
+
// use adapter::utils::create_node_adapter_from_plugin_callbacks;
|
|
18
|
+
pub use rspack_binding_options::{normalize_bundle_options, NodeLoaderAdapter, RawOptions};
|
|
19
|
+
|
|
20
|
+
#[cfg(all(not(all(target_os = "linux", target_arch = "aarch64", target_env = "musl"))))]
|
|
21
|
+
#[global_allocator]
|
|
22
|
+
static ALLOC: mimalloc_rust::GlobalMiMalloc = mimalloc_rust::GlobalMiMalloc;
|
|
23
|
+
|
|
24
|
+
pub fn create_external<T>(value: T) -> External<T> {
|
|
25
|
+
External::new(value)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
pub type Rspack = Arc<Mutex<rspack::Compiler>>;
|
|
29
|
+
|
|
30
|
+
#[napi(object)]
|
|
31
|
+
|
|
32
|
+
pub struct PluginCallbacks {
|
|
33
|
+
pub done_callback: JsFunction,
|
|
34
|
+
}
|
|
35
|
+
impl Debug for PluginCallbacks {
|
|
36
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
37
|
+
f.debug_struct("PluginCallbacks")
|
|
38
|
+
.field("done_callback", &"done_adapter")
|
|
39
|
+
.finish()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
pub struct RspackBindingContext {
|
|
44
|
+
pub rspack: Rspack,
|
|
45
|
+
// pub resolver: Arc<Resolver>,
|
|
46
|
+
}
|
|
47
|
+
#[napi(object)]
|
|
48
|
+
pub struct RspackError {
|
|
49
|
+
pub message: String,
|
|
50
|
+
}
|
|
51
|
+
#[napi(object)]
|
|
52
|
+
pub struct Stats {
|
|
53
|
+
pub errors: Vec<RspackError>,
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
impl<'a> From<rspack_core::Stats<'a>> for Stats {
|
|
57
|
+
fn from(rspack_stats: rspack_core::Stats) -> Self {
|
|
58
|
+
Self {
|
|
59
|
+
errors: rspack_stats
|
|
60
|
+
.compilation
|
|
61
|
+
.diagnostic
|
|
62
|
+
.iter()
|
|
63
|
+
.map(|d| RspackError {
|
|
64
|
+
message: d.message.clone(),
|
|
65
|
+
})
|
|
66
|
+
.collect(),
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
#[napi]
|
|
72
|
+
pub fn init_trace_subscriber(env: Env) -> Result<()> {
|
|
73
|
+
utils::init_custom_trace_subscriber(env)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[napi(ts_return_type = "ExternalObject<RspackInternal>")]
|
|
77
|
+
#[allow(clippy::too_many_arguments)]
|
|
78
|
+
pub fn new_rspack(
|
|
79
|
+
env: Env,
|
|
80
|
+
mut options: RawOptions,
|
|
81
|
+
plugin_callbacks: Option<PluginCallbacks>,
|
|
82
|
+
) -> Result<External<RspackBindingContext>> {
|
|
83
|
+
#[cfg(debug_assertions)]
|
|
84
|
+
{
|
|
85
|
+
if let Some(module) = options.module.as_mut() {
|
|
86
|
+
for rule in &mut module.rules {
|
|
87
|
+
if let Some(uses) = rule.uses.as_mut() {
|
|
88
|
+
for item in uses {
|
|
89
|
+
if let Some(loader) = item.loader.as_ref() {
|
|
90
|
+
// let (env_ptr, loader_ptr) = unsafe { (env.raw(), loader.raw()) };
|
|
91
|
+
if let Ok(display_name) = get_named_property_value_string(env, loader, "displayName")
|
|
92
|
+
{
|
|
93
|
+
item.__loader_name = Some(display_name);
|
|
94
|
+
} else if let Ok(name) = get_named_property_value_string(env, loader, "name") {
|
|
95
|
+
item.__loader_name = Some(name);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
let node_adapter = create_node_adapter_from_plugin_callbacks(&env, plugin_callbacks)?;
|
|
104
|
+
let mut compiler_options =
|
|
105
|
+
normalize_bundle_options(options).map_err(|e| Error::from_reason(format!("{:?}", e)))?;
|
|
106
|
+
if let Some(node_adapter) = node_adapter {
|
|
107
|
+
compiler_options
|
|
108
|
+
.plugins
|
|
109
|
+
.push(Box::new(node_adapter) as Box<dyn rspack_core::Plugin>);
|
|
110
|
+
}
|
|
111
|
+
// TODO: this way or passing env as context to `normalize_bundle_option`?
|
|
112
|
+
compiler_options
|
|
113
|
+
.module
|
|
114
|
+
.rules
|
|
115
|
+
.iter_mut()
|
|
116
|
+
.try_for_each(|rule| {
|
|
117
|
+
rule.uses.iter_mut().try_for_each(|loader| {
|
|
118
|
+
let casted = loader.as_any_mut();
|
|
119
|
+
if let Some(adapter) = casted.downcast_mut::<NodeLoaderAdapter>() {
|
|
120
|
+
adapter.unref(&env)
|
|
121
|
+
} else {
|
|
122
|
+
Ok(())
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
.map_err(|e| Error::from_reason(format!("failed to unref tsfn {:?}", e)))?;
|
|
127
|
+
|
|
128
|
+
let rspack = rspack::rspack(compiler_options, vec![]);
|
|
129
|
+
|
|
130
|
+
// let resolver = rspack.resolver.clone();
|
|
131
|
+
Ok(create_external(RspackBindingContext {
|
|
132
|
+
rspack: Arc::new(Mutex::new(rspack)),
|
|
133
|
+
// resolver,
|
|
134
|
+
}))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[napi(
|
|
138
|
+
ts_args_type = "rspack: ExternalObject<RspackInternal>",
|
|
139
|
+
ts_return_type = "Promise<Stats>"
|
|
140
|
+
)]
|
|
141
|
+
pub fn build(env: Env, binding_context: External<RspackBindingContext>) -> Result<napi::JsObject> {
|
|
142
|
+
let compiler = binding_context.rspack.clone();
|
|
143
|
+
env.execute_tokio_future(
|
|
144
|
+
async move {
|
|
145
|
+
let mut compiler = compiler.lock().await;
|
|
146
|
+
let rspack_stats = compiler
|
|
147
|
+
.build()
|
|
148
|
+
.await
|
|
149
|
+
.map_err(|e| Error::new(napi::Status::GenericFailure, format!("{:?}", e)))?;
|
|
150
|
+
|
|
151
|
+
let stats: Stats = rspack_stats.into();
|
|
152
|
+
if stats.errors.is_empty() {
|
|
153
|
+
println!("build success");
|
|
154
|
+
} else {
|
|
155
|
+
println!("build failed");
|
|
156
|
+
}
|
|
157
|
+
Ok(stats)
|
|
158
|
+
},
|
|
159
|
+
|_env, ret| Ok(ret),
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[napi(
|
|
164
|
+
// ts_args_type = "rspack: ExternalObject<RspackInternal>, changedFile: string[]",
|
|
165
|
+
ts_args_type = "rspack: ExternalObject<RspackInternal>",
|
|
166
|
+
// ts_return_type = "Promise<[diff: Record<string, string>, map: Record<string, string>]>"
|
|
167
|
+
ts_return_type = "Promise<Record<string, string>>"
|
|
168
|
+
)]
|
|
169
|
+
pub fn rebuild(
|
|
170
|
+
env: Env,
|
|
171
|
+
binding_context: External<RspackBindingContext>,
|
|
172
|
+
changed_file: Vec<String>,
|
|
173
|
+
) -> Result<napi::JsObject> {
|
|
174
|
+
let compiler = binding_context.rspack.clone();
|
|
175
|
+
env.execute_tokio_future(
|
|
176
|
+
async move {
|
|
177
|
+
let mut compiler = compiler.lock().await;
|
|
178
|
+
let _rspack_stats = compiler
|
|
179
|
+
.rebuild(changed_file)
|
|
180
|
+
.await
|
|
181
|
+
.map_err(|e| Error::new(napi::Status::GenericFailure, format!("{:?}", e)))?;
|
|
182
|
+
|
|
183
|
+
let stats: Stats = _rspack_stats.into();
|
|
184
|
+
println!("rebuild success");
|
|
185
|
+
Ok(stats)
|
|
186
|
+
},
|
|
187
|
+
|_env, ret| Ok(ret),
|
|
188
|
+
)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// #[napi(
|
|
192
|
+
// ts_args_type = "rspack: ExternalObject<RspackInternal>, source: string, resolveOptions: ResolveOptions",
|
|
193
|
+
// ts_return_type = "ResolveResult"
|
|
194
|
+
// )]
|
|
195
|
+
// pub fn resolve(
|
|
196
|
+
// binding_context: External<RspackBindingContext>,
|
|
197
|
+
// source: String,
|
|
198
|
+
// resolve_options: ResolveOptions,
|
|
199
|
+
// ) -> Result<ResolveResult> {
|
|
200
|
+
// let resolver = (*binding_context).resolver.clone();
|
|
201
|
+
// let res = resolver.resolve(Path::new(&resolve_options.resolve_dir), &source);
|
|
202
|
+
// match res {
|
|
203
|
+
// Ok(val) => {
|
|
204
|
+
// if let nodejs_resolver::ResolveResult::Path(p) = val {
|
|
205
|
+
// Ok(ResolveResult {
|
|
206
|
+
// status: true,
|
|
207
|
+
// path: Some(p.to_string_lossy().to_string()),
|
|
208
|
+
// })
|
|
209
|
+
// } else {
|
|
210
|
+
// Ok(ResolveResult {
|
|
211
|
+
// status: false,
|
|
212
|
+
// path: None,
|
|
213
|
+
// })
|
|
214
|
+
// }
|
|
215
|
+
// }
|
|
216
|
+
// Err(err) => Err(Error::new(Status::GenericFailure, err)),
|
|
217
|
+
// }
|
|
218
|
+
// }
|
|
219
|
+
|
|
220
|
+
// #[napi(object)]
|
|
221
|
+
// pub struct ResolveOptions {
|
|
222
|
+
// pub resolve_dir: String,
|
|
223
|
+
// }
|
|
224
|
+
|
|
225
|
+
// #[napi(object)]
|
|
226
|
+
// pub struct ResolveResult {
|
|
227
|
+
// pub status: bool,
|
|
228
|
+
// pub path: Option<String>,
|
|
229
|
+
// }
|
|
230
|
+
|
|
231
|
+
// #[napi]
|
|
232
|
+
// pub fn resolve_file(base_dir: String, import_path: String) -> Result<String> {
|
|
233
|
+
// let resolver = Resolver::new(nodejs_resolver::ResolverOptions {
|
|
234
|
+
// extensions: vec!["less", "css", "scss", "sass", "js"]
|
|
235
|
+
// .into_iter()
|
|
236
|
+
// .map(|s| s.to_owned())
|
|
237
|
+
// .collect(),
|
|
238
|
+
// ..Default::default()
|
|
239
|
+
// });
|
|
240
|
+
// match resolver.resolve(Path::new(&base_dir), &import_path) {
|
|
241
|
+
// Ok(res) => {
|
|
242
|
+
// if let nodejs_resolver::ResolveResult::Path(abs_path) = res {
|
|
243
|
+
// match abs_path.to_str() {
|
|
244
|
+
// Some(s) => Ok(s.to_owned()),
|
|
245
|
+
// None => Err(Error::new(
|
|
246
|
+
// napi::Status::GenericFailure,
|
|
247
|
+
// "unable to create string from path".to_owned(),
|
|
248
|
+
// )),
|
|
249
|
+
// }
|
|
250
|
+
// } else {
|
|
251
|
+
// Ok(import_path)
|
|
252
|
+
// }
|
|
253
|
+
// }
|
|
254
|
+
// Err(msg) => Err(Error::new(Status::GenericFailure, msg)),
|
|
255
|
+
// }
|
|
256
|
+
// }
|
|
257
|
+
|
|
258
|
+
#[napi::module_init]
|
|
259
|
+
fn init() {
|
|
260
|
+
use backtrace::Backtrace;
|
|
261
|
+
use std::panic::set_hook;
|
|
262
|
+
|
|
263
|
+
set_hook(Box::new(|panic_info| {
|
|
264
|
+
let backtrace = Backtrace::new();
|
|
265
|
+
println!("Panic: {:?}\nBacktrace: {:?}", panic_info, backtrace);
|
|
266
|
+
std::process::exit(1)
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// for dts generation only
|
|
271
|
+
#[napi(object)]
|
|
272
|
+
pub struct RspackInternal {}
|
package/src/utils.rs
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
use std::ffi::CStr;
|
|
2
|
+
use std::io::Write;
|
|
3
|
+
use std::ptr;
|
|
4
|
+
|
|
5
|
+
use napi::{check_status, Env, Error, NapiRaw, Result};
|
|
6
|
+
use napi_derive::napi;
|
|
7
|
+
use once_cell::sync::OnceCell;
|
|
8
|
+
|
|
9
|
+
static CUSTOM_TRACE_SUBSCRIBER: OnceCell<bool> = OnceCell::new();
|
|
10
|
+
|
|
11
|
+
/// Try to resolve the string value of a given named property
|
|
12
|
+
pub fn get_named_property_value_string<T: NapiRaw>(
|
|
13
|
+
env: Env,
|
|
14
|
+
object: T,
|
|
15
|
+
property_name: &str,
|
|
16
|
+
) -> Result<String> {
|
|
17
|
+
let mut bytes_with_nul: Vec<u8> = Vec::with_capacity(property_name.len() + 1);
|
|
18
|
+
|
|
19
|
+
write!(&mut bytes_with_nul, "{}", property_name)?;
|
|
20
|
+
write!(&mut bytes_with_nul, "{}", '\0')?;
|
|
21
|
+
|
|
22
|
+
let mut value_ptr = ptr::null_mut();
|
|
23
|
+
|
|
24
|
+
check_status!(
|
|
25
|
+
unsafe {
|
|
26
|
+
napi_sys::napi_get_named_property(
|
|
27
|
+
env.raw(),
|
|
28
|
+
object.raw(),
|
|
29
|
+
CStr::from_bytes_with_nul_unchecked(&bytes_with_nul).as_ptr(),
|
|
30
|
+
&mut value_ptr,
|
|
31
|
+
)
|
|
32
|
+
},
|
|
33
|
+
"failed to get the value"
|
|
34
|
+
)?;
|
|
35
|
+
|
|
36
|
+
let mut str_len = 0;
|
|
37
|
+
check_status!(
|
|
38
|
+
unsafe {
|
|
39
|
+
napi_sys::napi_get_value_string_utf8(env.raw(), value_ptr, ptr::null_mut(), 0, &mut str_len)
|
|
40
|
+
},
|
|
41
|
+
"failed to get the value"
|
|
42
|
+
)?;
|
|
43
|
+
|
|
44
|
+
str_len += 1;
|
|
45
|
+
let mut buf = Vec::with_capacity(str_len);
|
|
46
|
+
let mut copied_len = 0;
|
|
47
|
+
|
|
48
|
+
check_status!(
|
|
49
|
+
unsafe {
|
|
50
|
+
napi_sys::napi_get_value_string_utf8(
|
|
51
|
+
env.raw(),
|
|
52
|
+
value_ptr,
|
|
53
|
+
buf.as_mut_ptr(),
|
|
54
|
+
str_len,
|
|
55
|
+
&mut copied_len,
|
|
56
|
+
)
|
|
57
|
+
},
|
|
58
|
+
"failed to get the value"
|
|
59
|
+
)?;
|
|
60
|
+
|
|
61
|
+
// Vec<i8> -> Vec<u8> See: https://stackoverflow.com/questions/59707349/cast-vector-of-i8-to-vector-of-u8-in-rust
|
|
62
|
+
let mut buf = std::mem::ManuallyDrop::new(buf);
|
|
63
|
+
|
|
64
|
+
let buf = unsafe { Vec::from_raw_parts(buf.as_mut_ptr() as *mut u8, copied_len, copied_len) };
|
|
65
|
+
|
|
66
|
+
String::from_utf8(buf).map_err(|_| Error::from_reason("failed to get property"))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[napi]
|
|
70
|
+
pub fn init_custom_trace_subscriber(
|
|
71
|
+
mut env: Env,
|
|
72
|
+
// trace_out_file_path: Option<String>,
|
|
73
|
+
) -> Result<()> {
|
|
74
|
+
CUSTOM_TRACE_SUBSCRIBER.get_or_init(|| {
|
|
75
|
+
let guard = rspack_core::log::enable_tracing_by_env_with_chrome_layer();
|
|
76
|
+
if let Some(guard) = guard {
|
|
77
|
+
env
|
|
78
|
+
.add_env_cleanup_hook(guard, |flush_guard| {
|
|
79
|
+
flush_guard.flush();
|
|
80
|
+
drop(flush_guard);
|
|
81
|
+
})
|
|
82
|
+
.expect("Should able to initialize cleanup for custom trace subscriber");
|
|
83
|
+
}
|
|
84
|
+
true
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
Ok(())
|
|
88
|
+
}
|
package/tests/answer.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const answer = 42
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import assert from 'assert'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import log from 'why-is-node-running'
|
|
4
|
+
import binding from '..'
|
|
5
|
+
import { RawOptions } from '../binding.d'
|
|
6
|
+
|
|
7
|
+
describe('binding', () => {
|
|
8
|
+
it('work', async () => {
|
|
9
|
+
const options: RawOptions = {
|
|
10
|
+
entries: { main: path.resolve(__dirname, './index.js') },
|
|
11
|
+
// entryFilename: path.resolve(__dirname, 'dist/main.js'),
|
|
12
|
+
}
|
|
13
|
+
const instance = binding.newRspack(JSON.stringify(options))
|
|
14
|
+
await binding.build(instance)
|
|
15
|
+
// setTimeout(() => {
|
|
16
|
+
// log();
|
|
17
|
+
// }, 5000);
|
|
18
|
+
})
|
|
19
|
+
})
|
package/tests/index.js
ADDED