Skip to content

Commit

Permalink
Merge branch 'develop' into feat/data-table-medusa-ui
Browse files Browse the repository at this point in the history
  • Loading branch information
kasperkristensen authored Jan 13, 2025
2 parents a0f0c49 + 1ba2fad commit 51f97f1
Show file tree
Hide file tree
Showing 13 changed files with 313 additions and 89 deletions.
7 changes: 7 additions & 0 deletions .changeset/stupid-plums-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@medusajs/admin-vite-plugin": patch
"@medusajs/admin-bundler": patch
"@medusajs/medusa": patch
---

feat(admin-bundler,admin-vite-plugin): Support loading loading admin extensions from plugins.
1 change: 1 addition & 0 deletions packages/admin/admin-bundler/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.16",
"compression": "^1.7.4",
"glob": "^11.0.0",
"postcss": "^8.4.32",
"tailwindcss": "^3.3.6",
"vite": "^5.2.11"
Expand Down
1 change: 0 additions & 1 deletion packages/admin/admin-bundler/src/lib/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { VIRTUAL_MODULES } from "@medusajs/admin-shared"
import path from "path"
import { Config } from "tailwindcss"
import type { InlineConfig } from "vite"

import { BundlerOptions } from "../types"

export async function getViteConfig(
Expand Down
57 changes: 57 additions & 0 deletions packages/admin/admin-bundler/src/lib/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { readFileSync } from "fs"
import { glob } from "glob"
import path from "path"
import { UserConfig } from "vite"

export async function plugin() {
const vite = await import("vite")
const entries = await glob("src/admin/**/*.{ts,tsx,js,jsx}")

const entryPoints = entries.reduce((acc, entry) => {
// Convert src/admin/routes/brands/page.tsx -> admin/routes/brands/page
const outPath = entry
.replace(/^src\//, "")
.replace(/\.(ts|tsx|js|jsx)$/, "")

acc[outPath] = path.resolve(process.cwd(), entry)
return acc
}, {} as Record<string, string>)

const pkg = JSON.parse(
readFileSync(path.resolve(process.cwd(), "package.json"), "utf-8")
)
const external = new Set([
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.peerDependencies || {}),
"react",
"react-dom",
"react/jsx-runtime",
"react-router-dom",
"@medusajs/admin-sdk",
])

const pluginConfig: UserConfig = {
build: {
lib: {
entry: entryPoints,
formats: ["es"],
},
minify: false,
outDir: path.resolve(process.cwd(), "dist"),
rollupOptions: {
external: [...external],
output: {
globals: {
react: "React",
"react-dom": "React-dom",
"react/jsx-runtime": "react/jsx-runtime",
},
preserveModules: true,
entryFileNames: `[name].js`,
},
},
},
}

await vite.build(pluginConfig)
}
4 changes: 4 additions & 0 deletions packages/admin/admin-vite-plugin/src/babel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ import {
isTemplateLiteral,
isVariableDeclaration,
isVariableDeclarator,
Node,
ObjectExpression,
ObjectMethod,
ObjectProperty,
SpreadElement,
StringLiteral,
VariableDeclarator,
} from "@babel/types"

/**
Expand Down Expand Up @@ -58,6 +60,7 @@ export type {
ExportDefaultDeclaration,
ExportNamedDeclaration,
File,
Node,
NodePath,
ObjectExpression,
ObjectMethod,
Expand All @@ -66,4 +69,5 @@ export type {
ParserOptions,
SpreadElement,
StringLiteral,
VariableDeclarator,
}
106 changes: 78 additions & 28 deletions packages/admin/admin-vite-plugin/src/routes/generate-menu-items.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import {
NESTED_ROUTE_POSITIONS,
NestedRoutePosition,
} from "@medusajs/admin-shared"
import fs from "fs/promises"
import { outdent } from "outdent"
import {
File,
isIdentifier,
isObjectProperty,
isStringLiteral,
Node,
ObjectProperty,
parse,
ParseResult,
traverse,
Expand All @@ -16,7 +23,6 @@ import {
normalizePath,
} from "../utils"
import { getRoute } from "./helpers"
import { NESTED_ROUTE_POSITIONS } from "@medusajs/admin-shared"

type RouteConfig = {
label: boolean
Expand Down Expand Up @@ -142,48 +148,47 @@ async function getRouteConfig(file: string): Promise<RouteConfig | null> {
}

let config: RouteConfig | null = null
let configFound = false

try {
traverse(ast, {
ExportNamedDeclaration(path) {
/**
* For bundled files, the config will not be a named export,
* but instead a variable declaration.
*/
VariableDeclarator(path) {
if (configFound) {
return
}

const properties = getConfigObjectProperties(path)
if (!properties) {
return
}

const hasProperty = (name: string) =>
properties.some(
(prop) => isObjectProperty(prop) && isIdentifier(prop.key, { name })
)
config = processConfigProperties(properties, file)

const hasLabel = hasProperty("label")
if (!hasLabel) {
if (config) {
configFound = true
}
},
/**
* For unbundled files, the `config` will always be a named export.
*/
ExportNamedDeclaration(path) {
if (configFound) {
return
}

const nested = properties.find(
(prop) =>
isObjectProperty(prop) && isIdentifier(prop.key, { name: "nested" })
)

const nestedValue = nested ? (nested as any).value.value : undefined

if (nestedValue && !NESTED_ROUTE_POSITIONS.includes(nestedValue)) {
logger.error(
`Invalid nested route position: "${nestedValue}". Allowed values are: ${NESTED_ROUTE_POSITIONS.join(
", "
)}`,
{
file,
}
)
const properties = getConfigObjectProperties(path)
if (!properties) {
return
}

config = {
label: hasLabel,
icon: hasProperty("icon"),
nested: nestedValue,
config = processConfigProperties(properties, file)

if (config) {
configFound = true
}
},
})
Expand All @@ -197,6 +202,51 @@ async function getRouteConfig(file: string): Promise<RouteConfig | null> {
return config
}

function processConfigProperties(
properties: Node[],
file: string
): RouteConfig | null {
const hasProperty = (name: string) =>
properties.some(
(prop) => isObjectProperty(prop) && isIdentifier(prop.key, { name })
)

const hasLabel = hasProperty("label")
if (!hasLabel) {
return null
}

const nested = properties.find(
(prop) =>
isObjectProperty(prop) && isIdentifier(prop.key, { name: "nested" })
) as ObjectProperty | undefined

let nestedValue: string | undefined = undefined

if (isStringLiteral(nested?.value)) {
nestedValue = nested.value.value
}

if (
nestedValue &&
!NESTED_ROUTE_POSITIONS.includes(nestedValue as NestedRoutePosition)
) {
logger.error(
`Invalid nested route position: "${nestedValue}". Allowed values are: ${NESTED_ROUTE_POSITIONS.join(
", "
)}`,
{ file }
)
return null
}

return {
label: hasLabel,
icon: hasProperty("icon"),
nested: nestedValue,
}
}

function generateRouteConfigName(index: number): string {
return `RouteConfig${index}`
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type RouteResult = {
export async function generateRoutes(sources: Set<string>) {
const files = await getFilesFromSources(sources)
const results = await getRouteResults(files)

const imports = results.map((result) => result.imports).flat()
const code = generateCode(results)

Expand Down
11 changes: 9 additions & 2 deletions packages/admin/admin-vite-plugin/src/routes/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { normalizePath } from "../utils"
import { normalizePath, VALID_FILE_EXTENSIONS } from "../utils"

export function getRoute(file: string): string {
const importPath = normalizePath(file)
return importPath
.replace(/.*\/admin\/(routes)/, "")
.replace(/\[([^\]]+)\]/g, ":$1")
.replace(/\/page\.(tsx|jsx)/, "")
.replace(
new RegExp(
`/page\\.(${VALID_FILE_EXTENSIONS.map((ext) => ext.slice(1)).join(
"|"
)})$`
),
""
)
}
46 changes: 44 additions & 2 deletions packages/admin/admin-vite-plugin/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
type ExportNamedDeclaration,
type NodePath,
type ParserOptions,
type VariableDeclarator,
} from "./babel"

export function normalizePath(file: string) {
Expand Down Expand Up @@ -48,7 +49,7 @@ export function generateModule(code: string) {
}
}

const VALID_FILE_EXTENSIONS = [".tsx", ".jsx"]
export const VALID_FILE_EXTENSIONS = [".tsx", ".jsx", ".js"]

/**
* Crawls a directory and returns all files that match the criteria.
Expand Down Expand Up @@ -96,8 +97,25 @@ export async function crawl(
* Extracts and returns the properties of a `config` object from a named export declaration.
*/
export function getConfigObjectProperties(
path: NodePath<ExportNamedDeclaration>
path: NodePath<ExportNamedDeclaration | VariableDeclarator>
) {
if (isVariableDeclarator(path.node)) {
const configDeclaration = isIdentifier(path.node.id, { name: "config" })
? path.node
: null

if (
configDeclaration &&
isCallExpression(configDeclaration.init) &&
configDeclaration.init.arguments.length > 0 &&
isObjectExpression(configDeclaration.init.arguments[0])
) {
return configDeclaration.init.arguments[0].properties
}

return null
}

const declaration = path.node.declaration

if (isVariableDeclaration(declaration)) {
Expand Down Expand Up @@ -126,6 +144,30 @@ export async function hasDefaultExport(
ExportDefaultDeclaration() {
hasDefaultExport = true
},
AssignmentExpression(path) {
if (
path.node.left.type === "MemberExpression" &&
path.node.left.object.type === "Identifier" &&
path.node.left.object.name === "exports" &&
path.node.left.property.type === "Identifier" &&
path.node.left.property.name === "default"
) {
hasDefaultExport = true
}
},
ExportNamedDeclaration(path) {
const specifiers = path.node.specifiers
if (
specifiers?.some(
(s) =>
s.type === "ExportSpecifier" &&
s.exported.type === "Identifier" &&
s.exported.name === "default"
)
) {
hasDefaultExport = true
}
},
})
return hasDefaultExport
}
Expand Down
Loading

0 comments on commit 51f97f1

Please sign in to comment.