jfmmm
jfmmm

Reputation: 391

How to load npm module type definition in Monaco using Webpack and react create-react-app

I wanted to load some module type definitions in Monaco inside a react app for tutorial purposes.

I actually managed to get it working after much pain but in a very hacky way. So I'm not asking how to do it, but rather, how to do it right.

The part I hope I can solve with Webpack is that right now I made a Node.js script that take all the .d.ts file it can find in the build folder of a private npm module and save them inside a large .json file.

in this format

{ [filePath]: 'fileContentAsString' }

Then in react I import that json and call addExtraLib for each one.

for (const filePath in sdkTypesJson) {
  // We add every .d.ts file to Monaco
  monaco.languages.typescript.typescriptDefaults.addExtraLib(
    sdkTypesJson[filePath],
    'file:///' + filePath.replace('dist/src/', '')
  );
}

Is there a way with some webpack magic to avoid having to create the json file?

Upvotes: 3

Views: 2076

Answers (1)

jfmmm
jfmmm

Reputation: 391

After two days of pain and misery, this is what I found work the best.

const files = require.context('!!raw-loader!./node_modules/my-module-name/dist/src/', true, /\.d.ts$/);

files.keys().forEach((key: string) => {
  // We add every .d.ts file to Monaco
  Monaco.languages.typescript.typescriptDefaults.addExtraLib(
    files(key).default,
    'file:///node_modules/my-module-name/' + key.substr(2)
  );
});

require.context tell Webpack to bundle all file from that path that matches the regex. Here I get all the .d.ts files containing the type description of my module.

!!raw-loader! tell to load the file without trying to execute it. It would crash the browser. You need to install the raw-loader module for Webpack.

Then you can have code like this in your embedded Monaco instance and have the typing Intellisense working like it would in vs-code.

import {
  function
} from '@my-module-name';

export default async function run(value: string) {
  alert('Code ran. Value passed is ' + value);
};

Back in your app code, you can then get the code from Monaco and eval the code. In my case when the user press a 'run' button in a react app.

const model = editor.current.getModel();

if (model && sdk.current && editorType === 'text') {
  // Be cool if Monaco worker could transpile the file to js. Don't work for some reason.
  // Monaco.languages.typescript.getTypeScriptWorker()
  //   .then(function(worker) {
  //     worker(model.uri)
  //       .then(function(client) {
  //         client.getEmitOutput(model.uri.toString()).then((output: any) => {
  //           console.log(output);
  //         });
  //       });
  //   });

  // @ts-ignore
  const js = window.ts.transpile(model.getValue());
  const setup = `const exports = { default: null };`;
  const final = setup + ' ' + js;

  try {
    const runMethod = eval(final);
    const newState = await runMethod('My value');
  } catch (error) {
    console.error(error);
    alert(error);
  }
}

In theory, you should be able to ask the Monaco typescript worker to transpile the file for you but I couldn't manage to make it work.

So I loaded the typescriptServices in my react index.html file with this line for now.

<script src="https://unpkg.com/typescript@latest/lib/typescriptServices.js"></script>

Upvotes: 7

Related Questions