modern-monaco 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -20,15 +20,12 @@ Meet the modern version of [Monaco Editor](https://www.npmjs.com/package/monaco-
20
20
 
21
21
  ## Installation
22
22
 
23
- You can install `modern-monaco` from NPM:
23
+ You can install modern-monaco from NPM:
24
24
 
25
25
  ```bash
26
- npm i modern-monaco typescript
26
+ npm i modern-monaco
27
27
  ```
28
28
 
29
- > [!Note]
30
- > The `typescript` package is required by the JavaScript/TypeScript LSP worker. We recommend `typescript@5.5.x` or later.
31
-
32
29
  Or import it from [esm.sh](https://esm.sh/) CDN in the browser without a build step:
33
30
 
34
31
  ```js
@@ -37,7 +34,7 @@ import * from "https://esm.sh/modern-monaco"
37
34
 
38
35
  ## Usage
39
36
 
40
- `modern-monaco` provides three modes to create a browser-based code editor:
37
+ modern-monaco provides three modes to create a browser-based code editor:
41
38
 
42
39
  - **Lazy**: pre-highlight code with Shiki while loading the `editor-core.js` in the background.
43
40
  - **SSR**: render a mock editor on the server side and hydrates it on the client side.
@@ -45,11 +42,11 @@ import * from "https://esm.sh/modern-monaco"
45
42
 
46
43
  ### Lazy Mode
47
44
 
48
- [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.
45
+ [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.
49
46
 
50
- By pre-highlighting code with Shiki while loading editor modules in the background, `modern-monaco` can significantly reduce loading screen time.
47
+ By pre-highlighting code with Shiki while loading editor modules in the background, modern-monaco can significantly reduce loading screen time.
51
48
 
52
- 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.
49
+ To create a Monaco editor lazily, you need to add a `<monaco-editor>` 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.
53
50
 
54
51
  ```html
55
52
  <!-- index.html -->
@@ -64,8 +61,8 @@ import { lazy, Workspace } from "modern-monaco";
64
61
  // create a workspace with initial files
65
62
  const workspace = new Workspace({
66
63
  initialFiles: {
67
- "index.html": `<html>...</body></html>`,
68
- "main.js": `console.log("Hello, world!")`
64
+ "index.html": `<html><body>...</body></html>`,
65
+ "main.js": `console.log("Hello, world!")`,
69
66
  },
70
67
  entryFile: "index.html",
71
68
  });
@@ -73,8 +70,9 @@ const workspace = new Workspace({
73
70
  // initialize the editor lazily
74
71
  await lazy({ workspace });
75
72
 
76
- // open a file in the workspace
77
- workspace.openTextDocument("main.js");
73
+ // write a file and open it in the editor
74
+ workspace.fs.writeFile("util.js", "export function add(a, b) { return a + b; }");
75
+ workspace.openTextDocument("util.js");
78
76
  ```
79
77
 
80
78
  ### SSR Mode
@@ -86,29 +84,31 @@ import { renderToWebComponent } from "modern-monaco/ssr";
86
84
 
87
85
  export default {
88
86
  async fetch(req) {
89
- const ssrOut = await renderToWebComponent(
87
+ const editorHTML = await renderToWebComponent(
90
88
  `console.log("Hello, world!")`,
91
89
  {
92
- theme: "OneDark-Pro",
93
90
  language: "javascript",
91
+ theme: "OneDark-Pro",
94
92
  userAgent: req.headers.get("user-agent"), // detect default font for different platforms
95
93
  },
96
94
  );
97
95
  return new Response(
98
96
  html`
99
- ${ssrOut}
100
- <script type="module">
101
- import { hydrate } from "https://esm.sh/modern-monaco";
102
- // hydrate the editor
103
- hydrate();
104
- </script>
105
- `,
97
+ ${editorHTML}
98
+ <script type="module">
99
+ import { hydrate } from "https://esm.sh/modern-monaco";
100
+ // hydrate the editor
101
+ hydrate();
102
+ </script>
103
+ `,
106
104
  { headers: { "Content-Type": "text/html" } },
107
105
  );
108
106
  },
109
107
  };
110
108
  ```
111
109
 
110
+ SSR Demo: https://modern-monaco-demo.vercel.app ([Source](https://github.com/pi0/modern-monaco-demo) by [@pi0](https://github.com/pi0))
111
+
112
112
  ### Manual Mode
113
113
 
114
114
  You can also create a [Monaco editor](https://microsoft.github.io/monaco-editor/docs.html) instance manually. It loads themes and language grammars automatically.
@@ -132,12 +132,12 @@ You can also create a [Monaco editor](https://microsoft.github.io/monaco-editor/
132
132
 
133
133
  ## Using Workspace
134
134
 
135
- `modern-monaco` provides VSCode-like workspace features, such as edit history, file system provider, and more.
135
+ modern-monaco provides VSCode-like workspace features, such as edit history, file system provider, and more.
136
136
 
137
137
  ```js
138
138
  import { lazy, Workspace } from "modern-monaco";
139
139
 
140
- // 1. create a workspace with initial files
140
+ // create a workspace with initial files
141
141
  const workspace = new Workspace({
142
142
  /** The name of the workspace, used for project isolation. Default is "default". */
143
143
  name: "project-name",
@@ -150,94 +150,19 @@ const workspace = new Workspace({
150
150
  entryFile: "index.html",
151
151
  });
152
152
 
153
- // 2. use the workspace in lazy mode
153
+ // use the workspace in lazy mode
154
154
  lazy({ workspace });
155
155
 
156
- // 3. open a file in the workspace
156
+ // open a file in the workspace
157
157
  workspace.openTextDocument("main.js");
158
158
  ```
159
159
 
160
- ### Adding `tsconfig.json`
161
-
162
- You can add a `tsconfig.json` file to configure the TypeScript compiler options for the TypeScript language service.
163
-
164
- ```js
165
- const tsconfig = {
166
- "compilerOptions": {
167
- "strict": true,
168
- "noUnusedLocals": true,
169
- "noUnusedParameters": true,
170
- "noFallthroughCasesInSwitch": true,
171
- },
172
- };
173
- const workspace = new Workspace({
174
- initialFiles: {
175
- "tsconfig.json": JSON.stringify(tsconfig, null, 2),
176
- },
177
- });
178
- ```
179
-
180
- ### Using Import Maps
160
+ ### Custom Workspace FileSystem
181
161
 
182
- `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.
183
-
184
- ```js
185
- const indexHtml = html`<!DOCTYPE html>
186
- <html>
187
- <head>
188
- <script type="importmap">
189
- {
190
- "imports": {
191
- "react": "https://esm.sh/react@18",
192
- "react-dom/": "https://esm.sh/react-dom@18/"
193
- }
194
- }
195
- </script>
196
- </head>
197
- <body>
198
- <div id="root"></div>
199
- <script type="module" src="app.tsx"></script>
200
- </body>
201
- </html>
202
- `;
203
- const appTsx = `import { createRoot } from "react-dom/client";
204
-
205
- createRoot(document.getElementById("root")).render(<div>Hello, world!</div>);
206
- `;
207
-
208
- const workspace = new Workspace({
209
- initialFiles: {
210
- "index.html": indexHtml,
211
- "app.tsx": appTsx,
212
- },
213
- });
214
- ```
215
-
216
- You can also provide an import map object as the `lsp.typescript.importMap` option in the `lazy`, `init`, or `hydrate` functions.
217
-
218
- ```js
219
- lazy({
220
- lsp: {
221
- typescript: {
222
- importMap: {
223
- "react": "https://esm.sh/react@18",
224
- "react-dom/": "https://esm.sh/react-dom@18/",
225
- },
226
- },
227
- },
228
- });
229
- ```
230
-
231
- > [!Note]
232
- > By default, `modern-monaco` uses `react` or `preact` in the `importmap` script as the `jsxImportSource` option for the TypeScript worker.
233
- > To use a custom `jsxImportSource` option, add the `@jsxRuntime` specifier in the `importmap` script.
234
-
235
- ### Using Custom FileSystem
236
-
237
- You can provide a custom filesystem implementation to override the default IndexedDB filesystem.
162
+ 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.
238
163
 
239
164
  ```ts
240
- import { lazy, type FileSystem, Workspace } from "modern-monaco";
165
+ import { type FileSystem, lazy, Workspace } from "modern-monaco";
241
166
 
242
167
  class CustomFileSystem implements FileSystem {
243
168
  // Custom FileSystem implementation
@@ -250,11 +175,15 @@ const workspace = new Workspace({
250
175
  },
251
176
  customFS: new CustomFileSystem(),
252
177
  });
178
+
179
+ lazy({ workspace });
253
180
  ```
254
181
 
182
+ Please refer to the [FileSystem](./types/workspace.d.ts#L54) interface for more details.
183
+
255
184
  ## Editor Theme & Language Grammars
256
185
 
257
- `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.
186
+ 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.
258
187
 
259
188
  ### Setting the Editor Theme
260
189
 
@@ -275,7 +204,7 @@ lazy({
275
204
  > [!Note]
276
205
  > The theme ID should be one of the [Shiki Themes](https://shiki.style/themes).
277
206
 
278
- `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:
207
+ 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:
279
208
 
280
209
  ```js
281
210
  import OneDark from "tm-themes/themes/OneDark-Pro.json" with { type: "json" };
@@ -287,14 +216,14 @@ lazy({
287
216
 
288
217
  ### Pre-loading Language Grammars
289
218
 
290
- 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.
219
+ 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.
291
220
 
292
221
  ```js
293
222
  import markdown from "tm-grammars/markdown.json" with { type: "json" };
294
223
 
295
224
  lazy({
296
225
  langs: [
297
- // load language grammars from CDN
226
+ // load language grammars from CDN, these language ids must be defined in the `tm-grammars` package
298
227
  "html",
299
228
  "css",
300
229
  "javascript",
@@ -356,14 +285,14 @@ For manual mode, check [here](https://microsoft.github.io/monaco-editor/docs.htm
356
285
 
357
286
  ## Language Server Protocol (LSP)
358
287
 
359
- `modern-monaco` by default supports full LSP features for the following languages:
288
+ modern-monaco by default supports full LSP features for the following languages:
360
289
 
361
290
  - HTML
362
291
  - CSS/SCSS/LESS
363
292
  - JavaScript/TypeScript
364
293
  - JSON
365
294
 
366
- Additionally, `modern-monaco` supports features like:
295
+ Additionally, modern-monaco supports features like:
367
296
 
368
297
  - File System Provider for import completions
369
298
  - Embedded languages in HTML
@@ -372,7 +301,7 @@ Additionally, `modern-monaco` supports features like:
372
301
 
373
302
  > [!Note]
374
303
  > You don't need to set `MonacoEnvironment.getWorker` for LSP support.
375
- > `modern-monaco` automatically loads the required LSP workers.
304
+ > modern-monaco automatically loads the required LSP workers.
376
305
 
377
306
  ### LSP Language Configuration
378
307
 
@@ -414,9 +343,93 @@ export interface LSPLanguageConfig {
414
343
  }
415
344
  ```
416
345
 
346
+ ### Import Maps
347
+
348
+ 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.
349
+
350
+ ```js
351
+ const indexHtml = html`<!DOCTYPE html>
352
+ <html>
353
+ <head>
354
+ <script type="importmap">
355
+ {
356
+ "imports": {
357
+ "react": "https://esm.sh/react@18",
358
+ "react-dom/": "https://esm.sh/react-dom@18/"
359
+ }
360
+ }
361
+ </script>
362
+ </head>
363
+ <body>
364
+ <div id="root"></div>
365
+ <script type="module" src="app.tsx"></script>
366
+ </body>
367
+ </html>
368
+ `;
369
+ const appTsx = `import { createRoot } from "react-dom/client";
370
+
371
+ createRoot(document.getElementById("root")).render(<div>Hello, world!</div>);
372
+ `;
373
+
374
+ const workspace = new Workspace({
375
+ initialFiles: {
376
+ "index.html": indexHtml,
377
+ "app.tsx": appTsx,
378
+ },
379
+ });
380
+ ```
381
+
382
+ You can also provide an import map object as the `lsp.typescript.importMap` option in the `lazy`, `init`, or `hydrate` functions.
383
+
384
+ ```js
385
+ lazy({
386
+ lsp: {
387
+ typescript: {
388
+ importMap: {
389
+ "react": "https://esm.sh/react@18",
390
+ "react-dom/": "https://esm.sh/react-dom@18/",
391
+ },
392
+ },
393
+ },
394
+ });
395
+ ```
396
+
397
+ ### Adding `tsconfig.json`
398
+
399
+ You can add a `tsconfig.json` file to configure the TypeScript compiler options for the TypeScript language service.
400
+
401
+ ```js
402
+ const tsconfig = {
403
+ "compilerOptions": {
404
+ "target": "ES2022",
405
+ "strict": true,
406
+ },
407
+ };
408
+ const workspace = new Workspace({
409
+ initialFiles: {
410
+ "tsconfig.json": JSON.stringify(tsconfig, null, 2),
411
+ },
412
+ });
413
+ ```
414
+
415
+ You can also manually add the TypeScript compiler options as the `lsp.typescript.compilerOptions` option in the `lazy`, `init`, or `hydrate` functions.
416
+
417
+ ```js
418
+ lazy({
419
+ lsp: {
420
+ typescript: {
421
+ compilerOptions: {
422
+ target: "ES2022",
423
+ strict: true,
424
+ },
425
+ },
426
+ },
427
+ });
428
+ ```
429
+
417
430
  ## Using the `core` Module
418
431
 
419
- `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.
432
+ 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.
420
433
 
421
434
  ```js
422
435
  import { lazy } from "modern-monaco/core";
package/dist/core.js CHANGED
@@ -11,7 +11,7 @@ function createWebWorker(url, name) {
11
11
  }
12
12
 
13
13
  // src/core.ts
14
- import { getLanguageIdFromPath, initShiki, setDefaultWasmLoader, tmGrammars, tmThemes } from "./shiki.js";
14
+ import { getExtnameFromLanguageId, getLanguageIdFromPath, initShiki, setDefaultWasmLoader, tmGrammars, tmThemes } from "./shiki.js";
15
15
  import { initShikiMonacoTokenizer, registerShikiMonacoTokenizer } from "./shiki.js";
16
16
  import { render } from "./shiki.js";
17
17
  import { getWasmInstance } from "./shiki-wasm.js";
@@ -281,6 +281,10 @@ async function lazy(options, hydrate2) {
281
281
  } catch (error) {
282
282
  if (error instanceof ErrorNotFound) {
283
283
  if (code) {
284
+ const dirname = filename.split("/").slice(0, -1).join("/");
285
+ if (dirname) {
286
+ await workspace.fs.createDirectory(dirname);
287
+ }
284
288
  await workspace.fs.writeFile(filename, code);
285
289
  workspace._openTextDocument(filename, editor);
286
290
  } else {
@@ -355,7 +359,8 @@ async function loadMonaco(highlighter, workspace, lsp, onDidEditorWorkerResolve)
355
359
  }
356
360
  return worker;
357
361
  },
358
- getLanguageIdFromUri: (uri) => getLanguageIdFromPath(uri.path)
362
+ getLanguageIdFromUri: (uri) => getLanguageIdFromPath(uri.path),
363
+ getExtnameFromLanguageId
359
364
  });
360
365
  monaco.editor.registerLinkOpener({
361
366
  async open(link) {
@@ -177729,6 +177729,11 @@ Object.assign(editor, {
177729
177729
  if (!language2 && uri) {
177730
177730
  language2 = MonacoEnvironment.getLanguageIdFromUri?.(uri);
177731
177731
  }
177732
+ if (!uri) {
177733
+ const extname3 = MonacoEnvironment.getExtnameFromLanguageId?.(language2) ?? "txt";
177734
+ const uuid = Math.round((Date.now() + Math.random()) * 1e3).toString(36);
177735
+ uri = "file:///.inmemory/" + uuid + "." + extname3;
177736
+ }
177732
177737
  return createModel2(value, language2, uri);
177733
177738
  },
177734
177739
  getModel: (uri) => {