react-monaco-editor: Not working with nextjs?

Describe the bug i try to use react-monaco-editor with next.js based on this tutorial. But all time i have a lot of errors:

Uncaught (in promise) Error: Unexpected usage
    at EditorSimpleWorker.push../node_modules/monaco-editor/esm/vs/editor/common/services/editorSimpleWorker.js.EditorSimpleWorker.loadForeignModule (editorSimpleWorker.js:540)
    at webWorker.js:54
Uncaught TypeError: Failed to construct 'URL': Invalid URL
    at push../node_modules/normalize-url/index.js.module.exports (index.js:46)
    at getReloadUrl (hotModuleReplacement.js:74)
    at hotModuleReplacement.js:92
    at NodeList.forEach (<anonymous>)
    at reloadStyle (hotModuleReplacement.js:89)
    at update (hotModuleReplacement.js:119)
    at invokeFunc (debounce.js:95)
    at trailingEdge (debounce.js:144)
    at timerExpired (debounce.js:132)
Uncaught (in promise) Error: Unexpected usage
    at EditorSimpleWorker.push../node_modules/monaco-editor/esm/vs/editor/common/services/editorSimpleWorker.js.EditorSimpleWorker.loadForeignModule (editorSimpleWorker.js:540)
    at webWorker.js:54

my code:

import React from 'react'
import PropTypes from 'prop-types'
import * as monaco from '@timkendrick/monaco-editor/dist/external'
import MonacoEditor from 'react-monaco-editor'
window.MonacoEnvironment = { baseUrl: '/monaco-editor-external' }

/**
 * @param {string} code
 * @returns {*}
 * @constructor
 */
const CodeEditor = ({code}) => {
    const options = {
        selectOnLineNumbers: true
    }

    return (
        <MonacoEditor
            // width="800"
            height="600"
            language="javascript"
            theme="vs"
            // theme="vs-dark"
            value={''}
            options={options}
            onChange={() => {}}
            editorDidMount={() => {}}
        />
    )
}

CodeEditor.propTypes = {
    code: PropTypes.string.isRequired,
}

export default CodeEditor

in server.js added:

...
    server.use(
        '/monaco-editor-external',
        express.static(`${__dirname}/node_modules/@timkendrick/monaco-editor/dist/external`)
    )
...

To Reproduce install “react-monaco-editor”: “^0.34.0” “monaco-editor”: “^0.20.0” “monaco-editor-webpack-plugin”: “^1.9.0” “@timkendrick/monaco-editor”: “0.0.9” “next”: “^9.2.1” “react”: “^16.12.0”

Expected behavior work without errors

Environment (please complete the following information):

  • OS: mac
  • Browser chrome
  • Bundler webpack

guys, please, help me. I spent for that a lot of time, i tried use with/without MonacoWebpackPlugin, with/without @stoplight/monaco, but i still got error 😦

About this issue

  • Original URL
  • State: open
  • Created 4 years ago
  • Reactions: 4
  • Comments: 27

Commits related to this issue

Most upvoted comments

The following is what worked for me.

  • NO NEXT CONFIG REQUIRED
  • NO TRANSPILE MODULES
  • NO EXTERNAL REFERENCES
  • PROBABLY NO SSR EITHER (but ehh, wasn’t necessary for my use case)

package.json

// ...
  "dependencies": {
    "@monaco-editor/react": "^4.2.2",
// ... NO OTHER MONACO IMPORTS

some typescript file

import Editor from '@monaco-editor/react'
import {
  Dispatch,
  MutableRefObject,
  SetStateAction,
  useEffect,
  useRef
} from 'react'

//
// So... typings weren't working when I implemented Monaco, and we had to ship,
// so these placeholder types were put in place so tests passed... please fix
// these before going production. imo Monaco provides typings, they just didn't
// work when we tried them (VSCode wouldn't recognize them, tslint complained.)
//

export type MonacoEditorOptions = {
  stopRenderingLineAfter: number
}

export type MonacoEditorA = MutableRefObject<any>
export type MonacoEditorB = MutableRefObject<any>
export type MonacoTextModal = any

export type MonacoOnInitializePane = (
  monacoEditorRef: MonacoEditorA,
  editorRef: MonacoEditorB,
  model: MonacoTextModal
) => void

export type ScriptEditorProps = {
  // usage: const [code, setCode] = useState<string>('default value')
  code: string
  setCode: Dispatch<SetStateAction<string>>

  // see: https://microsoft.github.io/monaco-editor/api/modules/monaco.editor.html
  editorOptions: MonacoEditorOptions

  onInitializePane: MonacoOnInitializePane
}

//
// End of placeholder typings
//

const ScriptEditor = (props: ScriptEditorProps): JSX.Element => {
  const { code, setCode, editorOptions, onInitializePane } = props

  const monacoEditorRef = useRef<any | null>(null)
  const editorRef = useRef<any | null>(null)

  // monaco takes years to mount, so this may fire repeatedly without refs set
  useEffect(() => {
    if (monacoEditorRef?.current) {
      // again, monaco takes years to mount and load, so this may load as null
      const model: any = monacoEditorRef.current.getModels()

      if (model?.length > 0) {
        // finally, do editor's document initialization here
        onInitializePane(monacoEditorRef, editorRef, model)
      }
    }
  })

  return <Editor
    height="42.9em" // preference
    language="go"   // preference
    onChange={(value, _event) => {
      setCode(value)
    }}
    onMount={(editor, monaco) => {
      monacoEditorRef.current = monaco.editor
      editorRef.current = editor
    }}
    options={editorOptions}
    theme="vs-dark" // preference
    value={code}
    width="60em"    // preference
  />
}

export default ScriptEditor

example usage

import ScriptEditor, { MonacoOnInitializePane } from '../components/ScriptEditor'
import { useState } from 'react'

const Index = () => {
  const [code, setCode] = useState<string>(`
  // Hello, World!
  const foo = 1 + 1
`)

  const onInitializePane: MonacoOnInitializePane = (
    monacoEditorRef,
    editorRef,
    model
  ) => {
    editorRef.current.setScrollTop(1)
    editorRef.current.setPosition({
      lineNumber: 2,
      column: 0,
    })
    editorRef.current.focus()
    monacoEditorRef.current.setModelMarkers(model[0], 'owner', null)
  }

  return <ScriptEditor
    code={code}
    setCode={setCode}
    editorOptions={{
      stopRenderingLineAfter: 1000,
    }}
    onInitializePane={onInitializePane}
  />
}

@subwaymatch thx, you saved my day!

@monaco-editor/react just works out of the box, no need to mess up with next.js config

Ok figured it out 😄

next.js.config:

const withCSS = require("@zeit/next-css");
const withFonts = require("next-fonts");
const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");

module.exports = withCSS(
  withFonts({
    webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
      config.plugins.push(
        new MonacoWebpackPlugin({
          languages: ["javascript", "typescript"],
          filename: "static/[name].worker.js"
        })
      );

      return config;
    }
  })
);

Now, in the index.js page file:

import React from "react";
import dynamic from "next/dynamic";

const MonacoEditor = dynamic(import("react-monaco-editor"), { ssr: false });

function IndexPage() {
  return (
    <MonacoEditor
      editorDidMount={() => {
        window.MonacoEnvironment.getWorkerUrl = (
          _moduleId: string,
          label: string
        ) => {
          if (label === "json") return "_next/static/json.worker.js";
          if (label === "css") return "_next/static/css.worker.js";
          if (label === "html") return "_next/static/html.worker.js";
          if (label === "typescript" || label === "javascript")
            return "_next/static/ts.worker.js";
          return "_next/static/editor.worker.js";
        };
      }}
    />
  );
}

export default IndexPage;

If you read all the way down, turns out worker-loader did nothing, so you can remove that.

They key is to set the MonacoWebpackPlugin filename key to have the static prefix, so it’s added to the .next/static build output, which is then exposed via a HTTP request under _next/static/.

Happy coding!

I’ve spent a few days trying to make react-monaco-editor with next.js. It just didn’t work 😩. While next.js has an example with react-monaco-editor in the official repo, it breaks as soon as I add Tailwind or sass. Even if I do get it to work with the tutorial here, I don’t like the idea of polluting the Webpack config (and having to install extra packages).

A much, much better alternative IMHO is to use @monaco-editor/react. I’m using css modules with sass, and it works like magic (especially after spending a few days with this package). I also didn’t have to fiddle with next.config.js. For those who may be interested, there’s a simplified CodeSandbox example with Next.js by the author of that package.

Screenshot of how I’m using it image

After some experiments, I got it working with the help of next-transpile-modules.

// next.config.js (in my case blitz.config.ts as I use Blitz.js, but it shouldn't matter)
const nextTranspileModules = require("next-transpile-modules")
...
const withTM = nextTranspileModules(["monaco-editor"])
...
module exports = withTM({
  webpack: (config, { buildId, dev, isServer, defaultLoaders, webpack }) => {
    config.plugins.push(
        new MonacoWebpackPlugin({
          languages: ["json"],
          features: [],
          filename: "static/[name].worker.js",
        })
      )
  }
})

Also, make sure to import the editor dynamically and disable SSR:

import { dynamic } from "blitz"

const MonacoEditor = dynamic(() => import("react-monaco-editor"), { ssr: false })

export const ModuleEditor = () => {
  return (
    <div>
      <MonacoEditor height={300} language="json" />
    </div>
  )
}

Also, make sure you only have react-monaco-editor and not(!) monaco-editor in your dependencies (not sure, but otherwise, it didn’t work for me).

By the way, the reason why I don’t like to use @monaco-editor/react is that I don’t want to depend on an external CDN from where the workers are downloaded from.

I hope this helps someone.

If you check the console while you type it will probably spit out a load of errors, which are related to the service worker not running

By default Monaco just tries to load the works from a base url like site.com/ts.worker.js

That doesn’t exist with next js, so you have to provide Monaco with the paths which next exposes them as

Trying to get this working myself, has been rather painful so far, lots of Googling & reading…

So far I have it working with javascript syntax, but using say typescript throws errors about the service workers not working. Putting this here to document where I have got to for others:

First off, you need to install the CSS & font loader plugins for NextJS:

yarn add @zeit/next-css next-fonts

This allows the css which monaco imports internally to be parsed, and also the fonts they bundle/load to be parsed:

// next.config.js
const withCSS = require("@zeit/next-css");
const withFonts = require("next-fonts");

module.exports = withCSS(withFonts({}));

Next up, I added the webpack plugin. If I leave the languages out it complains (even though the docs suggest it defaults to them all?):

// next.config.js
const withCSS = require('@zeit/next-css');
const withFonts = require('next-fonts');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');

module.exports = withCSS(withFonts({
  config.plugins.push(new MonacoWebpackPlugin({
    languages: ['javascript']
  }));
}));

Then I had some errors about the service works not being found… so I added the worker-loader:

yarn add worker-loader

I added the rules to parse any of the “[language].worker.js” files:

// next.config.js
const withCSS = require('@zeit/next-css');
const withFonts = require('next-fonts');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');

module.exports = withCSS(withFonts({
  config.plugins.push(new MonacoWebpackPlugin({
    languages: ['javascript']
  }));

  config.module.rules.push({
    test: /\.worker\.js$/,
    use: {
      loader: 'worker-loader',
        options: { inline: true }
      }
   });

   return config;
}));

Now back in my pages file, I import the module. It doesn’t seem to be compatible with SSR, so I just load it on the client (don’t think this is a big issue anyway, who wants code to be indexed?):

// pages/index.js

import React, { useEffect } from "react";
import dynamic from "next/dynamic";

const MonacoEditor = dynamic(import("react-monaco-editor"), { ssr: false });

function IndexPage() {
  return (
    <MonacoEditor
      width="800"
      height="600"
      language="javascript"
      theme="vs-dark"
      value={`const foo = 'bar';`}
      options={{
        selectOnLineNumbers: true
      }}
      onChange={console.log}
    />
  );
}

export default IndexPage;

So this currently works, with the worker seemingly doing it’s job highlighting in the background. If the worker isn’t loaded, there is a lot of lag when typing:

image

@subwaymatch thx, you saved my day! @monaco-editor/react just works out of the box, no need to mess up with next.js config

Have you experienced Module not found: Can't resolve '@monaco-editor/react' error while using @monaco-editor/react with nextjs?

It means you’ve not installed it.

Of course, i’m aware of that. turns out i’ve installed @monaco-editor/react but did not installed monaco-editor and the error throws irrelevant Module not found: Can't resolve '@monaco-editor/react'. Thanks for the reply anyway! good day

@subwaymatch thx, you saved my day!

@monaco-editor/react just works out of the box, no need to mess up with next.js config

Not working for me.

@AlbinoGeek Thank you soo much for the simple working example!

I’ve spent a few days trying to make react-monaco-editor with next.js. It just didn’t work 😩. While next.js has an example with react-monaco-editor in the official repo, it breaks as soon as I add Tailwind or sass. Even if I do get it to work with the tutorial here, I don’t like the idea of polluting the Webpack config (and having to install extra packages).

A much, much better alternative IMHO is to use @monaco-editor/react. I’m using css modules with sass, and it works like magic (especially after spending a few days with this package). I also didn’t have to fiddle with next.config.js. For those who may be interested, there’s a simplified CodeSandbox example with Next.js by the author of that package.

Screenshot of how I’m using it image

What are you using to compile the code ? :3

I’ve followed @Ehesp to change the next.config.js and index.js, and now it shows the error message…

./node_modules/monaco-editor/esm/vs/base/browser/ui/codiconLabel/codicon/codicon.css
ModuleParseError: Module parse failed: Unexpected character '' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)

Any solution ?😭

nice work @Ehesp ! do you mind explaining a little why you need to “override the worker URL that monaco uses to load the workers”? it’s not clear why you had to do that. i tried your steps without it and it still worked. was a little laggy at the start and then became pretty good. was it purely a perf improvement thing?