steal icon indicating copy to clipboard operation
steal copied to clipboard

Error loading submodules: Steal looks for incorrect path

Open kredati opened this issue 7 years ago • 4 comments

How often can you reproduce it?

  • [x] Always
  • [ ] Sometimes
  • [ ] Rarely
  • [ ] Unable
  • [ ] I didn’t try

Description: This may be a bug that's specific to a library I'm using, but it does seem to indicate a divergence between the semantics of node's require and those implemented in Steal.

Also: apologies if this is a known issue or I'm doing something obvious wrong. I searched around as best I could. There are some things going on here that I don't really understand.

When loading a submodule using CommonJS require, Steal attempts to load /path/to/submodule.js; node's implementation allows (I don't know if it requires) /path/to/submodule/index.js to load properly.

Here's the actual example, using the reactive library Flyd. On its own, Flyd works just great with Steal. However, if you want to use something beyond Flyd's small core—e.g. filter— you must add them thus:

const flyd = require('flyd'), filter = require('flyd/module/filter')

Easy enough. The filter module lives at node_modules/flyd/module/filter/index.js.

Steal, however, can't handle this. It apparently dies trying to load node_modules/flyd/module/filter.js.

The obvious workaround is to explicitly require the submodule: require('flyd/module/filter/index.js'), but then that requires things in the same directory structure with the same expectations, and fails to load other scripts (and dies a similar death).

In the node REPL, however, this all works just fine:

> const flyd = require('flyd'), filter = require('flyd/module/filter')
> filter 
//-> [Function]

Steps to reproduce:

Set up a blank project:

npm init
npm install --save-dev steal
npm install --save flyd

index.html:

<html><body>
<script src="./node_modules/steal/steal.js"></script>
</body></html>

index.js:

const flyd = require('flyd'), foo = flyd.stream('foo')
window.foo = foo

const filter = require('flyd/module/filter'), filtered = filter(x => true)(foo)
window.filtered = filtered

Expected results: index.js loads without incident; foo and filtered are identical Flyd streams in its namespace; both foo and filtered are accessible globally.

Actual results: Errors:

VM64:1 GET http://127.0.0.1:8080/node_modules/flyd/module/filter.js 404 (Not Found) at VM1148:1
VM1148:1 GET http://127.0.0.1:8080/node_modules/flyd/lib.js 404 (Not Found) at VM1148:1
Error: Error loading "[email protected]#index" at http://127.0.0.1:8080/index.js at steal.js:7266
http://127.0.0.1:8080/node_modules/flyd/module/filter.js:6:23)
    at eval (http://127.0.0.1:8080/node_modules/flyd/module/filter.js:12:4)
    at eval (http://127.0.0.1:8080/node_modules/flyd/module/filter.js:12:98)
  Evaluating http://127.0.0.1:8080/node_modules/flyd/module/filter.js
  Evaluating http://127.0.0.1:8080/index.js

(Each of these has a long stack trace which I won't include here, but can provide if it's helpful. Also FYI, while node_modules/flyd/lib.js does not exist, node_modules/flyd/lib/index.js does.)

foo loads as a global variable pointing to a Flyd stream containing 'foo'.

window.filtered remains undefined.

Environment:

Software Version
Steal version 1.5.13
Steal-tools version n/a
node -v 8.4.0
npm -v 5.4.0
Browser Chrome 60
Operating system macOS 10.12.6

kredati avatar Sep 04 '17 16:09 kredati

Breaking example can be reduced down to:

const flyd = require('flyd');
const filter = require('flyd/module/filter');

matthewp avatar Sep 05 '17 13:09 matthewp

This is kind of a weird one. Going to try and explain what is happening the best I can (more for people who might try and implement a fix):

  1. flyd resolves to flyd/lib/index.js.
  2. flyd/module/filter/index.js requires ../../lib which resolves to flyd/lib but the actual file is flyd/lib/index.js.
  3. These are actually the same modules, but we get 2 instance.
  4. flyd/lib returns an empty object instead of the export of flyd/lib/index. Not sure why that is, circular ref perhaps?

A little backstory on how Steal resolves requires that are pointing at a folder.

  1. It tries to load the folder as a module (being in the browser, it doesn't know if something is a folder, a file, or doesn't exist at all, all it can do is try a request).
  2. When it fails, it then loads folder/index.js since that is the convention in Node.js.
  3. Even if that succeeds, the name remains folder, and that is used as the unique identifier in Steal's module registry.

So I think this is the cause of the problem. Rather than fixing the wrong path and loading a module twice (in this case), when the folder/index.js is detected, we should change the source of this module to instead point to that one; in other words with should do module.exports = require("./lib/index"). This will prevent duplicate module definitions, and instead give you only a small wrapper module.

matthewp avatar Sep 05 '17 14:09 matthewp

@kredati This is a good bug! Thanks for submitting, we'll try and get it on the next sprint.

matthewp avatar Sep 05 '17 14:09 matthewp

@matthewp Thanks!

Also: Steal is awesome! Thanks for it.

kredati avatar Sep 05 '17 15:09 kredati