jspm / generator

JSPM Import Map Generator
Apache License 2.0
166 stars 21 forks source link

jspm doesn't play nice with local packages #223

Closed nex3 closed 1 year ago

nex3 commented 1 year ago

I'm trying to use jspm as a low-friction way to generate an import map for some package dependencies. One of those dependencies is on the local filesystem, loaded with essentially "other-pkg": "file:../../pkg/other-pkg". When I run generator.install('other-pkg') it generates an import map that includes the relative URL to that package's entrypoint: ../../pkg/other-pkg/index.js.

This behavior makes sense as a default, but I'm just trying to serve my local package directory—the other-pkg directory won't be served. I tried passing defaultProvider: 'nodemodules' in the hope that it would cause the import map to point to node_modules/other-pkg/index.js, but it didn't do so for reasons I don't entirely understand.

I also tried using npm pack, which is usually an effective general workaround for local dependencies being weird. My package.json ended up including "other-pkg": "file:../../pkg/other-pkg/other-pkg-1.2.3.tgz", though, and JSPM choked on this entirely saying it was "Unable to resolve file:///.../other-pkg-1.2.3.tgs/".

guybedford commented 1 year ago

I've carefully read what you've described and it seems to me that everything is behaving as designed here in JSPM itself.

As far as I can understand your use case is to generate mappings for an installed package that is pointing to another location in the file system than the one through the "dependencies" field.

When doing a generator install, it is possible to specify a custom target for a package via generator.install({ alias: 'pkg-name', target: 'other-pkg' }). Combining this with the defaultProvider: 'nodemodules' should then achieve your use case.

JSPM will respect the package.json for install ranges when one is not already set in the generator.install arguments. That is the package.json ranges will take precedence over the install when no target range is specified.

There may well be some refinements on the above behaviour we could make further though.

nex3 commented 1 year ago

As far as I can understand your use case is to generate mappings for an installed package that is pointing to another location in the file system than the one through the "dependencies" field.

When you're using file: dependencies, there are two reasonable interpretations of "the [location] through the 'dependencies field". It could either mean the path literally referred to by file: (the more literalistic interpretation) or the path in node_modules to which npm linked the package (the interpretation that's more consistent with non-file: dependencies). I think the latter is more likely to be functional, because it's guaranteed to be accessible to the server if any packages are, but what I'm asking for here is just the ability to explicitly designate which interpretation I want.

For tarball dependencies, though, there is no ambiguity. If I have "other-pkg": "file:.../other-pkg-1.2.3.tgz", jspm's behavior today is simply broken. It crashes. The contents of that package are only available from node_modules, and jspm instead tries to read them from a directory that doesn't exist.

Bubblyworld commented 1 year ago

A minimal example, with a local directory and local tarball dependency: edgecase.tgz

{
  "name": "pkg",
  "type": "module",
  "exports": "./index.js",
  "dependencies": {
    "dep": "file:../dep",
    "tar": "file:../tar.tgz"
  }
}

Linking the index.js module does indeed bomb jspm:

$ jspm link ./index.js
   Error: Unable to resolve file:///edgecase/tar.tgz/ resolving tar imported from file:///edgecase/tar.tgz/.

I think it comes down to whether jspm should behave like the npm installer, or like the node resolver, when it's linking/installing stuff. It's currently doing the former (i.e. treating file deps as file:// URLs, see here), which I think is the right default because most packages it traces through won't have been installed, for instance. But for the nodemodules provider maybe we should do the latter for the root package, since in that case you're basically saying "give me an import map that works like node does, against my node_modules", right? Either way, jspm should handle tarball dependencies without throwing its toys!

@nex3 You might be able to work around the problem with resolutions (see https://github.com/jspm/generator#resolutions) for the file deps, it allows you to specify custom resolution overrides:

$ jspm link ./index.js --provider nodemodules --resolution tar=./node_modules/tar --resolution dep=./node_modules/dep && cat importmap.json
{
  "env": [
    "browser",
    "development",
    "module"
  ],
  "imports": {
    "dep": "./node_modules/dep/index.js",
    "tar": "./node_modules/tar/index.js"
  },
  "scopes": {
    "./node_modules/": {
      "@lit/reactive-element": "./node_modules/@lit/reactive-element/development/reactive-element.js",
      "lit": "./node_modules/lit/index.js",
      "lit-element/lit-element.js": "./node_modules/lit-element/development/lit-element.js",
      "lit-html": "./node_modules/lit-html/development/lit-html.js",
      "lit-html/is-server.js": "./node_modules/lit-html/development/is-server.js",
      "react": "./node_modules/tar/node_modules/react/index.js"
    }
  }
}
Bubblyworld commented 1 year ago

Generator should map into node_modules correctly for this use-case now, let us know if you hit any snags 🙏

nex3 commented 1 year ago

Looks great, thanks folks!