https://github.com/esm-dev/modern-monaco
A modern version of Monaco Editor.
https://github.com/esm-dev/modern-monaco
Last synced: about 1 month ago
JSON representation
A modern version of Monaco Editor.
- Host: GitHub
- URL: https://github.com/esm-dev/modern-monaco
- Owner: esm-dev
- License: mit
- Created: 2024-02-08T15:02:38.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2026-03-01T13:19:28.000Z (4 months ago)
- Last Synced: 2026-03-01T14:58:27.988Z (4 months ago)
- Language: TypeScript
- Homepage:
- Size: 825 KB
- Stars: 1,535
- Watchers: 7
- Forks: 34
- Open Issues: 21
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
- awesome - esm-dev/modern-monaco - A modern version of Monaco Editor. (TypeScript)
README
> [!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](https://github.com/esm-dev/modern-monaco/issues) page.
# Modern Monaco
Meet the modern version of [Monaco Editor](https://www.npmjs.com/package/monaco-editor):
- Easy to use, no `MonacoEnvironment` setup, web workers, or CSS loaders required.
- Uses [Shiki](https://shiki.style) 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](https://esm.sh) CDN for type checking.
- Uses [import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) for resolving **bare specifier** imports in JavaScript/TypeScript.
- VSCode `window` APIs like `showInputBox`, `showQuickPick`, etc.
- Embedded languages (importmap/CSS/JavaScript) in HTML.
- Inline `html` and `css` in JavaScript/TypeScript.
- Auto-closing HTML/JSX tags.
## Installation
You can install modern-monaco from NPM:
```bash
npm i modern-monaco
```
Or import it from [esm.sh](https://esm.sh/) CDN in the browser without a build step:
```js
import * from "https://esm.sh/modern-monaco"
```
## Usage
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.
### Lazy Mode
[monaco-editor](https://www.npmjs.com/package/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 `` custom element in the HTML of your app, then call the `lazy` function provided by modern-monaco. You may also need a `Workspace` object to manage editor models without calling the native Monaco APIs.
```html
```
```js
// app.js
import { lazy, Workspace } from "modern-monaco";
// create a workspace with initial files
const workspace = new Workspace({
initialFiles: {
"index.html": `...`,
"main.js": `console.log("Hello, world!")`,
},
entryFile: "index.html",
});
// initialize the editor lazily
lazy({ workspace });
// write a file and open it in the editor
await workspace.fs.writeFile("util.js", "export function add(a, b) { return a + b; }");
workspace.openTextDocument("util.js");
```
### SSR Mode
SSR mode returns an instant pre-rendered editor on the server side and hydrates it on the client side.
```js
import { renderToWebComponent } from "modern-monaco/ssr";
export default {
async fetch(req) {
const editorHTML = await renderToWebComponent(
`console.log("Hello, world!")`,
{
language: "javascript",
theme: "vitesse-dark",
userAgent: req.headers.get("user-agent"), // detect default font for different platforms
},
);
return new Response(
/* html */ `
${editorHTML}
import { hydrate } from "https://esm.sh/modern-monaco";
// hydrate the editor
hydrate();
`,
{ headers: { "Content-Type": "text/html" } },
);
},
};
```
SSR Demo: https://modern-monaco-demo.vercel.app ([Source](https://github.com/pi0/modern-monaco-demo) by [@pi0](https://github.com/pi0))
### Manual Mode
You can also create a [Monaco editor](https://microsoft.github.io/monaco-editor/docs.html) instance manually. It loads themes and language grammars automatically.
```html
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"));
```
## Using Workspace
modern-monaco provides VSCode-like workspace features, such as edit history, file system provider, and more.
```js
import { lazy, Workspace } from "modern-monaco";
// 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": `Hello, world!`,
"main.js": `console.log("Hello, world!")`,
},
/** File to open when the editor is loaded for the first time. */
entryFile: "index.html",
});
// use the workspace in lazy mode
lazy({ workspace });
// open a file in the workspace
workspace.openTextDocument("main.js");
```
### Custom Workspace FileSystem
By default, modern-monaco uses `IndexedDB` as the workspace filesystem to persist the editor changes. With a custom filesystem, you can implement your own persistence logic.
```ts
import { type FileSystem, lazy, Workspace } from "modern-monaco";
class CustomFileSystem implements FileSystem {
// Custom FileSystem implementation
}
const workspace = new Workspace({
initialFiles: {
"index.html": indexHtml,
"app.tsx": appTsx,
},
customFS: new CustomFileSystem(),
});
lazy({ workspace });
```
Please refer to the [FileSystem](./types/workspace.d.ts#L54) interface for more details.
## Editor Theme & Language Grammars
modern-monaco uses [Shiki](https://shiki.style) for syntax highlighting with extensive grammars and themes. By default, it loads themes and grammars from esm.sh on demand.
### Setting the Editor Theme
To set the editor theme, you can add a `theme` attribute to the `` element.
```html
```
Or add a `defaultTheme` option to the `lazy`, `init`, or `hydrate` function.
```js
lazy({
defaultTheme: "one-dark-pro",
});
```
> [!Note]
> The theme ID should be one of the [Shiki Themes](https://shiki.style/themes).
You can also load multiple themes by passing an array of theme inputs to the `themes` option.
```js
const monaco = await init({
themes: [
"one-light",
"one-dark-pro",
],
});
monaco.editor.create(document.getElementById("editor"), {
theme: "one-light",
});
// update the editor theme
monaco.editor.setTheme("one-dark-pro");
```
modern-monaco loads the theme data from the CDN when a theme ID is provided. You can also load a theme from a JSON file:
```js
import OneDarkPro from "tm-themes/themes/one-dark-pro.json" with { type: "json" };
lazy({
themes: [
// load language grammars from CDN, these language ids must be defined in the `tm-grammars` package
"one-light",
// import theme from `tm-themes` package without extra HTTP requests, but increases the bundle size
OneDarkPro,
// load theme from a URL
"https://example.com/themes/mytheme.json",
// load theme from an asset file
"/assets/mytheme.json",
// dynamically import
() => import("tm-themes/one-light.json", { with: { type: "json" } }),
// hand-crafted theme
{
name: "mytheme",
base: "vs-dark",
colors: {/* ... */},
tokenColors: [/* ... */],
},
],
});
```
### Pre-loading Language Grammars
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.
```js
import markdown from "tm-grammars/markdown.json" with { type: "json" };
lazy({
langs: [
// load language grammars from CDN, these language ids must be defined in the `tm-grammars` package
"html",
"css",
"javascript",
"json",
// import language grammar from `tm-grammars` package without extra HTTP requests, but increases the bundle size
markdown,
// load language grammar from a URL
"https://example.com/grammars/mylang.json",
// load language grammar from an asset file
"/assets/mylang.json",
// 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"
cdn: "https://esm.sh",
});
```
## Editor Options
You can set editor options as attributes in the `` element. The editor options are the same as [`editor.EditorOptions`](https://microsoft.github.io/monaco-editor/docs.html#variables/editor.EditorOptions.html).
```html
```
For SSR mode, you can set editor options in the `renderToWebComponent` function.
```js
import { renderToWebComponent } from "modern-monaco/ssr";
const html = await renderToWebComponent(
`console.log("Hello, world!")`,
{
theme: "vitesse-dark",
language: "javascript",
fontFamily: "Geist Mono",
fontSize: 16,
},
);
```
For manual mode, check [here](https://microsoft.github.io/monaco-editor/docs.html#functions/editor.create.html) for more details.
## Language Server Protocol (LSP)
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` and `css` 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.
### LSP Language Configuration
You can configure built-in LSPs in the `lazy`, `init`, or `hydrate` functions.
```js
lazy({
lsp: {
// formatting options for all languages
formatting: {/* ... */},
// configure LSP for languages
html: {/* ... */},
css: {/* ... */},
json: {/* ... */},
typescript: {/* ... */},
},
});
```
The `LSPConfig` interface is defined as:
```ts
export interface LSPConfig {
/** Formatting options. */
formatting?: {
/** Size of a tab in spaces. Default: 4. */
tabSize?: number;
/** Prefer spaces over tabs. Default: true.*/
insertSpaces?: boolean;
/** Trim trailing whitespace on a line. Default: true. */
trimTrailingWhitespace?: boolean;
/** Insert a newline character at the end of the file if one does not exist. Default: false. */
insertFinalNewline?: boolean;
/** Trim all newlines after the final newline at the end of the file. Default: false. */
trimFinalNewlines?: boolean;
/** Semicolon preference for JavaScript and TypeScript. Default: "insert". */
semicolon?: "ignore" | "insert" | "remove";
};
/** HTML language configuration. */
html?: {
/** Defines whether the standard HTML tags are shown. Default is true. */
useDefaultDataProvider?: boolean;
/** Provides a set of custom data providers. */
dataProviders?: { [providerId: string]: HTMLDataV1 };
/** Provides a set of custom HTML tags. */
customTags?: ITagData[];
/** The default value for empty attributes. Default is "empty". */
attributeDefaultValue?: "empty" | "singlequotes" | "doublequotes";
/** Whether to hide end tag suggestions. Default is false. */
hideEndTagSuggestions?: boolean;
/** Whether to hide auto complete proposals. Default is false. */
hideAutoCompleteProposals?: boolean;
/** Whether to show the import map code lens. Default is true. */
importMapCodeLens?: boolean;
/** Options for the diagnostics. */
diagnosticsOptions?: DiagnosticsOptions;
};
/** CSS language configuration. */
css?: {
/** Defines whether the standard CSS properties, at-directives, pseudoClasses and pseudoElements are shown. */
useDefaultDataProvider?: boolean;
/** Provides a set of custom data providers. */
dataProviders?: { [providerId: string]: CSSDataV1 };
/** A list of valid properties that not defined in the standard CSS properties. */
validProperties?: string[];
/** Options for the diagnostics. */
diagnosticsOptions?: DiagnosticsOptions;
};
/** JSON language configuration. */
json?: {
/** Whether to show the import map code lens. Default is true. */
importMapCodeLens?: boolean;
/** Defines whether comments are allowed or not. Default is disallowed. */
allowComments?: boolean;
/** A list of known schemas and/or associations of schemas to file names. */
schemas?: JSONSchemaSource[];
/** The severity of reported comments. Default is "error". */
comments?: SeverityLevel;
/** The severity of reported trailing commas. Default is "error". */
trailingCommas?: SeverityLevel;
/** The severity of problems from schema validation. Default is "warning". */
schemaValidation?: SeverityLevel;
/** The severity of problems that occurred when resolving and loading schemas. Default is "warning". */
schemaRequest?: SeverityLevel;
/** Options for the diagnostics. */
diagnosticsOptions?: DiagnosticsOptions;
};
/** TypeScript language configuration. */
typescript?: {
/** The default import maps. */
importMap?: ImportMap;
/** The compiler options. */
compilerOptions?: ts.CompilerOptions;
/** Options for the diagnostics. */
diagnosticsOptions?: DiagnosticsOptions;
};
}
```
You can also set the diagnostics options for each language by adding the `diagnosticsOptions` option to the `lsp` options:
```js
lazy({
lsp: {
css: {
diagnosticsOptions: {
// filter out unknown property errors
filter: (diagnostic) => diagnostic.code !== "unknownProperty",
},
},
json: {
diagnosticsOptions: {
// disable syntax and semantic validation
validate: false,
},
},
typescript: {
diagnosticsOptions: {
// ignore type not found errors (code 2307)
codesToIgnore: [2307],
},
},
},
});
```
### Import Maps
modern-monaco uses [import maps](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script/type/importmap) to resolve **bare specifier** imports in JavaScript/TypeScript. By default, modern-monaco detects the `importmap` from the root `index.html` in the workspace.
```js
const indexHtml = /* html */ `
{
"imports": {
"react": "https://esm.sh/react@18",
"react-dom/": "https://esm.sh/react-dom@18/"
}
}
`;
const appTsx = `import { createRoot } from "react-dom/client";
createRoot(document.getElementById("root")).render(
Hello, world!);
`;
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.
```js
lazy({
lsp: {
typescript: {
importMap: {
"react": "https://esm.sh/react@18",
"react-dom/": "https://esm.sh/react-dom@18/",
},
},
},
});
```
### Adding `tsconfig.json`
You can add a `tsconfig.json` file to configure the TypeScript compiler options for the TypeScript language service.
```js
const tsconfig = {
"compilerOptions": {
"target": "ES2022",
"strict": true,
},
};
const workspace = new Workspace({
initialFiles: {
"tsconfig.json": JSON.stringify(tsconfig, null, 2),
},
});
```
You can also manually add the TypeScript compiler options as the `lsp.typescript.compilerOptions` option in the `lazy`, `init`, or `hydrate` functions.
```js
lazy({
lsp: {
typescript: {
compilerOptions: {
target: "ES2022",
strict: true,
},
},
},
});
```
## Using the `core` Module
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.
```js
import { lazy } from "modern-monaco/core";
lazy();
```
## Loading editor modules from a custom CDN
By default, modern-monaco loads editor modules from `https://esm.sh`. You can customize the CDN URL by providing an import map in your HTML.
```html
{
"imports": {
"modern-monaco": "https://mycdn.com/modern-monaco@:version/dist/index.mjs",
"modern-monaco/editor-core": "https://mycdn.com/modern-monaco@:version/dist/editor-core.mjs",
"modern-monaco/lsp": "https://mycdn.com/modern-monaco@:version/dist/lsp/index.mjs",
"typescript": "https://mycdn.com/typescript@:version/lib/typescript.js"
}
}
```
## License
[MIT License](https://opensource.org/licenses/MIT)