Search code examples
visual-studio-codees6-modules

how to import mjs in vscode?


Is it possible to make a vscode extension made of mjs files?

because I tried to make an extension with mjs files only, in order to have full es6 features without TypeScript. But it does not run:

If I make the extension with $ vsce package it does not give any error but it makes an extension that does not work when installed: the contributions that I've put in the package.json are present but vscode shows an error popup that says

Activating extension 'my.ext' failed: Must use import to load ES Module: c:\vsext\extension.mjs.

and every command I try to run gives an error

command 'my.cmd' not found

If I run the extension on the debugger, and the breakpoint on uncaught exception option flagged, it breaks on /src/vs/workbench/api/node/extHostExtensionService.ts:88.

After further search, I noticed that this exception is generatend when the script tries to load the first mjs module.

there is something I can do in order to include my mjs library files?

I think that this behaviour could also impact the use of npm modules with mjs files.


EDIT

Found (kind of) a way using esm:

The idea is to use esm to handle es6 imports and share the vscode object between imported modules

this could seem quite tricky but when I tried to just import * as vscode from "vscode" im my mjs files, vscode complained that can't find module vscode.

so I've done the following

  • add 'esm' as dependency
  • in all the files where vscode is used, remove the import of vscode and add something like this function
var vscode; // will store vscode obj
export function IMPORTVSCODE(vscodeInstance){
    vscode = vscodeInstance
}
  • create a init file where you require vscode (with node native require) and your main module (with esm require)
  • in the init file call main.IMPORTVSCODE(vscode)
  • on all the files A that imports a file B that need vscode, before use the exported stuff from file B, call B.IMPORTVSCODE(vscode)

for example

// init.js
const vscode = require("vscode")
const esm = require("esm")(module/*, options*/)
const main = esm("./main.mjs")
main.IMPORTVSCODE(vscode)
module.exports = main

// main.mjs
import * as other from "./other.mjs"
var vscode; // will store vscode obj
export function IMPORTVSCODE(vscodeInstance){
    vscode = vscodeInstance
}
function activate(){
  other.IMPORTVSCODE(vscode)
  other.methodThatNeedsVscode()
}

// other.mjs
var vscode; // will store vscode obj
export function IMPORTVSCODE(vscodeInstance){
    vscode = vscodeInstance
}
export function methodThatNeedsVscode(){
    vscode // ...use vscode
}

My extension runs fine now!

But I think that better ideas could be found, so if somebody has some finest solutions, please share them


Solution

  • Yes, you can use pure ESM (thank god), even though you have to start through with CJS. The trick is to use await import:

    /**
     * @param {import('vscode').ExtensionContext} context - The context.
     */
    async function activate(context) {
      (await import('./register-hello-world.mjs')).activate(context);
      (await import('./register-hello-tensor.mjs')).activate(context);
    }
    module.exports.activate = activate;
    

    register-hello-world.mjs:

    import {add} from '#customImport';
    import {vscode} from '#vscode';
    /**
     * This method is called when your extension is activated, which
     * happens the very first time the command is executed.
     * @param {import('vscode').ExtensionContext} context - The context.
     */
    export function activate(context) {
      // Use the console to output diagnostic information (console.log) and errors (console.error)
      // This line of code will only be executed once when your extension is activated
      console.log('> register extension.helloWorld');
      // The command has been defined in the package.json file
      // Now provide the implementation of the command with registerCommand
      // The commandId parameter must match the command field in package.json
      const disposable = vscode.commands.registerCommand('extension.helloWorld', () => {
        vscode.window.showInformationMessage(`Hello World! add(10, 20) is ${add(10, 20)}`);
      });
      context.subscriptions.push(disposable);
    }
    

    Now you may wonder - what is that import {vscode} from '#vscode';?

    You can basically use your package.json like an importmap:

    {
      "name": "vscode-extension-transformers",
      "displayName": "vscode-extension-transformers",
      "description": "Transformers.js example for VS Code",
      "version": "1.0.0",
      "publisher": "kungfooman",
      "repository": "https://github.com/kungfooman/vscode-extension-transformers/",
      "scripts": {
        "watch": "echo Begone, watch build step! Embracing true ESM power!",
        "vscode:prepublish": "echo Begone, prepublish build step! Embracing true ESM power!",
        "lint": "eslint \"src/**/*.js\""
      },
      "engines": {
        "vscode": "^1.74.0"
      },
      "categories": [
        "Other"
      ],
      "activationEvents": [],
      "main": "./src/extension.js",
      "contributes": {
        "commands": [
          {
            "command": "extension.helloWorld",
            "title": "Hello World"
          },
          {
            "command": "extension.helloTensor",
            "title": "Hello Tensor"
          }
        ]
      },
      "devDependencies": {
        "@types/node": "^16.18.34",
        "@types/vscode": "^1.73.0",
        "eslint": "^8.26.0"
      },
      "imports": {
        "#customImport": "./src/customImport.mjs",
        "#vscode": "./src/vscode.mjs"
      },
      "dependencies": {
        "@xenova/transformers": "^2.6.1"
      }
    }
    
    

    And ./src/vscode.mjs has to look like this:

    import {createRequire} from 'node:module';
    const require = createRequire(import.meta.url);
    export const vscode = require('vscode');
    

    Tada, problems solved - have fun with ESM. If you use TypeScript, drop it now: it just creates extra headaches through AST transformations etc.

    Too much code? I made a complete test repo here:

    https://github.com/kungfooman/vscode-extension-transformers