Skip to content
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

Merged
merged 14 commits into from
Apr 23, 2023
Merged

Dynamic import #5703

merged 14 commits into from
Apr 23, 2023

Conversation

mununki
Copy link
Member

@mununki mununki commented Sep 27, 2022

Introduce the dynamic import (see forum RFC, discussion)

In this guide, we'll explain how to use the dynamic import for module and module value. This new feature allows you to express the dynamic import syntax in Javascript in ReScript without having to be concerned about the actual file names and paths. We'll go through several examples to help you to understand how to use this feature.

  1. Importing a module value
let forEach = await Js.import(Belt.Array.forEach)
let _ = [1, 2]->forEach(_ => ())

compiled to

var forEach = await import("rescript/lib/es6/belt_Array.js").then(function (m) {
  return m.forEach;
});
Curry._2(forEach, [1, 2], (function (param) { }));
  1. Importing a module
module M = await Belt.Array
let forEach2 = M.forEach
let _ = [1, 2]->forEach2(_ => ())

compiled to

var M = await import("rescript/lib/es6/belt_Array.js");
var forEach2 = M.forEach;
Curry._2(forEach2, [1, 2], (function (param) { }));
  1. Lazy importing a React.component
    In cases where React components need to be lazy-loaded, you can use the following approach:
// LazyComponent.res
@react.component
let make = () => React.string("Lazy")

// App.res
module LazyC = await LazyComponent
let c = <LazyC />

Parsetree

$ bsc -dparsetree import.res

# Dynamic import of value

  structure_item 
    Pstr_value Nonrec
    [
      <def>
        pattern 
          Ppat_var "forEach" 
        expression 
          Pexp_apply
          expression 
            Pexp_ident "Js.Promise.unsafe_await" 
          [
            <arg>
            Nolabel
              expression 
                attribute  "res.await"
                  []
                Pexp_apply
                expression 
                  Pexp_ident "Js.import" 
                [
                  <arg>
                  Nolabel
                    expression 
                      Pexp_ident "Belt.Array.forEach" 
                ]
          ]
    ]

# Dynamic import of module

  structure_item 
    Pstr_modtype "__BeltArray1__" 
      module_type 
        Pmty_typeof
        module_expr 
          attribute  "res.await"
            []
          Pmod_ident "Belt.Array" 
  structure_item 
    Pstr_module
    "M" 
      module_expr 
        attribute  "res.await"
          []
        Pmod_unpack
        expression 
          Pexp_apply
          expression 
            Pexp_ident "Js.Promise.unsafe_await" 
          [
            <arg>
            Nolabel
              expression 
                Pexp_apply
                expression 
                  Pexp_ident "Js.import" 
                [
                  <arg>
                  Nolabel
                    expression 
                      Pexp_constraint
                      expression 
                        Pexp_pack
                        module_expr
                          Pmod_ident "Belt.Array" 
                      core_type 
                        Ptyp_package "__BeltArray1__" 
                        []
                ]
          ]

Lambda

$ bsc -drawlambda Import.res

(let
  (forEach/1002 = (?await (#import (field:forEach/27 (global Belt_Array!)))))
  (seq (apply forEach/1002 (makearray 1 2) (function param/1102 void 0a))
    (let
      (M/1104 =
         (?await (#import (let (let/1347 =a 0a) (global Belt_Array!))))
       forEach2/1175 = (field:forEach/27 M/1104))
      (seq
        (apply forEach2/1175 (makearray 1 2) (function param/1176 void 0a))
        (makeblock module/exports forEach/1002 M/1104 forEach2/1175)))))
  • Add .then(m => m.value) for dynamic import a value into generated js output.
  • Check if module or value only for import arg
  • Construct J.module_id from J.expression a18edd1
  • Refer to the Js_package_info.module_system
  • Improving the module mechanism

@mununki mununki force-pushed the import-mwk branch 4 times, most recently from 229257d to 326977a Compare October 5, 2022 18:00
@mununki mununki changed the base branch from import to master October 5, 2022 18:00
@mununki mununki changed the title [WIP] Add muscles to skeleton for import Dynamic import Oct 9, 2022
@@ -0,0 +1,8 @@
let each = Js.import(Belt.List.forEach)
Copy link
Collaborator

@cristianoc cristianoc Oct 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps wrap it in lazy, or add () => and check that both ways it work fine.
In terms of: the code generated works, and the load happens on use.

Copy link
Collaborator

@cristianoc cristianoc Oct 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just double checking: the current example loads eagerly right?
Actually not sure whether it should. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I'll check after adding a test of lazy evaluation with () =>

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've fixed the lazy evalutation e864128

@mununki
Copy link
Member Author

mununki commented Oct 9, 2022

I'm a little afraid to arg drilling ~output_prefix for most of the functions in lam_compile.ml. Does it seem better to use a single ref?

Copy link
Collaborator

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't looked at the implementation yet, except trying examples, which seem to be working well and running fine from node.

Added comments for the module-level mechanism.

let _ = list{1, 2, 3}->eachIntAsync(n => Js.log2("async", n))

module type BeltList = module type of Belt.List
let beltAsModule = Js.import(module(Belt.List: BeltList))
Copy link
Collaborator

@cristianoc cristianoc Oct 9, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mattdamon108

Since this encoding is going to be painful to use, as discussed, what is an alternative encoding to represent:

module M = await import(Belt.List)

How about this:

module M = @res.await Belt.List

that's already the way normal await for values is currently represented internally.

Actual syntax sugar to be added later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I lost some of the context from the discussion. It's been a while to me 😃

module M = @res.await Belt.List

It makes sense to me. I'll fix it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the module mechanism

module M = @res.await Belt.List // 1
let each = M.forEach // 2

What would the js output be?

let each = import("...").then(m => m.forEach)

If so, how about without expression 2?

let m = import("...") // ??

Without the expression 2, only module binding generating the value seems a little weird to me. What do you think?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can dynamic import without the module mechanism:

let default = Js.import(Comp.default)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Regarding the module mechanism

module M = @res.await Belt.List // 1
let each = M.forEach // 2

What would the js output be?

let each = import("...").then(m => m.forEach)

If so, how about without expression 2?

let m = import("...") // ??

Without the expression 2, only module binding generating the value seems a little weird to me. What do you think?

Line 2 is another line. The mechanism should operate only on line 1.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cristianoc ⬆️ Is it what you intended?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cristianoc ⬆️ Is it what you intended?

That looks great. With capitalised "M" I guess.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a syntax question. module type of X is not available in inlined way?

module M = unpack(@res.await (Js.import(module(Belt.List: module type of Belt.List))))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

module type BeltList = module type of Belt.List
module M = unpack(@res.await (Js.import(module(Belt.List: BeltList))))
var M = await import("../../lib/js/belt_List.js");
var each = M.forEach;

This works fine, but the inlined module type of Belt.List would be better.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it can't be inlined, but not clear why.

@cristianoc
Copy link
Collaborator

I'm a little afraid to arg drilling ~output_prefix for most of the functions in lam_compile.ml. Does it seem better to use a single ref?

Or maybe neither? Not looked too much at the implementation so far, until the feature is complete for modules too.

Copy link
Collaborator

@cristianoc cristianoc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Started to look at the implementation code, just a bit.

In general, I'm now wondering what range of cases are supported, and what should happen when unsupported cases are used. Presumably some kind of error.
For example:

let three = Js.import(3)

or

module M = { let x = 10 }
let i = Js.import(M.x)

match args with
| [ e ] -> (
match e.expression_desc with
| _ -> (
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary pattern match

match module_value with
| Some value -> wrap_then (import_of_path path) value
| None -> import_of_path path)
| None -> assert false))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is None possible? If so what should happen?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not possible, I will clean up

let module_id, module_value =
match module_of_expression e.expression_desc with
| [ module_ ] -> module_
| _ -> assert false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's an example where this happens?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One of example would be let three = Js.import(3). It asserts false.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this could be a dedicated error message about import instead.

Js_packages_info.map packages_info (fun { module_system } -> module_system)
in
match module_systems with
| [] -> assert false
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the code, there's test mode where the module system is empty. Not sure what this implies in practice.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, when I print the dump lambda IR, the module system seems empty.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, when bsc is called directly.
How about making it more robust, by e.g. choosing a default if not found. Say es6.

(args : J.expression list) : J.expression =
let rec module_of_expression = function
| J.Var (J.Qualified (module_id, value)) -> [ (module_id, value) ]
| J.Caml_block (exprs, _, _, _) ->
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What set of cases is this match expected to cover?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need anymore. It was for the packaged module module(Belt.List). Now the packaged module will be unpacked.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is the place to give an error if the pattern is not recognised.
There are other places in this file where a warning is issued. Hopefully an error can be issued too.
It seems impossible to prevent errors by syntactic restrictions, as @res.await Belt.List is OK normally, but an error if there's a local module Belt.

@mununki
Copy link
Member Author

mununki commented Oct 12, 2022

I've implemented the module mechanism.

// original
module M = @res.await Belt.List

// transformed to
module type BeltList0 = module type of Belt.List // 1
module M = unpack(@res.await Js.import(module(Belt.List: BeltList0)))

I've looked into typecore, packaged module can't inferred the type with Pmty_typeof, instead Ptyp_package. I think I should add module binding as 1.

// output
var M = await import("../../lib/js/belt_List.js");

@cristianoc
Copy link
Collaborator

cristianoc commented Oct 12, 2022

I've implemented the module mechanism.

// original

module M = @res.await Belt.List



// transformed to

module type BeltList0 = module type of Belt.List // 1

module M = unpack(@res.await Js.import(module(Belt.List: BeltList0)))

I've looked into typecore, packaged module can't inferred the type with Pmty_typeof, instead Ptyp_package. I think I should add module binding as 1.

// output

var M = await import("../../lib/js/belt_List.js");

Can you print a dump of the lambda IR of the code generated by "module M = @res.async Belt.List"?

Still hoping that 90% of this can be avoided.

@mununki
Copy link
Member Author

mununki commented Oct 12, 2022

Can you print a dump of the lambda IR of the code generated by "module M = @res.async Belt.List"?

Still hoping that 90% of this can be avoided.

$ bsc -drawlambda -bs-loc jscomp/test/Import.res

(let (M/1092 = (?await (#import (let (let/1404 =a 0a) (global Belt_List!)))))
  (makeblock module/exports M/1092))

@cristianoc
Copy link
Collaborator

Can you print a dump of the lambda IR of the code generated by "module M = @res.async Belt.List"?

Still hoping that 90% of this can be avoided.


$ bsc -drawlambda -bs-loc jscomp/test/Import.res



(let (M/1092 = (?await (#import (let (let/1404 =a 0a) (global Belt_List!)))))

  (makeblock module/exports M/1092))

Can that same code be generated directly instead?

@mununki
Copy link
Member Author

mununki commented Oct 12, 2022

Can you print a dump of the lambda IR of the code generated by "module M = @res.async Belt.List"?

Still hoping that 90% of this can be avoided.


$ bsc -drawlambda -bs-loc jscomp/test/Import.res



(let (M/1092 = (?await (#import (let (let/1404 =a 0a) (global Belt_List!)))))

  (makeblock module/exports M/1092))

Can that same code be generated directly instead?

Sorry, I don't get your point. What do you mean generating the same code directly?

@cristianoc
Copy link
Collaborator

Can you print a dump of the lambda IR of the code generated by "module M = @res.async Belt.List"?

Still hoping that 90% of this can be avoided.


$ bsc -drawlambda -bs-loc jscomp/test/Import.res



(let (M/1092 = (?await (#import (let (let/1404 =a 0a) (global Belt_List!)))))

  (makeblock module/exports M/1092))

Can that same code be generated directly instead?

Sorry, I don't get your point. What do you mean generating the same code directly?

I was hoping one could use a more lightweight way to compile modules rather than generate the pack/unpack mode, like the compilation for value is. But it looks like compilation for modules is not so easy and unclear how to change to obtain the desired lambda code directly.

So just need to make sure that the generated code does not lead to surprising errors, such as repeated definition of the same module type (I think that's take care of automatically already) or other forms of errors.

@cristianoc
Copy link
Collaborator

For example this currently gives a type error:

module type BeltList1 = {}
module N  = @res.await Belt.List

@mununki
Copy link
Member Author

mununki commented Oct 13, 2022

I was hoping one could use a more lightweight way to compile modules rather than generate the pack/unpack mode, like the compilation for value is. But it looks like compilation for modules is not so easy and unclear how to change to obtain the desired lambda code directly.

Yes. I was exploring how to make a desirable lambda directly without pack/unpack, but no gain so far. Not clear with it.
Is there any other way to convey information to the lambda besides using external?

@mununki
Copy link
Member Author

mununki commented Dec 8, 2022

For example this currently gives a type error:

module type BeltList1 = {}
module N  = @res.await Belt.List

For quick fix for this case, I think this would be working.

module M = {
  module type BeltList0 = module type of Belt.List
  include unpack(await Js.import(module(Belt.List: BeltList0)))
}

@mununki
Copy link
Member Author

mununki commented Dec 9, 2022

For quick fix for this case, I think this would be working.

module M = {
  module type BeltList0 = module type of Belt.List
  include unpack(await Js.import(module(Belt.List: BeltList0)))
}

Not a good idea, using include and declaring module structure generate all the functions of Belt.List in js.

@mununki
Copy link
Member Author

mununki commented Dec 17, 2022

This PR gets too far from the latest, so I've made code diffs based on the latest.

@mununki
Copy link
Member Author

mununki commented Dec 20, 2022

Here's what I've explored to seek how to implement the module mechanism.

module M = @res.await Belt.List
  1. Preventing the alias with unpack and pack the module
    module M is an alias. The lambda representation would optimize it. Belt.List would be sitting in the final position that is using it. We can try to change the translmod to prevent alias but I'm afraid to do so to avoid any side effects. I think we can use pack first-class module to prevent alias.

  2. Unpack not working without core_type package
    Not sure why unpack is working only with core_type package, but it is. It leads me to add the type declaration:

    module type BeltList0 = module type of Belt.List

Currenlty, I think generating module type name safely as possible to avoid a surprising error such as __BeltList0__ would be the solution for the module mechanism implementation. Can you advise me where I can look into for better implementation? @cristianoc

@cristianoc
Copy link
Collaborator

Here's what I've explored to seek how to implement the module mechanism.

module M = @res.await Belt.List
  1. Preventing the alias with unpack and pack the module
    module M is an alias. The lambda representation would optimize it. Belt.List would be sitting in the final position that is using it. We can try to change the translmod to prevent alias but I'm afraid to do so to avoid any side effects. I think we can use pack first-class module to prevent alias.
  2. Unpack not working without core_type package
    Not sure why unpack is working only with core_type package, but it is. It leads me to add the type declaration:
    module type BeltList0 = module type of Belt.List

Currenlty, I think generating module type name safely as possible to avoid a surprising error such as __BeltList0__ would be the solution for the module mechanism implementation. Can you advise me where I can look into for better implementation? @cristianoc

Yes looks like getting something that works consistently is the way to go. The use of temporary module aliases is a bit indirect but the generated code seems fine. As long as unintended aliasing of the name is avoided, it should be good.

@pd4d10
Copy link

pd4d10 commented Mar 29, 2023

Looking forward to this feature. Any update?

@mununki
Copy link
Member Author

mununki commented Apr 23, 2023

Rebase on master is done.

@@ -25,6 +25,6 @@
(** Compile single lambda IR to JS IR *)

val compile_recursive_lets :
Lam_compile_context.t -> (Ident.t * Lam.t) list -> Js_output.t
output_prefix:string -> Js_packages_info.module_system -> Lam_compile_context.t -> (Ident.t * Lam.t) list -> Js_output.t
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About invasive:

This and the function below are the only 2 entry points where the 2 new parameters are passed in.
No need to thread them though each recursive call if they never change and are passed from the outside.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once the number of changes is reduced, it should be possible to just isolate what change is responsible.
E.g. perhaps first remove Pimport, then remove gradually every other change in turn, locally, until the tests stops failing and one finds out the root cause.

print_if_pipe ppf Clflags.dump_rawlambda Printlambda.lambda lambda
|> Lam_compile_main.compile outputprefix exports
|> Lam_compile_main.compile outputprefix module_system exports
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is the root cause breaking the tests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems delaying the evaluation of js_program causing the test failure.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See how it goes #6191

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bingo.

@@ -166,7 +166,7 @@ let after_parsing_impl ppf outputprefix (ast : Parsetree.structure) =
in
let js_program =
print_if_pipe ppf Clflags.dump_rawlambda Printlambda.lambda lambda
|> Lam_compile_main.compile outputprefix exports
|> Lam_compile_main.compile outputprefix NodeJS exports
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about passing NodeJS as always here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does not sound right. Unless, this is only used by the code you have added?
Can you explain the context more here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can try using Js_packages_info.t inside lam_compile_primitive directly where the module_system needs to know.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does not sound right. Unless, this is only used by the code you have added? Can you explain the context more here?

Pimport needs to know about the information what module_system is. That's why the argument drilling was all over the place in lam along with output_prefix. We can leave the output_prefix but module_system.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would help to first reduce the number of lines changes. As right now it's really hard to see where this is used.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eg. instead of
let rec foo x y ... and bar x y ... and ...
do

let toplevel x y =
let rec foo ... and bar ...

So most of the change becomes only indentation.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap, let me try experiments to get module_system from Js_packages_info inside Pimport.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeap, let me try experiments to get module_system from Js_packages_info inside Pimport.

Yes that would clean all of this, if it works.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side comment: The previous design did not require a module_system when writing a js_program with lam because it only needed a module_system to represent the import(top of file) and export(bottom of file) of the js output. However, the dynamic import feature requires a module_system in the js_program as well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Side side comment. I wonder for how long we'll need to support all these module systems. It would be nice to converge to es6 at some point.

- get module_system inside compiling Pimport primitive
@mununki
Copy link
Member Author

mununki commented Apr 23, 2023

Can't make reducing enough, because output_prefix still needs inside compiling lam primitive Pimport. Now it passes the test and looks fine to me ae62547

@mununki
Copy link
Member Author

mununki commented Apr 23, 2023

Syntax surface, parser for await Module is merged here too.

Comment on lines +43 to +52
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
Copy link
Member Author

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.

Copy link
Member Author

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.

@mununki
Copy link
Member Author

mununki commented Apr 23, 2023

Ah.. I got your point now 😄 2b69527

@cristianoc
Copy link
Collaborator

Anything left open in this PR?

@mununki
Copy link
Member Author

mununki commented Apr 23, 2023

There's nothing left at the moment.
I'm still digging into the compiler's js_package_info and module_system, but if there are improvements to be made, they can be made in a separate PR later.

@mununki mununki merged commit 1ec3a41 into master Apr 23, 2023
13 checks passed
@mununki mununki deleted the import-mwk branch April 23, 2023 16:21
@mununki mununki mentioned this pull request Apr 23, 2023
@cristianoc
Copy link
Collaborator

@mununki looks like the changelog was missing

@mununki
Copy link
Member Author

mununki commented Apr 23, 2023

Oops, It's been so long since I've worked on it that I forgot again. I'll PR it right away.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants