🥣 Cereal-ize JSON data into literally-typed constants, enums, & options in Nuxt.
- Install the module
pnpm add nuxt-cereal
- Create a config
// ~/cereal.config.ts
import { defineCerealConfig } from "nuxt-cereal/config";
export default defineCerealConfig({
constants: {
foo: "an example string",
},
enums: {
bar: ["primary", "secondary", "tertiary"],
},
options: {
baz: [
{ key: 1, label: "One" },
{ key: 2, label: "Two" },
],
},
});
- Activate the module
// ~/nuxt.config.ts
import cereal from "./cereal.config";
export default defineNuxtConfig({
modules: ["nuxt-cereal"],
cereal,
});
- Eat some cereal, you are done!
Define a JSON config object for:
constants
A key/value collection of static strings & numbersenums
A key/value collection of static string arrays & number arraysoptions
A key/value collection of static option arrays w/key
&label
properties
The config object is "cereal-ized" into read-only literals & implemented in Nuxt using a set of utility types/composables.
Type templates are created using the JSON definitions & are automatically available in composables/components:
Type | Description |
---|---|
ConstantTemplate |
Template constants value type (string or number ) |
Constant |
String-literal keys of the constants cereal config |
ConstantValue<C extends Constant> |
Literal value of the provided constants config key |
EnumTemplate |
Template enums value type (string[] or number[] ) |
Enum |
String-literal keys of the enums cereal config |
EnumValue<E extends Enum> |
Literal values of the provided enums config key |
EnumValueItem<E extends Enum> |
Template values of the provided enums config key |
OptionTemplate |
Template options value type (key & label props) |
Option |
String-literal keys of the options cereal config |
OptionValue<O extends Option> |
Literal values of the provided options config key |
OptionValueItem<O extends Option> |
Template values of the provided options config key |
These utility types can be useful when creating components. Let's say we have a button component that has a variant
property that can be set to either filled
, outlined
, or plain
. We can configure that option as an enum
and use the helper types to define our component properties:
// ~/cereal.config.ts
export default defineCerealConfig({
enums: {
buttonVariant: ["filled", "outlined", "plain"],
},
});
<script setup lang="ts">
// ~/components/Button.vue
defineProps<{
variant: EnumValue<"buttonVariant">; // a string-literal type of the options in our config
}>();
</script>
<template>
<button :data-variant="variant">
<slot />
</button>
</template>
Type-safe utility functions that enable access to the configuration literal values are automatically imported in any component/composable:
Function | Description |
---|---|
isConstant(key) |
Check if a provided string is a constants key & cast to Constant if valid |
useConstant(key) |
Grab the literal value represented by the provided constants key |
useConstantsConfig() |
Grab the entire constants configuration as an object literal |
useConstantsKeys() |
Grab an array ofavailable constants configuration keys |
isEnum(key) |
Check if a provided string is a enums key & cast to Enum if valid |
useEnum(key) |
Grab the literal value represented by the provided enums key |
useEnumsConfig() |
Grab the entire enums configuration as an object literal |
useEnumsKeys() |
Grab an array ofavailable enums configuration keys |
isOption(key) |
Check if a provided string is a options key & cast to Option if valid |
useOption(key) |
Grab the literal value represented by the provided options key |
useOptionsConfig() |
Grab the entire options configuration as an object literal |
useOptionsKeys() |
Grab an array ofavailable options configuration keys |
These functions can be leveraged w/ generics to extend the power of our components even further. Imagine we have a pre-defined set of dropdowns needed for a form. We can quickly build a component that uses our cereal-ized data to provide a type-safe selector that only needs a key
to get started:
// ~/cereal.config.ts
export default defineCerealConfig({
options: {
breakfast: [
{ key: "cereal", label: "Cereal" },
{ key: "pancakes", label: "Pancakes" },
{ key: "eggs", label: "Eggs" },
],
lunch: [
{ key: "sandwhich", label: "Sandwhich" },
{ key: "soup", label: "Soup" },
{ key: "salad", label: "Salad" },
],
dinner: [
{ key: "steak", label: "Steak" },
{ key: "lobster", label: "Lobster" },
{ key: "risotto", label: "Risotto" },
],
},
});
<script setup lang="ts" generic="O extends Option">
// ~/components/Select.vue
const props = defineProps<{
option: O; // "breakfast" | "lunch" | "dinner"
modelValue?: OptionValueItem<O>["key"]; // if "breakfast" is the option, allowed values are "cereal" | "pancakes" | "eggs"
}>();
const emits = defineEmits<{
"update:modelValue": [OptionValueItem<O>];
}>();
const options = useOption(props.option);
const modelValue = useVModel(props, "modelValue", emits, { passive: true }); // https://vueuse.org/core/useVModel/#usevmodel
</script>
<template>
<select v-model="modelValue">
<option v-for="o in options" :key="o.key" :value="o.key">
{{ o.label }}
</option>
</select>
</template>
MIT License © 2024-PRESENT Alexander Thorwaldson