sidwebworks / web-pen

Open-source Codepen on steroids
https://web-pen.vercel.app
GNU General Public License v3.0
17 stars 1 forks source link

Better Intellisense for Code completion #4

Closed sidwebworks closed 2 years ago

sidwebworks commented 2 years ago

Need to figure out a way to integrate the ESBUILD plugins to export some form of dependency chart so that can be used to fetch typing externally and then fed into Monaco's TSC options.

sidwebworks commented 2 years ago

Update: I have managed to get Monaco detect external types libs, and created a custom Web worker to fetch types in the background. however I haven't yet tried to integrate it with the Esbuild fetch plugin. I think I will output a json object of all the top level imported modules out of my result and create a dependency file

sidwebworks commented 2 years ago

UPDATE: Esbuild actually gives u an metafile output which I used to create a list of all the top level imports that file has.

const result = await build({
            entryPoints: ["index.js"],
            bundle: true,
            write: false,
            metafile: true,
            legalComments: "none",
            plugins: [unpkgPathPlugin(), fetchPlugin(rawCode, lang)],
            define: {
                "process.env.NODE_ENV": `"production"`,
                global: "window",
            },
        });

        const imports = result.metafile?.inputs["a:index.js"].imports
            .map((el) => el.path.replace("a:https://unpkg.com/", ""))
            .filter((e) => !e.includes("/"));

Combining it with a types web worker and a setting up a listener makes the intellisense work properly.

const loadTypes = (types) => {
    const disposables: any = [];
    const monaco = window && window.monaco;

    const dependencies = types.map((e) => ({ name: e, version: "@latest" })) || [];

    if (!typesWorker) {
        typesWorker = new Worker(
            new URL("../../workers/fetch-types.worker.js", import.meta.url)
        );
    }

    dependencies.forEach((dep) => {
        typesWorker.postMessage({
            name: dep.name,
            version: dep.version,
        });
    });

    typesWorker.addEventListener("message", (event) => {
        // name,
        // version,
        // typings: result,
        const key = `node_modules/${event.data.name}/index.d.ts`;
        const source = event.data.typings[key];

        // const path = `${MONACO_LIB_PREFIX}${event.data.name}`;
        const libUri = `file:///node_modules/@types/${event.data.name}/index.d.ts`;

        disposables.push(
            monaco.languages.typescript.javascriptDefaults.addExtraLib(source, libUri)
        );
        disposables.push(
            monaco.languages.typescript.typescriptDefaults.addExtraLib(source, libUri)
        );

        // When resolving definitions and references, the editor will try to use created models.
        // Creating a model for the library allows "peek definition/references" commands to work with the library.
    });

    return {
        dispose() {
            disposables.forEach((d) => d.dispose());
            if (typesWorker) {
                typesWorker.terminate();
            }
        },
    };
};
sidwebworks commented 2 years ago

Although there are a lot of spots for improvement, Like this initial implementation doesn't cache the types, so that's a TODO.