steal
steal copied to clipboard
Error loading submodules: Steal looks for incorrect path
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 require
s 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 |
Breaking example can be reduced down to:
const flyd = require('flyd');
const filter = require('flyd/module/filter');
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):
-
flyd
resolves toflyd/lib/index.js
. -
flyd/module/filter/index.js
requires../../lib
which resolves toflyd/lib
but the actual file isflyd/lib/index.js
. - These are actually the same modules, but we get 2 instance.
-
flyd/lib
returns an empty object instead of the export offlyd/lib/index
. Not sure why that is, circular ref perhaps?
A little backstory on how Steal resolves requires that are pointing at a folder.
- 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).
- When it fails, it then loads
folder/index.js
since that is the convention in Node.js. - 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.
@kredati This is a good bug! Thanks for submitting, we'll try and get it on the next sprint.
@matthewp Thanks!
Also: Steal is awesome! Thanks for it.