split sidebars + optimize generated files loading

This commit is contained in:
Shahed Nasser
2025-03-10 10:32:45 +02:00
parent 87b041800a
commit 8b1ee3705e
17 changed files with 26105 additions and 25913 deletions
+25 -7
View File
@@ -2,19 +2,37 @@
import { EditButton as UiEditButton } from "docs-ui"
import { usePathname } from "next/navigation"
import { useMemo } from "react"
import { generatedEditDates } from "../../generated/edit-dates.mjs"
import { useCallback, useEffect, useState } from "react"
const EditButton = () => {
const pathname = usePathname()
const [editDate, setEditDate] = useState<string | undefined>()
const editDate = useMemo(
() =>
// const editDate = useMemo(
// () =>
// (generatedEditDates as Record<string, string>)[
// `app${pathname.replace(/\/$/, "")}/page.mdx`
// ],
// [pathname]
// )
const loadEditDate = useCallback(async () => {
const generatedEditDates = (await import("../../generated/edit-dates.mjs"))
.generatedEditDates
setEditDate(
(generatedEditDates as Record<string, string>)[
`app${pathname.replace(/\/$/, "")}/page.mdx`
],
[pathname]
)
]
)
}, [pathname])
useEffect(() => {
void loadEditDate()
}, [loadEditDate])
if (!editDate) {
return <></>
}
return (
<UiEditButton
@@ -10,7 +10,6 @@ import {
} from "remark-rehype-plugins"
import MDXComponents from "@/components/MDXComponents"
import mdxOptions from "../../../mdx-options.mjs"
import { slugChanges } from "../../../generated/slug-changes.mjs"
import { filesMap } from "../../../generated/files-map.mjs"
import { Metadata } from "next"
import { cache } from "react"
@@ -112,6 +111,8 @@ const loadFile = cache(
const monoRepoPath = path.resolve("..", "..", "..")
const pathname = `/references/${slug.join("/")}`
const slugChanges = (await import("../../../generated/slug-changes.mjs"))
.slugChanges
const fileDetails =
slugChanges.find((f) => f.newSlug === pathname) ||
filesMap.find((f) => f.pathname === pathname)
@@ -1,31 +1,38 @@
"use client"
import { usePathname } from "next/navigation"
import { useMemo } from "react"
import { useCallback, useEffect, useState } from "react"
import { EditButton as UiEditButton } from "docs-ui"
import { filesMap } from "../../generated/files-map.mjs"
import { generatedEditDates } from "../../generated/edit-dates.mjs"
const EditButton = () => {
const pathname = usePathname()
const filePath = useMemo(
() => filesMap.find((file) => file.pathname === pathname),
[pathname]
)
const [editDate, setEditDate] = useState<string | undefined>()
const [filePath, setFilePath] = useState<string | undefined>()
const editDate = useMemo(
() =>
(generatedEditDates as Record<string, string>)[
const loadData = useCallback(async () => {
const filesMap = await import("../../generated/files-map.mjs")
const generatedEditDates = await import("../../generated/edit-dates.mjs")
setFilePath(
filesMap.filesMap.find((file) => file.pathname === pathname)?.filePath ||
undefined
)
setEditDate(
(generatedEditDates.generatedEditDates as Record<string, string>)[
`app${pathname.replace(/\/$/, "")}/page.mdx`
],
[pathname]
)
]
)
}, [pathname])
if (!filePath) {
useEffect(() => {
void loadData()
}, [loadData])
if (!editDate || !filePath) {
return <></>
}
return <UiEditButton filePath={filePath.filePath} editDate={editDate} />
return <UiEditButton filePath={filePath} editDate={editDate} />
}
export default EditButton
+2 -3
View File
@@ -1,5 +1,4 @@
import { DocsConfig, Sidebar } from "types"
import { generatedSidebars } from "../generated/sidebar.mjs"
import { DocsConfig } from "types"
import { globalConfig } from "docs-ui"
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
@@ -9,7 +8,7 @@ export const config: DocsConfig = {
titleSuffix: "Medusa Development Resources",
baseUrl,
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
sidebars: generatedSidebars as Sidebar.Sidebar[],
sidebars: [],
project: {
title: "Development Resources",
key: "resources",
@@ -0,0 +1,320 @@
const generatedgeneratedHowToTutorialsSidebarSidebar = {
"sidebar_id": "how-to-tutorials",
"title": "How-To & Tutorials",
"items": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/how-to-tutorials",
"title": "Overview",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/examples",
"title": "Example Snippets",
"children": []
},
{
"type": "separator"
},
{
"loaded": true,
"isPathHref": true,
"type": "category",
"title": "How-To Guides",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "sub-category",
"title": "Server",
"autogenerate_tags": "howTo+server",
"autogenerate_as_ref": true,
"sort_sidebar": "alphabetize",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Actor Type",
"path": "https://docs.medusajs.com/resources/commerce-modules/auth/create-actor-type",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Auth Provider",
"path": "https://docs.medusajs.com/resources/references/auth/provider",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Cache Module",
"path": "https://docs.medusajs.com/resources/architectural-modules/cache/create",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Event Module",
"path": "https://docs.medusajs.com/resources/architectural-modules/event/create",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Fulfillment Provider",
"path": "https://docs.medusajs.com/resources/references/fulfillment/provider",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Notification Provider",
"path": "https://docs.medusajs.com/resources/references/notification-provider-module",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Payment Provider",
"path": "https://docs.medusajs.com/resources/references/payment/provider",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Create Tax Provider",
"path": "https://docs.medusajs.com/resources/references/tax/provider",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Get Variant Price with Taxes",
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/price-with-taxes",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Get Variant Prices",
"path": "https://docs.medusajs.com/resources/commerce-modules/product/guides/price",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Handle Password Reset Event",
"path": "https://docs.medusajs.com/resources/commerce-modules/auth/reset-password",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Send Notification",
"path": "https://docs.medusajs.com/resources/architectural-modules/notification/send-notification",
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "sub-category",
"title": "Admin",
"autogenerate_tags": "howTo+admin",
"autogenerate_as_ref": true,
"sort_sidebar": "alphabetize",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/admin-components",
"title": "Overview",
"children": []
},
{
"type": "separator"
},
{
"loaded": true,
"isPathHref": true,
"type": "category",
"title": "Components",
"autogenerate_path": "/admin-components/components",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "category",
"title": "Layouts",
"autogenerate_path": "/admin-components/layouts",
"children": []
}
]
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "category",
"title": "Tutorials",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"title": "Custom Item Pricing",
"path": "/examples/guides/custom-item-price",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"title": "Wishlist",
"path": "/plugins/guides/wishlist",
"description": "Learn how to build a wishlist plugin.",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "sub-category",
"title": "Extend Modules",
"autogenerate_tags": "tutorial+extendModule",
"autogenerate_as_ref": true,
"sort_sidebar": "alphabetize",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Extend Cart",
"path": "https://docs.medusajs.com/resources/commerce-modules/cart/extend",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Extend Customer",
"path": "https://docs.medusajs.com/resources/commerce-modules/customer/extend",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Extend Product",
"path": "https://docs.medusajs.com/resources/commerce-modules/product/extend",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "ref",
"title": "Extend Promotion",
"path": "https://docs.medusajs.com/resources/commerce-modules/promotion/extend",
"children": []
}
]
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "category",
"title": "Deployment",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/deployment",
"title": "Overview",
"children": []
},
{
"type": "separator"
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"title": "Medusa Cloud",
"path": "https://medusajs.com/pricing",
"children": []
},
{
"type": "separator"
},
{
"loaded": true,
"isPathHref": true,
"type": "category",
"title": "Self-Hosting",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "https://docs.medusajs.com/learn/deployment/general",
"title": "General",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/deployment/medusa-application/railway",
"title": "Railway",
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "category",
"title": "Next.js Starter",
"autogenerate_path": "/deployment/storefront",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/deployment/storefront/vercel",
"title": "Vercel",
"description": "",
"children": []
}
]
}
]
}
]
}
export default generatedgeneratedHowToTutorialsSidebarSidebar
@@ -0,0 +1,168 @@
const generatedgeneratedRecipesSidebarSidebar = {
"sidebar_id": "recipes",
"title": "Recipes",
"items": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes",
"title": "Overview",
"children": []
},
{
"type": "separator"
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/marketplace",
"title": "Marketplace",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/marketplace/examples/vendors",
"title": "Example: Vendors",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/marketplace/examples/restaurant-delivery",
"title": "Example: Restaurant-Delivery",
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/subscriptions",
"title": "Subscriptions",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/subscriptions/examples/standard",
"title": "Example",
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/digital-products",
"title": "Digital Products",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/digital-products/examples/standard",
"title": "Example",
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/erp",
"title": "Integrate ERP",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/erp/odoo",
"title": "Example: Odoo Integration",
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/b2b",
"title": "B2B",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/commerce-automation",
"title": "Commerce Automation",
"children": [
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/commerce-automation/restock-notification",
"title": "Example: Restock Notifications",
"children": []
}
]
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/ecommerce",
"title": "Ecommerce",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/multi-region-store",
"title": "Multi-Region Store",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/omnichannel",
"title": "Omnichannel Store",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/oms",
"title": "OMS",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/personalized-products",
"title": "Personalized Products",
"children": []
},
{
"loaded": true,
"isPathHref": true,
"type": "link",
"path": "/recipes/pos",
"title": "POS",
"children": []
}
]
}
export default generatedgeneratedRecipesSidebarSidebar
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -149,7 +149,7 @@ const nextConfig = {
"*": ["node_modules/@medusajs/icons"],
},
experimental: {
optimizePackageImports: ["@medusajs/icons", "@medusajs/ui"],
optimizePackageImports: ["@medusajs/icons", "@medusajs/ui", "elkjs"],
},
}
+31 -2
View File
@@ -1,9 +1,13 @@
"use client"
import {
SidebarProvider as UiSidebarProvider,
useScrollController,
} from "docs-ui"
import { config } from "@/config"
import { usePathname } from "next/navigation"
import { useCallback, useEffect, useState } from "react"
import { Sidebar } from "types"
import { getSidebarForPath } from "../utils/get-sidebar-for-path"
type SidebarProviderProps = {
children?: React.ReactNode
@@ -11,11 +15,36 @@ type SidebarProviderProps = {
const SidebarProvider = ({ children }: SidebarProviderProps) => {
const { scrollableElement } = useScrollController()
const [sidebar, setSidebar] = useState<Sidebar.Sidebar | undefined>()
const pathname = usePathname()
const loadSidebar = useCallback(
async () => getSidebarForPath(pathname),
[pathname]
)
useEffect(() => {
loadSidebar()
.then(setSidebar)
.catch((error) => {
console.error("Error loading sidebar:", error)
})
}, [loadSidebar])
return (
<UiSidebarProvider
scrollableElement={scrollableElement}
sidebars={config.sidebars}
sidebars={
sidebar
? [sidebar]
: [
{
sidebar_id: "default",
title: "Default",
items: [],
},
]
}
>
{children}
</UiSidebarProvider>
+8 -2
View File
@@ -1,10 +1,16 @@
import { generateEditedDates, generateSidebar } from "build-scripts"
import {
generateEditedDates,
generateSidebar,
generateSplitSidebars,
} from "build-scripts"
import { main as generateSlugChanges } from "./generate-slug-changes.mjs"
import { main as generateFilesMap } from "./generate-files-map.mjs"
import { sidebar } from "../sidebar.mjs"
async function main() {
await generateSidebar(sidebar)
await generateSplitSidebars({
sidebars: sidebar,
})
// await generateSlugChanges()
// await generateFilesMap()
// await generateEditedDates()
@@ -0,0 +1,53 @@
import { Sidebar } from "types"
const getDefaultSidebar = async () =>
import("@/generated/generated-resources-sidebar.mjs") as Promise<{
default: Sidebar.Sidebar
}>
const sidebarMappings: {
module: () => Promise<{ default: Sidebar.Sidebar }>
paths: string[]
}[] = [
{
module: async () =>
import("@/generated/generated-recipes-sidebar.mjs") as Promise<{
default: Sidebar.Sidebar
}>,
paths: ["/recipes/"],
},
{
module: async () =>
import("@/generated/generated-how-to-tutorials-sidebar.mjs") as Promise<{
default: Sidebar.Sidebar
}>,
paths: [
"/how-to-tutorials",
"/examples",
"/admin-components",
"/plugins/guides",
"/deployment",
],
},
]
export async function getSidebarForPath(
currentPath: string
): Promise<Sidebar.Sidebar> {
const sidebarMapping = sidebarMappings.find(({ paths }) =>
paths.some((path) => {
if (currentPath.startsWith(path)) {
return true
}
const regex = new RegExp(`^${path.replace(/\/$/, "")}(/|$)`)
return regex.test(currentPath)
})
)
if (sidebarMapping) {
const sidebarModule = await sidebarMapping.module()
return sidebarModule.default
}
return await getDefaultSidebar().then((module) => module.default)
}
@@ -2,19 +2,29 @@
import { EditButton as UiEditButton } from "docs-ui"
import { usePathname } from "next/navigation"
import { useMemo } from "react"
import { generatedEditDates } from "../../generated/edit-dates.mjs"
import { useCallback, useEffect, useState } from "react"
const EditButton = () => {
const pathname = usePathname()
const [editDate, setEditDate] = useState<string | undefined>()
const editDate = useMemo(
() =>
const loadEditDate = useCallback(async () => {
const generatedEditDates = (await import("../../generated/edit-dates.mjs"))
.generatedEditDates
setEditDate(
(generatedEditDates as Record<string, string>)[
`app${pathname.replace(/\/$/, "")}/page.mdx`
],
[pathname]
)
]
)
}, [pathname])
useEffect(() => {
void loadEditDate()
}, [loadEditDate])
if (!editDate) {
return <></>
}
return (
<UiEditButton
@@ -12,14 +12,26 @@ export type ItemsToAdd = Sidebar.SidebarItem & {
sidebar_position?: number
}
type GenerateSidebarOptions = {
export type GenerateSidebarOptions = {
addNumbering?: boolean
writeToFile?: boolean
}
const customGenerators: Record<string, () => Promise<ItemsToAdd[]>> = {
"core-flows": getCoreFlowsRefSidebarChildren,
}
function sortItems(itemA: ItemsToAdd, itemB: ItemsToAdd): number {
const itemASidebarPosition = itemA.sidebar_position || 0
const itemBSidebarPosition = itemB.sidebar_position || 0
if (itemASidebarPosition > itemBSidebarPosition) {
return 1
}
return itemASidebarPosition < itemBSidebarPosition ? -1 : 0
}
async function getAutogeneratedSidebarItems(
dir: string,
nested = false
@@ -211,21 +223,27 @@ async function checkItems(
export async function generateSidebar(
sidebars: Sidebar.RawSidebar[],
options?: GenerateSidebarOptions
): Promise<void> {
): Promise<void | Sidebar.RawSidebar[]> {
const path = await import("path")
const { writeFileSync } = await import("fs")
const normalizedSidebars: Sidebar.RawSidebar[] = []
const { writeToFile = true, ...restOptions } = options || {}
validateSidebarUniqueIds(sidebars)
for (const sidebarItem of sidebars) {
normalizedSidebars.push({
...sidebarItem,
items: await checkItems(sidebarItem.items, options),
items: await checkItems(sidebarItem.items, restOptions),
})
}
if (!writeToFile) {
return normalizedSidebars
}
const generatedDirPath = path.resolve("generated")
if (!existsSync(generatedDirPath)) {
@@ -245,14 +263,3 @@ export async function generateSidebar(
}
)
}
function sortItems(itemA: ItemsToAdd, itemB: ItemsToAdd): number {
const itemASidebarPosition = itemA.sidebar_position || 0
const itemBSidebarPosition = itemB.sidebar_position || 0
if (itemASidebarPosition > itemBSidebarPosition) {
return 1
}
return itemASidebarPosition < itemBSidebarPosition ? -1 : 0
}
@@ -0,0 +1,51 @@
import { Sidebar } from "types"
import { generateSidebar, GenerateSidebarOptions } from "./index.js"
import path from "path"
import { existsSync, mkdirSync } from "fs"
import { writeFile } from "fs/promises"
function toCamelCase(str: string) {
return str
.replace(/-([a-z])/g, (g) => g[1].toUpperCase())
.replace(/^./, (g) => g.toUpperCase())
}
export async function generateSplitSidebars({
sidebars,
options,
}: {
sidebars: Sidebar.RawSidebar[]
options?: GenerateSidebarOptions
}) {
const generatedDirPath = path.resolve("generated")
if (!existsSync(generatedDirPath)) {
mkdirSync(generatedDirPath)
}
for (const sidebarItem of sidebars) {
const generatedSidebar = (
(await generateSidebar([sidebarItem], {
...options,
writeToFile: false,
})) as Sidebar.RawSidebar[]
)[0]
const varName = `generated${toCamelCase(sidebarItem.sidebar_id)}Sidebar`
await writeFile(
path.resolve(
generatedDirPath,
`generated-${sidebarItem.sidebar_id}-sidebar.mjs`
),
`const generated${varName}Sidebar = ${JSON.stringify(
generatedSidebar,
null,
2
)}\n\nexport default generated${varName}Sidebar`,
{
flag: "w",
}
)
}
}
+1
View File
@@ -1,6 +1,7 @@
export * from "./generate-edited-dates.js"
export * from "./generate-llms-full.js"
export * from "./generate-sidebar.js"
export * from "./generate-split-sidebars.js"
export * from "./retrieve-mdx-pages.js"
export * from "./utils/get-core-flows-ref-sidebar-children.js"
@@ -536,6 +536,12 @@ export const SidebarProvider = ({
)
}, [isBrowser, desktopSidebarOpen])
useEffect(() => {
if (initialSidebars[0].sidebar_id !== sidebars[0].sidebar_id) {
resetItems()
}
}, [initialSidebars])
const updatePersistedCategoryState = (title: string, opened: boolean) => {
const storageData = JSON.parse(
localStorage.getItem(categoriesStorageKey) || "{}"