suren-atoyan / monaco-react

Monaco Editor for React - use the monaco-editor in any React application without needing to use webpack (or rollup/parcel/etc) configuration files / plugins

Home Page:https://monaco-react.surenatoyan.com/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Always load when used in electron

vaemc opened this issue · comments

Hi brother ,Always load when used in electron,Can't find the reason

image

Hello @vaemc, sure, we will try!

Could you please show the console?

I'm sorry to have kept you waiting for a long time and found that the console has no important information

image

Hello @vaemc. Sorry for the late answer.

Could you please provide me a minimal code snippet, so I can reproduce your problem.

I have already created an electron app with minimal config and used this library and it works very well so far. Also, we have "users" of this library who are using electron and there were no issues.

Thank you!

@SurenAt93 I've got the same problem and I'm using this electron base.
Github

Steps:

  1. Include @monaco-editor/react in package.json
  2. Run yarn install
  3. Include the sample code
  4. Run yarn dev

Edit:
Looks like monaco.init() promise is never returned and it return removeEditor

Thank you @LiamRiddell. I will check it right now.

@LiamRiddell you are completely right, monaco.init() never ends. And the problem is that it can't load sources from CDN. The only thing is missing here is error handling during loading sources from cdn, which I will add in the next version.

@vaemc @LiamRiddell So, we have already faced this, and there is a solution for that, please read this discussion and let me know if there are still questions.

Thank you for your support!

@SurenAt93 Thanks for taking a look so fast!

I was close to solving last night then... since I changed loader config using monaco.config() but I was still using CDN.

Nonetheless this is by far cleanest implementation of Monaco, Thanks :)

Glad to hear that, will keep it open yet; to wait also @vaemc.

@SurenAt93 Just testing now. I still can't get this working. Here's the config below:

monaco.config({
  urls: {
    monacoLoader: '../node_modules/monaco-editor/min/vs/loader.js',
    monacoBase: '../node_modules/monaco-editor/min/vs'
  }
});

It finds the loader JS file and then just sits loading still. I've tried copying the file into the app

Okay, so I've managed to find the issue. It HAS to be an absolute data URI or it will fail to load. See below:

import Editor, { monaco } from '@monaco-editor/react';
const path = require('path')

// https://github.com/microsoft/monaco-editor-samples/blob/master/electron-amd-nodeIntegration/electron-index.html
function uriFromPath(_path) {
  let pathName = path.resolve(_path).replace(/\\/g, '/');

  if (pathName.length > 0 && pathName.charAt(0) !== '/') {
    pathName = `/${pathName}`;
  }
  return encodeURI(`file://${pathName}`);
}

monaco.config({
  urls: {
    monacoLoader: uriFromPath(
      path.join(__dirname, '../node_modules/monaco-editor/min/vs/loader.js')
    ),
    monacoBase: uriFromPath(
      path.join(__dirname, '../node_modules/monaco-editor/min/vs')
    )
  }
});

export default function MonacoEditorComponent() {
  const [isEditorReady, setIsEditorReady] = useState(false);
  const valueGetter = useRef(null);

  function handleEditorDidMount(_valueGetter) {
    setIsEditorReady(true);
    valueGetter.current = _valueGetter;
  }

  return (
    <>
      <Editor
        height="100%"
        width="100%"
        language="javascript"
        theme="dark"
        value="// write your code here"
        editorDidMount={handleEditorDidMount}
      />
    </>
  );
}

Ah, I see. Huh, it takes a long, but now everything is clear.

So, two things I need to do in the near future;

  1. Add a section about possible problems while using this package with electron.
  2. Handle error while loading monaco files from CDN or other places.

@LiamRiddell Again, thank you for your support.

@SurenAt93 I think your two action points are good idea.

I'm currently struggling to try to get underlying Monaco instance from Editor component. We can use the monaco.init() utility but then I have to handle the creation of the whole component.

Editor.js

// Original   
editorDidMount(editorRef.current.getValue.bind(editorRef.current), editorRef.current);
// Extended
editorDidMount(editorRef.current.getValue.bind(editorRef.current), monacoRef.current, editorRef.current);

The reason behind it is so people can register themes and languages without having to do lots of heavy lifting.

If you're happy I could make PR?

  1. It's not a right from a logical point of view. Let me explain; editorDidMount is in single editor instance, maximum it can give you should be editorInstance. It isn't allowed to give you access to monacoInstance, you shouldn't be able to change global things from inside of a single instance <- technically you can do it (I'll show it bellow) as you can do something with window object, but things should be clear, it's out of responsibilities of editorDidMount.
  2. We can use the monaco.init() utility but then I have to handle the creation of the whole component <- maybe there is a misunderstanding? You do not need to handle the creation of component. For example, if you need to do something with monaco instance:
import React from "react";
import ReactDOM from "react-dom";

import Editor, { monaco } from "@monaco-editor/react";

monaco
  .config({
    // ...
  })
  .init()
  .then(monacoInstance => {
    /* here is the instance of monaco, so you can use the `monaco.languages` or whatever you want */
    console.log("Log ::: Monaco ::: ", monacoInstance);
  })
  .catch(error =>
    console.error("An error occurred during initialization of Monaco: ", error)
  );

const App = _ => <Editor height="90vh" language="javascript" />;

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

or, if you really need to use it from inside of component, it's also possible:

import React, { useEffect } from "react";
import ReactDOM from "react-dom";

import Editor, { monaco } from "@monaco-editor/react";

const App = _ => {
  useEffect(_ => {
    monaco
      .init()
      .then(monacoInstance => {
        /* here is the instance of monaco, so you can use the `monaco.languages` or whatever you want */
        console.log("Log ::: Monaco ::: ", monacoInstance);
      });
  }, []);

  return <Editor height="90vh" language="javascript" />;
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
  1. Themes and languages are global things, they can be made separately;
import { monaco } from "@monaco-editor/react";

monaco
  .init()
  .then(monacoInstance => {
    monacoInstance.editor.defineTheme('my-super-theme', {
      // ...
    });
  });

The same for language creation.

Note that monaco.init() will work once and will return the same instance in further calls.

@SurenAt93 Yep, I've misunderstood the utility. Thanks for clarifying, I've fixed it in my code too :)

LiamRiddell's Solution #48 (comment) only works (for me), if I disable webSecurity in Electron, or it refuses to load the resources.
"Not allowed to load local resource".
I dont know how to feel about that.

mainWindow = new BrowserWindow({
	webPreferences: {
		webSecurity: false, // <- here
		nodeIntegration: true,
	},
})

@5cript are you trying to load Monaco from node_modules? Could you please provide the part of your code where you configure the Monaco?

electron version 7.1.8 btw.

This is basically just copy paste.

// in global scope of one of my files.
import Editor, { monaco } from '@monaco-editor/react';

function uriFromPath(_path) {
    let pathName = path.resolve(_path).replace(/\\/g, '/');

    if (pathName.length > 0 && pathName.charAt(0) !== '/') {
        pathName = `/${pathName}`;
    }
    return encodeURI(`file://${pathName}`);
}

monaco.config({
    urls: {
        monacoLoader: uriFromPath(
            // same result from node_modules. Does not do anything, if i get the path wrong.
            path.join(__dirname, '../public/vs/loader.js')
        ),
        monacoBase: uriFromPath(
            path.join(__dirname, '../public/vs')
        )
    }
});

I never tried deploying the application yet.

@5cript one more question: do you also use this boilerplate? I just want to reproduce your case in my side.

Awesome @LiamRiddell method solved my problem! thank you all @suren-atoyan

I was having the same problem and managed to fix it while also keeping webSecurity: true.

The issue is that Monaco uses an AMD loader, and Electron uses CJS (when nodeIntegration: true).

I found that the easiest solution is to delete the references to all CJS related properties from the window before attempting to use Monaco:

delete window.require;
delete window.exports;
delete window.module;

// Optionally change the loader paths, it will work regardless
// In my case I wanted to copy these files into a local directory
monaco.config({
    urls: {
        monacoLoader: '/monaco-editor/min/vs/loader.js',
        monacoBase: '/monaco-editor/min/vs'
    }
});

const App = <Editor />

I found this solution by chance on the Electron website: https://www.electronjs.org/docs/faq#i-can-not-use-jqueryrequirejsmeteorangularjs-in-electron

Thank you for reply @richard-livingston. The problem is that the behavior is different with different electron setups. Some of them work without a hitch, some of them work with additional config (with changed urls), some of them give strange errors. I am wondering what setup/boilerplate do you use? And BTW, @5cript does it help you?

I have to test that later when I have the chance, but I'm confident that it will.

Hi @suren-atoyan

I am using create-react-app, electron ({webSecurity: true, nodeIntegration: true}) rescripts (allows rewrite of webpack.config so that node built-ins are not "empty").

I followed this guide: https://www.codementor.io/@randyfindley/how-to-build-an-electron-app-using-create-react-app-and-electron-builder-ss1k0sfer

@5cript good luck.

I was having the same problem and managed to fix it while also keeping webSecurity: true.

The issue is that Monaco uses an AMD loader, and Electron uses CJS (when nodeIntegration: true).

I found that the easiest solution is to delete the references to all CJS related properties from the window before attempting to use Monaco:

delete window.require;
delete window.exports;
delete window.module;

// Optionally change the loader paths, it will work regardless
// In my case I wanted to copy these files into a local directory
monaco.config({
    urls: {
        monacoLoader: '/monaco-editor/min/vs/loader.js',
        monacoBase: '/monaco-editor/min/vs'
    }
});

const App = <Editor />

I found this solution by chance on the Electron website: https://www.electronjs.org/docs/faq#i-can-not-use-jqueryrequirejsmeteorangularjs-in-electron

Worked for me with electron-webpack. But for local files loading, I still disabled webSecurity: false. It was not allowing without that

With electron-webpack it is working fine in dev mode & production on windows. On Ubuntu, it is not working in Dev mode. But working in prod mode

monaco.config({ paths: { vs: path.join(__static, "/vs") } });

The difference what I found in dev mode was in windows it is trying to load with file:/// URL. But on ubuntu, it is trying to load with localhost:9080. Hence it was failing.

The output of path.join(__static, "/vs") is pointing to absolute paths in both Ubuntu& windows

ubuntu - /home/user/workspace/Git/bla/static/vs
windows - D:\Git\bla\static\vs

I don't know why in ubuntu monaco is trying to load the resources using localhost server instead of file:/// URI

commented

TypeError: Cannot set property 'configScriptSrc' of undefined

Did you ever make that mistake? In the manner of @LiamRiddell

use: electron + typescript

const path: any = require('path')
function uriFromPath(_path: string) {
    let pathName: string = path.resolve(_path).replace(/\\/g, '/');

    if (pathName.length > 0 && pathName.charAt(0) !== '/') {
        pathName = `/${pathName}`;
    }
    return encodeURI(`file://${pathName}`);
}
const monacoConfig: (obj: any) => any = monaco.config; 
monacoConfig({
    urls: {
        monacoLoader: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs/loader.js')
        ),
        monacoBase: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs')
        )
    }
});

TypeError: Cannot set property 'configScriptSrc' of undefined

Did you ever make that mistake? In the manner of @LiamRiddell

use: electron + typescript

const path: any = require('path')
function uriFromPath(_path: string) {
    let pathName: string = path.resolve(_path).replace(/\\/g, '/');

    if (pathName.length > 0 && pathName.charAt(0) !== '/') {
        pathName = `/${pathName}`;
    }
    return encodeURI(`file://${pathName}`);
}
const monacoConfig: (obj: any) => any = monaco.config; 
monacoConfig({
    urls: {
        monacoLoader: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs/loader.js')
        ),
        monacoBase: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs')
        )
    }
});

hmm, strange. BTW; monaco.config({ urls: ... }) is deprecated, use monaco.config({ paths: ... }) instead. It's described here

commented

TypeError: Cannot set property 'configScriptSrc' of undefined
Did you ever make that mistake? In the manner of @LiamRiddell
use: electron + typescript

const path: any = require('path')
function uriFromPath(_path: string) {
    let pathName: string = path.resolve(_path).replace(/\\/g, '/');

    if (pathName.length > 0 && pathName.charAt(0) !== '/') {
        pathName = `/${pathName}`;
    }
    return encodeURI(`file://${pathName}`);
}
const monacoConfig: (obj: any) => any = monaco.config; 
monacoConfig({
    urls: {
        monacoLoader: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs/loader.js')
        ),
        monacoBase: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs')
        )
    }
});

hmm, strange. BTW; monaco.config({ urls: ... }) is deprecated, use monaco.config({ paths: ... }) instead. It's described here

thank you .
image

I don't know why this is undefind

TypeError: Cannot set property 'configScriptSrc' of undefined
Did you ever make that mistake? In the manner of @LiamRiddell
use: electron + typescript

const path: any = require('path')
function uriFromPath(_path: string) {
    let pathName: string = path.resolve(_path).replace(/\\/g, '/');

    if (pathName.length > 0 && pathName.charAt(0) !== '/') {
        pathName = `/${pathName}`;
    }
    return encodeURI(`file://${pathName}`);
}
const monacoConfig: (obj: any) => any = monaco.config; 
monacoConfig({
    urls: {
        monacoLoader: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs/loader.js')
        ),
        monacoBase: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs')
        )
    }
});

hmm, strange. BTW; monaco.config({ urls: ... }) is deprecated, use monaco.config({ paths: ... }) instead. It's described here

thank you .
image

I don't know why this is undefind

could you please change the version of the library?

commented

TypeError: Cannot set property 'configScriptSrc' of undefined
Did you ever make that mistake? In the manner of @LiamRiddell
use: electron + typescript

const path: any = require('path')
function uriFromPath(_path: string) {
    let pathName: string = path.resolve(_path).replace(/\\/g, '/');

    if (pathName.length > 0 && pathName.charAt(0) !== '/') {
        pathName = `/${pathName}`;
    }
    return encodeURI(`file://${pathName}`);
}
const monacoConfig: (obj: any) => any = monaco.config; 
monacoConfig({
    urls: {
        monacoLoader: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs/loader.js')
        ),
        monacoBase: uriFromPath(
            path.join(__dirname, '../../../node_modules/monaco-editor/min/vs')
        )
    }
});

hmm, strange. BTW; monaco.config({ urls: ... }) is deprecated, use monaco.config({ paths: ... }) instead. It's described here

thank you .
image
I don't know why this is undefind

could you please change the version of the library?

I'm using @monaco-editor/react@3.4.1.
Ha ha, I've used the monaco-editor as an alternative and I'm not going to struggle. Thank you for your reply.

dev env

I am using absolute URL node_modules/monaco-editor/min/vs/ to loader dependence,
But how can I loader dependence in application after build, which used in other computor
is there a way to build the dependence into application?