npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@okikio/resolve.imports

v1.0.0

Published

A tiny (613b), correct, general-purpose, and configurable subpath "imports" resolver without file-system reliance (forked from @lukeed's `resolve.exports`)

Downloads

11

Readme

@okikio/resolve.imports CI

A tiny (613b), correct, general-purpose, and configurable subpath "imports" resolver without file-system reliance. A fork of resolve.exports, but for imports.

Warning: Unlike resolve.exports, @okikio/resolve.imports doesn't have a default entry. This means that you must be explicit about the subpath to resolve

Why?

Hopefully, this module may serve as a reference point (and/or be used directly) so that the varying tools and bundlers within the ecosystem can share a common approach with one another as well as with the native Node.js implementation.

With the push for ESM, we must be very careful and avoid fragmentation. If we, as a community, begin propagating different dialects of "imports" resolution, then we're headed for deep trouble. It will make supporting (and using) "imports" nearly impossible, which may force its abandonment and along with it, its benefits.

Let's have nice things.

TODO

  • [x] imports string
  • [x] imports object (single entry)
  • [x] imports object (multi entry)
  • [x] nested / recursive conditions
  • [x] imports arrayable
  • [x] directory mapping (#foobar/ => /foobar/)
  • [x] directory mapping (#foobar/* => ./other/*.js)
  • [x] directory mapping w/ conditions
  • [x] directory mapping w/ nested conditions
  • [ ] ~~legacy fields (main vs module vs ...)~~
  • [ ] ~~legacy "browser" files object~~

Install

$ npm install @okikio/resolve.imports

Usage

Please see /test/ for examples.

import { resolve, legacy } from "@okikio/resolve.imports";

const contents = {
  name: "foobar",
  module: "dist/module.mjs",
  main: "dist/require.js",
  imports: {
    "#deps": {
      import: "./dist/module.mjs",
      require: "./dist/require.js",
    },
    "#lite": {
      worker: {
        browser: "./lite/worker.brower.js",
        node: "./lite/worker.node.js",
      },
      import: "./lite/module.mjs",
      require: "./lite/require.js",
    },
  },
};

// be explicit about the subpath to resolve, unlike `resolve.exports`
// there is no default entry
resolve(contents, "#lite"); //=> "./lite/module.mjs"

// Assume `require` usage
resolve(contents, "#deps", { require: true }); //=> "./dist/require.js"
resolve(contents, "#lite", { require: true }); //=> "./lite/require.js"

// Throws "Missing <entry> export in <name> package" Error
resolve(contents, "foobar/hello");
resolve(contents, "./hello/world");

// Add custom condition(s)
resolve(contents, "#lite", {
  conditions: ["worker"],
}); // => "./lite/worker.node.js"

// Toggle "browser" condition
resolve(contents, "#lite", {
  conditions: ["worker"],
  browser: true,
}); // => "./lite/worker.browser.js"

API

resolve(pkg, entry, options?)

Returns: string or undefined

Traverse the "exports" within the contents of a package.json file. If the contents does not contain an "exports" map, then undefined will be returned.

Successful resolutions will always result in a string value. This will be the value of the resolved mapping itself – which means that the output is a relative file path.

This function may throw an Error if:

  • the requested entry cannot be resolved (aka, not defined in the "exports" map)
  • an entry was resolved but no known conditions were found (see options.conditions)

pkg

Type: object Required: true

The package.json contents.

entry

Type: string Required: false Default: . (aka, root)

The desired target entry, or the original import path.

When entry is not a relative path (aka, does not start with '.'), then entry is given the './' prefix.

When entry begins with the package name (determined via the pkg.name value), then entry is truncated and made relative.

When entry is already relative, it is accepted as is.

Examples

Assume we have a module named "foobar" and whose pkg contains "name": "foobar".

| entry value | treated as | reason | | -------------------- | ---------- | ------------------------------------------------------- | | null / undefined | Error | be explicit about the subpath import being used | | '#' | './src' | subpath import of '#' | | 'foobar' | Error | all imports must be subpath imports starting with '#' | | '#/lite' | './lite' | value was relative | | '#lite' | './lite' | value was not relative & did not have pkg.name prefix |

options.require

Type: boolean Default: false

When truthy, the "require" field is added to the list of allowed/known conditions.

When falsey, the "import" field is added to the list of allowed/known conditions instead.

options.browser

Type: boolean Default: false

When truthy, the "browser" field is added to the list of allowed/known conditions.

options.conditions

Type: string[] Default: []

Provide a list of additional/custom conditions that should be accepted when seen.

Important: The order specified within options.conditions does not matter. The matching order/priority is always determined by the "imports" map's key order.

For example, you may choose to accept a "production" condition in certain environments. Given the following pkg content:

const contents = {
  // ...
  imports: {
    "#dep": {
      worker: "./index.worker.js",
      require: "./index.require.js",
      production: "./index.prod.js",
      import: "./index.import.mjs",
    }
  },
};

resolve(contents, "#dep");
//=> "./index.import.mjs"

resolve(contents, "#dep", {
  conditions: ["production"],
}); //=> "./index.prod.js"

resolve(contents, "#dep", {
  conditions: ["production"],
  require: true,
}); //=> "./index.require.js"

resolve(contents, "#dep", {
  conditions: ["production", "worker"],
  require: true,
}); //=> "./index.worker.js"

resolve(contents, "#dep", {
  conditions: ["production", "worker"],
}); //=> "./index.worker.js"

options.unsafe

Type: boolean Default: false

Important: You probably do not want this option! It will break out of Node's default resolution conditions.

When enabled, this option will ignore all other options except options.conditions. This is because, when enabled, options.unsafe does not assume or provide any default conditions except the "default" condition.

resolve(contents, "#dep");
//=> Conditions: ["default", "import", "node"]

resolve(contents, "#dep", { unsafe: true });
//=> Conditions: ["default"]

resolve(contents, "#dep", { unsafe: true, require: true, browser: true });
//=> Conditions: ["default"]

In other words, this means that trying to use options.require or options.browser alongside options.unsafe will have no effect. In order to enable these conditions, you must provide them manually into the options.conditions list:

resolve(contents, "#dep", {
  unsafe: true,
  conditions: ["require"],
});
//=> Conditions: ["default", "require"]

resolve(contents, "#dep", {
  unsafe: true,
  conditions: ["browser", "require", "custom123"],
});
//=> Conditions: ["default", "browser", "require", "custom123"]

License

MIT © Okiki Ojo