feat(dashboard,admin-vite-plugin): Add product zones and fix zone change effect (#7427)

**What**
- Adds injection zones to the product domain.
- Fixes an issue where changing the `zone` in a widget config to another valid widget would not trigger a HMR event.
- Fixes an issue where UI Routes would not work in production.
This commit is contained in:
Kasper Fabricius Kristensen
2024-05-23 19:13:42 +00:00
committed by GitHub
parent 8a070d5d85
commit 6ec6e2c7b6
6 changed files with 114 additions and 59 deletions
@@ -424,8 +424,7 @@ function createRoutePath(file: string) {
async function generateRouteEntrypoint( async function generateRouteEntrypoint(
sources: Set<string>, sources: Set<string>,
type: "page" | "link", type: "page" | "link"
base = ""
) { ) {
const files = ( const files = (
await Promise.all( await Promise.all(
@@ -464,7 +463,7 @@ async function generateRouteEntrypoint(
${type}s: [${validatedRoutes ${type}s: [${validatedRoutes
.map((file, index) => { .map((file, index) => {
return type === "page" return type === "page"
? `{ path: "${createRoutePath(file)}", file: "${base + file}" }` ? `{ path: "${createRoutePath(file)}", Component: RouteExt${index} }`
: `{ path: "${createRoutePath(file)}", ...routeConfig${index} }` : `{ path: "${createRoutePath(file)}", ...routeConfig${index} }`
}) })
.join(", ")}], .join(", ")}],
@@ -496,7 +495,6 @@ export type MedusaVitePlugin = (config?: MedusaVitePluginOptions) => Vite.Plugin
export const medusaVitePlugin: MedusaVitePlugin = (options) => { export const medusaVitePlugin: MedusaVitePlugin = (options) => {
const _extensionGraph = new Map<string, Set<string>>() const _extensionGraph = new Map<string, Set<string>>()
const _sources = new Set<string>(options?.sources ?? []) const _sources = new Set<string>(options?.sources ?? [])
let _base = ""
let server: Vite.ViteDevServer | undefined let server: Vite.ViteDevServer | undefined
let watcher: Vite.FSWatcher | undefined let watcher: Vite.FSWatcher | undefined
@@ -507,7 +505,7 @@ export const medusaVitePlugin: MedusaVitePlugin = (options) => {
return await generateWidgetEntrypoint(_sources, options.get) return await generateWidgetEntrypoint(_sources, options.get)
} }
case "route": case "route":
return await generateRouteEntrypoint(_sources, options.get, _base) return await generateRouteEntrypoint(_sources, options.get)
default: default:
return null return null
} }
@@ -580,6 +578,55 @@ export const medusaVitePlugin: MedusaVitePlugin = (options) => {
_extensionGraph.set(file, imports) _extensionGraph.set(file, imports)
} }
if (_extensionGraph.has(file)) {
const modules = _extensionGraph.get(file)
if (!modules) {
return
}
for (const moduleId of modules) {
const module = server?.moduleGraph.getModuleById(moduleId)
if (!module || !module.id) {
continue
}
const matchedInjectionZone = getWidgetZone(module.id)
/**
* If the widget is imported in a module that does not match the new
* zone value, we need to reload the module, so the widget will be removed.
*/
if (!zoneValues.includes(matchedInjectionZone)) {
modules.delete(moduleId)
await server?.reloadModule(module)
}
}
const imports = new Set<string>(modules)
/**
* If the widget is not currently being imported by the virtual module that
* matches its zone value, we need to reload the module, so the widget will be added.
*/
for (const zoneValue of zoneValues) {
const zonePath = getWidgetImport(zoneValue)
const moduleId = getVirtualId(zonePath)
const resolvedModuleId = resolveVirtualId(moduleId)
if (!modules.has(resolvedModuleId)) {
const module = server?.moduleGraph.getModuleById(resolvedModuleId)
if (module) {
imports.add(resolvedModuleId)
await server?.reloadModule(module)
}
}
}
_extensionGraph.set(file, imports)
}
} }
if (event === "add") { if (event === "add") {
@@ -732,16 +779,6 @@ export const medusaVitePlugin: MedusaVitePlugin = (options) => {
return { return {
name: "@medusajs/admin-vite-plugin", name: "@medusajs/admin-vite-plugin",
enforce: "pre", enforce: "pre",
configResolved(config) {
if (config.server?.middlewareMode) {
/**
* If we are in middleware mode, we need to set the base to the <base> + "@fs".
*
* This ensures that the page components are lazy-loaded correctly.
*/
_base = `${config.base}@fs`
}
},
configureServer(_server) { configureServer(_server) {
server = _server server = _server
watcher = _server.watcher watcher = _server.watcher
@@ -6,14 +6,14 @@ import { RouteObject } from "react-router-dom"
export const settingsRouteRegex = /^\/settings\// export const settingsRouteRegex = /^\/settings\//
export const createRouteMap = ( export const createRouteMap = (
routes: { path: string; file: string }[], routes: { path: string; Component: () => JSX.Element }[],
ignore?: string ignore?: string
): RouteObject[] => { ): RouteObject[] => {
const root: RouteObject[] = [] const root: RouteObject[] = []
const addRoute = ( const addRoute = (
pathSegments: string[], pathSegments: string[],
file: string, Component: () => JSX.Element,
currentLevel: RouteObject[] currentLevel: RouteObject[]
) => { ) => {
if (!pathSegments.length) { if (!pathSegments.length) {
@@ -33,23 +33,22 @@ export const createRouteMap = (
route.children.push({ route.children.push({
path: "", path: "",
async lazy() { async lazy() {
const { default: Component } = await import(/* @vite-ignore */ file)
return { Component } return { Component }
}, },
}) })
} else { } else {
route.children ||= [] route.children ||= []
addRoute(remainingSegments, file, route.children) addRoute(remainingSegments, Component, route.children)
} }
} }
routes.forEach(({ path, file }) => { routes.forEach(({ path, Component }) => {
// Remove the ignore segment from the path if it is provided // Remove the ignore segment from the path if it is provided
const cleanedPath = ignore const cleanedPath = ignore
? path.replace(ignore, "").replace(/^\/+/, "") ? path.replace(ignore, "").replace(/^\/+/, "")
: path.replace(/^\/+/, "") : path.replace(/^\/+/, "")
const pathSegments = cleanedPath.split("/").filter(Boolean) const pathSegments = cleanedPath.split("/").filter(Boolean)
addRoute(pathSegments, file, root) addRoute(pathSegments, Component, root)
}) })
return root return root
+1 -1
View File
@@ -7,7 +7,7 @@ declare module "virtual:medusa/widgets/*" {
} }
declare module "virtual:medusa/routes/pages" { declare module "virtual:medusa/routes/pages" {
const pages: { path: string; file: string }[] const pages: { path: string; Component: () => JSX.Element }[]
export default { export default {
pages, pages,
@@ -1,23 +1,20 @@
import { Outlet, useLoaderData, useParams } from "react-router-dom" import { Outlet, useLoaderData, useParams } from "react-router-dom"
import { JsonViewSection } from "../../../components/common/json-view-section" import { JsonViewSection } from "../../../components/common/json-view-section"
import { useProduct } from "../../../hooks/api/products"
import { ProductAttributeSection } from "./components/product-attribute-section" import { ProductAttributeSection } from "./components/product-attribute-section"
import { ProductGeneralSection } from "./components/product-general-section" import { ProductGeneralSection } from "./components/product-general-section"
import { ProductMediaSection } from "./components/product-media-section" import { ProductMediaSection } from "./components/product-media-section"
import { ProductOptionSection } from "./components/product-option-section" import { ProductOptionSection } from "./components/product-option-section"
import { ProductOrganizationSection } from "./components/product-organization-section"
import { ProductSalesChannelSection } from "./components/product-sales-channel-section" import { ProductSalesChannelSection } from "./components/product-sales-channel-section"
import { ProductVariantSection } from "./components/product-variant-section" import { ProductVariantSection } from "./components/product-variant-section"
import { productLoader } from "./loader" import { productLoader } from "./loader"
// import after from "medusa-admin:widgets/product/details/after" import after from "virtual:medusa/widgets/product/details/after"
// @ts-ignore - virtual module
// import obj from "virtual:config"
import before from "virtual:medusa/widgets/product/details/before" import before from "virtual:medusa/widgets/product/details/before"
// import sideAfter from "medusa-admin:widgets/product/details/side/after" import sideAfter from "virtual:medusa/widgets/product/details/side/after"
// import sideBefore from "medusa-admin:widgets/product/details/side/before" import sideBefore from "virtual:medusa/widgets/product/details/side/before"
import { useProduct } from "../../../hooks/api/products"
import { ProductOrganizationSection } from "./components/product-organization-section"
// TODO: Use product domain translations only // TODO: Use product domain translations only
export const ProductDetail = () => { export const ProductDetail = () => {
@@ -53,37 +50,35 @@ export const ProductDetail = () => {
<ProductMediaSection product={product} /> <ProductMediaSection product={product} />
<ProductOptionSection product={product} /> <ProductOptionSection product={product} />
<ProductVariantSection product={product} /> <ProductVariantSection product={product} />
{/* {after.widgets.map((w, i) => { {after.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
<w.Component /> <w.Component />
</div> </div>
) )
})} */} })}
<div className="hidden lg:block"> <div className="hidden lg:block">
<JsonViewSection data={product} root="product" /> <JsonViewSection data={product} root="product" />
</div> </div>
</div> </div>
<div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 lg:mt-0 lg:max-w-[400px]"> <div className="mt-2 flex w-full max-w-[100%] flex-col gap-y-2 lg:mt-0 lg:max-w-[400px]">
{/* {sideBefore.widgets.map((w, i) => { {sideBefore.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
<w.Component /> <w.Component />
</div> </div>
) )
})} */} })}
<ProductSalesChannelSection product={product} /> <ProductSalesChannelSection product={product} />
<ProductOrganizationSection product={product} /> <ProductOrganizationSection product={product} />
<ProductAttributeSection product={product} /> <ProductAttributeSection product={product} />
{/* {sideAfter.widgets.map((w, i) => { {sideAfter.widgets.map((w, i) => {
return ( return (
<div key={i}> <div key={i}>
<w.Component /> <w.Component />
</div> </div>
) )
})} */} })}
<div className="lg:hidden"> <div className="lg:hidden">
<JsonViewSection data={product} root="product" /> <JsonViewSection data={product} root="product" />
</div> </div>
@@ -1,9 +1,26 @@
import { ProductListTable } from "./components/product-list-table" import { ProductListTable } from "./components/product-list-table"
import after from "virtual:medusa/widgets/product/list/after"
import before from "virtual:medusa/widgets/product/list/before"
export const ProductList = () => { export const ProductList = () => {
return ( return (
<div className="flex flex-col gap-y-2"> <div className="flex flex-col gap-y-2">
{before.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component />
</div>
)
})}
<ProductListTable /> <ProductListTable />
{after.widgets.map((w, i) => {
return (
<div key={i}>
<w.Component />
</div>
)
})}
</div> </div>
) )
} }
+16 -9
View File
@@ -1,19 +1,25 @@
import inject from "@medusajs/admin-vite-plugin" import inject from "@medusajs/admin-vite-plugin"
import react from "@vitejs/plugin-react" import react from "@vitejs/plugin-react"
import { defineConfig } from "vite" import { defineConfig, loadEnv } from "vite"
const BASE = "/"
const BACKEND_URL = "http://localhost:9000"
console.log(inject)
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
const BASE = env.VITE_MEDUSA_BASE || "/"
const BACKEND_URL = env.VITE_MEDUSA_BACKEND_URL || "http://localhost:9000"
/**
* Add this to your .env file to specify the project to load admin extensions from.
*/
const MEDUSA_PROJECT = env.VITE_MEDUSA_PROJECT || null
const sources = MEDUSA_PROJECT ? [MEDUSA_PROJECT] : []
return {
plugins: [ plugins: [
react(), react(),
inject({ inject({
debug: true, sources,
sources: ["/Users/kasperkristensen/work/v2-server/src/admin"],
}), }),
], ],
define: { define: {
@@ -23,4 +29,5 @@ export default defineConfig({
server: { server: {
open: true, open: true,
}, },
}
}) })