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

Adds workbox.streams #1439

Merged
merged 8 commits into from
Apr 26, 2018
Merged

Adds workbox.streams #1439

merged 8 commits into from
Apr 26, 2018

Conversation

jeffposnick
Copy link
Contributor

R: @addyosmani @gauntface

Fixes #621

I'll add some inline comments with anything that I'd imagine could be contentious/questionable to aid in the review.

@@ -3,15 +3,17 @@ module.exports = async (swUrl) => {
let error = await global.__workbox.webdriver.executeAsyncScript((swUrl, cb) => {
const onStateChangePromise = (registration, desiredState) => {
return new Promise((resolve, reject) => {
if (registration.installing === null) {
throw new Error('Regstration is not installing');
if (desiredState === 'activated' && registration.active) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I made some tweaks to this logic because I've seen some flakiness with the latest Safari TP, where registration.installing was null and this function gave up.

I think it's less prone to race conditions with these changes, since if we have a registration.active and the state we care about is 'activated', that should be fine.

@@ -18,7 +18,7 @@
// https://fetch.spec.whatwg.org/#headers-class
class Headers {
constructor(obj = {}) {
this.obj = obj;
this.obj = Object.assign({}, obj);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This works around an issue that I encountered in the tests.

https://github.com/pinterest/service-workers/blob/master/packages/service-worker-mock/models/Headers.js looks like it's much more robust than it used to be, and perhaps we should migrate off of our own mock at some point in the future.

};
}

export default {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm (still) not 100% sure what belongs in the _default.mjs's named exports and what doesn't.

I think this leads to a decent public interface, and I can use workbox.streams.strategy(), workbox.streams.isSupported(), etc. with this setup. But please double-check my thinking.


import './_version.mjs';

export {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Similar to my previous comment about uncertainty re: what should be in the _default.mjs export, I'm not 100% sure here, either.

fullyStreamedReject = reject;
});

if (process.env.NODE_ENV !== 'production') {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This series of log messages looks like the following in .dev.:

screen shot 2018-04-19 at 4 03 10 pm

Choose a reason for hiding this comment

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

Could you change this to a single group:

Concatenating 5 sources.

And instead of ...reading..., Reached the end of that source under each group, could you just add success or failure along with the final response.

While I understand the desire to log the requests as the state changes, it doesn't work with console groups as everything gets out of sync and ends up accidentally nesting and mixing - instead you need to capture the logs and then log everything at once.

@@ -0,0 +1,41 @@
const {expect} = require('chai');
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Creating meaningful node-based mocks for all the primitives needed by workbox.streams wasn't feasible, so I'm relying on the integration tests to exercise most of the actual logic.

() => `<p>this was executed at ${Date.now()}</p>`,
({event}) => httpBinStrategy.makeRequest({
event,
request: 'https://httpbin.org/bytes/12',

Choose a reason for hiding this comment

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

don't use a third party.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Switched.

Copy link

@gauntface gauntface left a comment

Choose a reason for hiding this comment

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

Core logic looks good. Largely minor comments on the log and demo.

event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
await Promise.all([
cache.put(START_CACHE_KEY, new Response('<html><head></head><body>')),

Choose a reason for hiding this comment

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

Would be preferable to make this a little more "fun".

Perhaps you could have an iframe that displays a HTML page indicating its not streamed and with SW installed it loads a HTML page with a header and footer with a party popper in the middle. This highlights a clear use case of header and footer and party popper highlights its working.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

* `'text/html'` will be used by default.
* @return {workbox.routing.Route~handlerCallback}
*
* @memberof workbox.streams

Choose a reason for hiding this comment

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

Does this work if you run gulp docs?

I think you need to define the @module workbox.streams before you assign memberof's

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it works as expected? Here's what the gulp docs output looks like:


screen shot 2018-04-23 at 1 17 20 pm


screen shot 2018-04-23 at 1 17 31 pm


It's not styled, obviously, but neither are the other modules' docs. And that appears to be the correct info.

concatenate,
concatenateToResponse,
isSupported,
strategy,

Choose a reason for hiding this comment

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

I'm not sure strategy is the best name here. workbox.streams.join() workbox.streams.run() workbox.streams.merge() would explain the intent a little easier.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

join() and merge() are effectively synonyms of the existing concatenate(), and I feel like using those names would confuse things and not make it clear what's different about strategy().

The thinking behind strategy() is that this is an interface that matches the interfaces exposed under workbox.strategies.*. So if a developer is familiar with one of the existing strategy interfaces, they'd presumably get that workbox.streams.strategy() is doing the same thing.

If you'd really prefer something other than strategy(), I guess I'd be okay with runt(), but handle() would be my first alternative choice. Or we could stick with strategy() 😄

Choose a reason for hiding this comment

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

I'm not sure this having the same API means it should have the same name unless it is behaving the same.

// TODO: This should be possible to do by constructing a ReadableStream, but
// I can't get it to work. As a hack, construct a new Response, and use the
// reader associated with its body.
return new Response(source).body.getReader();

Choose a reason for hiding this comment

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

cc @jaffathecake in case he has any ideas

fullyStreamedReject = reject;
});

if (process.env.NODE_ENV !== 'production') {

Choose a reason for hiding this comment

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

Could you change this to a single group:

Concatenating 5 sources.

And instead of ...reading..., Reached the end of that source under each group, could you just add success or failure along with the final response.

While I understand the desire to log the requests as the state changes, it doesn't work with console groups as everything gets out of sync and ends up accidentally nesting and mixing - instead you need to capture the logs and then log everything at once.

logStartOfGroup = true;
if (process.env.NODE_ENV !== 'production') {
logger.log('Reached the end of that source.');
logger.groupEnd();

Choose a reason for hiding this comment

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

Dynamic log groups like this will cause issues - we had with some of the previous strategies. Instead you need to stash the logs you want to make and then log everything together.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the heads-up about nesting and groups. I've refactored that.

I don't have access to the new Response at this point in the code, only to the ReadableStream. That doesn't end up exposing any useful information when it's logged to the console.

I'm going to take advantage of the fact that the Response will normally be logged by folks who use our Router class, and consider that sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It now looks like:
screen shot 2018-04-23 at 2 07 06 pm

@coveralls
Copy link

Coverage Status

Coverage decreased (-1.8%) to 87.296% when pulling 5de7d21 on streams-take-two into d3e3a88 on master.

Copy link

@gauntface gauntface left a comment

Choose a reason for hiding this comment

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

Would be good to get @philipwalton's +2

},
});

return {done, stream};
Copy link
Member

Choose a reason for hiding this comment

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

I remember we had a discussion before about whether done was really needed for waitUntil or whether respondWith was sufficient to keep the SW alive. Did we ever confirm that one way or another?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I confirmed with Jake that while the relevant specifications are pointing towards this no longer being required in the future, the current implementation in Chrome (and likely other browsers) does require explicitly calling waitUntil() to guarantee the SW is kept alive until the stream is drained.

Copy link
Member

Choose a reason for hiding this comment

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

Ok, cool. Thanks for confirming.

@philipwalton
Copy link
Member

LGTM

@jeffposnick jeffposnick merged commit 45cb8d0 into master Apr 26, 2018
@jeffposnick jeffposnick deleted the streams-take-two branch April 26, 2018 19:37
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.

4 participants