@@ -420,14 +420,151 @@ mod bundle {
420420 }
421421}
422422
423+ #[ cfg( target_arch = "wasm32" ) ]
424+ mod bundle {
425+ use super :: * ;
426+ use napi:: { Env , JsFunction , JsString , NapiRaw , NapiValue , Ref } ;
427+ use std:: cell:: UnsafeCell ;
428+
429+ #[ js_function( 1 ) ]
430+ pub fn bundle ( ctx : CallContext ) -> napi:: Result < JsUnknown > {
431+ use transformer:: JsVisitor ;
432+
433+ let opts = ctx. get :: < JsObject > ( 0 ) ?;
434+ let mut visitor = if let Ok ( visitor) = opts. get_named_property :: < JsObject > ( "visitor" ) {
435+ Some ( JsVisitor :: new ( * ctx. env , visitor) )
436+ } else {
437+ None
438+ } ;
439+
440+ let resolver = opts. get_named_property :: < JsObject > ( "resolver" ) ?;
441+ let read = resolver. get_named_property :: < JsFunction > ( "read" ) ?;
442+ let resolve = if resolver. has_named_property ( "resolve" ) ? {
443+ let resolve = resolver. get_named_property :: < JsFunction > ( "resolve" ) ?;
444+ Some ( ctx. env . create_reference ( resolve) ?)
445+ } else {
446+ None
447+ } ;
448+ let config: BundleConfig = ctx. env . from_js_value ( opts) ?;
449+
450+ let provider = JsSourceProvider {
451+ env : ctx. env . clone ( ) ,
452+ resolve,
453+ read : ctx. env . create_reference ( read) ?,
454+ inputs : UnsafeCell :: new ( Vec :: new ( ) ) ,
455+ } ;
456+
457+ // This is pretty silly, but works around a rust limitation that you cannot
458+ // explicitly annotate lifetime bounds on closures.
459+ fn annotate < ' i , ' o , F > ( f : F ) -> F
460+ where
461+ F : FnOnce ( & mut StyleSheet < ' i , ' o , AtRule < ' i > > ) -> napi:: Result < ( ) > ,
462+ {
463+ f
464+ }
465+
466+ let res = compile_bundle (
467+ & provider,
468+ & config,
469+ visitor. as_mut ( ) . map ( |visitor| annotate ( |stylesheet| stylesheet. visit ( visitor) ) ) ,
470+ ) ;
471+
472+ match res {
473+ Ok ( res) => res. into_js ( * ctx. env ) ,
474+ Err ( err) => Err ( err. into_js_error ( * ctx. env , None ) ?) ,
475+ }
476+ }
477+
478+ struct JsSourceProvider {
479+ env : Env ,
480+ resolve : Option < Ref < ( ) > > ,
481+ read : Ref < ( ) > ,
482+ inputs : UnsafeCell < Vec < * mut String > > ,
483+ }
484+
485+ impl Drop for JsSourceProvider {
486+ fn drop ( & mut self ) {
487+ if let Some ( resolve) = & mut self . resolve {
488+ drop ( resolve. unref ( self . env ) ) ;
489+ }
490+ drop ( self . read . unref ( self . env ) ) ;
491+ }
492+ }
493+
494+ unsafe impl Sync for JsSourceProvider { }
495+ unsafe impl Send for JsSourceProvider { }
496+
497+ // This relies on Binaryen's Asyncify transform to allow Rust to call async JS functions from sync code.
498+ // See the comments in async.mjs for more details about how this works.
499+ extern "C" {
500+ fn await_promise_sync (
501+ promise : napi:: sys:: napi_value ,
502+ result : * mut napi:: sys:: napi_value ,
503+ error : * mut napi:: sys:: napi_value ,
504+ ) ;
505+ }
506+
507+ fn get_result ( env : Env , mut value : JsUnknown ) -> napi:: Result < JsString > {
508+ if value. is_promise ( ) ? {
509+ let mut result = std:: ptr:: null_mut ( ) ;
510+ let mut error = std:: ptr:: null_mut ( ) ;
511+ unsafe { await_promise_sync ( value. raw ( ) , & mut result, & mut error) } ;
512+ if !error. is_null ( ) {
513+ let error = unsafe { JsUnknown :: from_raw ( env. raw ( ) , error) ? } ;
514+ return Err ( napi:: Error :: from ( error) ) ;
515+ }
516+ if result. is_null ( ) {
517+ return Err ( napi:: Error :: new ( napi:: Status :: GenericFailure , "No result" . into ( ) ) ) ;
518+ }
519+
520+ value = unsafe { JsUnknown :: from_raw ( env. raw ( ) , result) ? } ;
521+ }
522+
523+ value. try_into ( )
524+ }
525+
526+ impl SourceProvider for JsSourceProvider {
527+ type Error = napi:: Error ;
528+
529+ fn read < ' a > ( & ' a self , file : & Path ) -> Result < & ' a str , Self :: Error > {
530+ let read: JsFunction = self . env . get_reference_value_unchecked ( & self . read ) ?;
531+ let file = self . env . create_string ( file. to_str ( ) . unwrap ( ) ) ?;
532+ let mut source: JsUnknown = read. call ( None , & [ file] ) ?;
533+ let source = get_result ( self . env , source) ?. into_utf8 ( ) ?. into_owned ( ) ?;
534+
535+ // cache the result
536+ let ptr = Box :: into_raw ( Box :: new ( source) ) ;
537+ let inputs = unsafe { & mut * self . inputs . get ( ) } ;
538+ inputs. push ( ptr) ;
539+ // SAFETY: this is safe because the pointer is not dropped
540+ // until the JsSourceProvider is, and we never remove from the
541+ // list of pointers stored in the vector.
542+ Ok ( unsafe { & * ptr } )
543+ }
544+
545+ fn resolve ( & self , specifier : & str , originating_file : & Path ) -> Result < PathBuf , Self :: Error > {
546+ if let Some ( resolve) = & self . resolve {
547+ let resolve: JsFunction = self . env . get_reference_value_unchecked ( resolve) ?;
548+ let specifier = self . env . create_string ( specifier) ?;
549+ let originating_file = self . env . create_string ( originating_file. to_str ( ) . unwrap ( ) ) ?;
550+ let result: JsUnknown = resolve. call ( None , & [ specifier, originating_file] ) ?;
551+ let result = get_result ( self . env , result) ?. into_utf8 ( ) ?;
552+ Ok ( PathBuf :: from_str ( result. as_str ( ) ?) . unwrap ( ) )
553+ } else {
554+ Ok ( originating_file. with_file_name ( specifier) )
555+ }
556+ }
557+ }
558+ }
559+
423560#[ cfg_attr( not( target_arch = "wasm32" ) , module_exports) ]
424561fn init ( mut exports : JsObject ) -> napi:: Result < ( ) > {
425562 exports. create_named_method ( "transform" , transform) ?;
426563 exports. create_named_method ( "transformStyleAttribute" , transform_style_attribute) ?;
564+ exports. create_named_method ( "bundle" , bundle:: bundle) ?;
427565
428566 #[ cfg( not( target_arch = "wasm32" ) ) ]
429567 {
430- exports. create_named_method ( "bundle" , bundle:: bundle) ?;
431568 exports. create_named_method ( "bundleAsync" , bundle:: bundle_async) ?;
432569 }
433570
0 commit comments