refactor(build): share Monaco public asset copy helper

This commit is contained in:
Shantur Rathore
2026-02-10 10:49:05 +00:00
parent ee9da95044
commit 0d96a9f9ff
6 changed files with 191 additions and 164 deletions

View File

@@ -1,7 +1,7 @@
import { defineConfig, externalizeDepsPlugin } from "electron-vite" import { defineConfig, externalizeDepsPlugin } from "electron-vite"
import solid from "vite-plugin-solid" import solid from "vite-plugin-solid"
import { resolve } from "path" import { resolve } from "path"
import fs from "fs" import { copyMonacoPublicAssets } from "../ui/scripts/monaco-public-assets.js"
const uiRoot = resolve(__dirname, "../ui") const uiRoot = resolve(__dirname, "../ui")
const uiSrc = resolve(uiRoot, "src") const uiSrc = resolve(uiRoot, "src")
@@ -9,88 +9,28 @@ const uiRendererRoot = resolve(uiRoot, "src/renderer")
const uiRendererEntry = resolve(uiRendererRoot, "index.html") const uiRendererEntry = resolve(uiRendererRoot, "index.html")
const uiRendererLoadingEntry = resolve(uiRendererRoot, "loading.html") const uiRendererLoadingEntry = resolve(uiRendererRoot, "loading.html")
function copyMonacoPublicAssets(opts: { warn: (message: string) => void }) {
const publicDir = resolve(uiRendererRoot, "public")
const destRoot = resolve(publicDir, "monaco/vs")
const candidates = [
// Workspace root hoisted deps.
resolve(__dirname, "../../node_modules/monaco-editor/min/vs"),
// UI package local deps.
resolve(uiRoot, "node_modules/monaco-editor/min/vs"),
]
const sourceRoot = candidates.find((p) => fs.existsSync(resolve(p, "loader.js")))
if (!sourceRoot) {
opts.warn("Monaco source directory not found; skipping copy")
return
}
const copyRecursive = (src: string, dest: string) => {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
fs.mkdirSync(dest, { recursive: true })
for (const entry of fs.readdirSync(src)) {
copyRecursive(resolve(src, entry), resolve(dest, entry))
}
return
}
fs.copyFileSync(src, dest)
}
// Keep the working tree clean; these assets are generated.
try {
fs.rmSync(destRoot, { recursive: true, force: true })
} catch {
// ignore
}
fs.mkdirSync(destRoot, { recursive: true })
// Copy core Monaco runtime.
for (const dir of ["base", "editor", "platform"] as const) {
const src = resolve(sourceRoot, dir)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, dir))
}
}
// loader.js is required.
copyRecursive(resolve(sourceRoot, "loader.js"), resolve(destRoot, "loader.js"))
// Copy baseline rich language packages + workers.
for (const lang of ["typescript", "html", "json", "css"] as const) {
const src = resolve(sourceRoot, "language", lang)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, "language", lang))
}
}
// Copy baseline basic tokenizers.
for (const lang of ["python", "markdown", "cpp", "kotlin"] as const) {
const src = resolve(sourceRoot, "basic-languages", lang)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, "basic-languages", lang))
}
}
// Copy monaco.contribution.js entrypoints (needed by some loads).
const monacoContribution = resolve(sourceRoot, "basic-languages", "monaco.contribution.js")
if (fs.existsSync(monacoContribution)) {
copyRecursive(monacoContribution, resolve(destRoot, "basic-languages", "monaco.contribution.js"))
}
const underscoreContribution = resolve(sourceRoot, "basic-languages", "_.contribution.js")
if (fs.existsSync(underscoreContribution)) {
copyRecursive(underscoreContribution, resolve(destRoot, "basic-languages", "_.contribution.js"))
}
}
function prepareMonacoPublicAssets() { function prepareMonacoPublicAssets() {
return { return {
name: "prepare-monaco-public-assets", name: "prepare-monaco-public-assets",
configureServer(server: any) { configureServer(server: any) {
copyMonacoPublicAssets({ warn: (msg) => server.config.logger.warn(msg) }) copyMonacoPublicAssets({
uiRendererRoot: uiRendererRoot,
warn: (msg: string) => server.config.logger.warn(msg),
sourceRoots: [
resolve(__dirname, "../../node_modules/monaco-editor/min/vs"),
resolve(uiRoot, "node_modules/monaco-editor/min/vs"),
],
})
}, },
buildStart(this: any) { buildStart(this: any) {
copyMonacoPublicAssets({ warn: (msg) => this.warn(msg) }) copyMonacoPublicAssets({
uiRendererRoot: uiRendererRoot,
warn: (msg: string) => this.warn(msg),
sourceRoots: [
resolve(__dirname, "../../node_modules/monaco-editor/min/vs"),
resolve(uiRoot, "node_modules/monaco-editor/min/vs"),
],
})
}, },
} }
} }

View File

@@ -3,6 +3,7 @@
const fs = require("fs") const fs = require("fs")
const path = require("path") const path = require("path")
const { execSync } = require("child_process") const { execSync } = require("child_process")
const { pathToFileURL } = require("url")
const root = path.resolve(__dirname, "..") const root = path.resolve(__dirname, "..")
const workspaceRoot = path.resolve(root, "..", "..") const workspaceRoot = path.resolve(root, "..", "..")
@@ -10,6 +11,20 @@ const uiRoot = path.resolve(root, "..", "ui")
const uiDist = path.resolve(uiRoot, "src", "renderer", "dist") const uiDist = path.resolve(uiRoot, "src", "renderer", "dist")
const uiLoadingDest = path.resolve(root, "src-tauri", "resources", "ui-loading") const uiLoadingDest = path.resolve(root, "src-tauri", "resources", "ui-loading")
async function ensureMonacoAssets() {
const helperPath = path.join(uiRoot, "scripts", "monaco-public-assets.js")
const helperUrl = pathToFileURL(helperPath).href
const { copyMonacoPublicAssets } = await import(helperUrl)
copyMonacoPublicAssets({
uiRendererRoot: path.join(uiRoot, "src", "renderer"),
warn: (msg) => console.warn(`[dev-prep] ${msg}`),
sourceRoots: [
path.resolve(workspaceRoot, "node_modules", "monaco-editor", "min", "vs"),
path.resolve(uiRoot, "node_modules", "monaco-editor", "min", "vs"),
],
})
}
function ensureUiBuild() { function ensureUiBuild() {
const loadingHtml = path.join(uiDist, "loading.html") const loadingHtml = path.join(uiDist, "loading.html")
if (fs.existsSync(loadingHtml)) { if (fs.existsSync(loadingHtml)) {
@@ -42,5 +57,11 @@ function copyUiLoadingAssets() {
console.log(`[dev-prep] copied loader bundle from ${uiDist}`) console.log(`[dev-prep] copied loader bundle from ${uiDist}`)
} }
ensureUiBuild() ;(async () => {
copyUiLoadingAssets() await ensureMonacoAssets()
ensureUiBuild()
copyUiLoadingAssets()
})().catch((err) => {
console.error("[dev-prep] failed:", err)
process.exit(1)
})

View File

@@ -2,6 +2,7 @@
const fs = require("fs") const fs = require("fs")
const path = require("path") const path = require("path")
const { execSync } = require("child_process") const { execSync } = require("child_process")
const { pathToFileURL } = require("url")
const root = path.resolve(__dirname, "..") const root = path.resolve(__dirname, "..")
const workspaceRoot = path.resolve(root, "..", "..") const workspaceRoot = path.resolve(root, "..", "..")
@@ -37,6 +38,20 @@ const braceExpansionPath = path.join(
const viteBinPath = path.join(uiRoot, "node_modules", ".bin", "vite") const viteBinPath = path.join(uiRoot, "node_modules", ".bin", "vite")
async function ensureMonacoAssets() {
const helperPath = path.join(uiRoot, "scripts", "monaco-public-assets.js")
const helperUrl = pathToFileURL(helperPath).href
const { copyMonacoPublicAssets } = await import(helperUrl)
copyMonacoPublicAssets({
uiRendererRoot: path.join(uiRoot, "src", "renderer"),
warn: (msg) => console.warn(`[prebuild] ${msg}`),
sourceRoots: [
path.resolve(workspaceRoot, "node_modules", "monaco-editor", "min", "vs"),
path.resolve(uiRoot, "node_modules", "monaco-editor", "min", "vs"),
],
})
}
function ensureServerBuild() { function ensureServerBuild() {
const distPath = path.join(serverRoot, "dist") const distPath = path.join(serverRoot, "dist")
const publicPath = path.join(serverRoot, "public") const publicPath = path.join(serverRoot, "public")
@@ -223,12 +238,18 @@ function copyUiLoadingAssets() {
console.log(`[prebuild] prepared UI loading assets from ${uiDist}`) console.log(`[prebuild] prepared UI loading assets from ${uiDist}`)
} }
ensureServerDevDependencies() ;(async () => {
ensureUiDevDependencies() ensureServerDevDependencies()
ensureRollupPlatformBinary() ensureUiDevDependencies()
ensureServerDependencies() await ensureMonacoAssets()
ensureServerBuild() ensureRollupPlatformBinary()
ensureUiBuild() ensureServerDependencies()
copyServerArtifacts() ensureServerBuild()
stripNodeModuleBins() ensureUiBuild()
copyUiLoadingAssets() copyServerArtifacts()
stripNodeModuleBins()
copyUiLoadingAssets()
})().catch((err) => {
console.error("[prebuild] failed:", err)
process.exit(1)
})

View File

@@ -0,0 +1,7 @@
export type CopyMonacoPublicAssetsParams = {
uiRendererRoot: string
warn?: (message: string) => void
sourceRoots?: string[]
}
export function copyMonacoPublicAssets(params: CopyMonacoPublicAssetsParams): void

View File

@@ -0,0 +1,97 @@
import fs from "fs"
import { resolve } from "path"
/**
* Copy Monaco's AMD `min/vs` assets into the UI renderer public folder.
*
* Monaco is loaded at runtime via `/monaco/vs/loader.js`. These assets are gitignored
* and generated on demand in dev/build so the repo stays clean.
*
* @param {object} params
* @param {string} params.uiRendererRoot Absolute path to `packages/ui/src/renderer`.
* @param {(message: string) => void} [params.warn] Warning logger.
* @param {string[]} [params.sourceRoots] Optional override list of `.../monaco-editor/min/vs` roots.
*/
export function copyMonacoPublicAssets(params) {
const uiRendererRoot = params?.uiRendererRoot
if (!uiRendererRoot) {
throw new Error("copyMonacoPublicAssets: uiRendererRoot is required")
}
const warn = params?.warn ?? ((message) => console.warn(message))
const publicDir = resolve(uiRendererRoot, "public")
const destRoot = resolve(publicDir, "monaco/vs")
const candidates =
params?.sourceRoots?.length > 0
? params.sourceRoots
: [
// Workspace root hoisted deps.
resolve(process.cwd(), "node_modules/monaco-editor/min/vs"),
// UI package local deps (covers non-hoisted installs).
resolve(process.cwd(), "packages/ui/node_modules/monaco-editor/min/vs"),
]
const sourceRoot = candidates.find((p) => fs.existsSync(resolve(p, "loader.js")))
if (!sourceRoot) {
warn("Monaco source directory not found; skipping copy")
return
}
const copyRecursive = (src, dest) => {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
fs.mkdirSync(dest, { recursive: true })
for (const entry of fs.readdirSync(src)) {
copyRecursive(resolve(src, entry), resolve(dest, entry))
}
return
}
fs.copyFileSync(src, dest)
}
// Keep the working tree clean; these assets are generated.
try {
fs.rmSync(destRoot, { recursive: true, force: true })
} catch {
// ignore
}
fs.mkdirSync(destRoot, { recursive: true })
// Copy core Monaco runtime.
for (const dir of ["base", "editor", "platform"]) {
const src = resolve(sourceRoot, dir)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, dir))
}
}
// loader.js is required.
copyRecursive(resolve(sourceRoot, "loader.js"), resolve(destRoot, "loader.js"))
// Copy baseline rich language packages + workers.
for (const lang of ["typescript", "html", "json", "css"]) {
const src = resolve(sourceRoot, "language", lang)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, "language", lang))
}
}
// Copy baseline basic tokenizers.
for (const lang of ["python", "markdown", "cpp", "kotlin"]) {
const src = resolve(sourceRoot, "basic-languages", lang)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, "basic-languages", lang))
}
}
// Copy monaco.contribution.js entrypoints (needed by some loads).
const monacoContribution = resolve(sourceRoot, "basic-languages", "monaco.contribution.js")
if (fs.existsSync(monacoContribution)) {
copyRecursive(monacoContribution, resolve(destRoot, "basic-languages", "monaco.contribution.js"))
}
const underscoreContribution = resolve(sourceRoot, "basic-languages", "_.contribution.js")
if (fs.existsSync(underscoreContribution)) {
copyRecursive(underscoreContribution, resolve(destRoot, "basic-languages", "_.contribution.js"))
}
}

View File

@@ -3,84 +3,11 @@ import { defineConfig } from "vite"
import solid from "vite-plugin-solid" import solid from "vite-plugin-solid"
import { VitePWA } from "vite-plugin-pwa" import { VitePWA } from "vite-plugin-pwa"
import { resolve } from "path" import { resolve } from "path"
import { copyMonacoPublicAssets } from "./scripts/monaco-public-assets.js"
const uiPackageJson = JSON.parse(fs.readFileSync(resolve(__dirname, "package.json"), "utf-8")) as { version?: string } const uiPackageJson = JSON.parse(fs.readFileSync(resolve(__dirname, "package.json"), "utf-8")) as { version?: string }
const uiVersion = uiPackageJson.version ?? "0.0.0" const uiVersion = uiPackageJson.version ?? "0.0.0"
function copyMonacoPublicAssets(opts: { warn: (message: string) => void }) {
const publicDir = resolve(__dirname, "src/renderer/public")
const destRoot = resolve(publicDir, "monaco/vs")
const candidates = [
resolve(__dirname, "../../node_modules/monaco-editor/min/vs"),
resolve(__dirname, "node_modules/monaco-editor/min/vs"),
]
const sourceRoot = candidates.find((p) => fs.existsSync(resolve(p, "loader.js")))
if (!sourceRoot) {
opts.warn("Monaco source directory not found; skipping copy")
return
}
fs.mkdirSync(destRoot, { recursive: true })
const copyRecursive = (src: string, dest: string) => {
const stat = fs.statSync(src)
if (stat.isDirectory()) {
fs.mkdirSync(dest, { recursive: true })
for (const entry of fs.readdirSync(src)) {
copyRecursive(resolve(src, entry), resolve(dest, entry))
}
return
}
fs.copyFileSync(src, dest)
}
// Keep the working tree clean; these assets are generated.
try {
fs.rmSync(destRoot, { recursive: true, force: true })
} catch {
// ignore
}
fs.mkdirSync(destRoot, { recursive: true })
// Copy core Monaco runtime.
for (const dir of ["base", "editor", "platform"] as const) {
const src = resolve(sourceRoot, dir)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, dir))
}
}
// loader.js is required.
copyRecursive(resolve(sourceRoot, "loader.js"), resolve(destRoot, "loader.js"))
// Copy baseline rich language packages + workers.
for (const lang of ["typescript", "html", "json", "css"] as const) {
const src = resolve(sourceRoot, "language", lang)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, "language", lang))
}
}
// Copy baseline basic tokenizers.
for (const lang of ["python", "markdown", "cpp", "kotlin"] as const) {
const src = resolve(sourceRoot, "basic-languages", lang)
if (fs.existsSync(src)) {
copyRecursive(src, resolve(destRoot, "basic-languages", lang))
}
}
// Copy monaco.contribution.js entrypoints (needed by some loads).
const monacoContribution = resolve(sourceRoot, "basic-languages", "monaco.contribution.js")
if (fs.existsSync(monacoContribution)) {
copyRecursive(monacoContribution, resolve(destRoot, "basic-languages", "monaco.contribution.js"))
}
const underscoreContribution = resolve(sourceRoot, "basic-languages", "_.contribution.js")
if (fs.existsSync(underscoreContribution)) {
copyRecursive(underscoreContribution, resolve(destRoot, "basic-languages", "_.contribution.js"))
}
}
export default defineConfig({ export default defineConfig({
root: "./src/renderer", root: "./src/renderer",
plugins: [ plugins: [
@@ -90,10 +17,24 @@ export default defineConfig({
// Ensure Monaco's AMD assets exist in `root/public` for both dev server and builds. // Ensure Monaco's AMD assets exist in `root/public` for both dev server and builds.
// These files are gitignored and generated on demand. // These files are gitignored and generated on demand.
configureServer(server) { configureServer(server) {
copyMonacoPublicAssets({ warn: (msg) => server.config.logger.warn(msg) }) copyMonacoPublicAssets({
uiRendererRoot: resolve(__dirname, "src/renderer"),
warn: (msg) => server.config.logger.warn(msg),
sourceRoots: [
resolve(__dirname, "../../node_modules/monaco-editor/min/vs"),
resolve(__dirname, "node_modules/monaco-editor/min/vs"),
],
})
}, },
buildStart() { buildStart() {
copyMonacoPublicAssets({ warn: (msg) => this.warn(msg) }) copyMonacoPublicAssets({
uiRendererRoot: resolve(__dirname, "src/renderer"),
warn: (msg) => this.warn(msg),
sourceRoots: [
resolve(__dirname, "../../node_modules/monaco-editor/min/vs"),
resolve(__dirname, "node_modules/monaco-editor/min/vs"),
],
})
}, },
}, },
{ {