Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standard way of resolving import maps #364

Closed
deer opened this issue Jan 23, 2024 · 6 comments · Fixed by #369
Closed

Standard way of resolving import maps #364

deer opened this issue Jan 23, 2024 · 6 comments · Fixed by #369

Comments

@deer
Copy link
Contributor

deer commented Jan 23, 2024

Is there no standard way of resolving import maps?

I have a simple project:

tree
.
├── a.ts
├── b.ts
└── deno.jsonc
//a.ts
import { foo } from "hiddenBehindMap";

console.log(foo);
//b.ts
export const foo = "foo";
//deno.jsonc
{
  "imports": {
    "hiddenBehindMap": "./b.ts"
  }
}

And then I have a slightly less simple test file:

import { createGraph } from "https://deno.land/x/deno_graph@0.63.5/mod.ts";
import { resolve, toFileUrl } from "https://deno.land/std@0.212.0/path/mod.ts";

const path = resolve("./a.ts");
const fileUrlString = toFileUrl(path).toString();
let graph = await createGraph(fileUrlString);
console.log(graph);

const imports = (await import("./deno.json", { with: { type: "json" } }))
  .default.imports as Record<string, string>;
graph = await createGraph(fileUrlString, {
  resolve: createCustomResolver(imports),
});
console.log(graph);

function createCustomResolver(imports: Record<string, string>) {
  return (specifier: string, referrer: string) => {
    for (const key of Object.keys(imports)) {
      if (specifier.startsWith(key)) {
        specifier = specifier.replace(key, imports[key]);
        break;
      }
    }
    return new URL(specifier, referrer).toString();
  };
}

When I run the above I get the following output:

{
  roots: [ "file:///Users/reed/code/denoland/deno_graph/test_issue/a.ts" ],
  modules: [
    {
      kind: "esm",
      dependencies: [ { specifier: "hiddenBehindMap", code: [Object] } ],
      size: 90,
      mediaType: "TypeScript",
      specifier: "file:///Users/reed/code/denoland/deno_graph/test_issue/a.ts"
    }
  ],
  redirects: {}
}
{
  roots: [ "file:///Users/reed/code/denoland/deno_graph/test_issue/a.ts" ],
  modules: [
    {
      kind: "esm",
      dependencies: [ { specifier: "hiddenBehindMap", code: [Object] } ],
      size: 90,
      mediaType: "TypeScript",
      specifier: "file:///Users/reed/code/denoland/deno_graph/test_issue/a.ts"
    },
    {
      kind: "esm",
      size: 25,
      mediaType: "TypeScript",
      specifier: "file:///Users/reed/code/denoland/deno_graph/test_issue/b.ts"
    }
  ],
  redirects: {}
}

Is there something I'm missing? I would expect a standard way of passing in my Record<string, string> and having the resolution take place in the same way the cli does it.

This came up when trying to programmatically chase down dependencies of a fresh plugin. See denoland/fresh#2266 for the real code. Basically I need valid URLs for all dependencies of a plugin, in order to get the content of the files so that tailwind can extract the classes. This is of course only possible if the import map resolution takes place.

@iuioiua
Copy link
Contributor

iuioiua commented Jan 24, 2024

Hey Reed, could you please clarify the exact expected behaviour? Were you hoping for graph.modules[0].dependencies[0].specifier from the 2nd console.log(graph) to be a file URL?

@deer
Copy link
Contributor Author

deer commented Jan 25, 2024

Perhaps I'm expecting deno_graph to do too much? Maybe this import map resolution logic lives in deno itself.

But to clarify by example: whenever I click on an import in vs code, it takes me to the right spot. I want the same thing here. I have a file and an import map, and I want to get the same experience as in vs code. So yeah, fully resolved urls.

@lucacasonato
Copy link
Member

@deer What you are looking for is x/importmap. It let's you implement a resolver using an import map easially.

@deer
Copy link
Contributor Author

deer commented Jan 25, 2024

Thanks, that helps quite a bit. I'll keep playing with it and perhaps this issue can just be closed with a small documentation change.

@deer
Copy link
Contributor Author

deer commented Jan 26, 2024

The code went from this nightmare:

function createCustomResolver(
  imports: Record<string, string>,
  projectLocation: string,
const isLocal = projectLocation.startsWith("file://");
  const projectPath = isLocal
    ? path.fromFileUrl(projectLocation)
    : projectLocation;
  return (specifier: string, referrer: string) => {
    for (const key of Object.keys(imports)) {
      if (specifier.startsWith(key)) {
        if (imports[key] === "./") {
          const modifiedPath = path.join(
            projectPath,
            specifier.replace(key, ""),
          );
          return path.toFileUrl(modifiedPath).href;
        } else {
          specifier = specifier.replace(key, imports[key]);
        }
        break;
      }
    return new URL(specifier, referrer).toString();
  };
}

to the following:

function createCustomResolver(
  imports: ImportMap,
  baseURL: URL,
) {
  return (specifier: string, referrer: string) => {
    if (/^(?:\.\.\/)+|^\.\//.test(specifier)) {
      return path.join(path.dirname(referrer), specifier);
    }
    return resolveModuleSpecifier(specifier, imports, baseURL);
  };
}

I then discovered that https://deno.land/x/import_map is different than https://deno.land/x/importmap and that the first one is the official deno project. (I also discovered that my previous version wasn't correct in all cases, but was fine for the cases I was interested in.)

So now my code is:

function createCustomResolver(imports: ImportMap) {
  return (specifier: string, referrer: string) => {
    return imports.resolve(specifier, referrer);
  };
}

I think I've finally reached the end goal. Thanks again @lucacasonato.

@iuioiua
Copy link
Contributor

iuioiua commented Jan 28, 2024

You might be able to just use imports.resolve(), as it's almost the same signature as createCustomResolver().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants