Skip to content

Instantly share code, notes, and snippets.

@crutchcorn
Created February 9, 2025 09:32
Show Gist options
  • Save crutchcorn/080eaa40e56eff7a7984d9c5821cbc77 to your computer and use it in GitHub Desktop.
Save crutchcorn/080eaa40e56eff7a7984d9c5821cbc77 to your computer and use it in GitHub Desktop.
Fun
interface BuildtimeComponentParts<TProps, TReturn> {
// This is the rehype transformer, only used when parsing markdown
// Either during build time or when the markdown is loaded in the CMS rich text editor
transform: TransformFn<TProps, TReturn>;
}
interface RuntimeComponentParts<T> {
// This is the markup for the component that will be rendered
component: string;
// This script is run when the component is mounted, but also when a blog post is displayed that has the component
// It is lazily-initialized, so if a blog post does not have the component, this script will not be run
// This lazy-initialization works by parsing the JS itself into a string, then saving it to a `public/scripts` path during build time
// This allows us to avoid `new Function` or `eval` which are security risks when evaluating network-provided code
setup: (props: RehypeSetupProps<T>) => void;
// Only used in the CMS, when a component is removed dynamically
takedown: (props: RehypeTakedownProps<T>) => void;
}
// .... Okay, enough explaining ......
interface WindowCounterData {
count: number;
}
declare global {
interface Window {
compInfo: Record<string, WindowCounterData>;
}
}
interface CounterProps {
initialCount?: number;
}
// `counter` is now an object with `{transform, component, setup, takedown}`
// This is a builder pattern, so you can chain calls to `withBuildTime` and `withRuntime` and infer the types from the previous call.
const counter = createComponent<CounterProps>()
.withBuildTime({
transform: async (props) => {
return { count: props.attributes?.["initialCount"] ?? 0 };
},
})
.withRuntime({
// Dynamically transform the JSX to HyperScript (Static build) or React.createElement (CMS build) via Recma
component: js`(props) => {
return <button>The count is {props.count}</button>;
}`,
setup: (props) => {
window.compInfo[props.uniqueId] = window.compInfo[props.uniqueId] ?? {};
const compInfoObj = window.compInfo[props.uniqueId];
compInfoObj.count = props.attributes.count ?? 0;
props.rootNode.addEventListener("click", () => {
compInfoObj.count++;
props.rootNode.textContent = `The count is ${compInfoObj.count}`;
});
},
takedown: (props) => {
delete window.compInfo[props.uniqueId];
},
});
export default {
counter,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment