feat(admin, admin-ui, medusa-js, medusa-react, medusa): Support Admin Extensions (#4761)

Co-authored-by: Rares Stefan <948623+StephixOne@users.noreply.github.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
This commit is contained in:
Kasper Fabricius Kristensen
2023-08-17 14:14:45 +02:00
committed by GitHub
parent 26c78bbc03
commit f1a05f4725
189 changed files with 14570 additions and 12773 deletions

View File

@@ -0,0 +1,52 @@
import fse from "fs-extra"
import path from "node:path"
import webpack from "webpack"
import { CustomWebpackConfigArgs } from "../types"
import { logger } from "../utils"
import { validateArgs } from "../utils/validate-args"
import { getWebpackConfig } from "./get-webpack-config"
import { withCustomWebpackConfig } from "./with-custom-webpack-config"
export async function getCustomWebpackConfig(
appDir: string,
args: CustomWebpackConfigArgs
) {
validateArgs(args)
let config = getWebpackConfig(args)
const adminConfigPath = path.join(appDir, "src", "admin", "webpack.config.js")
const pathExists = await fse.pathExists(adminConfigPath)
if (pathExists) {
let webpackAdminConfig: ReturnType<typeof withCustomWebpackConfig>
try {
webpackAdminConfig = require(adminConfigPath)
} catch (e) {
logger.panic(
`An error occured while trying to load your custom Webpack config. See the error below for details:`,
{
error: e,
}
)
}
if (typeof webpackAdminConfig === "function") {
if (args.devServer) {
config.devServer = args.devServer
}
config = webpackAdminConfig(config, webpack)
if (!config) {
logger.panic(
"Nothing was returned from your custom webpack configuration"
)
}
}
}
return config
}

View File

@@ -0,0 +1,185 @@
import ReactRefreshPlugin from "@pmmmwh/react-refresh-webpack-plugin"
import HtmlWebpackPlugin from "html-webpack-plugin"
import MiniCssExtractPlugin from "mini-css-extract-plugin"
import path from "node:path"
import { SwcMinifyWebpackPlugin } from "swc-minify-webpack-plugin"
import type { Configuration } from "webpack"
import webpack from "webpack"
import WebpackBar from "webpackbar"
import { WebpackConfigArgs } from "../types"
import { getClientEnv } from "../utils"
import { webpackAliases } from "./webpack-aliases"
function formatPublicPath(path?: string) {
if (!path) {
return "/app/"
}
if (path === "/") {
return path
}
return path.endsWith("/") ? path : `${path}/`
}
export function getWebpackConfig({
entry,
dest,
cacheDir,
env,
options,
template,
reporting = "fancy",
}: WebpackConfigArgs): Configuration {
const isProd = env === "production"
const envVars = getClientEnv({
env,
backend: options?.backend,
path: options?.path,
})
const publicPath = formatPublicPath(options?.path)
const webpackPlugins = isProd
? [
new MiniCssExtractPlugin({
filename: "[name].[chunkhash].css",
chunkFilename: "[name].[chunkhash].css",
}),
new WebpackBar({
basic: reporting === "minimal",
fancy: reporting === "fancy",
}),
]
: [new MiniCssExtractPlugin()]
return {
mode: env,
bail: !!isProd,
devtool: isProd ? false : "eval-source-map",
entry: [entry],
output: {
path: dest,
filename: isProd ? "[name].[contenthash:8].js" : "[name].bundle.js",
chunkFilename: isProd
? "[name].[contenthash:8].chunk.js"
: "[name].chunk.js",
},
optimization: {
minimize: true,
minimizer: [new SwcMinifyWebpackPlugin()],
moduleIds: "deterministic",
runtimeChunk: true,
},
module: {
rules: [
{
test: /\.tsx?$/,
exclude: /node_modules/,
include: [cacheDir],
use: {
loader: "swc-loader",
options: {
jsc: {
parser: {
syntax: "typescript", // Use TypeScript syntax for parsing
jsx: true, // Enable JSX parsing
},
transform: {
react: {
runtime: "automatic",
},
},
},
},
},
},
{
test: /\.jsx?$/,
exclude: /node_modules/,
include: [cacheDir],
use: {
loader: "swc-loader",
options: {
jsc: {
parser: {
syntax: "ecmascript", // Use Ecmascript syntax for parsing
jsx: true, // Enable JSX parsing
},
transform: {
react: {
runtime: "automatic",
},
},
},
},
},
},
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
},
{
test: /\.svg$/,
oneOf: [
{
type: "asset/resource",
resourceQuery: /url/,
},
{
type: "asset/inline",
resourceQuery: /base64/,
},
{
issuer: /\.[jt]sx?$/,
use: ["@svgr/webpack"],
},
],
generator: {
filename: `images/${isProd ? "[name]-[hash][ext]" : "[name][ext]"}`,
},
},
{
test: /\.(eot|otf|ttf|woff|woff2)$/,
type: "asset/resource",
},
{
test: /\.(js|mjs)(\.map)?$/,
enforce: "pre",
use: ["source-map-loader"],
},
{
test: /\.m?jsx?$/,
resolve: {
fullySpecified: false,
},
},
],
},
resolve: {
alias: webpackAliases,
symlinks: false,
extensions: [".js", ".jsx", ".ts", ".tsx"],
mainFields: ["browser", "module", "main"],
modules: ["node_modules", path.resolve(__dirname, "..", "node_modules")],
fallback: {
readline: false,
path: false,
},
},
plugins: [
new HtmlWebpackPlugin({
inject: true,
template: template || path.resolve(__dirname, "..", "ui", "index.html"),
publicPath: publicPath,
}),
new webpack.DefinePlugin(envVars),
!isProd && new ReactRefreshPlugin(),
...webpackPlugins,
].filter(Boolean),
}
}

View File

@@ -0,0 +1,5 @@
import { getCustomWebpackConfig } from "./get-custom-webpack-config"
import { getWebpackConfig } from "./get-webpack-config"
import { withCustomWebpackConfig } from "./with-custom-webpack-config"
export { getCustomWebpackConfig, getWebpackConfig, withCustomWebpackConfig }

View File

@@ -0,0 +1,9 @@
import { ALIASED_PACKAGES } from "../constants"
/**
* Ensure that the admin-ui uses the same version of these packages as the project.
*/
export const webpackAliases = ALIASED_PACKAGES.reduce((acc, pkg) => {
acc[`${pkg}$`] = require.resolve(pkg)
return acc
}, {})

View File

@@ -0,0 +1,16 @@
import webpack, { type Configuration } from "webpack"
/**
* Helper function to create a custom webpack config that can be used to
* extend the default webpack config used to build the admin UI.
*/
export function withCustomWebpackConfig(
callback: (
config: Configuration,
webpackInstance: typeof webpack
) => Configuration
) {
return (config: Configuration, webpackInstance: typeof webpack) => {
return callback(config, webpackInstance)
}
}