@@ -221,6 +221,53 @@ function projectRootStaticPlugin(): Plugin {
221221 } ;
222222}
223223
224+ /**
225+ * Serve the Monaco AMD runtime at `/resources/monaco/vs/*` from
226+ * `react/node_modules/monaco-editor/min/vs/*` during dev. Mirrors the
227+ * `static` directory entry that lived in the deleted `react/craco.config.cjs`.
228+ *
229+ * `@monaco-editor/react` resolves the URL prefix via
230+ * loader.config({ paths: { vs: '/resources/monaco/vs' } })
231+ * (see `react/src/helper/monacoEditor.ts`). Self-hosting keeps Monaco
232+ * working in offline / air-gapped deployments where jsDelivr is unreachable.
233+ *
234+ * Production is unchanged: the root `copymonaco` script (`package.json:32`)
235+ * copies the same tree into `build/web/resources/monaco/vs/`.
236+ *
237+ * `min/vs` is Monaco's prebuilt AMD bundle — used here rather than the ESM
238+ * tree because `@monaco-editor/react` consumes the AMD form to keep the
239+ * Monaco worker chunks intact for runtime lazy-loading.
240+ */
241+ function monacoStaticPlugin ( ) : Plugin {
242+ const monacoRoot = resolve ( __dirname , 'node_modules/monaco-editor/min/vs' ) ;
243+ const URL_PREFIX = '/resources/monaco/vs/' ;
244+
245+ return {
246+ name : 'bai-monaco-static' ,
247+ apply : 'serve' ,
248+ configureServer ( server ) {
249+ server . middlewares . use ( ( req , res , next ) => {
250+ if ( ! req . url ) return next ( ) ;
251+ const url = req . url . split ( '?' ) [ 0 ] ;
252+ if ( ! url . startsWith ( URL_PREFIX ) ) return next ( ) ;
253+
254+ const rel = url . slice ( URL_PREFIX . length ) ;
255+ const filePath = join ( monacoRoot , rel ) ;
256+ // Path-traversal guard: `join` normalizes `..`, so prefix comparison
257+ // catches any URL that escapes `monacoRoot`.
258+ if ( ! filePath . startsWith ( monacoRoot ) ) return next ( ) ;
259+ if ( ! existsSync ( filePath ) || ! statSync ( filePath ) . isFile ( ) ) {
260+ return next ( ) ;
261+ }
262+ const mime =
263+ MIME [ extname ( filePath ) . toLowerCase ( ) ] ?? 'application/octet-stream' ;
264+ res . setHeader ( 'Content-Type' , mime ) ;
265+ createReadStream ( filePath ) . pipe ( res ) ;
266+ } ) ;
267+ } ,
268+ } ;
269+ }
270+
224271export default defineConfig ( ( { command, mode } ) => {
225272 const env = loadEnv ( mode , projectRoot , '' ) ;
226273 Object . assign ( process . env , env ) ;
@@ -394,7 +441,10 @@ export default defineConfig(({ command, mode }) => {
394441
395442 plugins : [
396443 // Must run before @vitejs /plugin-react so we own the HTML transform
397- // and the index.html resolution.
444+ // and the index.html resolution. Monaco's narrower prefix is matched
445+ // first so the more general projectRoot middleware never has to
446+ // reach into the filesystem for a `/resources/monaco/vs/*` request.
447+ monacoStaticPlugin ( ) ,
398448 projectRootStaticPlugin ( ) ,
399449 devAssetsReloadPlugin ( ) ,
400450
0 commit comments