From 6ec6e2c7b64d2cfce6f3210995f165437bae9bf7 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Thu, 23 May 2024 21:13:42 +0200 Subject: [PATCH] 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. --- .../admin-vite-plugin/src/plugin.ts | 67 ++++++++++++++----- .../dashboard/src/lib/extension-helpers.ts | 11 ++- packages/admin-next/dashboard/src/module.d.ts | 2 +- .../product-detail/product-detail.tsx | 27 +++----- .../products/product-list/product-list.tsx | 17 +++++ packages/admin-next/dashboard/vite.config.mts | 49 ++++++++------ 6 files changed, 114 insertions(+), 59 deletions(-) diff --git a/packages/admin-next/admin-vite-plugin/src/plugin.ts b/packages/admin-next/admin-vite-plugin/src/plugin.ts index ba079037eb..3abb6162ee 100644 --- a/packages/admin-next/admin-vite-plugin/src/plugin.ts +++ b/packages/admin-next/admin-vite-plugin/src/plugin.ts @@ -424,8 +424,7 @@ function createRoutePath(file: string) { async function generateRouteEntrypoint( sources: Set, - type: "page" | "link", - base = "" + type: "page" | "link" ) { const files = ( await Promise.all( @@ -464,7 +463,7 @@ async function generateRouteEntrypoint( ${type}s: [${validatedRoutes .map((file, index) => { return type === "page" - ? `{ path: "${createRoutePath(file)}", file: "${base + file}" }` + ? `{ path: "${createRoutePath(file)}", Component: RouteExt${index} }` : `{ path: "${createRoutePath(file)}", ...routeConfig${index} }` }) .join(", ")}], @@ -496,7 +495,6 @@ export type MedusaVitePlugin = (config?: MedusaVitePluginOptions) => Vite.Plugin export const medusaVitePlugin: MedusaVitePlugin = (options) => { const _extensionGraph = new Map>() const _sources = new Set(options?.sources ?? []) - let _base = "" let server: Vite.ViteDevServer | undefined let watcher: Vite.FSWatcher | undefined @@ -507,7 +505,7 @@ export const medusaVitePlugin: MedusaVitePlugin = (options) => { return await generateWidgetEntrypoint(_sources, options.get) } case "route": - return await generateRouteEntrypoint(_sources, options.get, _base) + return await generateRouteEntrypoint(_sources, options.get) default: return null } @@ -580,6 +578,55 @@ export const medusaVitePlugin: MedusaVitePlugin = (options) => { _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(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") { @@ -732,16 +779,6 @@ export const medusaVitePlugin: MedusaVitePlugin = (options) => { return { name: "@medusajs/admin-vite-plugin", enforce: "pre", - configResolved(config) { - if (config.server?.middlewareMode) { - /** - * If we are in middleware mode, we need to set the base to the + "@fs". - * - * This ensures that the page components are lazy-loaded correctly. - */ - _base = `${config.base}@fs` - } - }, configureServer(_server) { server = _server watcher = _server.watcher diff --git a/packages/admin-next/dashboard/src/lib/extension-helpers.ts b/packages/admin-next/dashboard/src/lib/extension-helpers.ts index d7dc724519..7a68a34923 100644 --- a/packages/admin-next/dashboard/src/lib/extension-helpers.ts +++ b/packages/admin-next/dashboard/src/lib/extension-helpers.ts @@ -6,14 +6,14 @@ import { RouteObject } from "react-router-dom" export const settingsRouteRegex = /^\/settings\// export const createRouteMap = ( - routes: { path: string; file: string }[], + routes: { path: string; Component: () => JSX.Element }[], ignore?: string ): RouteObject[] => { const root: RouteObject[] = [] const addRoute = ( pathSegments: string[], - file: string, + Component: () => JSX.Element, currentLevel: RouteObject[] ) => { if (!pathSegments.length) { @@ -33,23 +33,22 @@ export const createRouteMap = ( route.children.push({ path: "", async lazy() { - const { default: Component } = await import(/* @vite-ignore */ file) return { Component } }, }) } else { 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 const cleanedPath = ignore ? path.replace(ignore, "").replace(/^\/+/, "") : path.replace(/^\/+/, "") const pathSegments = cleanedPath.split("/").filter(Boolean) - addRoute(pathSegments, file, root) + addRoute(pathSegments, Component, root) }) return root diff --git a/packages/admin-next/dashboard/src/module.d.ts b/packages/admin-next/dashboard/src/module.d.ts index 1b69e9b6da..1a902bc062 100644 --- a/packages/admin-next/dashboard/src/module.d.ts +++ b/packages/admin-next/dashboard/src/module.d.ts @@ -7,7 +7,7 @@ declare module "virtual:medusa/widgets/*" { } declare module "virtual:medusa/routes/pages" { - const pages: { path: string; file: string }[] + const pages: { path: string; Component: () => JSX.Element }[] export default { pages, diff --git a/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx b/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx index d67e76cb18..781cd3d088 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-detail/product-detail.tsx @@ -1,23 +1,20 @@ import { Outlet, useLoaderData, useParams } from "react-router-dom" import { JsonViewSection } from "../../../components/common/json-view-section" +import { useProduct } from "../../../hooks/api/products" import { ProductAttributeSection } from "./components/product-attribute-section" import { ProductGeneralSection } from "./components/product-general-section" import { ProductMediaSection } from "./components/product-media-section" import { ProductOptionSection } from "./components/product-option-section" +import { ProductOrganizationSection } from "./components/product-organization-section" import { ProductSalesChannelSection } from "./components/product-sales-channel-section" import { ProductVariantSection } from "./components/product-variant-section" import { productLoader } from "./loader" -// import after from "medusa-admin:widgets/product/details/after" -// @ts-ignore - virtual module -// import obj from "virtual:config" +import after from "virtual:medusa/widgets/product/details/after" import before from "virtual:medusa/widgets/product/details/before" -// import sideAfter from "medusa-admin:widgets/product/details/side/after" -// import sideBefore from "medusa-admin:widgets/product/details/side/before" - -import { useProduct } from "../../../hooks/api/products" -import { ProductOrganizationSection } from "./components/product-organization-section" +import sideAfter from "virtual:medusa/widgets/product/details/side/after" +import sideBefore from "virtual:medusa/widgets/product/details/side/before" // TODO: Use product domain translations only export const ProductDetail = () => { @@ -53,37 +50,35 @@ export const ProductDetail = () => { - {/* {after.widgets.map((w, i) => { + {after.widgets.map((w, i) => { return (
) - })} */} - + })}
- {/* {sideBefore.widgets.map((w, i) => { + {sideBefore.widgets.map((w, i) => { return (
) - })} */} + })} - {/* {sideAfter.widgets.map((w, i) => { + {sideAfter.widgets.map((w, i) => { return (
) - })} */} - + })}
diff --git a/packages/admin-next/dashboard/src/routes/products/product-list/product-list.tsx b/packages/admin-next/dashboard/src/routes/products/product-list/product-list.tsx index a5f36e79f5..5d6c6a244e 100644 --- a/packages/admin-next/dashboard/src/routes/products/product-list/product-list.tsx +++ b/packages/admin-next/dashboard/src/routes/products/product-list/product-list.tsx @@ -1,9 +1,26 @@ 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 = () => { return (
+ {before.widgets.map((w, i) => { + return ( +
+ +
+ ) + })} + {after.widgets.map((w, i) => { + return ( +
+ +
+ ) + })}
) } diff --git a/packages/admin-next/dashboard/vite.config.mts b/packages/admin-next/dashboard/vite.config.mts index b95e67ec79..ae25c274ec 100644 --- a/packages/admin-next/dashboard/vite.config.mts +++ b/packages/admin-next/dashboard/vite.config.mts @@ -1,26 +1,33 @@ import inject from "@medusajs/admin-vite-plugin" import react from "@vitejs/plugin-react" -import { defineConfig } from "vite" - -const BASE = "/" -const BACKEND_URL = "http://localhost:9000" - -console.log(inject) +import { defineConfig, loadEnv } from "vite" // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - react(), - inject({ - debug: true, - sources: ["/Users/kasperkristensen/work/v2-server/src/admin"], - }), - ], - define: { - __BASE__: JSON.stringify(BASE), - __BACKEND_URL__: JSON.stringify(BACKEND_URL), - }, - server: { - open: true, - }, +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: [ + react(), + inject({ + sources, + }), + ], + define: { + __BASE__: JSON.stringify(BASE), + __BACKEND_URL__: JSON.stringify(BACKEND_URL), + }, + server: { + open: true, + }, + } })