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

#315 - Add watch-compile event #319

Merged
merged 7 commits into from Feb 9, 2018

Conversation

dwelch2344
Copy link
Contributor

Quick shoutout: I'm new to plugin development and not sure the best way to test / lint / etc, but happy to make changes and do the legwork as needed.

What did you implement:

We've got a plugin in development that needs access to the post-compiled code for our handler, but in local development but there's no way to detect compiled changes if using watch. We've toyed with adding a new event on lib/wpwatch
that plugins could hook on as needed.

See an example project at https://github.com/dwelch2344/serverless-webpack-watch-compile-demo and the issue #315
filed at #315

How did you implement it:

Added a watch-compile definition in index.js and a serverless.spawn of said event in lib/wpwatch.js

How can we verify it:

→ npm run debug

> serverless-webpack-watch-compile@1.1.0 debug /tmp/serverless-webpack-watch-compile
> node --inspect ./node_modules/.bin/serverless offline -P 5000 -s dev

Debugger listening on ws://127.0.0.1:9229/8ee583a5-086a-407b-829d-c1a971a4d39f
For help see https://nodejs.org/en/docs/inspector
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Bundling with Webpack...
Time: 519ms
         Asset     Size  Chunks             Chunk Names
    handler.js  5.52 kB       0  [emitted]  handler
handler.js.map  5.38 kB       0  [emitted]  handler
   [0] ./handler.js 2.35 kB {0} [built]
   [1] external "babel-runtime/core-js/promise" 42 bytes {0} [not cacheable]
   [2] external "babel-runtime/helpers/objectWithoutProperties" 42 bytes {0} [not cacheable]
   [3] external "babel-runtime/regenerator" 42 bytes {0} [not cacheable]
   [4] external "babel-runtime/core-js/json/stringify" 42 bytes {0} [not cacheable]
   [5] external "babel-runtime/helpers/asyncToGenerator" 42 bytes {0} [not cacheable]
   [6] external "source-map-support/register" 42 bytes {0} [not cacheable]
Serverless: Watching for changes...
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for hello:
Serverless: GET /hello

Serverless: Offline listening on http://localhost:5000
  • Now make a change in handler.js, and observe the plugin logs again
Time: 88ms
         Asset     Size  Chunks             Chunk Names
    handler.js  5.52 kB       0  [emitted]  handler
handler.js.map  5.38 kB       0  [emitted]  handler
   [0] ./handler.js 2.36 kB {0} [built]
   [1] external "babel-runtime/core-js/promise" 42 bytes {0} [not cacheable]
   [2] external "babel-runtime/helpers/objectWithoutProperties" 42 bytes {0} [not cacheable]
   [3] external "babel-runtime/regenerator" 42 bytes {0} [not cacheable]
   [4] external "babel-runtime/core-js/json/stringify" 42 bytes {0} [not cacheable]
   [5] external "babel-runtime/helpers/asyncToGenerator" 42 bytes {0} [not cacheable]
   [6] external "source-map-support/register" 42 bytes {0} [not cacheable]
Serverless: Watching for changes...
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
Serverless: Running after a compiled change detected
  • Verify your change is there and things worked as expected

Todos:

  • Write tests
  • Write documentation
  • Fix linting errors
  • Make sure code coverage hasn't dropped
  • Provide verification config / commands / resources
  • Enable "Allow edits from maintainers" for this PR
  • Update the messages below

Is this ready for review?: YES
Is it a breaking change?: NO

…vent control while doing local development
Copy link
Member

@HyperBrain HyperBrain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @dwelch2344 , good addition!

Please check my comments. Additionally, there should be unit tests to keep coverage high (check in the watch unit tests that the spawn is called). And you should spawn also for the other watch commands (with invoke local and run).

index.js Outdated
@@ -72,6 +72,12 @@ class ServerlessWebpack {
'compile',
],
},
"watch-compile": {
Copy link
Member

@HyperBrain HyperBrain Feb 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should move that as subcommand to the compile command (as entrypoint):

compile:
  ...
  commands: {
    watch: {
      type: 'entrypoint',
      lifecycleEvents: [
        'compile'
      ]
    }
  }

Then it is more universal and better located semantically.

Additionally add a hook to define the new hookable event in this.hooks like this:

this.hooks = [
  ...
  'webpack:compile:watch:compile': () => BbPromise.resolve();
};

so that it is owned by the plugin and can be addressed with
after:webpack:compile:watch:compile and the symmetric before.

The new event should be mentioned in the README too.

lib/wpwatch.js Outdated
@@ -51,6 +51,8 @@ module.exports = {
if (firstRun) {
firstRun = false;
callback();
}else{
this.serverless.pluginManager.spawn('webpack:watch-compile')
Copy link
Member

@HyperBrain HyperBrain Feb 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will be webpack:compile:watch then.

We might want to await the promise (spawn) here instead of immediately returning from the callback.
Otherwise we'd create a race condition if the next recompile is done while the event is still triggered.

@HyperBrain
Copy link
Member

If you need any help, please let me know.

@dwelch2344
Copy link
Contributor Author

Great feedback, thank you. Only one question:

And you should spawn also for the other watch commands (with invoke local and run).
Not sure I follow exactly. What does that entail / look like?

Again, fairly new to plugin development (and Serverless as a whole, actually) – but happy to do the legwork + reading if you can point me in the right direction / give me a few examples.

Thanks!

@HyperBrain
Copy link
Member

The other kinds of watches are (quite similar code) in run.js

@HyperBrain
Copy link
Member

HyperBrain commented Feb 5, 2018

... and no problem that you're a newcomer to plugin development 😃 . I'm happy to guide you - and if you have generic questions about the Serverless plugin system, it's also no problem for me to help.

@dwelch2344
Copy link
Contributor Author

@HyperBrain Okay, I think we're good. I've moved to the subcommand (nifty – definitely like those) and verified things still work as expected. Also adjusted tests in both run.test.js as well as wpwatch.test.js and coverage has actually gone up :)

Regarding spawning on the other watch commands: I think this particular hook is right where it needs to be, as it really is only on subsequent compiles via watch that we want to hook in the event (and thus the tests ensuring we don't spawn with 1 invocation but do with 2+ invocations). Is there a gap I'm missing / flaw in my thinking there?

@HyperBrain
Copy link
Member

@dwelch2344 Cool, I'll have a look later 😄

The current implementation you did is fine and should serve its purpose.

The only thing I mentioned before was this (maybe minor and optional thing):
With awaiting the spawn I meant that now the hooks are triggered by the watch implementation - which is perfectly done how it is. But the spawn() itself is not awaited within the watch callback, what means that it can happen that the hook is triggered, dependent plugins do something, but as the callback already returned, an additional hook invocation can happen while the previous one is not yet finished.

That might actually never happen ;-) - or can be fixed if it happens.

@HyperBrain
Copy link
Member

@dwelch2344 Just saw that the new hookable event should be added to the README (section "for developers"). Can you do that?

@HyperBrain
Copy link
Member

I just restarted the build (there is something not ok with the validate unit tests - but that's out of scope here). Now it succeeded.

@dwelch2344
Copy link
Contributor Author

@HyperBrain Ah thank you for the clarification! Yeah, that definitely makes sense and we should do that to keep things running in the right / clean order. Would hate to leave a landmine for someone (especially since we'll be using this soon 😄 )

So, to await for it, do I simply need to "return" the spawn invocation? Assuming it's a promise, and since it's the last call in this instance, I'd think that would do it (based on the other spawn calls above). Is that accurate? If not, mind pointing out an example?

@HyperBrain
Copy link
Member

HyperBrain commented Feb 6, 2018

do I simply need to "return" the spawn invocation?

I think that will not work, because the function we're calling the spawn in is webpack's watch callback - and I doubt that webpack will await it. We might need some more sophisticated stuff here, that will only return from the callback when the spawn returns.

Something that is similar to a lock/wait mechanism in other languages, that blocks the callback function until it is released. I'll try to investigate a proper solution tomorrow that handles a wait for a promise in a synchronous function. In theory the callback function itself must be wrapped somehow .... but let's see.

Queuing the spawn calls would probably not be an option, as the hookers might depend on the compile output's immutability

An option could be using a generator for or in the watch callback function. According to http://node.green/ they are supported down to Node 4.x (with some restrictions on functionality), so that the Serverless requirements are met. I'll do some experiments tomorrow, to see if that can effectively "stall" our callback and wait for the promise to return.

@dwelch2344
Copy link
Contributor Author

Ok. Quick question then: how do releases on this project work? Aka how long would it take to get this out into a release? If they're small / fast cycles for this kind of thing, could we cut a release and open an enhancement to do the proper solution (which I'm happy to continue work on)?

We've got a pretty nifty setup and this is the last piece we need to continue rolling our product out, so while we're monkey-patching it now to dev it'd be nice to sew it up asap. Which I realize is trying to make our problem yours, but figured worth asking 😄

Thanks

@HyperBrain
Copy link
Member

HyperBrain commented Feb 7, 2018

@dwelch2344 I commited a fix for the race condition and tested it locally with serverless offline. Now the event code is awaited correctly and watching resumes after the events have been processed.
Additionally I fixed a generic issue with unnecessary recompiles by checking the webpack compile hash as proposed on their site.
Can you pull the branch and do some tests too? We have to make sure that it now works expected before it can be merged.

I have to adapt the unit tests now and change the run watcher in the same way. Afterwards I'll merge if your tests were successful.

Regarding the release schedule: There are no fixed release cycles and I generally release, as soon as there are important things available and ready for the next minor or patch release. So we can get this out quite quickly 😄

@HyperBrain HyperBrain added this to the 4.3.0 milestone Feb 7, 2018
@dwelch2344
Copy link
Contributor Author

This is awesome! Thanks for being so responsive 👍 Will check out and validate ASAP, EOD at the latest.

On another note: would love to help contribute and learn from your plugin experience. Could we connect via another channel? Gtalk / twitter / slack / gitter? I'll come to you :)

@dwelch2344
Copy link
Contributor Author

Just confirmed! this looks great! Can we go with it?

@HyperBrain
Copy link
Member

Yes we can 😄 . I'll just check the local invoke watch and the run watch, then we're good to go.

... and of course we can connect - I assume you're in PST, so it's 9 hours difference but still enough overlap :-D

@dwelch2344
Copy link
Contributor Author

Actually I'm in MT, so 10 probably ;) But I keep odd hours often so happy to work around you. DM me your preferred contact method on twitter at twitter.com/david_welch ?

@HyperBrain HyperBrain merged commit 66cb262 into serverless-heaven:master Feb 9, 2018
@HyperBrain
Copy link
Member

I'll postpone the event trigger for local invoke and run to a second PR, because that implementation has to be cleaned up anyway.
You'll hear from me via DM this weekend ;-)

@dwelch2344 dwelch2344 deleted the watch-compile-hook branch February 9, 2018 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants