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

monaco-json doesn't work well with the ESM #1974

Closed
fabiospampinato opened this issue May 21, 2020 · 5 comments
Closed

monaco-json doesn't work well with the ESM #1974

fabiospampinato opened this issue May 21, 2020 · 5 comments
Assignees
Labels
bug Issue identified by VS Code Team member as probable bug
Milestone

Comments

@fabiospampinato
Copy link

monaco-editor version: 0.20.0
Browser: Electron's Chrome 80-something
OS: macOS 10.14.0

I just spent the better part of the afternoon trying to get the JSON language to work with Monaco, now the thing kind of works, but it depends on how I load the JSON language contribution, which in and of itself seems like a problem to me.

First of all for some context I'm creating a separate bundle for Monaco using this Webpack configuration:

/* IMPORT */

const fs = require ( 'fs' ),
      path = require ( 'path' ),
      {BundleAnalyzerPlugin} = require ( 'webpack-bundle-analyzer' ),
      MiniCssExtractPlugin = require ( 'mini-css-extract-plugin' ),
      MonacoWebpackPlugin = require ( 'monaco-editor-webpack-plugin' ),
      webpack = require ( 'webpack' );

/* VARIABLES */

const ROOT = process.cwd (),
      INPUT = path.join ( ROOT, 'src', 'apps', 'core', 'lib', 'monaco', 'entry.ts' ),
      STATIC = path.join ( ROOT, 'dist', 'static' ),
      OUTPUT = path.join ( STATIC, 'monaco' );

/* CONFIG */

const config = {
  mode: 'production',
  entry: {
    monaco: INPUT
  },
  output: {
    globalObject: 'self',
    library: 'monaco',
    libraryTarget: 'umd',
    path: OUTPUT
  },
  module: {
    rules: [
      {
        test: /\.tsx?$/,
        use: [{
          loader: 'ts-loader',
          options: {
            transpileOnly: true
          }
        }]
      },
      {
        test: /\.css$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader']
      },
      {
        test: /\.ttf$/,
        use: {
          loader: 'url-loader',
          options: {
            esModule: false
          }
        }
      }
    ]
  },
  plugins: [
    new BundleAnalyzerPlugin ({
      analyzerMode: 'static',
      defaultSizes: 'stat',
      reportFilename: path.join ( ROOT, 'report.monaco.html' ),
      openAnalyzer: false,
      logLevel: 'silent'
    }),
    new MiniCssExtractPlugin ({
      filename: 'monaco.css',
    }),
    new MonacoWebpackPlugin ({
      filename: 'monaco.worker.js',
      languages: [],
      features: [ //TODO
        '!accessibilityHelp',
        '!bracketMatching',
        '!caretOperations',
        '!clipboard',
        '!codeAction',
        '!codelens',
        '!colorDetector',
        '!comment',
        '!contextmenu',
        '!coreCommands',
        '!cursorUndo',
        '!dnd',
        '!find',
        '!folding',
        '!fontZoom',
        '!format',
        '!gotoError',
        '!gotoLine',
        '!gotoSymbol',
        '!hover',
        '!inPlaceReplace',
        '!inspectTokens',
        '!iPadShowKeyboard',
        '!linesOperations',
        '!links',
        '!multicursor',
        '!parameterHints',
        '!quickCommand',
        '!quickOutline',
        '!referenceSearch',
        '!rename',
        '!smartSelect',
        '!snippets',
        '!suggest',
        '!toggleHighContrast',
        '!toggleTabFocusMode',
        '!transpose',
        '!wordHighlighter',
        '!wordOperations',
        '!wordPartOperations'
      ]
    }),
    new webpack.optimize.LimitChunkCountPlugin ({
      maxChunks: 1
    }),
    {
      apply: compiler => {
        compiler.hooks.afterEmit.tap ( 'AfterEmitPlugin', () => {
          fs.copyFileSync ( path.join ( OUTPUT, 'monaco.worker.js' ), path.join ( STATIC, 'monaco.worker.js' ) );
        });
      }
    }
  ]
};

/* EXPORT */

module.exports = config;

The potentially interesting bit of that is the following:

    library: 'monaco',
    libraryTarget: 'umd',

Without these lines Monaco doesn't work at all for me, I don't remember why I added those lines to the configuration, as they don't seem to be mentioned in the sample configurations provided.

Those lines of configurations I think may assign Monaco to "window.monaco".

Getting to the JSON language:

Dynamic import

I'm loading all languages dynamically in order to minimize startup time.

Given that I'm loading the Monaco bundle like so: import * as Monaco from '/path/to/monaco.bundle.js';, before I load the JSON language dynamically the following is true: Monaco.languages === window.monaco.languages, after I load the JSON language contribution, 'monaco-editor/esm/vs/language/json/monaco.contribution' that equality doesn't hold anymore, so it looks like the language contribution is doing something funny with it.

Also after loading the language this way creating a JSON editor from the Monaco variable doesn't really work, the text is not highlighted at all, if I instead create a JSON editor from the window.monaco variable it works, but then the theme is revered back to the default one for some reason.

This is the content of that contribution file:

import '../../editor/editor.api.js';
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
'use strict';
var Emitter = monaco.Emitter;
// --- JSON configuration and defaults ---------
var LanguageServiceDefaultsImpl = /** @class */ (function () {
    function LanguageServiceDefaultsImpl(languageId, diagnosticsOptions, modeConfiguration) {
        this._onDidChange = new Emitter();
        this._languageId = languageId;
        this.setDiagnosticsOptions(diagnosticsOptions);
        this.setModeConfiguration(modeConfiguration);
    }
    Object.defineProperty(LanguageServiceDefaultsImpl.prototype, "onDidChange", {
        get: function () {
            return this._onDidChange.event;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(LanguageServiceDefaultsImpl.prototype, "languageId", {
        get: function () {
            return this._languageId;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(LanguageServiceDefaultsImpl.prototype, "modeConfiguration", {
        get: function () {
            return this._modeConfiguration;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(LanguageServiceDefaultsImpl.prototype, "diagnosticsOptions", {
        get: function () {
            return this._diagnosticsOptions;
        },
        enumerable: true,
        configurable: true
    });
    LanguageServiceDefaultsImpl.prototype.setDiagnosticsOptions = function (options) {
        this._diagnosticsOptions = options || Object.create(null);
        this._onDidChange.fire(this);
    };
    LanguageServiceDefaultsImpl.prototype.setModeConfiguration = function (modeConfiguration) {
        this._modeConfiguration = modeConfiguration || Object.create(null);
        this._onDidChange.fire(this);
    };
    ;
    return LanguageServiceDefaultsImpl;
}());
export { LanguageServiceDefaultsImpl };
var diagnosticDefault = {
    validate: true,
    allowComments: true,
    schemas: [],
    enableSchemaRequest: false
};
var modeConfigurationDefault = {
    documentFormattingEdits: true,
    documentRangeFormattingEdits: true,
    completionItems: true,
    hovers: true,
    documentSymbols: true,
    tokens: true,
    colors: true,
    foldingRanges: true,
    diagnostics: true,
    selectionRanges: true
};
var jsonDefaults = new LanguageServiceDefaultsImpl('json', diagnosticDefault, modeConfigurationDefault);
// Export API
function createAPI() {
    return {
        jsonDefaults: jsonDefaults
    };
}
monaco.languages.json = createAPI();
// --- Registration to monaco editor ---
function getMode() {
    return import('./jsonMode.js');
}
monaco.languages.register({
    id: 'json',
    extensions: ['.json', '.bowerrc', '.jshintrc', '.jscsrc', '.eslintrc', '.babelrc', '.har'],
    aliases: ['JSON', 'json'],
    mimetypes: ['application/json'],
});
monaco.languages.onLanguage('json', function () {
    getMode().then(function (mode) { return mode.setupMode(jsonDefaults); });
});

And it doesn't look right to me, it references a monaco variable without importing it, so perhaps that's part of the problem here.

Basically I couldn't get the JSON language to work at all when dynamically importing it.

Static import

If I load the JSON language contribution directly in my bundled Monaco entrypoint instead it works, and Monaco.languages remains equal to monaco.languages 🤷‍♂️

TL;DR: I think monaco-json basically doesn't work that well when using ESM.

@alexdima alexdima added this to the August 2020 milestone Sep 4, 2020
alexdima added a commit to microsoft/monaco-json that referenced this issue Sep 7, 2020
alexdima added a commit to microsoft/monaco-css that referenced this issue Sep 7, 2020
alexdima added a commit to microsoft/monaco-html that referenced this issue Sep 7, 2020
alexdima added a commit to microsoft/monaco-typescript that referenced this issue Sep 7, 2020
alexdima added a commit to microsoft/monaco-languages that referenced this issue Sep 8, 2020
@alexdima
Copy link
Member

alexdima commented Sep 8, 2020

I agree, the esm variants were using the global monaco. I have refactored the projects to avoid doing this.

@alexdima alexdima closed this as completed Sep 8, 2020
@alexdima alexdima added the bug Issue identified by VS Code Team member as probable bug label Sep 8, 2020
@alexdima alexdima self-assigned this Sep 8, 2020
pgherveou pushed a commit to pgherveou/monaco-typescript that referenced this issue Sep 21, 2020
pgherveou pushed a commit to pgherveou/monaco-typescript that referenced this issue Sep 21, 2020
* Format signature and parameter documentation as Markdown

* Add support for creating a custom webworker subclass

* Make a useful sample

* Tidy up

* Settle on customTSWorkerFactory

* Adds a vfs project

* Tightens the public API

* update deps

* update to typescript@4.0.2

* Add prettier

* Run prettier

* Use the global `monaco` only in the AMD case (see microsoft/monaco-editor#1974)

* Add deprecated diagnostic tag if a symbol is reported as deprecated

* Add deprecated tag to suggestion if entry is marked as deprecated

* Include tags in documentation string of suggestion items

* Align tag representation in suggestion items and hovers

* 4.0.0

* small style tweaks

* 4.0.1

* improve `.npmignore`

* Allows setting lib with shortnames

* Remove declare modifiers (microsoft/monaco-editor#2121)

* 4.0.2

* Update import TS

* Use typescript language for hover tooltip header

* update `package-lock.json`

* Adopt `async`

* Increase `printWidth`

* `var` -> `let`

* Fixes microsoft/monaco-editor#1937: Remove debugger statement

* Fixes microsoft/monaco-editor#1638: Protect against failing `getCodeFixesAtPosition` calls

* Fixes microsoft/monaco-editor#1998: make sure to always increase the version number

* Adopt latest `monaco-editor-core`, update to TS 4.0.3

* 4.1.0

* Add missing setWorkerOptions

* commit generated file

* update upstream and enable regex

Co-authored-by: Sebastian Pahnke <pahnke.sebastian@gmail.com>
Co-authored-by: Orta <git@orta.io>
Co-authored-by: Alex Dima <alexdima@microsoft.com>
Co-authored-by: Spencer <spencer@sf-n.com>
@fabiospampinato
Copy link
Author

@alexdima I'm still unable to load the JSON language on demand, the issue seems to be that the language definition and related files directly try to load other parts of Monaco themselves, while I have a separate bundle for it, which I just can't make those files use in any reasonable way. This is in contrast to the basic languages provided which just offer some exports that can be easily integrated with my Monaco bundle.

Should I perhaps patch monaco-json and use that directly? What would be the best way to load the advanced JSON contributions on demand?

@fabiospampinato
Copy link
Author

This is in contrast to the basic languages provided which just offer some exports that can be easily integrated with my Monaco bundle.

Actually since v0.21 some languages are loading large portions of the editor's code just to fetch an enum, e.g. languages.IndentAction.Indent, IMHO that enum should be defined somewhere and imported directly instead.

@fabiospampinato
Copy link
Author

fabiospampinato commented Oct 12, 2020

For posterity:

This webpack plugin made it so that a single instance of Monaco is loaded in my bundles:

    new webpack.NormalModuleReplacementPlugin (
      /\/fillers\/monaco-editor-core\.js$/,
      '/Users/fabio/Projects/notable/src/apps/core/lib/monaco/core.ts'
    )

The content of that core.ts file just exports the APIs those files expect to find.

That would work for the JSON contributions too, but there's this problematic statement at the start of that file:

import '../../editor/editor.api.js';

That would need to be redirected too or patched.

@vscodebot vscodebot bot locked and limited conversation to collaborators Oct 23, 2020
@microsoft microsoft unlocked this conversation Dec 28, 2020
@alexdima
Copy link
Member

@fabiospampinato I can take another look, what steps should I try?

The idea of these languages is that they are loaded after the editor core has been loaded. So **/monaco.contribution.ts would be loaded at start-up, but after the monaco editor core (since that piece of code uses editor api to register a language and then register a listener for when the language is needed).

When a model with that particular language id is instantiated, the editor will fire the onLanguage event listener, which would then load the needed jsonMode bundle. The jsonMode bundle would always load on a page where the editor core has been loaded, so any module bundler should exclude editor.api from bundling inside jsonMode.

When I use a webpack config like this one, I can see in the output that the editor.api is not repeated in each and every file, it is only bundled once in app.js.

@vscodebot vscodebot bot locked and limited conversation to collaborators Dec 31, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Issue identified by VS Code Team member as probable bug
Projects
None yet
Development

No branches or pull requests

2 participants