Skip to content

Commit fa43a8c

Browse files
committed
fix(FR-2606): restore Monaco self-hosting in Vite dev server
1 parent a55f50b commit fa43a8c

1 file changed

Lines changed: 51 additions & 1 deletion

File tree

react/vite.config.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
224271
export 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

Comments
 (0)