Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How To Add Monaco Editor to a Next.js app #355

Closed
swyxio opened this issue Jan 18, 2022 · 1 comment
Closed

How To Add Monaco Editor to a Next.js app #355

swyxio opened this issue Jan 18, 2022 · 1 comment

Comments

@swyxio
Copy link
Owner

swyxio commented Jan 18, 2022


source: devto
category: tutorial
devToUrl: "https://dev.to/swyx/how-to-add-monaco-editor-to-a-next-js-app-ha3"
devToReactions: 66
devToReadingTime: 4
devToPublishedAt: "2020-03-30T23:29:25.241Z"
devToViewsCount: 8390

Bottom Line Up Front

I use a slightly modified version of the steps mentioned in this GitHub comment. Modifications were necessary because I use TailwindCSS with Next.js.

{% youtube 13UVFrGe80o %}

Motivations

Monaco Editor is [the open source editor]
(https://github.com/microsoft/monaco-editor) used in VS Code, which itself is open source. I used to write my blogposts in VS Code, and as I make my own Dev.to CMS, I wanted to have all the familiar trappings of Monaco to help me out while I write.

Problems

However there are some issues we have to deal with:

  • Monaco is framework agnostic, so it requires writing some React bindings.
  • Monaco is written for a desktop Electron app, not for a server-side rendered web app.
    • This is solved by using import dynamic from "next/dynamic" and making Monaco a dynamic import.
  • Monaco also wants to offload syntax highlighting to web workers, and we need to figure that out
  • Next.js doesn't want any dependencies importing CSS from within node_modules, as this assumes a bundler and loader setup (e.g. webpack) and can have unintentional global CSS side effects (all global CSS is intended to be in _app.js).

We can solve this with a solution worked out by Elliot Hesp on GitHub and a config from Joe Haddad of the Next.js team.

Solution

The solution I use is informed by my usage of Tailwind CSS, which requires a recent version of PostCSS, which @zeit/next-css only has at 3.0 (because it is deprecated and not maintained).

I also use TypeScript, which introduces a small wrinkle, because Monaco Editor attaches a MonacoEnvironment global on the window object - I just @ts-ignore it.

// next.config.js

const MonacoWebpackPlugin = require("monaco-editor-webpack-plugin");
const withTM = require("next-transpile-modules")([
  // `monaco-editor` isn't published to npm correctly: it includes both CSS
  // imports and non-Node friendly syntax, so it needs to be compiled.
  "monaco-editor"
]);

module.exports = withTM({
  webpack: config => {
    const rule = config.module.rules
      .find(rule => rule.oneOf)
      .oneOf.find(
        r =>
          // Find the global CSS loader
          r.issuer && r.issuer.include && r.issuer.include.includes("_app")
      );
    if (rule) {
      rule.issuer.include = [
        rule.issuer.include,
        // Allow `monaco-editor` to import global CSS:
        /[\\/]node_modules[\\/]monaco-editor[\\/]/
      ];
    }

    config.plugins.push(
      new MonacoWebpackPlugin({
        languages: [
          "json",
          "markdown",
          "css",
          "typescript",
          "javascript",
          "html",
          "graphql",
          "python",
          "scss",
          "yaml"
        ],
        filename: "static/[name].worker.js"
      })
    );
    return config;
  }
});

and then in your Next.js app code:

import React from "react";
// etc

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

function App() {
  const [postBody, setPostBody] = React.useState("");
  // etc
  return (<div>
  {/* etc */}
    <MonacoEditor
      editorDidMount={() => {
        // @ts-ignore
        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";
        };
      }}
      width="800"
      height="600"
      language="markdown"
      theme="vs-dark"
      value={postBody}
      options={{
        minimap: {
          enabled: false
        }
      }}
      onChange={setPostBody}
    />
  </div>)
}

Since I'm using Tailwind, I'm also using PostCSS, which also tries to eliminate Monaco's CSS. You have to tell it to ignore that:

// postcss.config.js
const purgecss = [
  "@fullhuman/postcss-purgecss",
  {
    // https://purgecss.com/configuration.html#options
    content: ["./components/**/*.tsx", "./pages/**/*.tsx"],
    css: [],
    whitelistPatternsChildren: [/monaco-editor/], // so it handles .monaco-editor .foo .bar
    defaultExtractor: content => content.match(/[\w-/.:]+(?<!:)/g) || []
  }
];

Catch up on the Dev.to CMS LiveStream!

Copy link

Any solutions in 2023 with Next 13 since ziet is no longer supported? I'm trying to make a css viewer with fully integrated taiwind support.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants