14

I can see how to use addExtraLib in Monaco to add an ambient declaration file. What's not clear is how to use this function with an external declaration file so that Typescript code in the editor can do a:

import * as External from "external" 

External.foo();

On the Monaco set-up side, this doesn't seem to work:

// compiler options
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
    target: monaco.languages.typescript.ScriptTarget.ES2016,
    allowNonTsExtensions: true,
    moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
    module: monaco.languages.typescript.ModuleKind.CommonJS,
    noEmit: true,
    noLib: true,
    typeRoots: ["node_modules/@types"]
});

// extra libraries
monaco.languages.typescript.typescriptDefaults.addExtraLib(
    'export declare function foo():string;', 'node_modules/@types/external/index.d.ts');

monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
    noSemanticValidation: false,
    noSyntaxValidation: false
})
Joe Wood
  • 1,135
  • 1
  • 12
  • 24

3 Answers3

15

After playing around a little I found a solution. Basically, the file has to be loaded using createModel with an explicit file URL. If you do this then the relative file path for node_module/@types works. Here's my working solution that can be used in the playground:

// compiler options
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
    target: monaco.languages.typescript.ScriptTarget.ES2016,
    allowNonTsExtensions: true,
    moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
    module: monaco.languages.typescript.ModuleKind.CommonJS,
    noEmit: true,
    typeRoots: ["node_modules/@types"]
});

// extra libraries
monaco.languages.typescript.typescriptDefaults.addExtraLib(
    `export declare function next() : string`,
    'node_modules/@types/external/index.d.ts');

monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
    noSemanticValidation: false,
    noSyntaxValidation: false
})

var jsCode = `import * as x from "external"
    const tt : string = x.dnext();`;

monaco.editor.create(document.getElementById("container"), {
    model: monaco.editor.createModel(jsCode,"typescript",new monaco.Uri("file:///main.tsx")), 
});
Joe Wood
  • 1,135
  • 1
  • 12
  • 24
12

Joe's answer didn't work for me, fixed by prefixing the external type definition file path with file:///

Here's an updated example for the playground:

monaco.languages.typescript.typescriptDefaults.addExtraLib(
    'export declare function add(a: number, b: number): number',
    'file:///node_modules/@types/math/index.d.ts'
);

const model = monaco.editor.createModel(
    `import {add} from 'math';\nconst x = add(3, 5);\n`,
    'typescript',
    monaco.Uri.parse('file:///main.tsx')
);

monaco.editor.create(document.getElementById('container'), {model});

It's not necessary to provide the compiler options and diagnostic options.

Alexey Lebedev
  • 11,344
  • 3
  • 36
  • 46
0

As of April 2021 (monaco-editor@0.23.0), I wasn't able to get either of the prior solutions working without some additional details based on monaco-editor#2295, monaco-editor#1839, and https://stackoverflow.com/a/63349650. My use case required making type definitions available from several existing NPM packages (not just arbitrary paths to files) and this may have affected the solution. To summarize, I needed to:

  1. Bundle all of the .d.ts files from each package into a single file. TypeScript doesn't make this easy, and so instead I used dts-bundle-generator, but other solutions exist.
  2. Import the .d.ts content for each package using raw-loader or other plain-text loading alternatives.
  3. Call addExtraLib with the source for each module, adding an explicit declare module 'module-name' to the source code.

Full example below:

import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';

import source1 from '!!raw-loader!./types/package-one.d.ts';
import source2 from '!!raw-loader!./types/package-two.d.ts'

monaco.languages.typescript.typescriptDefaults.addExtraLib(
  `declare module '@my-project/package-one' { ${source1} }`,
  'file:///node_modules/@my-project/package-one/index.d.ts' // irrelevant?
);
monaco.languages.typescript.typescriptDefaults.addExtraLib(
  `declare module '@my-project/package-two' { ${source2} }`,
  'file:///node_modules/@my-project/package-two/index.d.ts' // irrelevant?
);

monaco.editor.create(document.getElementById('root'), {
    value: `
import { Foo } from '@my-project/package-one';

const foo = new Foo();
`,
    language: 'typescript',
    theme: 'vs-dark'
});
Don McCurdy
  • 7,980
  • 1
  • 25
  • 50
  • Try adding the `esModule=false` option to avoid having to declare modules: `const typings = require("!!raw-loader?esModule=false!./debugger-runtime.d.ts");` And yes, the lines marked `irrelevant` are indeed irrelevant. – Mike Lischke Apr 05 '21 at 09:23
  • And I did not bundle my typings files. I add multiple files in my React application, each just loading them with the require call from above. – Mike Lischke Apr 05 '21 at 09:24
  • Each of the packages referenced in my project contains dozens of typings files. Without a real `package.json` pointing to the `types` root, without using ES module imports, and without any reference to the real name of the package, I'm not sure how Monaco would be able to resolve imports of the package name? – Don McCurdy Apr 05 '21 at 16:54