-
Notifications
You must be signed in to change notification settings - Fork 455
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Dynamic import #5703
Dynamic import #5703
Changes from 13 commits
7968709
dd3c38d
e92854f
236f32e
b327950
18abaf8
d550479
738043f
feeaa73
faf65be
b5aec26
ae62547
6f0c22a
2b69527
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,8 +36,44 @@ let ensure_value_unit (st : Lam_compile_context.continuation) e : E.t = | |
| EffectCall Not_tail -> e | ||
(* NeedValue should return a meaningful expression*) | ||
|
||
let translate loc (cxt : Lam_compile_context.t) (prim : Lam_primitive.t) | ||
(args : J.expression list) : J.expression = | ||
let module_of_expression = function | ||
| J.Var (J.Qualified (module_id, value)) -> [ (module_id, value) ] | ||
| _ -> [] | ||
|
||
let get_module_system () = | ||
let package_info = Js_packages_state.get_packages_info () in | ||
let module_system = | ||
if Js_packages_info.is_empty package_info && !Js_config.js_stdout then | ||
[Js_packages_info.NodeJS] | ||
else Js_packages_info.map package_info (fun {module_system} -> module_system) | ||
in | ||
match module_system with | ||
| [module_system] -> module_system | ||
| _ -> NodeJS | ||
|
||
let import_of_path path = | ||
E.call | ||
~info:{ arity = Full; call_info = Call_na } | ||
(E.js_global "import") | ||
[ E.str path ] | ||
|
||
let wrap_then import value = | ||
let arg = Ident.create "m" in | ||
E.call | ||
~info:{ arity = Full; call_info = Call_na } | ||
(E.dot import "then") | ||
[ | ||
E.ocaml_fun ~return_unit:false ~async:false ~oneUnitArg:false [ arg ] | ||
[ | ||
{ | ||
statement_desc = J.Return (E.dot (E.var arg) value); | ||
comment = None; | ||
}; | ||
]; | ||
] | ||
|
||
let translate output_prefix loc (cxt : Lam_compile_context.t) | ||
(prim : Lam_primitive.t) (args : J.expression list) : J.expression = | ||
match prim with | ||
| Pis_not_none -> Js_of_lam_option.is_not_none (Ext_list.singleton_exn args) | ||
| Pcreate_extension s -> E.make_exception s | ||
|
@@ -78,6 +114,31 @@ let translate loc (cxt : Lam_compile_context.t) (prim : Lam_primitive.t) | |
| _ -> E.runtime_call Js_runtime_modules.option "nullable_to_opt" args | ||
) | ||
| _ -> assert false) | ||
(* Compile #import: The module argument for dynamic import is represented as a path, | ||
and the module value is expressed through wrapping it with promise.then *) | ||
| Pimport -> ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add comment describing what the transformation does. |
||
match args with | ||
| [ e ] -> ( | ||
let output_dir = Filename.dirname output_prefix in | ||
|
||
let module_id, module_value = | ||
match module_of_expression e.expression_desc with | ||
| [ module_ ] -> module_ | ||
| _ -> Location.raise_errorf ~loc | ||
"Invalid argument: Dynamic import requires a module or module value that is a file as argument. Passing a value or local module is not allowed." | ||
in | ||
|
||
let path = | ||
let module_system = get_module_system () in | ||
Js_name_of_module_id.string_of_module_id module_id ~output_dir module_system | ||
in | ||
|
||
match module_value with | ||
| Some value -> wrap_then (import_of_path path) value | ||
| None -> import_of_path path) | ||
| [] | _ -> | ||
Location.raise_errorf ~loc | ||
"Invalid argument: Dynamic import must take a single module or module value as its argument.") | ||
| Pjs_function_length -> E.function_length (Ext_list.singleton_exn args) | ||
| Pcaml_obj_length -> E.obj_length (Ext_list.singleton_exn args) | ||
| Pis_null -> E.is_null (Ext_list.singleton_exn args) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,6 +29,7 @@ | |
*) | ||
|
||
val translate : | ||
string -> | ||
Location.t -> | ||
Lam_compile_context.t -> | ||
Lam_primitive.t -> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,36 @@ let create_await_expression (e : Parsetree.expression) = | |
{txt = Ldot (Ldot (Lident "Js", "Promise"), "unsafe_await"); loc} | ||
in | ||
Ast_helper.Exp.apply ~loc unsafe_await [(Nolabel, e)] | ||
|
||
(* Transform `@res.await M` to unpack(@res.await Js.import(module(M: __M0__))) *) | ||
let create_await_module_expression ~module_type_name (e : Parsetree.module_expr) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment explaining what this transformation does. |
||
= | ||
let open Ast_helper in | ||
let remove_await_attribute = | ||
List.filter (fun ((loc, _) : Parsetree.attribute) -> loc.txt != "res.await") | ||
in | ||
{ | ||
e with | ||
pmod_desc = | ||
Pmod_unpack | ||
(create_await_expression | ||
(Exp.apply ~loc:e.pmod_loc | ||
(Exp.ident ~loc:e.pmod_loc | ||
{ | ||
txt = Longident.Ldot (Lident "Js", "import"); | ||
loc = e.pmod_loc; | ||
}) | ||
[ | ||
( Nolabel, | ||
Exp.constraint_ ~loc:e.pmod_loc | ||
(Exp.pack ~loc:e.pmod_loc | ||
{ | ||
e with | ||
pmod_attributes = | ||
remove_await_attribute e.pmod_attributes; | ||
}) | ||
(Typ.package ~loc:e.pmod_loc | ||
{txt = Lident module_type_name; loc = e.pmod_loc} | ||
[]) ); | ||
])); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -424,6 +424,13 @@ let local_module_name = | |
incr v; | ||
"local_" ^ string_of_int !v | ||
|
||
(* Unpack requires core_type package for type inference: | ||
Generate a module type name eg. __Belt_List__*) | ||
let local_module_type_name txt = | ||
"_" | ||
^ (Longident.flatten txt |> List.fold_left (fun ll l -> ll ^ "_" ^ l) "") | ||
^ "__" | ||
|
||
let expand_reverse (stru : Ast_structure.t) (acc : Ast_structure.t) : | ||
Ast_structure.t = | ||
if stru = [] then acc | ||
|
@@ -456,14 +463,15 @@ let expand_reverse (stru : Ast_structure.t) (acc : Ast_structure.t) : | |
} | ||
:: acc) | ||
|
||
let rec structure_mapper (self : mapper) (stru : Ast_structure.t) = | ||
let rec structure_mapper ~await_context (self : mapper) (stru : Ast_structure.t) | ||
= | ||
match stru with | ||
| [] -> [] | ||
| item :: rest -> ( | ||
match item.pstr_desc with | ||
| Pstr_extension (({txt = "bs.raw" | "raw"; loc}, payload), _attrs) -> | ||
Ast_exp_handle_external.handle_raw_structure loc payload | ||
:: structure_mapper self rest | ||
:: structure_mapper ~await_context self rest | ||
(* | Pstr_extension (({txt = "i"}, _),_) | ||
-> | ||
structure_mapper self rest *) | ||
|
@@ -483,10 +491,58 @@ let rec structure_mapper (self : mapper) (stru : Ast_structure.t) = | |
next | ||
| PSig _ | PTyp _ | PPat _ -> | ||
Location.raise_errorf ~loc "private extension is not support") | ||
| _ -> expand_reverse acc (structure_mapper self rest) | ||
| _ -> expand_reverse acc (structure_mapper ~await_context self rest) | ||
in | ||
aux [] stru | ||
| _ -> self.structure_item self item :: structure_mapper self rest) | ||
(* Dynamic import of module transformation: module M = @res.await Belt.List *) | ||
| Pstr_module | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a comment explaining what this transformation does. |
||
({pmb_expr = {pmod_desc = Pmod_ident {txt; loc}; pmod_attributes} as me} | ||
as mb) | ||
when Res_parsetree_viewer.hasAwaitAttribute pmod_attributes -> | ||
let item = self.structure_item self item in | ||
let safe_module_type_name = local_module_type_name txt in | ||
let has_local_module_name = | ||
Hashtbl.find_opt !await_context safe_module_type_name | ||
in | ||
(* module __Belt_List__ = module type of Belt.List *) | ||
let module_type_decl = | ||
match has_local_module_name with | ||
| Some _ -> [] | ||
| None -> | ||
let open Ast_helper in | ||
Hashtbl.add !await_context safe_module_type_name safe_module_type_name; | ||
[ | ||
Str.modtype ~loc | ||
(Mtd.mk ~loc | ||
{txt = safe_module_type_name; loc} | ||
~typ:(Mty.typeof_ ~loc me)); | ||
] | ||
in | ||
module_type_decl | ||
@ (* module M = @res.await Belt.List *) | ||
{ | ||
item with | ||
pstr_desc = | ||
Pstr_module | ||
{ | ||
mb with | ||
pmb_expr = | ||
Ast_await.create_await_module_expression | ||
~module_type_name:safe_module_type_name mb.pmb_expr; | ||
}; | ||
} | ||
:: structure_mapper ~await_context self rest | ||
| _ -> | ||
self.structure_item self item :: structure_mapper ~await_context self rest | ||
) | ||
|
||
let structure_mapper ~await_context (self : mapper) (stru : Ast_structure.t) = | ||
let await_saved = !await_context in | ||
let result = | ||
structure_mapper ~await_context:(ref (Hashtbl.create 10)) self stru | ||
in | ||
await_context := await_saved; | ||
result | ||
|
||
let mapper : mapper = | ||
{ | ||
|
@@ -497,7 +553,7 @@ let mapper : mapper = | |
signature_item = signature_item_mapper; | ||
value_bindings = Ast_tuple_pattern_flatten.value_bindings_mapper; | ||
structure_item = structure_item_mapper; | ||
structure = structure_mapper; | ||
structure = structure_mapper ~await_context:(ref (Hashtbl.create 10)); | ||
(* Ad-hoc way to internalize stuff *) | ||
label_declaration = | ||
(fun self lbl -> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/rescript-lang/rescript-compiler/blob/master/jscomp/core/lam_compile_main.ml#L294-L297
Not sure in what cases the module_system in Js_packages_info can be multiple in one runtime. For now, I've handled the case where there is only one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps we need some tests on fairly large projects.