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

How to configure NODE_PATH for the Electron instance? #44

Open
trusktr opened this issue Mar 7, 2020 · 24 comments
Open

How to configure NODE_PATH for the Electron instance? #44

trusktr opened this issue Mar 7, 2020 · 24 comments
Labels

Comments

@trusktr
Copy link

trusktr commented Mar 7, 2020

When karma-electron is symlinked into a project, the upward search path for node_modules folders may not include the application where we are running tests, therefore the tests will not be able to require dependencies because they won't be found.

I tried running karma with NODE_PATH=pwd/node_modules:$NODE_PATH karma ... in case this scenario happens (it isn't needed if karma-electron is under the project node_modules).

But this NODE_PATH does not propagate into the Electron instance.

Do you know how to set NODE_PATH (or something similar) for the Electron instance?

@trusktr
Copy link
Author

trusktr commented Mar 7, 2020

Seems like this is the same issue (but not solution): electron/electron#11 EDIT: well even if that require('module').globalPaths trick works, I don't want to have to write that in every test file.

@twolfson
Copy link
Owner

twolfson commented Mar 7, 2020

To paraphrase your issue: You'd like to load a file for each test file execution, is that right? (e.g. auto-insert require/similar at the start of each test file)

If that's the case, then we need to reframe your understanding a bit =/

Karma is a browser test runner, not a Node.js one. We need to look at everything through the lens of "what if this was executing in Chrome or Firefox"? (where we don't have sweet features like NODE_PATH)

Once we reframe it, then we realize we need to find support for this either in karma itself or your chosen test runner (e.g. mocha, jasmine)

karma easily supports this feature (I use it all the time for loading CSS files for visual testing/debugging)

Documentation:

Example:

If you'd like something even more custom, karma also supports custom HTML pages where scripts can be loaded as you wish:

Documentation:

Example:

@trusktr
Copy link
Author

trusktr commented Mar 7, 2020

To paraphrase your issue: You'd like to load a file for each test file execution, is that right? (e.g. auto-insert require/similar at the start of each test file)

That's not exactly my issue (but that is one way I thought I could possibly solve my issue).

The issue is that when a meta-package of mine (which contains karma and karma-electron, and this meta package has other things like TypeScript build steps, Webpack bundling steps, etc) is not installed normally in my project, but symlinked, then Node which is running in karma-electron's Electron instance will no longer find my project's node_modules.

To make it more clear, if I run console.log(module) in a test file, I see this:

Module {
...
  paths: [
    '/home/trusktr/src/lume+umbrella/packages/builder-js-package/node_modules/electron/dist/resources/default_app.asar/node_modules', 
    '/home/trusktr/src/lume+umbrella/packages/builder-js-package/node_modules/electron/dist/resources/node_modules'
  ],
...
}

But the thing is, my project is actually located at

/home/trusktr/src/lume+umbrella/packages/my-project

So Node will not find /home/trusktr/src/lume+umbrella/packages/my-project/node_modules where all my project dependencies are.

This is happening, because I tried to switch to Lerna for multi-project management, which symlinks projects together. Now karma and karme-electron are not directly inside of /home/trusktr/src/lume+umbrella/packages/my-project/node_modules like they are when nothing is symlinked.

/home/trusktr/src/lume+umbrella/packages/my-project/node_modules/builder-js-package is symlinked to /home/trusktr/src/lume+umbrella/packages/builder-js-package, where builder-js-package is my "meta package" that contains all my build scripts, test tools, etc.

Karma is a browser test runner, not a Node.js one

This I know, which is exactly why I'm using karma+karma-electron (because with Jest I can only get fake browser APIs which have bugs or missing features).

As for the rest of your previous comment, I think we can ignore it because it was misunderstanding.

I just want require calls in karma-electron's Electron instance to find my modules. (And this problem only happens when karma-electron is not located under my project node_modules like normal because it in another symlinked location)

EDIT: Or, wait, maybe I misunderstood: Are you saying, that if I add the files to karma includes that then this allows them to be required in Electron (effectively adding them to module.paths)?

@trusktr
Copy link
Author

trusktr commented Mar 7, 2020

Alrighty! I think there might be an easy fix for this in karma-electron. I found this line:

module.paths = module.paths.concat(__require('module')._nodeModulePaths('{{!karmaBasePath}}'));

Maybe, we can add an option for karma-electron called path, which allows us to specify additional paths that can be pushed onto that array.

Let me see about a pull request.

@trusktr
Copy link
Author

trusktr commented Mar 7, 2020

In case I can make it more clear, in my project, I install some NPM packages:

npm install mkdirp

Now in my test code, this normally works fine:

const mkdirp = require('mkdirp')

describe('foo', () => {
  it('bar', () => { /* ... use mkdirp ...*/ })
})

But the problem happens only when karma-electron is no longer in my project node_modules (because it is in a symlinked location), and in this case I get an error

Electron 2.0.18 (Node 8.9.3) ERROR
  Uncaught Error: Cannot find module 'mkdirp'
  at module.js:545:5

@trusktr
Copy link
Author

trusktr commented Mar 7, 2020

Thank you for your responsiveness on this by the way! You are more responsive than most any other project maintainer.

@twolfson
Copy link
Owner

twolfson commented Mar 7, 2020

Ah, I see. Thanks for explaining the issue in more detail =)

A PR won't be necessary, more detailed options coming up in next comment

@twolfson
Copy link
Owner

twolfson commented Mar 7, 2020

Great find! The mustache template you linked to is indeed how we correct electron dropping node_modules paths due to a similar problem

This template is loaded via a karma preprocessor to wrap our files listed in karma. Thus, it has the same browser context as any other JS in our tests =) This means any tricks used there, can be used more broadly

https://github.com/twolfson/karma-electron/blob/6.3.0/lib/karma-electron-preprocessor.js#L23-L91

To reiterate, module.paths was the solution you were looking for, not NODE_PATH, for adjusting node_modules path resolution inside of the JS runtime (e.g. Electron main, Electron renderer)

First off, I recommend giving it a try with a specific test file. To reuse your mkdirp example:

// I prefer to use `concat` to avoid contaminating the original array
module.paths = module.paths.concat(['path/to/custom/node_modules']);

// Normal `mkdirp` execution
const mkdirp = require('mkdirp')

describe('foo', () => {
  it('bar', () => { /* ... use mkdirp ...*/ })
})

If that works fine, then we've got 2 options for you:

  • Use Electron's preload feature, https://github.com/twolfson/karma-electron/tree/6.3.0#using-preload-with-browserwindow
    • preload file should be as simple as contents module.paths = module.paths.concat(['path/to/custom/node_modules']);
    • I'm iffy if this will propagate properly to child windows/iframes (how karma launches fresh contexts for test runs)
      • In fact, it might not since I'd prob have avoided the mustache template in that case
  • Use previously described options in How to configure NODE_PATH for the Electron instance? #44 (comment)
    • File should be loaded at start of files and use included: true flag
    • Same contents: module.paths = module.paths.concat(['path/to/custom/node_modules']);
    • If this doesn't work (pretty sure if should, otherwise CSS wouldn't be in same context as other test files), then there's probably a karma plugin which allows for appending content to each test file

@trusktr
Copy link
Author

trusktr commented Mar 8, 2020

Aha!! So, I couldn't get the above module.paths trick to work, but I got the test to pass by doing the following

const path = require('path')
process.env.NODE_PATH = path.resolve(process.cwd(), 'node_modules') + ':' + process.env.NODE_PATH
require('module').Module._initPaths()

const mkdirp = require('mkdirp')

describe('TODO, tests', () => {
	it('needs to be done', () => {
		expect(mkdirp).toBeInstanceOf(Function)
	})
})

thanks to this hint on StackOverflow: https://stackoverflow.com/a/33976627/454780.

@twolfson
Copy link
Owner

twolfson commented Mar 8, 2020

Great! Glad to hear it all worked out =)

@trusktr
Copy link
Author

trusktr commented Mar 8, 2020

Well so far that sample works, but I still want to avoid writing that inside every test file. Let me try your files/included suggestion.

Would that trick with NODE_PATH be worth putting in the mustache template to ensure that path.resolve(process.cwd(), 'node_modules') is always available, even when symlinked?

@twolfson
Copy link
Owner

twolfson commented Mar 8, 2020 via email

@twolfson
Copy link
Owner

twolfson commented Mar 8, 2020 via email

@trusktr
Copy link
Author

trusktr commented Mar 8, 2020

Alright, it worked! I changed my files config to this:

		files: [
			// include the augment-node-path.js file first
			path.resolve(__dirname, 'augment-node-path.js'),

			// include all the test files after augment-node-path.js (order matters)
			{pattern: 'dist/**/*.test.js', watched: false},
		],

And augment-node-path.j contains:

// Most of the time, this doesn't do anything. But when karma-electron is
// symlinked into the project's node_modules, the regular upward lookup for
// node_modules will not find the project's node_modules, so this explicitly
// adds that to NODE_PATH. The karma.config.js file ensures this is loaded
// before all test files.

const path = require('path')
process.env.NODE_PATH = path.resolve(process.cwd(), 'node_modules') + ':' + process.env.NODE_PATH
require('module').Module._initPaths()

@trusktr
Copy link
Author

trusktr commented Mar 8, 2020

I don't believe using cwd in that resolution is a common setup so I'd
prefer to leave it as a one off setup

I agree with that sentiment, but maybe we should consider the following:

With normal Node usage, the user runs node in their project, and it always resolves things in project/node_modules by default.

But for whatever reason, with karma-electron existing in a folder symlinked into the project node_modules it caused node (the one inside Electron) not to look at project/node_modules as expected, which seems like behavior that doesn't match regular node usage.

As an example, while karma-electron's Electron was failing to find anything in project/node_modules, I could still run the node repl in my terminal and successfully execute require('mkdirp'). So the behavior of Electron wasn't matching my regular expectations.

I wasn't trying to add a specific non-standard location to my NODE_PATH, which I agree would be a one-off setup; I was trying make something work in karma-electron that otherwise normally works with node.

@twolfson
Copy link
Owner

twolfson commented Mar 8, 2020 via email

@trusktr
Copy link
Author

trusktr commented Mar 31, 2020

if I open a terminal on my desktop and run ~/github/my-project/test.sh, I would expect
it to load files in said folder, not from my desktop (cwd)

That may depend on expectations, but I see what you mean.

One thing is for sure though:

  • when I run node in my folder, it resolves modules fine.
  • when I run karma with karma-electron, it doesn't resolve modules the same way

So there definitely is some problem that the end user shouldn't have to worry about.

The question is, what's the solution?

@twolfson
Copy link
Owner

I think there's a false dichotomy that you seem to be looping on =/

I've got 2 immediate thoughts on hopefully resolving this:

  • karma should be considered equivalent to running node path/to/test.js, not node. This changes the path resolution location
    • If there's a discrepancy in this context, then I'd be happy to look into fixing it
  • Iteration for karma tests should be done via the debug window or via rerunning tests (e.g. via the continuous run function)
    • This will keep path resolution 1:1 with what we expect to load
    • This would be akin to use a debugger line or updating/rerunning a file, a node REPL won't have a 1:1 behavior if we're using a different path resolution location

@trusktr
Copy link
Author

trusktr commented Apr 7, 2020

karma should be considered equivalent to running node path/to/test.js, not node. This changes the path resolution location

Well I am running both node and karma on files that are in my project, and ultimately my project contains a node_modules folder that has the desired dependencies. So I think in both cases, the dependency should be resolvable, but it isn't.

Or in other words, if I run node in my project, or node project/foo/file.js which runs a file inside my project, and similarly with karm and it running any test files inside my project, then I expect that it should traverse up and eventually find the dependencies in my project node_modules folder, but this isn't the case.

Just to clarify that a little more, I am not running node or karma on any files outside of my project, only on files inside my project. So I think the node resolution algorithm should therefore always end up in my project's node_modules folder and resolve the dependency as expected.

What I can do is provide a more clear example of my folder structure, and how things are symlinked, so that I can show that where karma is located shouldn't matter, but that rather the module resolution should happen relative to the files being executed (test files for example), which doesn't seem to be the case, and instead it seems to depend on the location of karma and karma-electron in the overall file system (karma and karma-electron may be located entirely outside of my project).

I need to find some time to show more clearly how my filesystem is laid out...

@twolfson
Copy link
Owner

twolfson commented Apr 7, 2020

Ah, I see. Maybe I'm misinformed of how module.paths works then. Yea, if you could provide an example gist, then that'd be great =D

@trusktr
Copy link
Author

trusktr commented Sep 13, 2021

I haven't had the time to make an example, but to try and describe it simply, suppose we have this folder structure:

user-home/
  project/
    path/
      test.js
    node_modules/
      karma -> ../../libs/karma (symlink)
      karma-electron -> ../../libs/karma-electron (symlink)
      some-lib/
        index.js
  libs/
    karma/
      ...
    karma-electron/
      ...

where test.js has this:

const someLib = require('some-lib/index.js')
console.log(someLib)

and assuming that the current working directory is user-home/project/.

If I run node path/test.js it will correctly log the export of some-lib/index.js because Node finds the dependency in my project's node_modules folder.

If I run karma (we're still inside of project/), then the dependency resolution will fail to find some-lib.

This problem only happens if karma and karma-electron are symlinked (therefore they are not actually inside of the project/node_modules/ folder).

When dependencies are not linked, module resolution works as expected (which seems to indicate that module resolution happens based on the actual location of karma and karma-electron, not based on the project location (unlike node).

When karma dependencies are linked and live somewhere outside of project/, the node_modules folder inside of project/ seems to be ignored during module resolution.

The workaround that I used is to add to ~/user-home/project/node_modules/ to NODE_PATH before executing the test bundles, which makes the module lookup work.

It seems that Electron running via karma-electron is not performing module resolution the same as when running node. Note that node is also not located inside project/, it is external, but it still takes into consideration the current working directory for module lookup based on where node is being executed, which is what doesn't happen when I execute a symlinked karma/karma-electron.

@twolfson
Copy link
Owner

I'm quite busy the next 2 nights so I don't have time to parse/dig into all of this. I'll give it a read by the end of the weekend =)

@twolfson
Copy link
Owner

Alright, so that explanation is great and well laid out. That being said, trying to reproduce that can either be very quick or very slow/painful by trying to guess what went went (and even if it's quick, it might not be the issue you're describing).

I definitely need a demonstration repo/similar to have a version of "here's it working in Node", "here's it not working in Electron"

I'll re-open the issue for now but that demo repo is needed to move forward =/

@twolfson twolfson reopened this Sep 15, 2021
@trusktr
Copy link
Author

trusktr commented Sep 15, 2021

I'm quite busy the next 2 nights so I don't have time to parse/dig into all of this. I'll give it a read by the end of the weekend =)

No rush, it took me more than a year to circle back with a reply. :)

I'll try to make a repo (I'll take my repo and strip everything out except one file, and hopefully that'll work). No guarantees on a timeline though. :)

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

No branches or pull requests

2 participants