Packages
lustre
3.0.0-rc.2
5.7.1
5.7.0
5.6.0
5.5.2
5.5.1
5.5.0
5.4.0
5.3.5
5.3.4
5.3.3
5.3.2
5.3.1
5.3.0
5.2.1
5.2.0
5.1.1
5.1.0
5.0.3
5.0.2
5.0.1
5.0.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.1
4.5.0
4.4.4
4.4.3
4.4.1
4.4.0
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.8
4.1.7
4.1.6
4.1.5
4.1.4
4.1.3
4.1.2
4.1.1
4.1.0
4.0.0
4.0.0-rc1
4.0.0-rc.2
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.2
3.0.0-rc.1
2.0.1
2.0.0
1.3.0
1.2.0
1.1.0
1.0.0
Create HTML templates, single page applications, Web Components, and real-time server components in Gleam!
Current section
Files
Jump to
Current section
Files
src/lustre.ffi.mjs
import * as Cmd from "./lustre/cmd.mjs";
import * as React from "react";
import * as ReactDOM from "react-dom/client";
const Dispatcher = React.createContext(null);
export const mount = (app, selector) => {
const el = document.querySelector(selector);
if (!el) {
console.warn(
[
"[lustre] Oops, it looks like I couldnt find an element on the ",
'page matching the selector "' + selector + '".',
"",
"Hint: make sure you arent running your script before the rest of ",
"the HTML document has been parsed! you can add the `defer` attribute ",
"to your script tag to make sure that cant happen.",
].join("\n")
);
return Promise.reject();
}
let dispatchRef = null;
let dispatchPromise = new Promise((resolve) => (dispatchRef = resolve));
ReactDOM.createRoot(el).render(
React.createElement(
React.StrictMode,
null,
React.createElement(
React.forwardRef((_, ref) => {
// When wrapped in `<React.StrictMode />` and when in development
// mode, React will run effects (and some other hooks) twice to
// help us debug potential issues that arise from impurity.
//
// This is a problem for our cmds because they are intentionally
// impure. We can/should expect user code to be pure, but we want
// to allow top-level impurity in the form of cmds.
//
// So we can keep the benefits of strict mode, we add an additional
// bit of state to track whether we need to run the cmds we have or
// not.
const [shouldRunCmds, setShouldRunCmds] = React.useState(true);
const [[state, cmds], dispatch] = React.useReducer(
([state, _], msg) => {
// Every time we call the user's update function we'll get back a
// new lot of cmds to run, so we need to set this flag to true to
// let our `useEffect` know it can run them!
setShouldRunCmds(true);
return app.update(state, msg);
},
app.init
);
React.useImperativeHandle(ref, () => dispatch, [dispatch]);
React.useEffect(() => {
if (shouldRunCmds && cmds) {
for (const cmd of Cmd.to_list(cmds)) {
cmd(dispatch);
}
// Once we've performed the side effects, we'll toggle this flag
// back to false so we don't run them again on subsequent renders
// or during development.
setShouldRunCmds(false);
}
}, [cmds, shouldRunCmds]);
return React.createElement(
Dispatcher.Provider,
{ value: dispatch },
React.createElement(({ state }) => app.render(state), { state })
);
}),
{ ref: dispatchRef }
)
)
);
return dispatchPromise;
};
// ELEMENTS --------------------------------------------------------------------
//
export const node = (tag, attributes, children) => {
const dispatch = React.useContext(Dispatcher);
const props = to_props(attributes, dispatch);
try {
return React.createElement(tag, props, ...children.toArray());
} catch (_) {
console.warn([
"[lustre] Something went wrong while trying to render a node with the ",
'tag "' + tag + "\". To prevent a runtime crash, I'm going to render an ",
"empty text node instead.",
"",
"Hint: make sure you arent trying to render a node with a tag that ",
"is compatible with the renderer you are using. For example, you can't ",
'render a "div" node with the terminal renderer.',
"",
"If you think this might be a bug, please open an issue at ",
"https://github.com/hayleigh-dot-dev/gleam-lustre/issues",
]);
return "";
}
};
//
export const stateful = (init, render) => {
const [state, setState] = React.useState(init);
return React.createElement(() => render(state, setState));
};
//
export const text = (content) => content;
//
export const fragment = (children) => {
return React.createElement(React.Fragment, {}, ...children.toArray());
};
//
export const map = (element, f) =>
React.createElement(() => {
const dispatch = React.useContext(Dispatcher);
const mappedDispatch = React.useCallback(
(msg) => dispatch(f(msg)),
[dispatch]
);
return React.createElement(
Dispatcher.Provider,
{ value: mappedDispatch },
React.createElement(element)
);
});
// HOOKS -----------------------------------------------------------------------
export const useLustreInternalDispatch = () => {
return React.useContext(Dispatcher);
};
// UTILS -----------------------------------------------------------------------
// This function takes a Gleam `List` of key/value pairs (in the form of a Gleam
// tuple, which is in turn a JavaScript array) and converts it into a normal
// JavaScript object.
//
export const to_object = (entries) => Object.fromEntries(entries.toArray());
const capitalise = (s = "") => s[0].toUpperCase() + s.slice(1);
const to_props = (attributes, dispatch) => {
return attributes.toArray().reduce((props, attr) => {
// The constructors for the `Attribute` type are not public in the
// gleam source to prevent users from constructing them directly.
// This has the unfortunate side effect of not letting us `instanceof`
// the constructors to pattern match on them and instead we have to
// rely on the structure to work out what kind of attribute it is.
//
if ("name" in attr && "value" in attr) {
const prop =
attr.name in props && typeof props[attr.name] === "string"
? props[attr.name] + " " + attr.value
: attr.value;
return { ...props, [attr.name]: prop };
}
// This case handles `Event` variants.
else if ("name" in attr && "handler" in attr) {
const name = "on" + capitalise(attr.name);
const handler = (e) => attr.handler(e, dispatch);
return { ...props, [name]: handler };
}
// This should Never Happen™️ but if it does we don't want everything
// to explode, so we'll print a friendly error, ignore the attribute
// and carry on as normal.
//
else {
console.warn(
[
"[lustre] Oops, I'm not sure how to handle attributes with ",
'the type "' + attr.constructor.name + '". Did you try calling ',
"this function from JavaScript by mistake?",
"",
"If not, it might be an error in lustre itself. Please open ",
"an issue at https://github.com/hayleigh-dot-dev/gleam-lustre/issues",
].join("\n")
);
return props;
}
}, {});
};