fix(medusa,utils,test-utils,types,framework,dashboard,admin-vite-plugin,admin-bundler): Fix broken plugin dependencies in development server (#11720)
**What**
- Reworks how admin extensions are loaded from plugins.
- Reworks how extensions are managed internally in the dashboard project.
**Why**
- Previously we loaded extensions from plugins the same way we do for extension found in a users application. This being scanning the source code for possible extensions in `.medusa/server/src/admin`, and including any extensions that were discovered in the final virtual modules.
- This was causing issues with how Vite optimizes dependencies, and would lead to CJS/ESM issues. Not sure of the exact cause of this, but the issue was pinpointed to Vite not being able to register correctly which dependencies to optimize when they were loaded through the virtual module from a plugin in `node_modules`.
**What changed**
- To circumvent the above issue we have changed to a different strategy for loading extensions from plugins. The changes are the following:
- We now build plugins slightly different, if a plugin has admin extensions we now build those to `.medusa/server/src/admin/index.mjs` and `.medusa/server/src/admin/index.js` for a ESM and CJS build.
- When determining how to load extensions from a source we follow these rules:
- If the source has a `medusa-plugin-options.json` or is the root application we determine that it is a `local` extension source, and load extensions as previously through a virtual module.
- If it has neither of the above, but has a `./admin` export in its package.json then we determine that it is a `package` extension, and we update the entry point for the dashboard to import the package and pass its extensions a long to the dashboard manager.
**Changes required by plugin authors**
- The change has no breaking changes, but requires plugin authors to update the `package.json` of their plugins to also include a `./admin` export. It should look like this:
```json
{
"name": "@medusajs/plugin",
"version": "0.0.1",
"description": "A starter for Medusa plugins.",
"author": "Medusa (https://medusajs.com)",
"license": "MIT",
"files": [
".medusa/server"
],
"exports": {
"./package.json": "./package.json",
"./workflows": "./.medusa/server/src/workflows/index.js",
"./.medusa/server/src/modules/*": "./.medusa/server/src/modules/*/index.js",
"./modules/*": "./.medusa/server/src/modules/*/index.js",
"./providers/*": "./.medusa/server/src/providers/*/index.js",
"./*": "./.medusa/server/src/*.js",
"./admin": {
"import": "./.medusa/server/src/admin/index.mjs",
"require": "./.medusa/server/src/admin/index.js",
"default": "./.medusa/server/src/admin/index.js"
}
},
}
```
This commit is contained in:
committed by
GitHub
parent
c1057410d9
commit
ec56a8bc85
@@ -4,7 +4,7 @@
|
||||
"description": "Bundler for the Medusa admin dashboard.",
|
||||
"author": "Kasper Kristensen <kasper@medusajs.com>",
|
||||
"scripts": {
|
||||
"build": "tsup && copyfiles -f ./src/index.html ./src/entry.tsx ./src/index.css ./dist"
|
||||
"build": "tsup"
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
@@ -20,7 +20,6 @@
|
||||
"devDependencies": {
|
||||
"@medusajs/types": "2.6.1",
|
||||
"@types/compression": "^1.7.5",
|
||||
"copyfiles": "^2.4.1",
|
||||
"express": "^4.21.0",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3"
|
||||
@@ -29,19 +28,16 @@
|
||||
"@medusajs/admin-shared": "2.6.1",
|
||||
"@medusajs/admin-vite-plugin": "2.6.1",
|
||||
"@medusajs/dashboard": "2.6.1",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.21.0",
|
||||
"get-port": "^5.1.1",
|
||||
"glob": "^10.3.10",
|
||||
"outdent": "^0.8.0",
|
||||
"postcss": "^8.4.32",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"vite": "^5.4.14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"packageManager": "yarn@3.2.1"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { InlineConfig } from "vite"
|
||||
import { BundlerOptions } from "../types"
|
||||
import { getViteConfig } from "./config"
|
||||
import { getViteConfig } from "../utils/config"
|
||||
|
||||
export async function build(options: BundlerOptions) {
|
||||
const vite = await import("vite")
|
||||
@@ -4,7 +4,7 @@ import path from "path"
|
||||
import type { InlineConfig, ViteDevServer } from "vite"
|
||||
|
||||
import { BundlerOptions } from "../types"
|
||||
import { getViteConfig } from "./config"
|
||||
import { getViteConfig } from "../utils/config"
|
||||
|
||||
const router = express.Router()
|
||||
|
||||
112
packages/admin/admin-bundler/src/commands/plugin.ts
Normal file
112
packages/admin/admin-bundler/src/commands/plugin.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { readFileSync } from "fs"
|
||||
import { builtinModules } from "node:module"
|
||||
import path from "path"
|
||||
import type { UserConfig } from "vite"
|
||||
import { clearPluginBuild } from "../plugins/clear-plugin-build"
|
||||
|
||||
interface PluginOptions {
|
||||
root: string
|
||||
outDir: string
|
||||
}
|
||||
|
||||
export async function plugin(options: PluginOptions) {
|
||||
const vite = await import("vite")
|
||||
const react = (await import("@vitejs/plugin-react")).default
|
||||
const medusa = (await import("@medusajs/admin-vite-plugin")).default
|
||||
|
||||
const pkg = JSON.parse(
|
||||
readFileSync(path.resolve(options.root, "package.json"), "utf-8")
|
||||
)
|
||||
const external = new Set([
|
||||
...Object.keys(pkg.dependencies || {}),
|
||||
...Object.keys(pkg.peerDependencies || {}),
|
||||
...Object.keys(pkg.devDependencies || {}),
|
||||
"react",
|
||||
"react/jsx-runtime",
|
||||
"react-router-dom",
|
||||
"@medusajs/js-sdk",
|
||||
"@medusajs/admin-sdk",
|
||||
"@tanstack/react-query",
|
||||
])
|
||||
|
||||
const outDir = path.resolve(options.root, options.outDir, "src/admin")
|
||||
const entryPoint = path.resolve(
|
||||
options.root,
|
||||
"src/admin/__admin-extensions__.js"
|
||||
)
|
||||
|
||||
/**
|
||||
* We need to ensure that the NODE_ENV is set to production,
|
||||
* otherwise Vite will build the dev version of React.
|
||||
*/
|
||||
const originalNodeEnv = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = "production"
|
||||
|
||||
const pluginConfig: UserConfig = {
|
||||
build: {
|
||||
lib: {
|
||||
entry: entryPoint,
|
||||
formats: ["es", "cjs"],
|
||||
fileName: "index",
|
||||
},
|
||||
emptyOutDir: false,
|
||||
minify: false,
|
||||
outDir,
|
||||
rollupOptions: {
|
||||
external: (id, importer) => {
|
||||
// If there's no importer, it's a direct dependency
|
||||
// Keep the existing external behavior
|
||||
if (!importer) {
|
||||
const idParts = id.split("/")
|
||||
const name = idParts[0]?.startsWith("@")
|
||||
? `${idParts[0]}/${idParts[1]}`
|
||||
: idParts[0]
|
||||
|
||||
const builtinModulesWithNodePrefix = [
|
||||
...builtinModules,
|
||||
...builtinModules.map((modName) => `node:${modName}`),
|
||||
]
|
||||
|
||||
return Boolean(
|
||||
(name && external.has(name)) ||
|
||||
(name && builtinModulesWithNodePrefix.includes(name))
|
||||
)
|
||||
}
|
||||
|
||||
// For transient dependencies (those with importers),
|
||||
// bundle them if they're not in our external set
|
||||
const idParts = id.split("/")
|
||||
const name = idParts[0]?.startsWith("@")
|
||||
? `${idParts[0]}/${idParts[1]}`
|
||||
: idParts[0]
|
||||
|
||||
return Boolean(name && external.has(name))
|
||||
},
|
||||
output: {
|
||||
preserveModules: false,
|
||||
interop: "auto",
|
||||
chunkFileNames: () => {
|
||||
return `_chunks/[name]-[hash]`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
medusa({
|
||||
pluginMode: true,
|
||||
sources: [path.resolve(options.root, "src/admin")],
|
||||
}),
|
||||
clearPluginBuild({ outDir }),
|
||||
],
|
||||
logLevel: "silent",
|
||||
clearScreen: false,
|
||||
}
|
||||
|
||||
await vite.build(pluginConfig)
|
||||
|
||||
/**
|
||||
* Restore the original NODE_ENV
|
||||
*/
|
||||
process.env.NODE_ENV = originalNodeEnv
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import App from "@medusajs/dashboard";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("medusa")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept()
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
@import "@medusajs/dashboard/css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
||||
/>
|
||||
<link rel="icon" href="data:," data-placeholder-favicon />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="medusa"></div>
|
||||
<script type="module" src="./entry.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
export { build } from "./lib/build"
|
||||
export { develop } from "./lib/develop"
|
||||
export { plugin } from "./lib/plugin"
|
||||
export { serve } from "./lib/serve"
|
||||
export { build } from "./commands/build"
|
||||
export { develop } from "./commands/develop"
|
||||
export { plugin } from "./commands/plugin"
|
||||
export { serve } from "./commands/serve"
|
||||
|
||||
export * from "./types"
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
import { readFileSync } from "fs"
|
||||
import { rm } from "fs/promises"
|
||||
import { glob } from "glob"
|
||||
import path from "path"
|
||||
import type { UserConfig } from "vite"
|
||||
|
||||
interface PluginOptions {
|
||||
root: string
|
||||
outDir: string
|
||||
}
|
||||
|
||||
export async function plugin(options: PluginOptions) {
|
||||
const vite = await import("vite")
|
||||
const react = (await import("@vitejs/plugin-react")).default
|
||||
const { nodeResolve } = await import("@rollup/plugin-node-resolve")
|
||||
const entries = await glob(`${options.root}/src/admin/**/*.{ts,tsx,js,jsx}`)
|
||||
|
||||
/**
|
||||
* If there is no entry point, we can skip the build
|
||||
*/
|
||||
if (entries.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const entryPoints = entries.reduce((acc, entry) => {
|
||||
const outPath = entry
|
||||
.replace(/^src\//, "")
|
||||
.replace(/\.(ts|tsx|js|jsx)$/, "")
|
||||
|
||||
acc[outPath] = path.resolve(options.root, entry)
|
||||
return acc
|
||||
}, {} as Record<string, string>)
|
||||
|
||||
const pkg = JSON.parse(
|
||||
readFileSync(path.resolve(options.root, "package.json"), "utf-8")
|
||||
)
|
||||
const external = new Set([
|
||||
...Object.keys(pkg.dependencies || {}),
|
||||
...Object.keys(pkg.peerDependencies || {}),
|
||||
...Object.keys(pkg.devDependencies || {}),
|
||||
"react",
|
||||
"react-dom",
|
||||
"react/jsx-runtime",
|
||||
"react-router-dom",
|
||||
"@medusajs/admin-sdk",
|
||||
"@tanstack/react-query",
|
||||
])
|
||||
|
||||
/**
|
||||
* We need to ensure that the NODE_ENV is set to production,
|
||||
* otherwise Vite will build the dev version of React.
|
||||
*/
|
||||
const originalNodeEnv = process.env.NODE_ENV
|
||||
process.env.NODE_ENV = "production"
|
||||
|
||||
const pluginConfig: UserConfig = {
|
||||
build: {
|
||||
lib: {
|
||||
entry: entryPoints,
|
||||
formats: ["es"],
|
||||
},
|
||||
emptyOutDir: false,
|
||||
minify: false,
|
||||
outDir: path.resolve(options.root, options.outDir),
|
||||
rollupOptions: {
|
||||
plugins: [nodeResolve() as any],
|
||||
external: [...external, /node_modules/],
|
||||
output: {
|
||||
globals: {
|
||||
react: "React",
|
||||
"react-dom": "React-dom",
|
||||
"react/jsx-runtime": "react/jsx-runtime",
|
||||
},
|
||||
preserveModules: true,
|
||||
entryFileNames: (chunkInfo) => {
|
||||
return `${chunkInfo.name.replace(`${options.root}/`, "")}.js`
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
{
|
||||
name: "clear-admin-plugin",
|
||||
buildStart: async () => {
|
||||
const adminDir = path.join(options.root, options.outDir, "admin")
|
||||
try {
|
||||
await rm(adminDir, { recursive: true, force: true })
|
||||
} catch (e) {
|
||||
// Directory might not exist, ignore
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
logLevel: "silent",
|
||||
clearScreen: false,
|
||||
}
|
||||
|
||||
await vite.build(pluginConfig)
|
||||
|
||||
/**
|
||||
* Restore the original NODE_ENV
|
||||
*/
|
||||
process.env.NODE_ENV = originalNodeEnv
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { rm } from "node:fs/promises"
|
||||
import path from "node:path"
|
||||
import type { Plugin } from "vite"
|
||||
|
||||
interface ClearPluginBuildOptions {
|
||||
outDir: string
|
||||
}
|
||||
|
||||
export const clearPluginBuild = (options: ClearPluginBuildOptions): Plugin => ({
|
||||
name: "medusa:clear-plugin-build",
|
||||
buildStart: async () => {
|
||||
const adminDir = path.join(options.outDir, "admin")
|
||||
try {
|
||||
await rm(adminDir, { recursive: true, force: true })
|
||||
} catch (e) {
|
||||
// Directory might not exist, ignore
|
||||
}
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,96 @@
|
||||
import path from "node:path"
|
||||
import type { Config } from "tailwindcss"
|
||||
import type { Plugin } from "vite"
|
||||
|
||||
interface InjectTailwindCSSOptions {
|
||||
entry: string
|
||||
sources?: string[]
|
||||
plugins?: string[]
|
||||
}
|
||||
|
||||
export const injectTailwindCSS = (
|
||||
options: InjectTailwindCSSOptions
|
||||
): Plugin => {
|
||||
return {
|
||||
name: "medusa:inject-tailwindcss",
|
||||
config: () => ({
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
require("tailwindcss")({
|
||||
config: createTailwindConfig(
|
||||
options.entry,
|
||||
options.sources,
|
||||
options.plugins
|
||||
),
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
function createTailwindConfig(
|
||||
entry: string,
|
||||
sources: string[] = [],
|
||||
plugins: string[] = []
|
||||
) {
|
||||
const root = path.join(entry, "**/*.{js,ts,jsx,tsx}")
|
||||
const html = path.join(entry, "index.html")
|
||||
|
||||
let dashboard = ""
|
||||
|
||||
try {
|
||||
dashboard = path.join(
|
||||
path.dirname(require.resolve("@medusajs/dashboard")),
|
||||
"**/*.{js,ts,jsx,tsx}"
|
||||
)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
let ui: string = ""
|
||||
|
||||
try {
|
||||
ui = path.join(
|
||||
path.dirname(require.resolve("@medusajs/ui")),
|
||||
"**/*.{js,ts,jsx,tsx}"
|
||||
)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const sourceExtensions = sources.map((s) =>
|
||||
path.join(s, "**/*.{js,ts,jsx,tsx}")
|
||||
)
|
||||
const pluginExtensions: string[] = []
|
||||
|
||||
for (const plugin of plugins) {
|
||||
try {
|
||||
const pluginPath = path.join(
|
||||
path.dirname(require.resolve(plugin)),
|
||||
"**/*.{js,ts,jsx,tsx}"
|
||||
)
|
||||
|
||||
pluginExtensions.push(pluginPath)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const config: Config = {
|
||||
presets: [require("@medusajs/ui-preset")],
|
||||
content: [
|
||||
html,
|
||||
root,
|
||||
dashboard,
|
||||
ui,
|
||||
...sourceExtensions,
|
||||
...pluginExtensions,
|
||||
],
|
||||
darkMode: "class",
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { Plugin } from "vite"
|
||||
import { writeStaticFiles as writeStaticFilesUtils } from "../utils/write-static-files"
|
||||
|
||||
interface WriteStaticFilesPluginOptions {
|
||||
plugins?: string[]
|
||||
}
|
||||
|
||||
export const writeStaticFiles = (
|
||||
options: WriteStaticFilesPluginOptions
|
||||
): Plugin => {
|
||||
return {
|
||||
name: "medusa:write-static-files",
|
||||
buildStart: async (ctx) => {
|
||||
await writeStaticFilesUtils(options.plugins)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -4,4 +4,5 @@ export type BundlerOptions = Required<Pick<AdminOptions, "path">> &
|
||||
Pick<AdminOptions, "vite" | "backendUrl" | "storefrontUrl"> & {
|
||||
outDir: string
|
||||
sources?: string[]
|
||||
plugins?: string[]
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { VIRTUAL_MODULES } from "@medusajs/admin-shared"
|
||||
import path from "path"
|
||||
import { Config } from "tailwindcss"
|
||||
import type { InlineConfig } from "vite"
|
||||
import { injectTailwindCSS } from "../plugins/inject-tailwindcss"
|
||||
import { writeStaticFiles } from "../plugins/write-static-files"
|
||||
import { BundlerOptions } from "../types"
|
||||
|
||||
export async function getViteConfig(
|
||||
@@ -14,7 +15,7 @@ export async function getViteConfig(
|
||||
const getPort = await import("get-port")
|
||||
const hmrPort = await getPort.default()
|
||||
|
||||
const root = path.resolve(__dirname, "./")
|
||||
const root = path.resolve(process.cwd(), ".medusa/client")
|
||||
|
||||
const backendUrl = options.backendUrl ?? ""
|
||||
const storefrontUrl = options.storefrontUrl ?? ""
|
||||
@@ -52,16 +53,15 @@ export async function getViteConfig(
|
||||
port: hmrPort,
|
||||
},
|
||||
},
|
||||
css: {
|
||||
postcss: {
|
||||
plugins: [
|
||||
require("tailwindcss")({
|
||||
config: createTailwindConfig(root, options.sources),
|
||||
}),
|
||||
],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
writeStaticFiles({
|
||||
plugins: options.plugins,
|
||||
}),
|
||||
injectTailwindCSS({
|
||||
entry: root,
|
||||
sources: options.sources,
|
||||
plugins: options.plugins,
|
||||
}),
|
||||
react(),
|
||||
medusa({
|
||||
sources: options.sources,
|
||||
@@ -76,40 +76,3 @@ export async function getViteConfig(
|
||||
|
||||
return baseConfig
|
||||
}
|
||||
|
||||
function createTailwindConfig(entry: string, sources: string[] = []) {
|
||||
const root = path.join(entry, "**/*.{js,ts,jsx,tsx}")
|
||||
const html = path.join(entry, "index.html")
|
||||
|
||||
let dashboard = ""
|
||||
|
||||
try {
|
||||
dashboard = path.join(
|
||||
path.dirname(require.resolve("@medusajs/dashboard")),
|
||||
"**/*.{js,ts,jsx,tsx}"
|
||||
)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
let ui: string = ""
|
||||
|
||||
try {
|
||||
ui = path.join(
|
||||
path.dirname(require.resolve("@medusajs/ui")),
|
||||
"**/*.{js,ts,jsx,tsx}"
|
||||
)
|
||||
} catch (_e) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
const extensions = sources.map((s) => path.join(s, "**/*.{js,ts,jsx,tsx}"))
|
||||
|
||||
const config: Config = {
|
||||
presets: [require("@medusajs/ui-preset")],
|
||||
content: [html, root, dashboard, ui, ...extensions],
|
||||
darkMode: "class",
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
||||
94
packages/admin/admin-bundler/src/utils/write-static-files.ts
Normal file
94
packages/admin/admin-bundler/src/utils/write-static-files.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { mkdir, writeFile } from "node:fs/promises"
|
||||
import { join } from "node:path"
|
||||
import outdent from "outdent"
|
||||
|
||||
export async function writeStaticFiles(plugins?: string[]) {
|
||||
const outDir = join(process.cwd(), ".medusa/client")
|
||||
|
||||
await mkdir(outDir, { recursive: true })
|
||||
|
||||
const promises = [
|
||||
writeCSSFile(outDir),
|
||||
writeEntryFile(outDir, plugins),
|
||||
writeHTMLFile(outDir),
|
||||
]
|
||||
|
||||
await Promise.all(promises)
|
||||
}
|
||||
|
||||
async function writeCSSFile(outDir: string) {
|
||||
const css = outdent`
|
||||
@import "@medusajs/dashboard/css";
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
`
|
||||
|
||||
await writeFile(join(outDir, "index.css"), css)
|
||||
}
|
||||
|
||||
function getPluginName(index: number) {
|
||||
return `plugin${index}`
|
||||
}
|
||||
|
||||
async function writeEntryFile(outDir: string, plugins?: string[]) {
|
||||
const entry = outdent`
|
||||
import App from "@medusajs/dashboard";
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import "./index.css";
|
||||
|
||||
${plugins
|
||||
?.map((plugin, idx) => `import ${getPluginName(idx)} from "${plugin}"`)
|
||||
.join("\n")}
|
||||
|
||||
let root = null
|
||||
|
||||
if (!root) {
|
||||
root = ReactDOM.createRoot(document.getElementById("medusa"))
|
||||
}
|
||||
|
||||
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App plugins={[${plugins
|
||||
?.map((_, idx) => getPluginName(idx))
|
||||
.join(", ")}]} />
|
||||
</React.StrictMode>
|
||||
)
|
||||
|
||||
|
||||
if (import.meta.hot) {
|
||||
import.meta.hot.accept()
|
||||
}
|
||||
`
|
||||
|
||||
await writeFile(join(outDir, "entry.jsx"), entry)
|
||||
}
|
||||
|
||||
async function writeHTMLFile(outDir: string) {
|
||||
const html = outdent`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta
|
||||
http-equiv="Content-Type"
|
||||
content="text/html; charset=UTF-8"
|
||||
/>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, user-scalable=no"
|
||||
/>
|
||||
<link rel="icon" href="data:," data-placeholder-favicon />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="medusa"></div>
|
||||
<script type="module" src="./entry.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
`
|
||||
|
||||
await writeFile(join(outDir, "index.html"), html)
|
||||
}
|
||||
Reference in New Issue
Block a user