Warning
This project is currently under active development. The API may change at any time. Use at your own risk. Please report any issues or feature requests on the issues page.
Meet the modern version of Monaco Editor:
- Easy to use, no
MonacoEnvironment
setup, web workers, or CSS loaders required. - Uses Shiki for syntax highlighting with extensive grammars and themes.
- Lazy loading: pre-highlight code with Shiki while loading
monaco-editor-core
in the background. - Supports server-side rendering (SSR).
- Workspace features (edit history, file system provider, persist protocol, etc.).
- Automatically loads
.d.ts
files from esm.sh CDN for type checking. - Uses import maps for resolving bare specifier imports in JavaScript/TypeScript.
- VSCode
window
APIs likeshowInputBox
,showQuickPick
, etc. - Embedded languages (importmap/CSS/JavaScript) in HTML.
- Inline
html
andcss
in JavaScript/TypeScript. - Auto-closing HTML/JSX tags.
You can install modern-monaco
from NPM:
npm i modern-monaco typescript
Note
The typescript
package is required by the JavaScript/TypeScript LSP worker. We recommend [email protected]
or later.
Or import it from esm.sh CDN in the browser without a build step:
import * from "https://esm.sh/modern-monaco"
modern-monaco
provides three modes to create a browser-based code editor:
- Lazy: pre-highlight code with Shiki while loading the
editor-core.js
in the background. - SSR: render a mock editor on the server side and hydrates it on the client side.
- Manual: create a Monaco editor instance manually.
monaco-editor is a large package with additional CSS/Worker modules that requires MonacoEnvironment
setup for language service support. modern-monaco
provides a simple yet smart way to load editor modules on demand.
By pre-highlighting code with Shiki while loading editor modules in the background, modern-monaco
can significantly reduce loading screen time.
To create a Monaco editor lazily, you need to add a <monaco-editor>
custom element in your app's HTML, then call the lazy
function from modern-monaco. You also need to provide a workspace object to manage files for the editor.
<!-- index.html -->
<monaco-editor></monaco-editor>
<script src="app.js" type="module"></script>
// app.js
import { lazy, Workspace } from "modern-monaco";
// create a workspace with initial files
const workspace = new Workspace({
initialFiles: {
"index.html": `<html>...</body></html>`,
"main.js": `console.log("Hello, world!")`
},
entryFile: "index.html",
});
// initialize the editor lazily
await lazy({ workspace });
// open a file in the workspace
workspace.openTextDocument("main.js");
SSR mode returns an instant pre-rendered editor on the server side and hydrates it on the client side.
import { renderToWebComponent } from "modern-monaco/ssr";
export default {
async fetch(req) {
const ssrOut = await renderToWebComponent(
`console.log("Hello, world!")`,
{
theme: "OneDark-Pro",
language: "javascript",
userAgent: req.headers.get("user-agent"), // detect default font for different platforms
},
);
return new Response(
html`
${ssrOut}
<script type="module">
import { hydrate } from "https://esm.sh/modern-monaco";
// hydrate the editor
hydrate();
</script>
`,
{ headers: { "Content-Type": "text/html" } },
);
},
};
You can also create a Monaco editor instance manually. It loads themes and language grammars automatically.
<div id="editor"></div>
<script type="module">
import { init } from "modern-monaco";
// load monaco-editor-core.js
const monaco = await init();
// create a Monaco editor instance
const editor = monaco.editor.create(document.getElementById("editor"));
// create and attach a model to the editor
editor.setModel(monaco.editor.createModel(`console.log("Hello, world!")`, "javascript"));
</script>
modern-monaco
provides VSCode-like workspace features, such as edit history, file system provider, and more.
import { lazy, Workspace } from "modern-monaco";
// 1. create a workspace with initial files
const workspace = new Workspace({
/** The name of the workspace, used for project isolation. Default is "default". */
name: "project-name",
/** Initial files in the workspace. */
initialFiles: {
"index.html": `<html><head><title>Hello, world!</title></head><body><script src="main.js"></script></body></html>`,
"main.js": `console.log("Hello, world!")`,
},
/** File to open when the editor is loaded for the first time. */
entryFile: "index.html",
});
// 2. use the workspace in lazy mode
lazy({ workspace });
// 3. open a file in the workspace
workspace.openTextDocument("main.js");
You can add a tsconfig.json
file to configure the TypeScript compiler options for the TypeScript language service.
const tsconfig = {
"compilerOptions": {
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
},
};
const workspace = new Workspace({
initialFiles: {
"tsconfig.json": JSON.stringify(tsconfig, null, 2),
},
});
modern-monaco
uses import maps to resolve bare specifier imports in JavaScript/TypeScript. By default, modern-monaco
detects the importmap
from the root index.html
in the workspace.
const indexHtml = html`<!DOCTYPE html>
<html>
<head>
<script type="importmap">
{
"imports": {
"react": "https://esm.sh/react@18",
"react-dom/": "https://esm.sh/react-dom@18/"
}
}
</script>
</head>
<body>
<div id="root"></div>
<script type="module" src="app.tsx"></script>
</body>
</html>
`;
const appTsx = `import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(<div>Hello, world!</div>);
`;
const workspace = new Workspace({
initialFiles: {
"index.html": indexHtml,
"app.tsx": appTsx,
},
});
You can also provide an import map object as the lsp.typescript.importMap
option in the lazy
, init
, or hydrate
functions.
lazy({
lsp: {
typescript: {
importMap: {
"react": "https://esm.sh/react@18",
"react-dom/": "https://esm.sh/react-dom@18/",
},
},
},
});
Note
By default, modern-monaco
uses react
or preact
in the importmap
script as the jsxImportSource
option for the TypeScript worker.
To use a custom jsxImportSource
option, add the @jsxRuntime
specifier in the importmap
script.
modern-monaco
uses Shiki for syntax highlighting with extensive grammars and themes. By default, it loads themes and grammars from esm.sh on demand.
To set the editor theme, you can add a theme
attribute to the <monaco-editor>
element.
<monaco-editor theme="OneDark-Pro"></monaco-editor>
Or set it in the lazy
, init
, or hydrate
function.
lazy({
theme: "OneDark-Pro",
});
Note
The theme ID should be one of the Shiki Themes.
modern-monaco
loads the theme data from the CDN when a theme ID is provided. You can also use a theme from the tm-themes
package:
import OneDark from "tm-themes/themes/OneDark-Pro.json" with { type: "json" };
lazy({
theme: OneDark,
});
By default, modern-monaco
loads language grammars when a specific language mode is attached to the editor. You can also pre-load language grammars by adding the langs
option to the lazy
, init
, or hydrate
functions. The langs
option is an array of language grammars, which can be a language grammar object, a language ID, or a URL to the language grammar.
import markdown from "tm-grammars/markdown.json" with { type: "json" };
lazy({
langs: [
// load language grammars from CDN
"html",
"css",
"javascript",
"json",
// load language grammar from a URL
"https://example.com/grammars/mylang.json",
// load language grammar from a local file
"/assets/mylang.json",
// use `tm-grammars` package without extra HTTP requests, but increases the bundle size
markdown,
// dynamically import
() => import("tm-grammars/markdown.json", { with: { type: "json" } }),
// hand-crafted language grammar
{
name: "mylang",
scopeName: "source.mylang",
patterns: [/* ... */],
},
],
// The CDN for loading language grammars and themes. Default is "https://esm.sh"
tmDownloadCDN: "https://esm.sh",
});
You can set editor options as attributes in the <monaco-editor>
element. The editor options are the same as editor.EditorOptions
.
<monaco-editor
theme="OneDark-Pro"
fontFamily="Geist Mono"
fontSize="16"
></monaco-editor>
For SSR mode, you can set editor options in the renderToWebComponent
function.
import { renderToWebComponent } from "modern-monaco/ssr";
const html = await renderToWebComponent(
`console.log("Hello, world!")`,
{
theme: "OneDark-Pro",
language: "javascript",
fontFamily: "Geist Mono",
fontSize: 16,
},
);
For manual mode, check here for more details.
modern-monaco
by default supports full LSP features for the following languages:
- HTML
- CSS/SCSS/LESS
- JavaScript/TypeScript
- JSON
Additionally, modern-monaco
supports features like:
- File System Provider for import completions
- Embedded languages in HTML
- Inline
html
andcss
in JavaScript/TypeScript - Auto-closing HTML/JSX tags
Note
You don't need to set MonacoEnvironment.getWorker
for LSP support.
modern-monaco
automatically loads the required LSP workers.
You can configure built-in LSPs in the lazy
, init
, or hydrate
functions.
lazy({
// configure LSP for each language
lsp: {
html: {/* ... */},
json: {/* ... */},
typescript: {/* ... */},
},
});
The LSPLanguageConfig
interface is defined as:
export interface LSPLanguageConfig {
html?: {
attributeDefaultValue?: "empty" | "singlequotes" | "doublequotes";
customTags?: ITagData[];
hideAutoCompleteProposals?: boolean;
};
css?: {};
json?: {
/** JSON schemas for JSON language service. */
schemas?: JSONSchemaSource[];
};
typescript?: {
/** The compiler options. */
compilerOptions?: ts.CompilerOptions;
/** The global import map. */
importMap?: ImportMap;
/** The version of TypeScript from the CDN. Default: ">= 5.0.0" */
tsVersion?: string;
};
}
modern-monaco
includes built-in grammars and LSP providers for HTML, CSS, JavaScript/TypeScript, and JSON. If you don't need these features, you can use the modern-monaco/core
sub-module to reduce the bundle size.
import { lazy } from "modern-monaco/core";
lazy();