Hello!
I need help to write a functor that enforces the relation between a Next.js page and it’s corresponding getServerSide props function.
First I tried creating a single functor that takes a type, then forces all the passed down functions (make page and also getServerSideProps) to return/accept compatible types. That was my desired solution because it was strongly typed, I can annotate the output of the functor, so only what is needed is exposed in the exports, and everything was nice and awesome until I faced the reality.
For reference, here is the code of the functor that compiles, is correct, but does not work in the real world:
module type PageSignature = {
[@decco]
type t;
let name: string;
let getServerSideProps:
Next.GetServerSideProps.context => PromiseResult.t(t, string);
let make: t => React.element;
};
module type GeneratedPage = {
let getServerSideProps:
Next.GetServerSideProps.context => Promise.t(Js.Json.t);
let default: (~props: Js.Json.t) => React.element;
};
module Make = (S: PageSignature) : GeneratedPage => {
[@decco]
type serverProps = {props: S.t};
// ... bla bla bla
}
If I have this already, why am I bothering you? well, the problem, is that I must do the serverSideProps and the default export assignments separated, because NextJs is not smart enough to remove code that is only used in getServerSideProps.
Any reference of any BE library to any intermediary variable will produce an import error.
To give you an example, here is what using that functor looks like:
include Dev__Page.Make({
[@decco]
type t = string;
let name = "Stories";
let make = title =>
<div>
<h1> title->React.string </h1>
<main> "Wow, worked"->React.string </main>
</div>;
let getServerSideProps = _ => referenceToBeFunction()
});
And the produced code (simplified):
// imports that use BE deps that are then used inside getServerSideProps
var include = Dev__Page$.Make({
t_encode: t_encode,
t_decode: t_decode,
name: "Stories",
getServerSideProps: getServerSideProps,
make: make
});
var getServerSideProps$1 = include.getServerSideProps;
var $$default = include.$$default;
export {
getServerSideProps$1 as getServerSideProps,
$$default ,
$$default as default,
}
Do you see those intermediary assignments? like getServerSideProps: getServerSideProps
and var getServerSideProps$1 = include.getServerSideProps;
. That is enough to break Next.js code split algorithm
(If you want a fantastic explanation of the problem this stack-overflow answer is awesome)
Then I thought that maybe I can make two functors, one depending on the output of the other and use the result of the second functor to generate the server side props, but I can’t imagine how to make this work.
I also tried returning a function in the functor that gets another function and wraps it to ensure the types match, but then the outside is not able to see the type of GeneratedPage.t
and therefore it fails to type check. Modified version fo the functor to do this:
type getProps('t) =
Next.GetServerSideProps.context => PromiseResult.t('t, string);
module type PageSignature = {
[@decco]
type t;
let name: string;
let make: t => React.element;
};
module type GeneratedPage = {
type t;
let makeGetServerSideProps:
(getProps(t), Next.GetServerSideProps.context) => Promise.t(Js.Json.t);
let default: (~props: Js.Json.t) => React.element;
};
module Make = (S: PageSignature) : GeneratedPage => {
/* same bla bla as before */
}
Then use it like this:
module P =
Dev__Page.Make({
[@decco]
type t = theReturnedType;
let name = "Stories";
let make = //bla bla bla react
});
let default = P.default;
let getServerSideProps =
P.makeGetServerSideProps( something => theReturnedType );
But, as I said, makeGetServerSideProps only accepts GeneratedPage.t
, so it is not valid.
Why do I need a functor in the first place? Well, there are some particularities around Next.js injecting props into the component, serialisation and error handling that I want to abstract, and I think a functor is the right tool for this and create a nice JS output.