@monaco-editor/loader vs ace-builds vs react-monaco-editor
Integrating Code Editors in React: Engine Selection and Implementation Strategies
@monaco-editor/loaderace-buildsreact-monaco-editorSimilar Packages:

Integrating Code Editors in React: Engine Selection and Implementation Strategies

ace-builds provides the distribution files for the Ace Editor, a mature, lightweight text editor engine. @monaco-editor/loader is a low-level utility for loading the Monaco Editor (the engine behind VS Code) in browser environments, handling AMD module resolution. react-monaco-editor is a React component wrapper that simplifies embedding the Monaco Editor into React applications. While ace-builds represents a different editor engine entirely, the Monaco packages offer varying levels of abstraction for integrating Microsoft's editor technology.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
@monaco-editor/loader4,960,445239107 kB2110 months agoMIT
ace-builds1,005,2613,09355.1 MB04 months agoBSD-3-Clause
react-monaco-editor143,9084,20660.4 kB78a year agoMIT

Integrating Code Editors in React: Engine Selection and Implementation Strategies

When embedding a code editor into a web application, architects typically choose between two dominant engines: Ace and Monaco. ace-builds distributes the Ace Editor, while @monaco-editor/loader and react-monaco-editor facilitate integrating the Monaco Editor. The decision isn't just about features โ€” it's about bundle weight, initialization complexity, and framework integration. Let's compare how these packages handle real-world implementation.

๐Ÿ—๏ธ Initialization: Manual Setup vs. Component Abstraction

ace-builds requires manual initialization on a DOM node.

  • You must import the build files and configure the editor instance yourself.
  • Gives you full control but requires more boilerplate code.
// ace-builds: Manual initialization
import ace from 'ace-builds';

const editor = ace.edit("editor-container");
editor.setTheme("ace/theme/monokai");
editor.session.setMode("ace/mode/javascript");
editor.setValue("function hello() {}");

@monaco-editor/loader handles the asynchronous loading of Monaco's AMD modules.

  • You must call init() and wait for the promise to resolve before creating the editor.
  • Ideal for custom setups where you don't want a React wrapper.
// @monaco-editor/loader: Custom loading
import * as monacoLoader from '@monaco-editor/loader';

monacoLoader.init().then((monaco) => {
  const editor = monaco.editor.create(document.getElementById("container"), {
    value: "function hello() {}",
    language: "javascript"
  });
});

react-monaco-editor abstracts initialization into a React component.

  • You simply render the <Editor /> component with props.
  • Fastest setup for React apps but hides lifecycle details.
// react-monaco-editor: Component abstraction
import Editor from 'react-monaco-editor';

function CodeEditor() {
  return (
    <Editor
      height="90vh"
      language="javascript"
      value="function hello() {}"
      theme="vs-dark"
    />
  );
}

โš™๏ธ Configuration: Direct Options vs. Prop Mapping

ace-builds uses direct method calls for configuration.

  • You call methods like setOptions on the editor instance.
  • Immediate effect, no re-render cycle involved.
// ace-builds: Direct configuration
editor.setOptions({
  fontSize: "14pt",
  showPrintMargin: false,
  wrap: true
});

@monaco-editor/loader passes configuration objects to the create method.

  • Options are defined once during instantiation.
  • Changing options later requires calling updateOptions on the instance.
// @monaco-editor/loader: Config object
const editor = monaco.editor.create(container, {
  value: "",
  fontSize: 14,
  automaticLayout: true
});

// Later update
editor.updateOptions({ fontSize: 16 });

react-monaco-editor passes configuration via React props.

  • Changing a prop triggers a re-render and updates the editor.
  • Easier to manage with React state but adds render overhead.
// react-monaco-editor: Prop-based config
function CodeEditor({ fontSize }) {
  const options = { fontSize, automaticLayout: true };
  return <Editor options={options} language="javascript" />;
}

๐Ÿ”„ Event Handling: DOM Listeners vs. React Callbacks

ace-builds relies on traditional event emitters.

  • You attach listeners using editor.on().
  • Must manually remove listeners to prevent memory leaks.
// ace-builds: Event emitter
editor.on("change", (delta) => {
  console.log("Content changed", delta);
});

// Cleanup required
// editor.off("change", handler);

@monaco-editor/loader uses Monaco's event system.

  • Events return a disposable object that must be disposed.
  • Critical for single-page applications to avoid leaks.
// @monaco-editor/loader: Disposable events
const listener = editor.onDidChangeModelContent((e) => {
  console.log("Model changed", e);
});

// Cleanup
// listener.dispose();

react-monaco-editor exposes events as callback props.

  • Pass functions like onChange directly to the component.
  • React handles the binding, but you still need to be aware of closure staleness.
// react-monaco-editor: Callback props
function CodeEditor() {
  const handleChange = (value) => {
    console.log("Value updated", value);
  };

  return <Editor onChange={handleChange} language="javascript" />;
}

๐Ÿงน Lifecycle Management: Manual Cleanup vs. Automatic Unmount

ace-builds requires explicit destruction.

  • You must call editor.destroy() when the component unmounts.
  • Forgetting this causes memory leaks and dangling event listeners.
// ace-builds: Manual cleanup
useEffect(() => {
  const editor = ace.edit("container");
  return () => {
    editor.destroy();
  };
}, []);

@monaco-editor/loader requires manual disposal of the editor instance.

  • Since it's a low-level loader, it doesn't manage React lifecycles for you.
  • You are responsible for calling editor.dispose() in useEffect cleanup.
// @monaco-editor/loader: Manual disposal
useEffect(() => {
  let editorInstance;
  monacoLoader.init().then((monaco) => {
    editorInstance = monaco.editor.create(container, options);
  });
  return () => {
    if (editorInstance) editorInstance.dispose();
  };
}, []);

react-monaco-editor handles cleanup internally.

  • The component disposes of the editor instance on unmount.
  • Reduces boilerplate but can sometimes cause issues if state updates occur after unmount.
// react-monaco-editor: Automatic cleanup
// No explicit cleanup code needed in parent component
function App() {
  return <Editor language="javascript" />;
}

๐Ÿ“ฆ Bundle Strategy: CDN vs. Bundler

ace-builds is often loaded via CDN or copied assets.

  • You can load modes and themes dynamically from a CDN.
  • Reduces initial bundle size but adds network requests.
// ace-builds: CDN configuration
ace.config.set('basePath', 'https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/');

@monaco-editor/loader supports configuring the CDN path for Monaco assets.

  • You can point paths to a CDN to avoid bundling the heavy editor code.
  • Useful for reducing main bundle size.
// @monaco-editor/loader: CDN config
monacoLoader.config({
  paths: {
    vs: 'https://cdn.jsdelivr.net/npm/monaco-editor@0.33.0/min/vs'
  }
});

react-monaco-editor typically bundles the editor or expects global setup.

  • Often requires additional webpack configuration to handle Monaco's workers.
  • Can be complex to configure for production builds without copying assets.
// react-monaco-editor: Webpack config (simplified)
// Requires CopyWebpackPlugin to move monaco assets to build folder
// Or using monaco-editor-webpack-plugin

๐Ÿ› ๏ธ Similarities: Shared Capabilities

Despite different engines and abstraction levels, these tools share core goals.

1. ๐Ÿ“ Syntax Highlighting

  • All support syntax highlighting for dozens of languages.
  • Configurable via mode (Ace) or language (Monaco).
// Ace
editor.session.setMode("ace/mode/python");

// Monaco
monaco.editor.create(el, { language: "python" });

2. ๐ŸŽจ Theming Support

  • Both engines offer built-in light and dark themes.
  • Custom themes can be defined programmatically.
// Ace
editor.setTheme("ace/theme/twilight");

// Monaco
monaco.editor.defineTheme('myTheme', { base: 'vs-dark', inherit: true, rules: [] });

3. ๐Ÿ”Œ Extension Points

  • Both allow custom keybindings and commands.
  • Enable IDE-like features (linting, formatting) via plugins or custom code.
// Ace
editor.commands.addCommand({ name: 'save', bindKey: 'Ctrl-S', exec: saveFunction });

// Monaco
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS, saveFunction);

๐Ÿ“Š Summary: Key Differences

Featureace-builds@monaco-editor/loaderreact-monaco-editor
EngineAce EditorMonaco EditorMonaco Editor
React IntegrationManual (useEffect)Manual (useEffect)Built-in Component
InitializationSynchronous (DOM ready)Asynchronous (Promise)Declarative (JSX)
CleanupManual (destroy())Manual (dispose())Automatic
Bundle WeightLighterHeavierHeavier
MaintenanceStable, LegacyActive, Low-levelCommunity, Less Active than Official

๐Ÿ’ก The Big Picture

ace-builds is the pragmatic choice for lightweight needs.

  • It's stable, fast, and doesn't demand heavy resources.
  • Best for simple text areas, admin panels, or legacy apps where Monaco is overkill.

@monaco-editor/loader is the power user's tool for Monaco.

  • It gives you the raw engine without React abstractions.
  • Best for custom frameworks, performance-critical apps, or when you need to manage loading logic precisely.

react-monaco-editor is the convenience wrapper.

  • It speeds up development but adds a layer of abstraction.
  • Note: For new React projects, strongly consider @monaco-editor/react instead, as it is more actively maintained by the loader's author. Use react-monaco-editor primarily for maintaining existing implementations.

Final Thought: The choice between Ace and Monaco is about feature density vs. performance. The choice between Loader and React Wrapper is about control vs. convenience. Match the tool to your team's capacity and your app's requirements.

How to Choose: @monaco-editor/loader vs ace-builds vs react-monaco-editor

  • @monaco-editor/loader:

    Choose @monaco-editor/loader if you need full control over the Monaco Editor initialization process without the constraints of a pre-built React component. It is ideal for custom integrations, non-React frameworks, or when you need to manage the editor lifecycle manually to optimize performance in complex applications.

  • ace-builds:

    Choose ace-builds if you require a lightweight, highly stable editor with a smaller footprint than Monaco. It is suitable for legacy systems, simple text editing needs, or environments where the heavy resource usage of Monaco is prohibitive, though it lacks some of the advanced IDE features of Monaco.

  • react-monaco-editor:

    Choose react-monaco-editor if you need a quick, drop-in React component for Monaco Editor and prefer a community-maintained wrapper. However, for new projects, evaluate @monaco-editor/react as it is more actively maintained; use this package primarily for maintaining existing codebases that already depend on it.

README for @monaco-editor/loader

@monaco-editor/loader ยท monthly downloads gitHub license npm version PRs welcome

The utility to easy setup monaco-editor into your browser

Synopsis

Configure and download monaco sources via its loader script, without needing to use webpack's (or any other module bundler's) configuration files

Motivation

It's been a while we are working with monaco editor. It's a great library and provides a powerful editor out of the box. Anyway, there were couple of problems related to the setup process. The main problem is the need to do some additional webpack configuration; that's not bad, but some quite useful tools, like CRA, aren't happy with that fact. The library @monaco-editor/react was being created to solve that problem - monaco editor wrapper for easy/one-line integration with React applications without needing to use webpack's (or any other module bundler's) configuration files. In that library, there was a utility that cares about the initialization process of monaco and overcomes the additional use of webpack configuration. That utility grows over time and now it's a separate library. Now, you can easily setup monaco into your browser, create your own editors, wrappers for React/Vue/Angular of whatever you want.

How it works

Monaco editor provides a script called loader, which itself provides tooling to download monaco sources. The library, under the hood, handles the configuration and loading part and gives us an easy-to-use API to interact with it

Documentation

Contents

Installation

npm install @monaco-editor/loader

or

yarn add @monaco-editor/loader

NOTE: For TypeScript type definitions, this package uses the monaco-editor package as a peer dependency. So, if you need types and don't already have the monaco-editor package installed, you will need to do so.

Introduction

The library exports types and the utility called loader, the last one has two methods

Usage

import loader from '@monaco-editor/loader';

loader.init().then(monaco => {
  monaco.editor.create(/* editor container, e.g. document.body */, {
    value: '// some comment',
    language: 'javascript',
  });
});

codesandbox

.config

By using the .config method we can configure the monaco loader. By default all sources come from CDN, you can change that behavior and load them from wherever you want

import loader from '@monaco-editor/loader';

// you can change the source of the monaco files
loader.config({ paths: { vs: '...' } });

// you can configure the locales
loader.config({ 'vs/nls': { availableLanguages: { '*': 'de' } } });

// or
loader.config({
  paths: {
    vs: '...',
  },
  'vs/nls' : {
    availableLanguages: {
      '*': 'de',
    },
  },
});

loader.init().then(monaco => { /* ... */ });

codesandbox

Configure the loader to load the monaco as an npm package

import loader from '@monaco-editor/loader';
import * as monaco from 'monaco-editor';

loader.config({ monaco });

loader.init().then(monacoInstance => { /* ... */ });

codesandbox

.init

The .init method handles the initialization process. It returns the monaco instance, wrapped with cancelable promise

import loader from '@monaco-editor/loader';

loader.init().then(monaco => {
  console.log('Here is the monaco instance', monaco);
});

codesandbox

import loader from '@monaco-editor/loader';

const cancelable = loader.init();

cancelable.then(monaco => {
  console.log('You will not see it, as it is canceled');
});

cancelable.cancel();

codesandbox

Notes

For electron users

In general it works fine with electron, but there are several cases that developers usually face to and sometimes it can be confusing. Here they are:

  1. Download process fails or if you use @monaco-editor/react You see loading screen stuck Usually, it's because your environment doesn't allow you to load external sources. By default, it loads monaco sources from CDN. You can see the default configuration. But sure you can change that behavior; the library is fully configurable. Read about it here. So, if you want to download it from your local files, you can do it like this:
import loader from '@monaco-editor/loader';

loader.config({ paths: { vs: '../path-to-monaco' } });

or, if you want to use it as an npm package, you can do it like this:

import loader from '@monaco-editor/loader';
import * as monaco from 'monaco-editor';

loader.config({ monaco });

loader.init().then(monacoInstance => { /* ... */ });
  1. Based on your electron environment it can be required to have an absolute URL The utility function taken from here can help you to achieve that. Let's imagine you have monaco-editor package installed and you want to load monaco from the node_modules rather than from CDN: in that case, you can write something like this:
function ensureFirstBackSlash(str) {
    return str.length > 0 && str.charAt(0) !== '/'
        ? '/' + str
        : str;
}

function uriFromPath(_path) {
    const pathName = path.resolve(_path).replace(/\\/g, '/');
    return encodeURI('file://' + ensureFirstBackSlash(pathName));
}

loader.config({
  paths: {
    vs: uriFromPath(
      path.join(__dirname, '../node_modules/monaco-editor/min/vs')
    )
  }
});

or, just use it as an npm package.

There were several issues about this topic that can be helpful too - 1 2 3 4

And if you use electron with monaco and have faced an issue different than the above-discribed ones, please let us know to make this section more helpful.

For Next.js users

The part of the source that should be pre-parsed is optimized for server-side rendering, so, in usual cases, it will work fine, but if you want to have access, for example, to monacoInstance you should be aware that it wants to access the document object, and it requires browser environment. Basically you just need to avoid running that part out of browser environment, there are several ways to do that. One of them is described here.

And if you use monaco with Next.js and have faced an issue different than the above-described one, please let us know to make this section more helpful.

License

MIT