-
Notifications
You must be signed in to change notification settings - Fork 32
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
Webpack Hot Module support + other options #69
base: master
Are you sure you want to change the base?
Conversation
Wow, that's impressive! However, since the adapter works so differently in HMR mode, I think it would make sense to keep your forked extension separate - especially because I don't know much about HMR, so you're certainly more qualified than me to maintain this. |
Thanks. That is up to you :) I've noticed the use of DidSaveTextDocument ... actually, this is also a drawback when using HMR : All tests are retired when saving any document (but there is no problem being notified of new/changed tests, since modified/created files are being ran by HMR automatically). As for compiling to files, it could indeed remove a bit of overhead, but not all of it since a worker has to be spawned each time (when scanning test or running/debugging them), which has to parse everything (i.e. load every test dependency). This is also quite an overhead which takes seconds for us. When using HMR, the worker is already spawned, and only changed modules are reloaded from disk when something changes. Which is really fast. Other (lighter) approaches that could work to detech changes on mapped files (since file watchers are hell), could be:
const originalSource = Symbol('original_source');
export function hookStack() {
const originalPrepare: any = Error.prepareStackTrace;
if (!originalPrepare) {
return function(){};
}
Error.prepareStackTrace = function(this: any, error: any, stack: any[]) {
// retreive orignal file names (non source mapped)
// and set them on error object
const sources = stack.map(f => f.isNative()
? '<native>'
: (f.getFileName() || f.getScriptNameOrSourceURL()));
error[originalSource] = sources;
return originalPrepare.apply(this, arguments);
};
return () => Error.prepareStackTrace = originalPrepare;
} And then, in const dispose = hookStack();
const err = new Error();
const stackFrames = stackTrace.parse(err);
// this contains the non source mapped file names
const originalFiles = (err as any)[originalSource];
dispose(); This way, "non source-mapped" file names could be sent to the extension, which could thus map |
Oh well, since I think about it, about reting exactly the right tests when changing a file: The worker could hook That should not be that hard to implement, and should be completely agnostic on configuration (ts-node, tsc --watch, etc...) |
I was also planning to use some kind of dependency analysis to retire only affected tests when a non-test file changes - when I find the time to do it. I was thinking about using an npm module like
Ah, yes, I had not thought about that. Damn. Perhaps I could teach the adapter to read a single test file (the one that changed) and then to weave the newly discovered tests into the test tree from the last test discovery. This could work well when you have lots of unit tests, but if you have end-to-end tests, those will draw in large parts of the application and so it wouldn't help much in that case... |
@oguimbal since Webpack introduces all of this bundling and source mapping I think a better long-term solution for this would be to search for/create a system for hot reloading modules in node itself (it's totally possible, you can delete modules from Note, relying on hot reloading in a persistent worker would have other consequences on flexibility. For example, I want to use this extension but I currently can't because there's no way to configure different mocha options for different directories of tests (unit vs end-to-end tests, for example). To support different mocha options in this way, you'd have to have a separate persistent worker process for each directory configuration. (And of course, if you have multiple project roots in your workspace, one persistent worker for each).
Yup, in my end-to-end tests, most changes to server code would have to rerun everything, plus my server code doesn't require any of the client-side bundles since they're only referenced via |
@jedwards1211 Sure, I get that. You're right, you would need several workers.
Regarding this... that's not exactly true. For two reasons:
An element of comparison: Running a single end-to-end test (when developing a feature by iteration ) has fallen for me from ~5-10 seconds to almost zero. Which is a very enjoyable experience, worthing the constraints of a background worker. |
I was talking more about the issue of figuring out exactly which tests need to be rerun when I change a file. Require analysis would mistakenly conclude none of my selenium integration tests need to be rerun when I change client side code for example, because the tests are only directly requiring server-side code. As far as patching code changes into watch mode, it seems like it might be good for mocha itself to support this? |
Yes right, this thing has been designed to test nodejs backend stuff. I've not given any thought about UI when writing this.
I'm not sure to understand your question. Are you speaking about HMR patching ? This PR is agnostic on that... it is just executing a JS bundle, and waiting for something to happen (test runs, test discoveries, etc). As it happens, I am using Weback for hot-reload, and it's webpack which does the job by reloading the required modules. HMR is an inside job of the generated bundle. This extension is not involved in it, nor is mocha. I suppose HMR could be handled somehow else, as long as (Did this answer your question ? or am I going astray ? - i'm French, I sometime miss some subtleties in english sentences) |
Oh, I was just saying, it would be nice if Mocha's watch mode could support rerunning tests without reloading all modules, then not only could this VSCode extension be able to rerun tests faster, but so could anyone who has to use Mocha from the CLI |
Right, I get you now 🎉 That's an interesting idea, I might give it some thought when I have time (I'm already giving a lot of time to opensource)... but maybe it's worth a pull request. (that said, I know from experience that having a feature request merged into a repo such as mocha is pretty tough when not coming from an insider... whatever the added value is. I guess that's a sane behaviour to avoid going in all directions when maintaining a popular repository) |
Oh I don't mean to sound demanding, it's just a thought. Really the main thing I was concerned about here is whether a change like this would make it even less likely that I can someday use this extension on projects that need different mocha configs for different test categories. |
TLDR / how to test
See this sample repository
Why ?
I am working on big typescript projects (hundreds of thousands of lines of codes, hundreds of tests).
This starts to be perticularly painful to me when it comes to testing. Your extension is great (thanks, by the way), but there is a huge overhead on every single test launch / test scan on big projects.
I modified (a bit :D ) your extension to leverage Webpack HMR in order to remove this overhead.
Example of the times I do not lose anymore (everything is amost instantaneous):
There are some tradeoffs (like losing guaranteed statelessness), but the resulting experience is perticularly joyful.
Given that this pull request modifies a lot of things, I would understand if you do not want to merge it. I created a fork of this extension that is quite OK for me.
However I tried to be as iso-functionality as possible, given that I dont have the time to test all the scenarios that this extension supports (launcher script, etc...)
Core changes & philosophy
When not enabling hmr support, there is no big change. However, when enabling it, there is only one worker that is spawned, and that stays up (it receives HMR updates).
A worker is now representated on the extension side by the class
WorkerInstance
(which is in the file I stupidely named listener-instance.ts)You will notice that
WorkerInstance
has multipleIWorkerSession
s.When HMR is enabled, multiple run sessions can run in a single worker. This is the purpose of this interface, which has two instances types, found in
load-session.ts
andrun-session.ts
(respectively "test scanning" & "test run" functionalities).Other details
I added two other extension options, which are unrelated to HMR:
mochaExplorer.nodeArgs
which allows you to pass optional arguments to nodejs (I am running node 8.10, and I need generators support, that is opt-in and I had no way to pass --harmony-async-iteration to the worker process)mochaExplorer.skipFrames
which supports skipping frames infindCallLocation(...)
... this is useful when using helper functions that are wrapping methods such asdescribe()
orit()
(in which case, "goto file" was pointing to the wrapper, not the actual test)Disclaimer
It works for us so far, but these changes are not (yet) extensively tested.