microsoft / monaco-editor

A browser based code editor
https://microsoft.github.io/monaco-editor/
MIT License
39.18k stars 3.52k forks source link

[Feature Request] Support for package.json conditional exports map, for extra libs typings acquisition #4464

Open JulianCataldo opened 3 months ago

JulianCataldo commented 3 months ago

Context

Description

Actually, the only way for the TypeScript worker to pick up added libraries via setExtraLibs or addExtraLib is by using the main or typings fields inside the package.json file, or without a package.json, by using my-lib/some-file.js or my-lib/dist/some-file.js.

It works perfectly with the legacy method, but not with the newer exports field, which is very useful for achieving this kind of layouts:

exports: {
    '.': {
        typings: './dist/index.d.ts',
        import: './dist/index.js',
    },
    './foo': {
        typings: './dist/foo.d.ts',
        import: './dist/foo.js',
    },
},

Then we can import like this: import 'my-lib/foo';. This is also better than using barrel files, which are becoming an anti-pattern, because of the side-effects risks, for bundlers, browsers, CDNs… I'm not 100% sure but all ATA (automatic types acquisition) mechanisms I tried are randomly breaking on some packages, maybe because of this limitation.

Actually with Monaco, it's not possible to consume a library subparts if it's under a dist., without resorting to single entry point with a barrel, or full, sound paths.

This is problematic because since Node 12, a lot of NPM packages are adopting the new practice while abandoning the older one, for legitimate reasons.

Monaco Editor Playground Link

Monaco Editor Playground Code

const libs = [
  // koala
  {
    content: `
    export declare const foo: "Hey";
      `,
    filePath: "/node_modules/@types/koala/lib.d.ts",
  },
  {
    content: `
    export const foo = "Hey";
      `,
    filePath: "/node_modules/@types/koala/lib.js",
  },
  {
    content: JSON.stringify({
      name: "koala",
      version: "1.0.0",
      // typings: "./lib.d.ts",
      // main: "./lib.js",

      // v--- NOT WORKING ---v
      exports: {
        ".": "./lib.js",
      },
      type: "module",
    }),
    filePath: "/node_modules/@types/koala/package.json",
  },

  // tiger
  {
    content: `
    export declare const bar: "Hey";
      `,
    filePath: "/node_modules/@types/tiger/lib.d.ts",
  },
  {
    content: JSON.stringify({
      name: "tiger",
      version: "1.0.0",
      main: "./lib.js",
      type: "module",
    }),
    filePath: "/node_modules/@types/tiger/package.json",
  },

  // rhino
  {
    content: `
        declare module 'rhino' {
                    export class Foo {
                        /**
                         * # Construct me!
                         */
                         constructor(readonly hello: string) { } } }
    `,
    filePath: "/ambient.d.ts",
  },
];

libs.forEach((file /* libs */) => {
  console.log(file);
  monaco.languages.typescript.typescriptDefaults.addExtraLib(
    file.content,
    "file://" + file.filePath
  );
});
JulianCataldo commented 3 months ago

As a side note, maybe the lack of "moduleResolution": "Bundler" (or "module": "Preserve") is the root cause of this.

For now, we only have those:

    export enum ModuleResolutionKind {
        Classic = 1,
        NodeJs = 2
    }

Also I'm not sure about how deep Monaco treats special files like tsconfig.json, package.json in the root project or dependencies, etc.