├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── basically.css ├── examples ├── backpressure │ ├── README.md │ ├── index.css │ ├── index.html │ └── index.js ├── fetch │ ├── README.md │ ├── index.css │ ├── index.html │ └── index.js ├── streaming-pwa │ ├── .babelrc │ ├── README.md │ ├── components │ │ ├── App.jsx │ │ ├── Book │ │ │ └── Book.jsx │ │ └── Results │ │ │ └── Results.jsx │ ├── css │ │ ├── main.css │ │ └── main.src.css │ ├── icons │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-192x192.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-72x72.png │ │ └── icon-96x96.png │ ├── js │ │ ├── index.js │ │ └── template.js │ ├── lighthouse-report.png │ ├── manifest.json │ ├── package.json │ ├── serve.js │ ├── sw.js │ └── yarn.lock └── teeing │ ├── README.md │ ├── index.css │ ├── index.html │ └── index.js ├── index.css ├── index.html ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at tejas@tejas.qa. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Literally just fork and PR and we can talk. 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | I have an issue with: 2 | * 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tejas Kumar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # _Basically_, [Streams](https://streams.spec.whatwg.org) 🌊 2 | So something (relatively) new and fun in browsers, are these two APIs: ReadableStream and WritableStream. 😮 3 | 4 | What they allow you to do, is basically send and receive chunks of _things_ progressively (that then combine into the final large _thing_), as opposed to moving around _entire things_ from server -> browser -> user. 5 | 6 | This is really cool. 7 | 8 | Streams have some fancy bells and whistles that we'll get to talk about in this project. I'll list them below, but I'd highly recommend starting with the root-level project ([index.html](https://github.com/TejasQ/basically-streams/blob/master/index.html), [index.js](https://github.com/TejasQ/basically-streams/blob/master/index.js), [index.css](https://github.com/TejasQ/basically-streams/blob/master/index.css)), and moving into the examples from there. 9 | 10 | ## Why this project? 11 | The answer to this question is _basically_ the same answer to _all the things_ in my "Basically" series: this project aims to explain streams to any level of developer: beginner, intermediate or advanced, focused primarly on the beginners. 12 | 13 | The hope is that the concept of streams within a browser would not be scary, causing developers to shy away from it; but rather, that this concept would be dispalyed for how simple it really is, enabling developers to embrace it and run with it, allowing the creation beautiful, streamy things. 14 | 15 | ## [Examples](https://github.com/TejasQ/basically-streams/blob/master/examples) 16 | In the next few hours, I am to push examples of cool stream features, such as: 17 | - _(Really)_ Progressive Web Apps ([Demo](https://server-stjntslidj.now.sh) | [Code](https://github.com/TejasQ/basically-streams/tree/master/examples/streaming-pwa)) 18 | - AJAX Requests with Streams (and progress bars) ([Demo](https://tejasq.github.io/basically-streams/examples/fetch) | [Code](https://github.com/TejasQ/basically-streams/blob/master/examples/fetch/index.js)) 19 | - Piping Streams ([Demo](https://tejasq.github.io/basically-streams/) | [Code](https://github.com/TejasQ/basically-streams/blob/master/index.js)) 20 | - Backpressure ([Demo](https://tejasq.github.io/basically-streams/examples/backpressure) | [Code](https://github.com/TejasQ/basically-streams/blob/master/examples/backpressure/index.js)) 21 | - Teeing (Forked streams) ([Demo](https://tejasq.github.io/basically-streams/examples/teeing) | [Code](https://github.com/TejasQ/basically-streams/blob/master/examples/teeing/index.js)) 22 | 23 | If you'd like to contribute _your_ use case or examples, pull requests are welcome! Wooo! 24 | -------------------------------------------------------------------------------- /basically.css: -------------------------------------------------------------------------------- 1 | /* 2 | Gotta have the pretty, y'all. 💄 3 | */ 4 | 5 | :root { 6 | --spacing: 16px; 7 | --colors__main: #a63d40; 8 | --colors__secondary: #0c6291; 9 | --colors__disabled: #666; 10 | --breakpoint__tablet: 768px; 11 | } 12 | 13 | * { 14 | box-sizing: border-box; 15 | text-rendering: optimizeLegibility; 16 | -webkit-font-smoothing: antialiased; 17 | } 18 | 19 | html, 20 | body { 21 | margin: 0; 22 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", 23 | "Segoe UI Emoji", "Segoe UI Symbol"; 24 | font-size: 13px; 25 | } 26 | 27 | a:link, 28 | a:visited { 29 | transition: 0.15s ease color; 30 | color: var(--colors__main); 31 | } 32 | 33 | a:hover { 34 | color: var(--colors__secondary); 35 | } 36 | 37 | @media screen and (min-width: var(--breakpoint__tablet)) { 38 | html, 39 | body { 40 | margin: var(--spacing) 0; 41 | } 42 | } 43 | 44 | img { 45 | max-width: 100%; 46 | margin-bottom: var(--spacing); 47 | } 48 | 49 | header, 50 | main { 51 | margin: 0 auto; 52 | max-width: 440px; 53 | } 54 | 55 | main { 56 | padding: 20px; 57 | } 58 | 59 | header { 60 | padding: var(--spacing); 61 | } 62 | 63 | h1 { 64 | margin: 0; 65 | font-size: 1.8rem; 66 | } 67 | 68 | ul { 69 | margin: calc(var(--spacing) * 2) 0 calc(var(--spacing) * 2) var(--spacing); 70 | padding: 0; 71 | } 72 | 73 | li + li { 74 | margin-top: var(--spacing); 75 | } 76 | 77 | @media screen and (min-width: var(--breakpoint__tablet)) { 78 | h1 { 79 | font-size: 2rem; 80 | } 81 | } 82 | 83 | p { 84 | margin: var(--spacing) auto; 85 | } 86 | 87 | table { 88 | width: 100%; 89 | border-collapse: collapse; 90 | } 91 | 92 | .table__left { 93 | width: 150px; 94 | } 95 | 96 | td { 97 | padding: calc(var(--spacing)/4); 98 | font-size: 1rem; 99 | } 100 | 101 | thead { 102 | font-size: 1rem; 103 | font-weight: bold; 104 | } 105 | 106 | button { 107 | width: 100%; 108 | padding: calc(var(--spacing)/2); 109 | border: 0; 110 | font-size: 1rem; 111 | transition: 0.15s ease background-color; 112 | cursor: pointer; 113 | background-color: var(--colors__main); 114 | color: white; 115 | -webkit-appearance: none; 116 | } 117 | 118 | button:hover { 119 | background-color: var(--colors__secondary); 120 | } 121 | 122 | button:disabled { 123 | background-color: var(--colors__disabled); 124 | } 125 | 126 | input { 127 | width: 100%; 128 | font: inherit; 129 | } 130 | -------------------------------------------------------------------------------- /examples/backpressure/README.md: -------------------------------------------------------------------------------- 1 | # Basically, Backpressure 2 | 3 | Streams support a cool thing called [backpressure](https://streams.spec.whatwg.org/#pipe-chains). This amazing concept basically enables two-way communication within a stream, where the consumer of the stream can speak to its source and be like _HEY MAN SLOW DOWN I CAN'T PROCESS THINGS THAT FAST_. 4 | 5 | Basically, you give a stream a maxiumum number of _things_ it can hold in its [underlying sink](https://streams.spec.whatwg.org/#ws-model), any more than that, and it says _dude, wait_. 6 | 7 | ## For example, 8 | 9 | Basically, [in this example](https://tejasq.github.io/basically-streams/examples/backpressure), we try and write to our WritableStream 5000 times in quick succession. We've configured the WritableStream to only be able to handle 1000 things in its queue, using what is called a CountQueuingStrategy's highWaterMark, which is the point at which the WritableStream starts applying backpressure, saying back off, bro. 10 | 11 | When you click Start, you should be able to see it all in action. 12 | 13 | Each time the progress bar stops, the WritableStream is applying backpressure. 14 | -------------------------------------------------------------------------------- /examples/backpressure/index.css: -------------------------------------------------------------------------------- 1 | .bar { 2 | height: 8px; 3 | border-radius: 10px; 4 | overflow: hidden; 5 | background: #eee; 6 | } 7 | 8 | .bar-fill { 9 | width: 0; 10 | height: 100%; 11 | background: red; 12 | transition: 0.3s width ease; 13 | } 14 | 15 | .backpressure-indicator { 16 | margin-bottom: calc(var(--spacing) / 4); 17 | visibility: hidden; 18 | font-size: 11px; 19 | font-weight: bold; 20 | color: red; 21 | animation: pulsate 1s ease infinite; 22 | } 23 | 24 | @keyframes pulsate { 25 | from { 26 | opacity: 0; 27 | } 28 | 50% { 29 | opacity: 1; 30 | } 31 | to { 32 | opacity: 0; 33 | } 34 | } 35 | 36 | .controls { 37 | display: flex; 38 | margin-top: var(--spacing); 39 | } 40 | 41 | .reset-button { 42 | display: none; 43 | margin-left: var(--spacing); 44 | } 45 | -------------------------------------------------------------------------------- /examples/backpressure/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Basically, Backpressure 8 | 9 | 10 |
11 |

Basically, Backpressure

12 |

Streams support a cool thing called backpressure. This amazing concept basically enables two-way communication within a stream, where the consumer of the stream can speak to its source and be like HEY MAN SLOW DOWN I CAN'T PROCESS THINGS THAT FAST.

13 |

Basically, you give a stream a maxiumum number of things it can hold in its underlying sink, any more than that, and it says dude, wait.

14 |

Here's an example.

15 |

Basically, we try and write to our WritableStream 5000 times in quick succession. We've configured the WritableStream to only be able to handle 1000 things in its queue, using what is called a CountQueuingStrategy's highWaterMark, which is the point at which the WritableStream starts applying backpressure, saying back off, bro.

16 |

When you click Start, you should be able to see it all in action.

17 |

Each time the progress bar stops, the WritableStream is applying backpressure.

18 | 19 |
20 |
21 |
22 |
23 |
24 | 25 | 26 |
27 |
28 |

More Examples 🎉

29 | 35 |

Further Reading: WhatWG Spec 36 |

37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /examples/backpressure/index.js: -------------------------------------------------------------------------------- 1 | // Get the DOM elements we interact with. 2 | const startButton = document.querySelector("#start-button") 3 | const resetButton = document.querySelector("#reset-button") 4 | const backpressureIndicator = document.querySelector("#backpressure-indicator") 5 | const doneIndicator = document.querySelector("#done-indicator") 6 | const progressBar = document.querySelector("#bar-fill") 7 | 8 | // How many things do we want to write to the WritableStream? 9 | const thingsToWrite = 5000 10 | 11 | // Our WritableStream should be able to only process this many things at a time. 12 | const thresholdToApplyBackpressure = 1000 13 | 14 | /* 15 | This defines a QueuingStrategy that basically defines the `highWaterMark`: the point at which 16 | the stream applies backpressure. 17 | */ 18 | const backpressure = new CountQueuingStrategy({ highWaterMark: thresholdToApplyBackpressure }) 19 | 20 | // This is our receiver stream 21 | const receiverStream = new WritableStream( 22 | { 23 | // When someone writes data to it, 24 | write(data) { 25 | // Fill the progress bar with a percentage of the total count 26 | progressBar.style.width = `${data / thingsToWrite * 100}%` 27 | }, 28 | 29 | // When the source closes, 30 | close() { 31 | // Update the startButton 32 | startButton.innerHTML = "Stream Closed." 33 | 34 | // Show the resetButton (thanks @ricea) 35 | resetButton.style.display = "block" 36 | }, 37 | }, 38 | /* 39 | The second argument to the WritableStream constructor is the 40 | QueuingStrategy. 41 | */ 42 | backpressure 43 | ) 44 | 45 | // We need an instance of this writer that we'll use. 46 | const writer = receiverStream.getWriter() 47 | 48 | /* 49 | This FILLS the writer's sync `limit` times. 50 | The second argument helps it keep track of which iteration it's on 51 | */ 52 | const fill = async (limit = thingsToWrite, count = 0) => { 53 | // If the writer's highWaterMark is reached, 54 | if (writer.desiredSize === 0) { 55 | // Set the backpressureIndicator and show it. 56 | backpressureIndicator.innerHTML = `Backpressure Detected (after ${count} items processed)` 57 | backpressureIndicator.style.visibility = "visible" 58 | 59 | // Wait for it to be free again to process more things. 60 | await writer.write( 61 | /* 62 | Make the next thing take a while to process 63 | in order to give the browser time to catch up, 64 | for demonstration purposes. 65 | */ 66 | await new Promise(resolve => 67 | setTimeout(() => { 68 | backpressureIndicator.style.visibility = "hidden" 69 | resolve(count) 70 | }, 1000) 71 | ) 72 | ) 73 | 74 | /* 75 | If the writer's highWaterMark isn't reached, 76 | as in, if it has room to breathe, 77 | */ 78 | } else { 79 | // Don't show the indicator 80 | backpressureIndicator.style.visibility = "hidden" 81 | 82 | // Write to the writer (that updates the width of the progress bar) 83 | writer.write(count) 84 | } 85 | 86 | // Increment the `count` so this function doesn't run infinitely 87 | count++ 88 | 89 | // If we've reached the limit, close the writer and stop the stream. 90 | if (count === limit) return writer.close() 91 | 92 | /* 93 | If not, repeat ALL these steps again, rewriting to the 94 | WritableStream instantly unless it applies backpressure. 95 | */ 96 | fill(limit, count++) 97 | } 98 | 99 | // Finally, when one clicks the start button, start things! 100 | startButton.addEventListener("click", () => { 101 | startButton.disabled = true 102 | fill() 103 | }) 104 | 105 | // Simple reset 106 | resetButton.addEventListener("click", () => location.reload()) 107 | -------------------------------------------------------------------------------- /examples/fetch/README.md: -------------------------------------------------------------------------------- 1 | # Basically, Streaming Fetch 2 | 3 | Historically, most progress bars for content loading type activity have been illusions: animations which fill up over a predetermined duration by the designer that do not actually relate to data being transferred at all. I've built some of these myself. 4 | 5 | With ReadableStreams in the WHATWG's Fetch API, we're able to have real deal progress bars. [Check it out!](https://tejasq.github.io/basically-streams/examples/fetch) On that page, enter an Image URL (preferably of a LARGE uncached image or something), and watch the progress bar ACTUALLY REFLECT the load status! So cool! 6 | 7 | Don't forget to [check out the source](https://github.com/TejasQ/basically-streams/blob/master/examples/fetch/index.js)! 8 | -------------------------------------------------------------------------------- /examples/fetch/index.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | background-size: cover; 4 | width: 100vw; 5 | height: 100vh; 6 | } 7 | 8 | /* 9 | Streams don't even work on mobile afaik but yolo 10 | */ 11 | @media (min-width: 768px) { 12 | main { 13 | width: 100%; 14 | max-width: 768px; 15 | display: grid; 16 | grid-template-columns: 1fr 1fr; 17 | grid-gap: var(--spacing); 18 | } 19 | .card-with-links-to-copy { 20 | grid-column: 2; 21 | grid-row: 1; 22 | } 23 | .card-with-actual-content { 24 | grid-column: 1; 25 | grid-row: 1; 26 | } 27 | } 28 | 29 | h2 { 30 | margin-top: 0; 31 | } 32 | 33 | .card { 34 | margin-top: 50px; 35 | padding: calc(var(--spacing) * 2); 36 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.14); 37 | background-color: white; 38 | } 39 | 40 | .data-copied { 41 | visibility: hidden; 42 | font-size: 11px; 43 | font-weight: bold; 44 | color: red; 45 | } 46 | 47 | .bar { 48 | height: 8px; 49 | margin-bottom: calc(var(--spacing) * 2); 50 | border-radius: 10px; 51 | overflow: hidden; 52 | background: #eee; 53 | } 54 | 55 | .bar-fill { 56 | width: 0; 57 | height: 100%; 58 | background: red; 59 | } 60 | 61 | .controls { 62 | display: flex; 63 | align-items: flex-end; 64 | margin-top: 0; 65 | margin-bottom: var(--spacing); 66 | } 67 | 68 | button { 69 | flex: 0 1 100px; 70 | } 71 | 72 | code { 73 | word-wrap: break-word; 74 | } 75 | 76 | label { 77 | font-size: 11px; 78 | font-weight: bold; 79 | line-height: 2; 80 | } 81 | 82 | .fetch-button { 83 | flex-grow: 1; 84 | margin-left: var(--spacing); 85 | } 86 | 87 | .reset-button { 88 | display: none; 89 | margin-left: var(--spacing); 90 | } 91 | -------------------------------------------------------------------------------- /examples/fetch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Basically, Streaming Fetch 8 | 9 | 10 |
11 | 28 |
29 |

Basically, Streaming Fetch

30 |

Historically, most progress bars for content loading type activity have been illusions: animations which fill up over a predetermined duration by the designer that do not actually relate to data being transferred at all. I've built some of these myself.

31 |

With ReadableStreams in the WHATWG's Fetch API, we're able to have real deal progress bars.

32 |

Check it out! Enter an Image URL below (preferably of a LARGE uncached image or something), and watch the progress bar ACTUALLY REFLECT the load status! This is so cool!

33 | 34 |

Sample image URLs are provided to the right. Simply click in the input field to copy the URL and paste it in the URL input field.

35 | 36 |
Data Copied!
37 |
38 |
39 | 40 | 46 |
47 | 48 | 49 |
50 | 51 |
52 |
53 |
54 |

Further Reading: WhatWG Spec 55 |

56 |
57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /examples/fetch/index.js: -------------------------------------------------------------------------------- 1 | // Get DOM elements to interact with. 2 | const fetchButton = document.querySelector("#fetch-button") 3 | const resetButton = document.querySelector("#reset-button") 4 | const barFill = document.querySelector("#bar-fill") 5 | const urlToFetch = document.querySelector("#url-to-fetch") 6 | 7 | // Called when `fetch` is clicked. 8 | const fetchURL = async () => { 9 | // Reset the bar to empty. 10 | barFill.style.width = 0 11 | barFill.style.background = "red" 12 | 13 | // Get the URL. 14 | const url = urlToFetch.value 15 | 16 | // Fetch! 17 | fetch(url).then(response => { 18 | // `response` is a stream! 19 | const reader = response.body.getReader() 20 | 21 | // Find out how big the response is. 22 | const length = response.headers.get("Content-Length") 23 | 24 | // Initialize how much we've received. Nothing so far. 25 | let received = 0 26 | 27 | // What happens when the stream delivers a chunk? 28 | const onReadChunk = chunk => { 29 | // Each chunk has a `done` property. If it's done, 30 | if (chunk.done) { 31 | // Update the UI so people know it's done. 32 | document.body.style.backgroundImage = `url(${url})` 33 | barFill.style.background = "green" 34 | resetButton.style.display = "block" 35 | return 36 | } 37 | 38 | // If it's not done, increment the received variable, and the bar's fill. 39 | received += chunk.value.length 40 | barFill.style.width = `${received / length * 100}%` 41 | 42 | // Keep reading, and keep doing this AS LONG AS IT'S NOT DONE. 43 | reader.read().then(onReadChunk) 44 | } 45 | 46 | // Do the first read(). 47 | reader.read().then(onReadChunk) 48 | }) 49 | } 50 | 51 | // Make the fetch button clickable. 52 | fetchButton.addEventListener("click", () => fetchURL()) 53 | 54 | // Make the click-to-copy links copiable. 55 | document.querySelectorAll(".click-to-copy").forEach(element => 56 | element.addEventListener("click", e => { 57 | e.target.select() 58 | document.execCommand("copy") 59 | document.querySelector(".data-copied").style.visibility = "visible" 60 | }) 61 | ) 62 | 63 | // Reset is essential 64 | resetButton.addEventListener("click", () => location.reload()) 65 | urlToFetch.addEventListener("click", e => (e.target.value = "")) 66 | -------------------------------------------------------------------------------- /examples/streaming-pwa/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/streaming-pwa/README.md: -------------------------------------------------------------------------------- 1 | # Basically, [_(Really)_ Progressive Web App](https://server-stjntslidj.now.sh)s 2 | ![Lighthouse Report](https://tejasq.github.io/basically-streams/examples/streaming-pwa/lighthouse-report.png) 3 | 4 | This project contains a [Progressive Web App](https://en.wikipedia.org/wiki/Progressive_web_app) ([see demo](https://server-stjntslidj.now.sh)) (PWA from this point on), that: 5 | 6 | - Caches data, and 7 | - Works offline 8 | 9 | Both of these things are pretty much basic prerequisites to call something a PWA. However, this project has some extra bells and whistles that make it _really_ progressive. It: 10 | 11 | - Uses [ReactDOM](https://reactjs.org/docs/react-dom.html)'s streaming renderer on the server to provide streaming HTML responses that leverage the browser's streaming HTML parser 🔥 12 | - Uses its serviceWorker to intercept requests for data and returns more streaming HTML when a request is made. 🔥 13 | 14 | This makes the entire application a _truly streamed_ application that delivers data incredibly fast (fastest I've seen). 15 | 16 | ## The Project 17 | The project itself is fairly trivial: it is a simple book search, that queries [Google's Public Books API](https://developers.google.com/books/) and returns books that you search for. The interesting part is that it returns streams, that are then processed in the client. 18 | 19 | The even more interesting part is that if the repsonses are cached, the service worker returns the stream instead, creating a `1-4ms` request-response cycle for all of this data. This is pretty cool. 😄 20 | 21 | ## Getting Started 22 | 23 | There's a [live demo](https://server-stjntslidj.now.sh) hosted on [now](https://now.sh/) that you can check out. If you'd like to run it locally, it's as simple as: 24 | 25 | - `git clone git@github.com:TejasQ/basically-streams.git` 26 | - `cd basically-streams/examples/streaming-pwa` 27 | - `yarn install` 28 | - `yarn start` 29 | 30 | The app will be accessible on `http://localhost:3000/` 31 | 32 | ## Why? 33 | 34 | This is part of my _Basically_ series that seeks to make concepts that could be scary to newer developers accessible and easy to grasp, in order to empower, enable, and inspire developers to create beautiful things all over the internet. 35 | 36 | I hope this helped you. :) 37 | 38 | ## Context 39 | 40 | This project is but a _small_ part of a larger project called [_Basically,_ Streams](https://github.com/TejasQ/basically-streams). If this interests you, be sure to check out the other examples and documentation! 😄 41 | 42 | ## PS: 43 | The 2 points I lost on the [Lighthouse Report](https://developers.google.com/web/tools/lighthouse/) above are because the CSS and JS are large and well documented. A trade-off I am happy to make. Go check out the source code! 44 | -------------------------------------------------------------------------------- /examples/streaming-pwa/components/App.jsx: -------------------------------------------------------------------------------- 1 | // Literally just to server render and deliver to the client side. 2 | import React from "react" 3 | 4 | const App = () => ( 5 |
6 |
7 |

🕵️ ‍📖

8 |
9 |
10 | 13 | 14 |
15 | 16 |
17 |
18 |
19 |

Oops

20 |

Your current browser doesn't support streaming :(

21 |
22 |
23 |
24 | ) 25 | 26 | export default App 27 | -------------------------------------------------------------------------------- /examples/streaming-pwa/components/Book/Book.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | const Book = ({ image, link, title, subtitle, author, description }) => ( 4 |
5 |
6 | {title} 7 |
8 |
9 |

10 | 11 | {title} {subtitle && `— ${subtitle}`} 12 | 13 |

14 |

{author}

15 |

{`${description ? description.slice(0, 550) : "No description"}...`}

16 |
17 |
18 | ) 19 | 20 | export default Book 21 | -------------------------------------------------------------------------------- /examples/streaming-pwa/components/Results/Results.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import Book from "../Book/Book" 4 | 5 | const Results = ({ results }) => ( 6 |
7 | {results.map(result => ( 8 | 16 | ))} 17 |
18 | ) 19 | 20 | export default Results 21 | -------------------------------------------------------------------------------- /examples/streaming-pwa/css/main.css: -------------------------------------------------------------------------------- 1 | *{box-sizing:border-box}body,html{margin:0;padding:0;font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased}main{width:100vw;height:100vh;padding:0 16px;text-align:center}h1{margin:0;font-size:100px}h2{margin:0}h3{margin:0;color:#999;font-size:16px;font-weight:600}h2 + h3{margin-top:8px}p{font-size:14px}.search-field{display:flex;align-items:flex-end;justify-content:center;margin-top:20px}.input-container{width:50%;text-align:left}button,input{padding:8px;border-radius:2px;font:inherit;font-size:18px}input{width:100%;border:1px solid #ddd}a:link,a:visited{text-decoration:none;color:#2660a4}a:hover{text-decoration:underline;color:#284b63}button{margin-left:16px;border:0;transition:0.15s background ease;background:#284b63;color:#f4f9e9;cursor:pointer;-webkit-appearance:none;margin-bottom:1px}button:hover{background:#2660a4}.search{position:fixed;left:0;right:0;padding:70px 16px 26px;background:rgba(255, 255, 255, 0.98)}.search-field .label{display:block;font-size:10px;font-weight:600;margin-bottom:8px}.results-container{max-width:748px;margin:0 auto;padding:330px 0 40px}.book{display:flex;align-items:center;border-radius:7px;box-shadow:0 5px 20px 0 rgba(0, 0, 0, 0.1)}.book + .book{margin-top:32px}.book__image{flex:1 1 25%}.book__image > img{width:100%}.book__info{flex:1 1 75%;padding:26px;text-align:left}#error{align-items:center;justify-content:center;flex-direction:column;display:flex;padding-top:330px}#error.hidden{display:none} -------------------------------------------------------------------------------- /examples/streaming-pwa/css/main.src.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, 10 | "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | } 14 | 15 | main { 16 | width: 100vw; 17 | height: 100vh; 18 | padding: 0 16px; 19 | text-align: center; 20 | } 21 | 22 | h1 { 23 | margin: 0; 24 | font-size: 100px; 25 | } 26 | 27 | h2 { 28 | margin: 0; 29 | } 30 | 31 | h3 { 32 | margin: 0; 33 | color: #999; 34 | font-size: 16px; 35 | font-weight: 600; 36 | } 37 | 38 | h2 + h3 { 39 | margin-top: 8px; 40 | } 41 | 42 | p { 43 | font-size: 14px; 44 | } 45 | 46 | .search-field { 47 | display: flex; 48 | align-items: flex-end; 49 | justify-content: center; 50 | margin-top: 20px; 51 | } 52 | 53 | .input-container { 54 | width: 50%; 55 | text-align: left; 56 | } 57 | 58 | input, 59 | button { 60 | padding: 8px; 61 | border-radius: 2px; 62 | font: inherit; 63 | font-size: 18px; 64 | } 65 | 66 | input { 67 | width: 100%; 68 | border: 1px solid #ddd; 69 | } 70 | 71 | a:link, 72 | a:visited { 73 | text-decoration: none; 74 | color: #2660a4; 75 | } 76 | 77 | a:hover { 78 | text-decoration: underline; 79 | color: #284b63; 80 | } 81 | 82 | button { 83 | margin-left: 16px; 84 | border: 0; 85 | transition: 0.15s background ease; 86 | background: #284b63; 87 | color: #f4f9e9; 88 | cursor: pointer; 89 | -webkit-appearance: none; 90 | margin-bottom: 1px; /* because the input next to it has a border :/ */ 91 | } 92 | 93 | button:hover { 94 | background: #2660a4; 95 | } 96 | 97 | .search { 98 | position: fixed; 99 | left: 0; 100 | right: 0; 101 | padding: 70px 16px 26px; 102 | background: rgba(255, 255, 255, 0.98); 103 | } 104 | 105 | .search-field .label { 106 | display: block; 107 | font-size: 10px; 108 | font-weight: 600; 109 | margin-bottom: 8px; 110 | } 111 | 112 | .results-container { 113 | max-width: 748px; 114 | margin: 0 auto; 115 | padding: 330px 0 40px; 116 | } 117 | 118 | .book { 119 | display: flex; 120 | align-items: center; 121 | border-radius: 7px; 122 | box-shadow: 0 5px 20px 0px rgba(0, 0, 0, 0.1); 123 | } 124 | 125 | .book + .book { 126 | margin-top: 32px; 127 | } 128 | 129 | .book__image { 130 | flex: 1 1 25%; 131 | } 132 | 133 | .book__image > img { 134 | width: 100%; 135 | } 136 | 137 | .book__info { 138 | flex: 1 1 75%; 139 | padding: 26px; 140 | text-align: left; 141 | } 142 | 143 | #error { 144 | align-items: center; 145 | justify-content: center; 146 | flex-direction: column; 147 | display: flex; 148 | padding-top: 330px; 149 | } 150 | 151 | #error.hidden { 152 | display: none; 153 | } 154 | -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-128x128.png -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-144x144.png -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-152x152.png -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-192x192.png -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-384x384.png -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-512x512.png -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-72x72.png -------------------------------------------------------------------------------- /examples/streaming-pwa/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/icons/icon-96x96.png -------------------------------------------------------------------------------- /examples/streaming-pwa/js/index.js: -------------------------------------------------------------------------------- 1 | // Static JS file. 2 | 3 | // First up, get DOM things we'll interact with. 4 | const searchField = document.querySelector("#search-field") 5 | const searchButton = document.querySelector("#search-button") 6 | const resultsContainer = document.querySelector("#results") 7 | const errorMessage = document.querySelector("#error") 8 | 9 | // Check if the browser doesn't support streaming 10 | if( typeof ReadableStream == 'undefined' || 11 | typeof WritableStream == 'undefined' ){ 12 | // If it doesn't, then: 13 | 14 | // Alert the user by showing an error message 15 | errorMessage.classList.remove("hidden") 16 | 17 | // Stop execution 18 | throw new Error("Browser does not support streaming") 19 | } 20 | 21 | // A function to get our books. 22 | const getBooks = () => { 23 | 24 | // Reset the results container. 25 | resultsContainer.innerHTML = "" 26 | 27 | // Make a note of the start time. 28 | const start = performance.now() 29 | 30 | // Fetch stuff. We use toLowerCase() to get a cached response if the text is the same but with different case. 31 | fetch(`/books/${searchField.value.toLowerCase()}`).then(response => { 32 | 33 | // Make a note of when we get the Response object. 34 | let firstResponse = performance.now() 35 | console.log(`Done! 🔥 Took ${firstResponse - start}ms`) 36 | 37 | // Pipe its stream to a new WritableStream, that 38 | response.body.pipeTo( 39 | new WritableStream({ 40 | 41 | // on write, updates the DOM with decoded HTML. 42 | write(piece) { 43 | const decoder = new TextDecoder() 44 | resultsContainer.innerHTML += decoder.decode(piece, { stream: true }) 45 | }, 46 | 47 | // When its finished, 48 | close() { 49 | 50 | // Make a note of how long it took from getting the stream, to rendering it. 51 | console.log(`Streaming done! 🔥 ${performance.now() - firstResponse}ms after request.`) 52 | }, 53 | }), 54 | ) 55 | }) 56 | } 57 | 58 | // On click, or on Enter, find books! 59 | searchButton.addEventListener("click", () => getBooks()) 60 | document.addEventListener("keyup", e => e.keyCode === 13 && getBooks()) 61 | 62 | // Set up our serviceWorker for offline things and faster streaming. 63 | if ("serviceWorker" in navigator) { 64 | navigator.serviceWorker 65 | .register("../sw.js") 66 | .then(() => console.log("serviceWorker registered. 😉")) 67 | .catch(error => console.error(`serviceWorker Registration failed with ${error} ☹️`)) 68 | } 69 | -------------------------------------------------------------------------------- /examples/streaming-pwa/js/template.js: -------------------------------------------------------------------------------- 1 | // Literally just HTML templates. 2 | export const startPage = ` 3 | 4 | 5 | Book search, y'all! 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ` 14 | 15 | export const endPage = ` 16 | 17 | 18 | ` 19 | -------------------------------------------------------------------------------- /examples/streaming-pwa/lighthouse-report.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TejasQ/basically-streams/75c12033f10fb85128b03cae7883cb4b9e7d7cb9/examples/streaming-pwa/lighthouse-report.png -------------------------------------------------------------------------------- /examples/streaming-pwa/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Book Detective", 3 | "short_name": "BookD", 4 | "start_url": "/", 5 | "background_color": "#ffffff", 6 | "theme_color": "#ffffff", 7 | "display": "fullscreen", 8 | "icons": [ 9 | { 10 | "src": "icons/icon-72x72.png", 11 | "sizes": "72x72", 12 | "type": "image/png" 13 | }, 14 | { 15 | "src": "icons/icon-96x96.png", 16 | "sizes": "96x96", 17 | "type": "image/png" 18 | }, 19 | { 20 | "src": "icons/icon-128x128.png", 21 | "sizes": "128x128", 22 | "type": "image/png" 23 | }, 24 | { 25 | "src": "icons/icon-144x144.png", 26 | "sizes": "144x144", 27 | "type": "image/png" 28 | }, 29 | { 30 | "src": "icons/icon-152x152.png", 31 | "sizes": "152x152", 32 | "type": "image/png" 33 | }, 34 | { 35 | "src": "icons/icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image/png" 38 | }, 39 | { 40 | "src": "icons/icon-384x384.png", 41 | "sizes": "384x384", 42 | "type": "image/png" 43 | }, 44 | { 45 | "src": "icons/icon-512x512.png", 46 | "sizes": "512x512", 47 | "type": "image/png" 48 | } 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /examples/streaming-pwa/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "start": "babel-node serve.js" 8 | }, 9 | "dependencies": { 10 | "babel-cli": "^6.26.0", 11 | "express": "^4.16.2", 12 | "node-fetch": "^1.7.3", 13 | "react": "^16.0.0", 14 | "react-dom": "^16.0.1" 15 | }, 16 | "devDependencies": { 17 | "babel-preset-env": "^1.6.1", 18 | "babel-preset-react": "^6.24.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/streaming-pwa/serve.js: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch" 2 | import express from "express" 3 | import React from "react" 4 | import { renderToStaticNodeStream } from "react-dom/server" 5 | 6 | import { startPage, endPage } from "./js/template" 7 | 8 | import App from "./components/App" 9 | import Results from "./components/Results/Results" 10 | 11 | const app = express() 12 | 13 | // Static things. 14 | app.use("/manifest.json", express.static("manifest.json")) 15 | app.use("/sw.js", express.static("sw.js")) 16 | app.use("/js", express.static("js")) 17 | app.use("/css", express.static("css")) 18 | app.use("/icons", express.static("icons")) 19 | 20 | // Our books endpoint. 21 | app.get("/books/:book", async (req, res) => { 22 | 23 | // It's HTML! 24 | res.set("Content-Type", "text/html") 25 | 26 | // Get our results and shape them. 27 | const results = await fetch( 28 | `https://www.googleapis.com/books/v1/volumes?q=title:${req.params.book}`, 29 | ) 30 | .then(r => r.json()) 31 | .then(r => r.items.map(item => item.volumeInfo)) 32 | 33 | // Create a stream from this component, and 34 | const stream = renderToStaticNodeStream() 35 | 36 | // Pipe it into the response. 37 | stream.pipe(res) 38 | }) 39 | 40 | // index.html 41 | app.use("/", (req, res) => { 42 | 43 | // We're serving HTML. 44 | res.set("Content-Type", "text/html") 45 | 46 | // Write just the first bit. 47 | res.write(startPage) 48 | 49 | // Create a stream from the HTML of this component 50 | const stream = renderToStaticNodeStream() 51 | 52 | /* 53 | Pipe it into the response. 54 | { end: false } doesn't close the stream when its finished 55 | so we can ourselves. 56 | */ 57 | stream.pipe(res, { end: false }) 58 | 59 | stream.on("end", () => { 60 | 61 | // We do that here. 62 | res.write(endPage) 63 | res.end() 64 | }) 65 | }) 66 | 67 | // Start! 68 | app.listen(3000, () => console.log("You can open the app at http://localhost:3000/ 😄")) 69 | -------------------------------------------------------------------------------- /examples/streaming-pwa/sw.js: -------------------------------------------------------------------------------- 1 | // Make a list of things to cache. 2 | const thingsToCache = ["/", "/manifest.json", "/index.html", "/css/main.css", "/js/index.js"] 3 | 4 | // Let's name our active cache. 5 | const activeCache = "myApp" 6 | 7 | // This function fetches a request and adds its response to the cache for later. :D 8 | const fetchAndCache = request => 9 | fetch(request).then(response => { 10 | caches.open(activeCache).then(cache => { 11 | cache.put(request, response) 12 | }) 13 | 14 | return response.clone() 15 | }) 16 | 17 | /* 18 | This function checks the cache for a cached response. 19 | If there isn't one, it fetches and then caches. :D 20 | */ 21 | const responseFromCacheOrFetchAndCache = request => 22 | caches.match(request).then(cachedItem => cachedItem || fetchAndCache(request)) 23 | 24 | // Here's what makes our app nice and fast 25 | const makeBookStream = request => { 26 | 27 | // Let's create a stream. 28 | const stream = new ReadableStream({ 29 | 30 | // On start, 31 | start(controller) { 32 | 33 | // This function processes a ReadableStream (from Response.body) 34 | const processStream = body => { 35 | 36 | // no Response.body stream available 37 | if (!body) return controller.close() 38 | 39 | // Get the reader. 40 | const reader = body.getReader() 41 | 42 | /* 43 | This function is called everytime the reader reads. 44 | It enqueues a chunk and then calls itself. 45 | This is a concept called recursion. Also, it's fun. :D 46 | */ 47 | const processChunk = chunk => { 48 | if (chunk.done) return controller.close() 49 | controller.enqueue(chunk.value) 50 | return reader.read().then(processChunk) 51 | } 52 | reader.read().then(processChunk) 53 | } 54 | 55 | // This kicks things off! 56 | responseFromCacheOrFetchAndCache(request).then(response => processStream(response.body)) 57 | }, 58 | }) 59 | 60 | return stream 61 | } 62 | 63 | 64 | // On install, cache all the things! 65 | this.addEventListener("install", event => 66 | event.waitUntil(caches.open(activeCache).then(cache => cache.addAll(thingsToCache))), 67 | ) 68 | 69 | // On fetch, 70 | this.addEventListener("fetch", async event => { 71 | const { request } = event 72 | 73 | // If we're looking for books, 74 | if (request.url.indexOf("/books/") > -1) { 75 | const headers = new Headers({ "Content-Type": "text/html" }) 76 | const response = new Response(makeBookStream(request), headers) 77 | 78 | // Respond with a HTML stream 79 | return event.respondWith(response) 80 | } 81 | 82 | // If we're looking for something else (css/js/etc.), get it from the cache, or fetch it. 83 | return event.respondWith(responseFromCacheOrFetchAndCache(request)) 84 | }) 85 | -------------------------------------------------------------------------------- /examples/streaming-pwa/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | abbrev@1: 6 | version "1.1.1" 7 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" 8 | 9 | accepts@~1.3.4: 10 | version "1.3.4" 11 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.4.tgz#86246758c7dd6d21a6474ff084a4740ec05eb21f" 12 | dependencies: 13 | mime-types "~2.1.16" 14 | negotiator "0.6.1" 15 | 16 | ajv@^4.9.1: 17 | version "4.11.8" 18 | resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" 19 | dependencies: 20 | co "^4.6.0" 21 | json-stable-stringify "^1.0.1" 22 | 23 | ansi-regex@^2.0.0: 24 | version "2.1.1" 25 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 26 | 27 | ansi-styles@^2.2.1: 28 | version "2.2.1" 29 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 30 | 31 | anymatch@^1.3.0: 32 | version "1.3.2" 33 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" 34 | dependencies: 35 | micromatch "^2.1.5" 36 | normalize-path "^2.0.0" 37 | 38 | aproba@^1.0.3: 39 | version "1.2.0" 40 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" 41 | 42 | are-we-there-yet@~1.1.2: 43 | version "1.1.4" 44 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" 45 | dependencies: 46 | delegates "^1.0.0" 47 | readable-stream "^2.0.6" 48 | 49 | arr-diff@^2.0.0: 50 | version "2.0.0" 51 | resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" 52 | dependencies: 53 | arr-flatten "^1.0.1" 54 | 55 | arr-flatten@^1.0.1: 56 | version "1.1.0" 57 | resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" 58 | 59 | array-flatten@1.1.1: 60 | version "1.1.1" 61 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 62 | 63 | array-unique@^0.2.1: 64 | version "0.2.1" 65 | resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" 66 | 67 | asap@~2.0.3: 68 | version "2.0.6" 69 | resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" 70 | 71 | asn1@~0.2.3: 72 | version "0.2.4" 73 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" 74 | dependencies: 75 | safer-buffer "~2.1.0" 76 | 77 | assert-plus@1.0.0, assert-plus@^1.0.0: 78 | version "1.0.0" 79 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 80 | 81 | assert-plus@^0.2.0: 82 | version "0.2.0" 83 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 84 | 85 | async-each@^1.0.0: 86 | version "1.0.1" 87 | resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" 88 | 89 | asynckit@^0.4.0: 90 | version "0.4.0" 91 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 92 | 93 | aws-sign2@~0.6.0: 94 | version "0.6.0" 95 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 96 | 97 | aws4@^1.2.1: 98 | version "1.6.0" 99 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" 100 | 101 | babel-cli@^6.26.0: 102 | version "6.26.0" 103 | resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1" 104 | dependencies: 105 | babel-core "^6.26.0" 106 | babel-polyfill "^6.26.0" 107 | babel-register "^6.26.0" 108 | babel-runtime "^6.26.0" 109 | commander "^2.11.0" 110 | convert-source-map "^1.5.0" 111 | fs-readdir-recursive "^1.0.0" 112 | glob "^7.1.2" 113 | lodash "^4.17.4" 114 | output-file-sync "^1.1.2" 115 | path-is-absolute "^1.0.1" 116 | slash "^1.0.0" 117 | source-map "^0.5.6" 118 | v8flags "^2.1.1" 119 | optionalDependencies: 120 | chokidar "^1.6.1" 121 | 122 | babel-code-frame@^6.26.0: 123 | version "6.26.0" 124 | resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" 125 | dependencies: 126 | chalk "^1.1.3" 127 | esutils "^2.0.2" 128 | js-tokens "^3.0.2" 129 | 130 | babel-core@^6.26.0: 131 | version "6.26.0" 132 | resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" 133 | dependencies: 134 | babel-code-frame "^6.26.0" 135 | babel-generator "^6.26.0" 136 | babel-helpers "^6.24.1" 137 | babel-messages "^6.23.0" 138 | babel-register "^6.26.0" 139 | babel-runtime "^6.26.0" 140 | babel-template "^6.26.0" 141 | babel-traverse "^6.26.0" 142 | babel-types "^6.26.0" 143 | babylon "^6.18.0" 144 | convert-source-map "^1.5.0" 145 | debug "^2.6.8" 146 | json5 "^0.5.1" 147 | lodash "^4.17.4" 148 | minimatch "^3.0.4" 149 | path-is-absolute "^1.0.1" 150 | private "^0.1.7" 151 | slash "^1.0.0" 152 | source-map "^0.5.6" 153 | 154 | babel-generator@^6.26.0: 155 | version "6.26.0" 156 | resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" 157 | dependencies: 158 | babel-messages "^6.23.0" 159 | babel-runtime "^6.26.0" 160 | babel-types "^6.26.0" 161 | detect-indent "^4.0.0" 162 | jsesc "^1.3.0" 163 | lodash "^4.17.4" 164 | source-map "^0.5.6" 165 | trim-right "^1.0.1" 166 | 167 | babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: 168 | version "6.24.1" 169 | resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" 170 | dependencies: 171 | babel-helper-explode-assignable-expression "^6.24.1" 172 | babel-runtime "^6.22.0" 173 | babel-types "^6.24.1" 174 | 175 | babel-helper-builder-react-jsx@^6.24.1: 176 | version "6.26.0" 177 | resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" 178 | dependencies: 179 | babel-runtime "^6.26.0" 180 | babel-types "^6.26.0" 181 | esutils "^2.0.2" 182 | 183 | babel-helper-call-delegate@^6.24.1: 184 | version "6.24.1" 185 | resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" 186 | dependencies: 187 | babel-helper-hoist-variables "^6.24.1" 188 | babel-runtime "^6.22.0" 189 | babel-traverse "^6.24.1" 190 | babel-types "^6.24.1" 191 | 192 | babel-helper-define-map@^6.24.1: 193 | version "6.26.0" 194 | resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" 195 | dependencies: 196 | babel-helper-function-name "^6.24.1" 197 | babel-runtime "^6.26.0" 198 | babel-types "^6.26.0" 199 | lodash "^4.17.4" 200 | 201 | babel-helper-explode-assignable-expression@^6.24.1: 202 | version "6.24.1" 203 | resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" 204 | dependencies: 205 | babel-runtime "^6.22.0" 206 | babel-traverse "^6.24.1" 207 | babel-types "^6.24.1" 208 | 209 | babel-helper-function-name@^6.24.1: 210 | version "6.24.1" 211 | resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" 212 | dependencies: 213 | babel-helper-get-function-arity "^6.24.1" 214 | babel-runtime "^6.22.0" 215 | babel-template "^6.24.1" 216 | babel-traverse "^6.24.1" 217 | babel-types "^6.24.1" 218 | 219 | babel-helper-get-function-arity@^6.24.1: 220 | version "6.24.1" 221 | resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" 222 | dependencies: 223 | babel-runtime "^6.22.0" 224 | babel-types "^6.24.1" 225 | 226 | babel-helper-hoist-variables@^6.24.1: 227 | version "6.24.1" 228 | resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" 229 | dependencies: 230 | babel-runtime "^6.22.0" 231 | babel-types "^6.24.1" 232 | 233 | babel-helper-optimise-call-expression@^6.24.1: 234 | version "6.24.1" 235 | resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" 236 | dependencies: 237 | babel-runtime "^6.22.0" 238 | babel-types "^6.24.1" 239 | 240 | babel-helper-regex@^6.24.1: 241 | version "6.26.0" 242 | resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" 243 | dependencies: 244 | babel-runtime "^6.26.0" 245 | babel-types "^6.26.0" 246 | lodash "^4.17.4" 247 | 248 | babel-helper-remap-async-to-generator@^6.24.1: 249 | version "6.24.1" 250 | resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" 251 | dependencies: 252 | babel-helper-function-name "^6.24.1" 253 | babel-runtime "^6.22.0" 254 | babel-template "^6.24.1" 255 | babel-traverse "^6.24.1" 256 | babel-types "^6.24.1" 257 | 258 | babel-helper-replace-supers@^6.24.1: 259 | version "6.24.1" 260 | resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" 261 | dependencies: 262 | babel-helper-optimise-call-expression "^6.24.1" 263 | babel-messages "^6.23.0" 264 | babel-runtime "^6.22.0" 265 | babel-template "^6.24.1" 266 | babel-traverse "^6.24.1" 267 | babel-types "^6.24.1" 268 | 269 | babel-helpers@^6.24.1: 270 | version "6.24.1" 271 | resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" 272 | dependencies: 273 | babel-runtime "^6.22.0" 274 | babel-template "^6.24.1" 275 | 276 | babel-messages@^6.23.0: 277 | version "6.23.0" 278 | resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" 279 | dependencies: 280 | babel-runtime "^6.22.0" 281 | 282 | babel-plugin-check-es2015-constants@^6.22.0: 283 | version "6.22.0" 284 | resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" 285 | dependencies: 286 | babel-runtime "^6.22.0" 287 | 288 | babel-plugin-syntax-async-functions@^6.8.0: 289 | version "6.13.0" 290 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" 291 | 292 | babel-plugin-syntax-exponentiation-operator@^6.8.0: 293 | version "6.13.0" 294 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" 295 | 296 | babel-plugin-syntax-flow@^6.18.0: 297 | version "6.18.0" 298 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" 299 | 300 | babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: 301 | version "6.18.0" 302 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" 303 | 304 | babel-plugin-syntax-trailing-function-commas@^6.22.0: 305 | version "6.22.0" 306 | resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" 307 | 308 | babel-plugin-transform-async-to-generator@^6.22.0: 309 | version "6.24.1" 310 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" 311 | dependencies: 312 | babel-helper-remap-async-to-generator "^6.24.1" 313 | babel-plugin-syntax-async-functions "^6.8.0" 314 | babel-runtime "^6.22.0" 315 | 316 | babel-plugin-transform-es2015-arrow-functions@^6.22.0: 317 | version "6.22.0" 318 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" 319 | dependencies: 320 | babel-runtime "^6.22.0" 321 | 322 | babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: 323 | version "6.22.0" 324 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" 325 | dependencies: 326 | babel-runtime "^6.22.0" 327 | 328 | babel-plugin-transform-es2015-block-scoping@^6.23.0: 329 | version "6.26.0" 330 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" 331 | dependencies: 332 | babel-runtime "^6.26.0" 333 | babel-template "^6.26.0" 334 | babel-traverse "^6.26.0" 335 | babel-types "^6.26.0" 336 | lodash "^4.17.4" 337 | 338 | babel-plugin-transform-es2015-classes@^6.23.0: 339 | version "6.24.1" 340 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" 341 | dependencies: 342 | babel-helper-define-map "^6.24.1" 343 | babel-helper-function-name "^6.24.1" 344 | babel-helper-optimise-call-expression "^6.24.1" 345 | babel-helper-replace-supers "^6.24.1" 346 | babel-messages "^6.23.0" 347 | babel-runtime "^6.22.0" 348 | babel-template "^6.24.1" 349 | babel-traverse "^6.24.1" 350 | babel-types "^6.24.1" 351 | 352 | babel-plugin-transform-es2015-computed-properties@^6.22.0: 353 | version "6.24.1" 354 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" 355 | dependencies: 356 | babel-runtime "^6.22.0" 357 | babel-template "^6.24.1" 358 | 359 | babel-plugin-transform-es2015-destructuring@^6.23.0: 360 | version "6.23.0" 361 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" 362 | dependencies: 363 | babel-runtime "^6.22.0" 364 | 365 | babel-plugin-transform-es2015-duplicate-keys@^6.22.0: 366 | version "6.24.1" 367 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" 368 | dependencies: 369 | babel-runtime "^6.22.0" 370 | babel-types "^6.24.1" 371 | 372 | babel-plugin-transform-es2015-for-of@^6.23.0: 373 | version "6.23.0" 374 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" 375 | dependencies: 376 | babel-runtime "^6.22.0" 377 | 378 | babel-plugin-transform-es2015-function-name@^6.22.0: 379 | version "6.24.1" 380 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" 381 | dependencies: 382 | babel-helper-function-name "^6.24.1" 383 | babel-runtime "^6.22.0" 384 | babel-types "^6.24.1" 385 | 386 | babel-plugin-transform-es2015-literals@^6.22.0: 387 | version "6.22.0" 388 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" 389 | dependencies: 390 | babel-runtime "^6.22.0" 391 | 392 | babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: 393 | version "6.24.1" 394 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" 395 | dependencies: 396 | babel-plugin-transform-es2015-modules-commonjs "^6.24.1" 397 | babel-runtime "^6.22.0" 398 | babel-template "^6.24.1" 399 | 400 | babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: 401 | version "6.26.0" 402 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz#0d8394029b7dc6abe1a97ef181e00758dd2e5d8a" 403 | dependencies: 404 | babel-plugin-transform-strict-mode "^6.24.1" 405 | babel-runtime "^6.26.0" 406 | babel-template "^6.26.0" 407 | babel-types "^6.26.0" 408 | 409 | babel-plugin-transform-es2015-modules-systemjs@^6.23.0: 410 | version "6.24.1" 411 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" 412 | dependencies: 413 | babel-helper-hoist-variables "^6.24.1" 414 | babel-runtime "^6.22.0" 415 | babel-template "^6.24.1" 416 | 417 | babel-plugin-transform-es2015-modules-umd@^6.23.0: 418 | version "6.24.1" 419 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" 420 | dependencies: 421 | babel-plugin-transform-es2015-modules-amd "^6.24.1" 422 | babel-runtime "^6.22.0" 423 | babel-template "^6.24.1" 424 | 425 | babel-plugin-transform-es2015-object-super@^6.22.0: 426 | version "6.24.1" 427 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" 428 | dependencies: 429 | babel-helper-replace-supers "^6.24.1" 430 | babel-runtime "^6.22.0" 431 | 432 | babel-plugin-transform-es2015-parameters@^6.23.0: 433 | version "6.24.1" 434 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" 435 | dependencies: 436 | babel-helper-call-delegate "^6.24.1" 437 | babel-helper-get-function-arity "^6.24.1" 438 | babel-runtime "^6.22.0" 439 | babel-template "^6.24.1" 440 | babel-traverse "^6.24.1" 441 | babel-types "^6.24.1" 442 | 443 | babel-plugin-transform-es2015-shorthand-properties@^6.22.0: 444 | version "6.24.1" 445 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" 446 | dependencies: 447 | babel-runtime "^6.22.0" 448 | babel-types "^6.24.1" 449 | 450 | babel-plugin-transform-es2015-spread@^6.22.0: 451 | version "6.22.0" 452 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" 453 | dependencies: 454 | babel-runtime "^6.22.0" 455 | 456 | babel-plugin-transform-es2015-sticky-regex@^6.22.0: 457 | version "6.24.1" 458 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" 459 | dependencies: 460 | babel-helper-regex "^6.24.1" 461 | babel-runtime "^6.22.0" 462 | babel-types "^6.24.1" 463 | 464 | babel-plugin-transform-es2015-template-literals@^6.22.0: 465 | version "6.22.0" 466 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" 467 | dependencies: 468 | babel-runtime "^6.22.0" 469 | 470 | babel-plugin-transform-es2015-typeof-symbol@^6.23.0: 471 | version "6.23.0" 472 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" 473 | dependencies: 474 | babel-runtime "^6.22.0" 475 | 476 | babel-plugin-transform-es2015-unicode-regex@^6.22.0: 477 | version "6.24.1" 478 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" 479 | dependencies: 480 | babel-helper-regex "^6.24.1" 481 | babel-runtime "^6.22.0" 482 | regexpu-core "^2.0.0" 483 | 484 | babel-plugin-transform-exponentiation-operator@^6.22.0: 485 | version "6.24.1" 486 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" 487 | dependencies: 488 | babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" 489 | babel-plugin-syntax-exponentiation-operator "^6.8.0" 490 | babel-runtime "^6.22.0" 491 | 492 | babel-plugin-transform-flow-strip-types@^6.22.0: 493 | version "6.22.0" 494 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" 495 | dependencies: 496 | babel-plugin-syntax-flow "^6.18.0" 497 | babel-runtime "^6.22.0" 498 | 499 | babel-plugin-transform-react-display-name@^6.23.0: 500 | version "6.25.0" 501 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" 502 | dependencies: 503 | babel-runtime "^6.22.0" 504 | 505 | babel-plugin-transform-react-jsx-self@^6.22.0: 506 | version "6.22.0" 507 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" 508 | dependencies: 509 | babel-plugin-syntax-jsx "^6.8.0" 510 | babel-runtime "^6.22.0" 511 | 512 | babel-plugin-transform-react-jsx-source@^6.22.0: 513 | version "6.22.0" 514 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" 515 | dependencies: 516 | babel-plugin-syntax-jsx "^6.8.0" 517 | babel-runtime "^6.22.0" 518 | 519 | babel-plugin-transform-react-jsx@^6.24.1: 520 | version "6.24.1" 521 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" 522 | dependencies: 523 | babel-helper-builder-react-jsx "^6.24.1" 524 | babel-plugin-syntax-jsx "^6.8.0" 525 | babel-runtime "^6.22.0" 526 | 527 | babel-plugin-transform-regenerator@^6.22.0: 528 | version "6.26.0" 529 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" 530 | dependencies: 531 | regenerator-transform "^0.10.0" 532 | 533 | babel-plugin-transform-strict-mode@^6.24.1: 534 | version "6.24.1" 535 | resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" 536 | dependencies: 537 | babel-runtime "^6.22.0" 538 | babel-types "^6.24.1" 539 | 540 | babel-polyfill@^6.26.0: 541 | version "6.26.0" 542 | resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" 543 | dependencies: 544 | babel-runtime "^6.26.0" 545 | core-js "^2.5.0" 546 | regenerator-runtime "^0.10.5" 547 | 548 | babel-preset-env@^1.6.1: 549 | version "1.6.1" 550 | resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.1.tgz#a18b564cc9b9afdf4aae57ae3c1b0d99188e6f48" 551 | dependencies: 552 | babel-plugin-check-es2015-constants "^6.22.0" 553 | babel-plugin-syntax-trailing-function-commas "^6.22.0" 554 | babel-plugin-transform-async-to-generator "^6.22.0" 555 | babel-plugin-transform-es2015-arrow-functions "^6.22.0" 556 | babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" 557 | babel-plugin-transform-es2015-block-scoping "^6.23.0" 558 | babel-plugin-transform-es2015-classes "^6.23.0" 559 | babel-plugin-transform-es2015-computed-properties "^6.22.0" 560 | babel-plugin-transform-es2015-destructuring "^6.23.0" 561 | babel-plugin-transform-es2015-duplicate-keys "^6.22.0" 562 | babel-plugin-transform-es2015-for-of "^6.23.0" 563 | babel-plugin-transform-es2015-function-name "^6.22.0" 564 | babel-plugin-transform-es2015-literals "^6.22.0" 565 | babel-plugin-transform-es2015-modules-amd "^6.22.0" 566 | babel-plugin-transform-es2015-modules-commonjs "^6.23.0" 567 | babel-plugin-transform-es2015-modules-systemjs "^6.23.0" 568 | babel-plugin-transform-es2015-modules-umd "^6.23.0" 569 | babel-plugin-transform-es2015-object-super "^6.22.0" 570 | babel-plugin-transform-es2015-parameters "^6.23.0" 571 | babel-plugin-transform-es2015-shorthand-properties "^6.22.0" 572 | babel-plugin-transform-es2015-spread "^6.22.0" 573 | babel-plugin-transform-es2015-sticky-regex "^6.22.0" 574 | babel-plugin-transform-es2015-template-literals "^6.22.0" 575 | babel-plugin-transform-es2015-typeof-symbol "^6.23.0" 576 | babel-plugin-transform-es2015-unicode-regex "^6.22.0" 577 | babel-plugin-transform-exponentiation-operator "^6.22.0" 578 | babel-plugin-transform-regenerator "^6.22.0" 579 | browserslist "^2.1.2" 580 | invariant "^2.2.2" 581 | semver "^5.3.0" 582 | 583 | babel-preset-flow@^6.23.0: 584 | version "6.23.0" 585 | resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" 586 | dependencies: 587 | babel-plugin-transform-flow-strip-types "^6.22.0" 588 | 589 | babel-preset-react@^6.24.1: 590 | version "6.24.1" 591 | resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" 592 | dependencies: 593 | babel-plugin-syntax-jsx "^6.3.13" 594 | babel-plugin-transform-react-display-name "^6.23.0" 595 | babel-plugin-transform-react-jsx "^6.24.1" 596 | babel-plugin-transform-react-jsx-self "^6.22.0" 597 | babel-plugin-transform-react-jsx-source "^6.22.0" 598 | babel-preset-flow "^6.23.0" 599 | 600 | babel-register@^6.26.0: 601 | version "6.26.0" 602 | resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" 603 | dependencies: 604 | babel-core "^6.26.0" 605 | babel-runtime "^6.26.0" 606 | core-js "^2.5.0" 607 | home-or-tmp "^2.0.0" 608 | lodash "^4.17.4" 609 | mkdirp "^0.5.1" 610 | source-map-support "^0.4.15" 611 | 612 | babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: 613 | version "6.26.0" 614 | resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" 615 | dependencies: 616 | core-js "^2.4.0" 617 | regenerator-runtime "^0.11.0" 618 | 619 | babel-template@^6.24.1, babel-template@^6.26.0: 620 | version "6.26.0" 621 | resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" 622 | dependencies: 623 | babel-runtime "^6.26.0" 624 | babel-traverse "^6.26.0" 625 | babel-types "^6.26.0" 626 | babylon "^6.18.0" 627 | lodash "^4.17.4" 628 | 629 | babel-traverse@^6.24.1, babel-traverse@^6.26.0: 630 | version "6.26.0" 631 | resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" 632 | dependencies: 633 | babel-code-frame "^6.26.0" 634 | babel-messages "^6.23.0" 635 | babel-runtime "^6.26.0" 636 | babel-types "^6.26.0" 637 | babylon "^6.18.0" 638 | debug "^2.6.8" 639 | globals "^9.18.0" 640 | invariant "^2.2.2" 641 | lodash "^4.17.4" 642 | 643 | babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: 644 | version "6.26.0" 645 | resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" 646 | dependencies: 647 | babel-runtime "^6.26.0" 648 | esutils "^2.0.2" 649 | lodash "^4.17.4" 650 | to-fast-properties "^1.0.3" 651 | 652 | babylon@^6.18.0: 653 | version "6.18.0" 654 | resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" 655 | 656 | balanced-match@^1.0.0: 657 | version "1.0.0" 658 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 659 | 660 | bcrypt-pbkdf@^1.0.0: 661 | version "1.0.2" 662 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" 663 | dependencies: 664 | tweetnacl "^0.14.3" 665 | 666 | binary-extensions@^1.0.0: 667 | version "1.10.0" 668 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" 669 | 670 | block-stream@*: 671 | version "0.0.9" 672 | resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" 673 | dependencies: 674 | inherits "~2.0.0" 675 | 676 | body-parser@1.18.2: 677 | version "1.18.2" 678 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" 679 | dependencies: 680 | bytes "3.0.0" 681 | content-type "~1.0.4" 682 | debug "2.6.9" 683 | depd "~1.1.1" 684 | http-errors "~1.6.2" 685 | iconv-lite "0.4.19" 686 | on-finished "~2.3.0" 687 | qs "6.5.1" 688 | raw-body "2.3.2" 689 | type-is "~1.6.15" 690 | 691 | boom@2.x.x: 692 | version "2.10.1" 693 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 694 | dependencies: 695 | hoek "2.x.x" 696 | 697 | brace-expansion@^1.1.7: 698 | version "1.1.11" 699 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 700 | dependencies: 701 | balanced-match "^1.0.0" 702 | concat-map "0.0.1" 703 | 704 | braces@^1.8.2: 705 | version "1.8.5" 706 | resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" 707 | dependencies: 708 | expand-range "^1.8.1" 709 | preserve "^0.2.0" 710 | repeat-element "^1.1.2" 711 | 712 | browserslist@^2.1.2: 713 | version "2.6.1" 714 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.6.1.tgz#cc65a05ad6131ebda26f076f2822ba1bc826376b" 715 | dependencies: 716 | caniuse-lite "^1.0.30000755" 717 | electron-to-chromium "^1.3.27" 718 | 719 | bytes@3.0.0: 720 | version "3.0.0" 721 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 722 | 723 | caniuse-lite@^1.0.30000755: 724 | version "1.0.30000756" 725 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000756.tgz#3da701c1521b9fab87004c6de7c97fa47dbeaad2" 726 | 727 | caseless@~0.12.0: 728 | version "0.12.0" 729 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" 730 | 731 | chalk@^1.1.3: 732 | version "1.1.3" 733 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 734 | dependencies: 735 | ansi-styles "^2.2.1" 736 | escape-string-regexp "^1.0.2" 737 | has-ansi "^2.0.0" 738 | strip-ansi "^3.0.0" 739 | supports-color "^2.0.0" 740 | 741 | chokidar@^1.6.1: 742 | version "1.7.0" 743 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" 744 | dependencies: 745 | anymatch "^1.3.0" 746 | async-each "^1.0.0" 747 | glob-parent "^2.0.0" 748 | inherits "^2.0.1" 749 | is-binary-path "^1.0.0" 750 | is-glob "^2.0.0" 751 | path-is-absolute "^1.0.0" 752 | readdirp "^2.0.0" 753 | optionalDependencies: 754 | fsevents "^1.0.0" 755 | 756 | co@^4.6.0: 757 | version "4.6.0" 758 | resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" 759 | 760 | code-point-at@^1.0.0: 761 | version "1.1.0" 762 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" 763 | 764 | combined-stream@^1.0.5, combined-stream@~1.0.5: 765 | version "1.0.5" 766 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 767 | dependencies: 768 | delayed-stream "~1.0.0" 769 | 770 | commander@^2.11.0: 771 | version "2.11.0" 772 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" 773 | 774 | concat-map@0.0.1: 775 | version "0.0.1" 776 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 777 | 778 | console-control-strings@^1.0.0, console-control-strings@~1.1.0: 779 | version "1.1.0" 780 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" 781 | 782 | content-disposition@0.5.2: 783 | version "0.5.2" 784 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 785 | 786 | content-type@~1.0.4: 787 | version "1.0.4" 788 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 789 | 790 | convert-source-map@^1.5.0: 791 | version "1.5.0" 792 | resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" 793 | 794 | cookie-signature@1.0.6: 795 | version "1.0.6" 796 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 797 | 798 | cookie@0.3.1: 799 | version "0.3.1" 800 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 801 | 802 | core-js@^1.0.0: 803 | version "1.2.7" 804 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" 805 | 806 | core-js@^2.4.0, core-js@^2.5.0: 807 | version "2.5.1" 808 | resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" 809 | 810 | core-util-is@1.0.2, core-util-is@~1.0.0: 811 | version "1.0.2" 812 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" 813 | 814 | cryptiles@2.x.x: 815 | version "2.0.5" 816 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 817 | dependencies: 818 | boom "2.x.x" 819 | 820 | dashdash@^1.12.0: 821 | version "1.14.1" 822 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 823 | dependencies: 824 | assert-plus "^1.0.0" 825 | 826 | debug@2.6.9, debug@^2.2.0, debug@^2.6.8: 827 | version "2.6.9" 828 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 829 | dependencies: 830 | ms "2.0.0" 831 | 832 | deep-extend@~0.4.0: 833 | version "0.4.2" 834 | resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" 835 | 836 | delayed-stream@~1.0.0: 837 | version "1.0.0" 838 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 839 | 840 | delegates@^1.0.0: 841 | version "1.0.0" 842 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" 843 | 844 | depd@1.1.1, depd@~1.1.1: 845 | version "1.1.1" 846 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" 847 | 848 | destroy@~1.0.4: 849 | version "1.0.4" 850 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 851 | 852 | detect-indent@^4.0.0: 853 | version "4.0.0" 854 | resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" 855 | dependencies: 856 | repeating "^2.0.0" 857 | 858 | ecc-jsbn@~0.1.1: 859 | version "0.1.2" 860 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" 861 | dependencies: 862 | jsbn "~0.1.0" 863 | safer-buffer "^2.1.0" 864 | 865 | ee-first@1.1.1: 866 | version "1.1.1" 867 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 868 | 869 | electron-to-chromium@^1.3.27: 870 | version "1.3.27" 871 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.27.tgz#78ecb8a399066187bb374eede35d9c70565a803d" 872 | 873 | encodeurl@~1.0.1: 874 | version "1.0.1" 875 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.1.tgz#79e3d58655346909fe6f0f45a5de68103b294d20" 876 | 877 | encoding@^0.1.11: 878 | version "0.1.12" 879 | resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" 880 | dependencies: 881 | iconv-lite "~0.4.13" 882 | 883 | escape-html@~1.0.3: 884 | version "1.0.3" 885 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 886 | 887 | escape-string-regexp@^1.0.2: 888 | version "1.0.5" 889 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 890 | 891 | esutils@^2.0.2: 892 | version "2.0.2" 893 | resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" 894 | 895 | etag@~1.8.1: 896 | version "1.8.1" 897 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 898 | 899 | expand-brackets@^0.1.4: 900 | version "0.1.5" 901 | resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" 902 | dependencies: 903 | is-posix-bracket "^0.1.0" 904 | 905 | expand-range@^1.8.1: 906 | version "1.8.2" 907 | resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" 908 | dependencies: 909 | fill-range "^2.1.0" 910 | 911 | express@^4.16.2: 912 | version "4.16.2" 913 | resolved "https://registry.yarnpkg.com/express/-/express-4.16.2.tgz#e35c6dfe2d64b7dca0a5cd4f21781be3299e076c" 914 | dependencies: 915 | accepts "~1.3.4" 916 | array-flatten "1.1.1" 917 | body-parser "1.18.2" 918 | content-disposition "0.5.2" 919 | content-type "~1.0.4" 920 | cookie "0.3.1" 921 | cookie-signature "1.0.6" 922 | debug "2.6.9" 923 | depd "~1.1.1" 924 | encodeurl "~1.0.1" 925 | escape-html "~1.0.3" 926 | etag "~1.8.1" 927 | finalhandler "1.1.0" 928 | fresh "0.5.2" 929 | merge-descriptors "1.0.1" 930 | methods "~1.1.2" 931 | on-finished "~2.3.0" 932 | parseurl "~1.3.2" 933 | path-to-regexp "0.1.7" 934 | proxy-addr "~2.0.2" 935 | qs "6.5.1" 936 | range-parser "~1.2.0" 937 | safe-buffer "5.1.1" 938 | send "0.16.1" 939 | serve-static "1.13.1" 940 | setprototypeof "1.1.0" 941 | statuses "~1.3.1" 942 | type-is "~1.6.15" 943 | utils-merge "1.0.1" 944 | vary "~1.1.2" 945 | 946 | extend@~3.0.0: 947 | version "3.0.2" 948 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" 949 | 950 | extglob@^0.3.1: 951 | version "0.3.2" 952 | resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" 953 | dependencies: 954 | is-extglob "^1.0.0" 955 | 956 | extsprintf@1.3.0, extsprintf@^1.2.0: 957 | version "1.3.0" 958 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" 959 | 960 | fbjs@^0.8.16: 961 | version "0.8.16" 962 | resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.16.tgz#5e67432f550dc41b572bf55847b8aca64e5337db" 963 | dependencies: 964 | core-js "^1.0.0" 965 | isomorphic-fetch "^2.1.1" 966 | loose-envify "^1.0.0" 967 | object-assign "^4.1.0" 968 | promise "^7.1.1" 969 | setimmediate "^1.0.5" 970 | ua-parser-js "^0.7.9" 971 | 972 | filename-regex@^2.0.0: 973 | version "2.0.1" 974 | resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" 975 | 976 | fill-range@^2.1.0: 977 | version "2.2.3" 978 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" 979 | dependencies: 980 | is-number "^2.1.0" 981 | isobject "^2.0.0" 982 | randomatic "^1.1.3" 983 | repeat-element "^1.1.2" 984 | repeat-string "^1.5.2" 985 | 986 | finalhandler@1.1.0: 987 | version "1.1.0" 988 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5" 989 | dependencies: 990 | debug "2.6.9" 991 | encodeurl "~1.0.1" 992 | escape-html "~1.0.3" 993 | on-finished "~2.3.0" 994 | parseurl "~1.3.2" 995 | statuses "~1.3.1" 996 | unpipe "~1.0.0" 997 | 998 | for-in@^1.0.1: 999 | version "1.0.2" 1000 | resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" 1001 | 1002 | for-own@^0.1.4: 1003 | version "0.1.5" 1004 | resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" 1005 | dependencies: 1006 | for-in "^1.0.1" 1007 | 1008 | forever-agent@~0.6.1: 1009 | version "0.6.1" 1010 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 1011 | 1012 | form-data@~2.1.1: 1013 | version "2.1.4" 1014 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" 1015 | dependencies: 1016 | asynckit "^0.4.0" 1017 | combined-stream "^1.0.5" 1018 | mime-types "^2.1.12" 1019 | 1020 | forwarded@~0.1.2: 1021 | version "0.1.2" 1022 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 1023 | 1024 | fresh@0.5.2: 1025 | version "0.5.2" 1026 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 1027 | 1028 | fs-readdir-recursive@^1.0.0: 1029 | version "1.0.0" 1030 | resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560" 1031 | 1032 | fs.realpath@^1.0.0: 1033 | version "1.0.0" 1034 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 1035 | 1036 | fsevents@^1.0.0: 1037 | version "1.1.2" 1038 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" 1039 | dependencies: 1040 | nan "^2.3.0" 1041 | node-pre-gyp "^0.6.36" 1042 | 1043 | fstream-ignore@^1.0.5: 1044 | version "1.0.5" 1045 | resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" 1046 | dependencies: 1047 | fstream "^1.0.0" 1048 | inherits "2" 1049 | minimatch "^3.0.0" 1050 | 1051 | fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.12: 1052 | version "1.0.12" 1053 | resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" 1054 | dependencies: 1055 | graceful-fs "^4.1.2" 1056 | inherits "~2.0.0" 1057 | mkdirp ">=0.5 0" 1058 | rimraf "2" 1059 | 1060 | gauge@~2.7.3: 1061 | version "2.7.4" 1062 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" 1063 | dependencies: 1064 | aproba "^1.0.3" 1065 | console-control-strings "^1.0.0" 1066 | has-unicode "^2.0.0" 1067 | object-assign "^4.1.0" 1068 | signal-exit "^3.0.0" 1069 | string-width "^1.0.1" 1070 | strip-ansi "^3.0.1" 1071 | wide-align "^1.1.0" 1072 | 1073 | getpass@^0.1.1: 1074 | version "0.1.7" 1075 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 1076 | dependencies: 1077 | assert-plus "^1.0.0" 1078 | 1079 | glob-base@^0.3.0: 1080 | version "0.3.0" 1081 | resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" 1082 | dependencies: 1083 | glob-parent "^2.0.0" 1084 | is-glob "^2.0.0" 1085 | 1086 | glob-parent@^2.0.0: 1087 | version "2.0.0" 1088 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" 1089 | dependencies: 1090 | is-glob "^2.0.0" 1091 | 1092 | glob@^7.0.5, glob@^7.1.2: 1093 | version "7.1.2" 1094 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" 1095 | dependencies: 1096 | fs.realpath "^1.0.0" 1097 | inflight "^1.0.4" 1098 | inherits "2" 1099 | minimatch "^3.0.4" 1100 | once "^1.3.0" 1101 | path-is-absolute "^1.0.0" 1102 | 1103 | glob@^7.1.3: 1104 | version "7.1.4" 1105 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" 1106 | dependencies: 1107 | fs.realpath "^1.0.0" 1108 | inflight "^1.0.4" 1109 | inherits "2" 1110 | minimatch "^3.0.4" 1111 | once "^1.3.0" 1112 | path-is-absolute "^1.0.0" 1113 | 1114 | globals@^9.18.0: 1115 | version "9.18.0" 1116 | resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" 1117 | 1118 | graceful-fs@^4.1.2: 1119 | version "4.1.15" 1120 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" 1121 | 1122 | graceful-fs@^4.1.4: 1123 | version "4.1.11" 1124 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 1125 | 1126 | har-schema@^1.0.5: 1127 | version "1.0.5" 1128 | resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" 1129 | 1130 | har-validator@~4.2.1: 1131 | version "4.2.1" 1132 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" 1133 | dependencies: 1134 | ajv "^4.9.1" 1135 | har-schema "^1.0.5" 1136 | 1137 | has-ansi@^2.0.0: 1138 | version "2.0.0" 1139 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 1140 | dependencies: 1141 | ansi-regex "^2.0.0" 1142 | 1143 | has-unicode@^2.0.0: 1144 | version "2.0.1" 1145 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" 1146 | 1147 | hawk@3.1.3, hawk@~3.1.3: 1148 | version "3.1.3" 1149 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 1150 | dependencies: 1151 | boom "2.x.x" 1152 | cryptiles "2.x.x" 1153 | hoek "2.x.x" 1154 | sntp "1.x.x" 1155 | 1156 | hoek@2.x.x: 1157 | version "2.16.3" 1158 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 1159 | 1160 | home-or-tmp@^2.0.0: 1161 | version "2.0.0" 1162 | resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" 1163 | dependencies: 1164 | os-homedir "^1.0.0" 1165 | os-tmpdir "^1.0.1" 1166 | 1167 | http-errors@1.6.2, http-errors@~1.6.2: 1168 | version "1.6.2" 1169 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" 1170 | dependencies: 1171 | depd "1.1.1" 1172 | inherits "2.0.3" 1173 | setprototypeof "1.0.3" 1174 | statuses ">= 1.3.1 < 2" 1175 | 1176 | http-signature@~1.1.0: 1177 | version "1.1.1" 1178 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 1179 | dependencies: 1180 | assert-plus "^0.2.0" 1181 | jsprim "^1.2.2" 1182 | sshpk "^1.7.0" 1183 | 1184 | iconv-lite@0.4.19, iconv-lite@~0.4.13: 1185 | version "0.4.19" 1186 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" 1187 | 1188 | inflight@^1.0.4: 1189 | version "1.0.6" 1190 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 1191 | dependencies: 1192 | once "^1.3.0" 1193 | wrappy "1" 1194 | 1195 | inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.3: 1196 | version "2.0.3" 1197 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 1198 | 1199 | ini@~1.3.0: 1200 | version "1.3.4" 1201 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" 1202 | 1203 | invariant@^2.2.2: 1204 | version "2.2.2" 1205 | resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" 1206 | dependencies: 1207 | loose-envify "^1.0.0" 1208 | 1209 | ipaddr.js@1.5.2: 1210 | version "1.5.2" 1211 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" 1212 | 1213 | is-binary-path@^1.0.0: 1214 | version "1.0.1" 1215 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" 1216 | dependencies: 1217 | binary-extensions "^1.0.0" 1218 | 1219 | is-buffer@^1.1.5: 1220 | version "1.1.6" 1221 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" 1222 | 1223 | is-dotfile@^1.0.0: 1224 | version "1.0.3" 1225 | resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" 1226 | 1227 | is-equal-shallow@^0.1.3: 1228 | version "0.1.3" 1229 | resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" 1230 | dependencies: 1231 | is-primitive "^2.0.0" 1232 | 1233 | is-extendable@^0.1.1: 1234 | version "0.1.1" 1235 | resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" 1236 | 1237 | is-extglob@^1.0.0: 1238 | version "1.0.0" 1239 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" 1240 | 1241 | is-finite@^1.0.0: 1242 | version "1.0.2" 1243 | resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" 1244 | dependencies: 1245 | number-is-nan "^1.0.0" 1246 | 1247 | is-fullwidth-code-point@^1.0.0: 1248 | version "1.0.0" 1249 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" 1250 | dependencies: 1251 | number-is-nan "^1.0.0" 1252 | 1253 | is-glob@^2.0.0, is-glob@^2.0.1: 1254 | version "2.0.1" 1255 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" 1256 | dependencies: 1257 | is-extglob "^1.0.0" 1258 | 1259 | is-number@^2.1.0: 1260 | version "2.1.0" 1261 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" 1262 | dependencies: 1263 | kind-of "^3.0.2" 1264 | 1265 | is-number@^3.0.0: 1266 | version "3.0.0" 1267 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" 1268 | dependencies: 1269 | kind-of "^3.0.2" 1270 | 1271 | is-posix-bracket@^0.1.0: 1272 | version "0.1.1" 1273 | resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" 1274 | 1275 | is-primitive@^2.0.0: 1276 | version "2.0.0" 1277 | resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" 1278 | 1279 | is-stream@^1.0.1: 1280 | version "1.1.0" 1281 | resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" 1282 | 1283 | is-typedarray@~1.0.0: 1284 | version "1.0.0" 1285 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 1286 | 1287 | isarray@1.0.0, isarray@~1.0.0: 1288 | version "1.0.0" 1289 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" 1290 | 1291 | isobject@^2.0.0: 1292 | version "2.1.0" 1293 | resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" 1294 | dependencies: 1295 | isarray "1.0.0" 1296 | 1297 | isomorphic-fetch@^2.1.1: 1298 | version "2.2.1" 1299 | resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" 1300 | dependencies: 1301 | node-fetch "^1.0.1" 1302 | whatwg-fetch ">=0.10.0" 1303 | 1304 | isstream@~0.1.2: 1305 | version "0.1.2" 1306 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 1307 | 1308 | js-tokens@^3.0.0, js-tokens@^3.0.2: 1309 | version "3.0.2" 1310 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 1311 | 1312 | jsbn@~0.1.0: 1313 | version "0.1.1" 1314 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 1315 | 1316 | jsesc@^1.3.0: 1317 | version "1.3.0" 1318 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" 1319 | 1320 | jsesc@~0.5.0: 1321 | version "0.5.0" 1322 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" 1323 | 1324 | json-schema@0.2.3: 1325 | version "0.2.3" 1326 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 1327 | 1328 | json-stable-stringify@^1.0.1: 1329 | version "1.0.1" 1330 | resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" 1331 | dependencies: 1332 | jsonify "~0.0.0" 1333 | 1334 | json-stringify-safe@~5.0.1: 1335 | version "5.0.1" 1336 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 1337 | 1338 | json5@^0.5.1: 1339 | version "0.5.1" 1340 | resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" 1341 | 1342 | jsonify@~0.0.0: 1343 | version "0.0.0" 1344 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" 1345 | 1346 | jsprim@^1.2.2: 1347 | version "1.4.1" 1348 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" 1349 | dependencies: 1350 | assert-plus "1.0.0" 1351 | extsprintf "1.3.0" 1352 | json-schema "0.2.3" 1353 | verror "1.10.0" 1354 | 1355 | kind-of@^3.0.2: 1356 | version "3.2.2" 1357 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" 1358 | dependencies: 1359 | is-buffer "^1.1.5" 1360 | 1361 | kind-of@^4.0.0: 1362 | version "4.0.0" 1363 | resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" 1364 | dependencies: 1365 | is-buffer "^1.1.5" 1366 | 1367 | lodash@^4.17.4: 1368 | version "4.17.14" 1369 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba" 1370 | 1371 | loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.1: 1372 | version "1.3.1" 1373 | resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" 1374 | dependencies: 1375 | js-tokens "^3.0.0" 1376 | 1377 | media-typer@0.3.0: 1378 | version "0.3.0" 1379 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 1380 | 1381 | merge-descriptors@1.0.1: 1382 | version "1.0.1" 1383 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 1384 | 1385 | methods@~1.1.2: 1386 | version "1.1.2" 1387 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 1388 | 1389 | micromatch@^2.1.5: 1390 | version "2.3.11" 1391 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" 1392 | dependencies: 1393 | arr-diff "^2.0.0" 1394 | array-unique "^0.2.1" 1395 | braces "^1.8.2" 1396 | expand-brackets "^0.1.4" 1397 | extglob "^0.3.1" 1398 | filename-regex "^2.0.0" 1399 | is-extglob "^1.0.0" 1400 | is-glob "^2.0.1" 1401 | kind-of "^3.0.2" 1402 | normalize-path "^2.0.1" 1403 | object.omit "^2.0.0" 1404 | parse-glob "^3.0.4" 1405 | regex-cache "^0.4.2" 1406 | 1407 | mime-db@~1.30.0: 1408 | version "1.30.0" 1409 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" 1410 | 1411 | mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.7: 1412 | version "2.1.17" 1413 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" 1414 | dependencies: 1415 | mime-db "~1.30.0" 1416 | 1417 | mime@1.4.1: 1418 | version "1.4.1" 1419 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" 1420 | 1421 | minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.4: 1422 | version "3.0.4" 1423 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 1424 | dependencies: 1425 | brace-expansion "^1.1.7" 1426 | 1427 | minimist@0.0.8: 1428 | version "0.0.8" 1429 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 1430 | 1431 | minimist@^1.2.0: 1432 | version "1.2.0" 1433 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 1434 | 1435 | "mkdirp@>=0.5 0", mkdirp@^0.5.1: 1436 | version "0.5.1" 1437 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 1438 | dependencies: 1439 | minimist "0.0.8" 1440 | 1441 | ms@2.0.0: 1442 | version "2.0.0" 1443 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 1444 | 1445 | nan@^2.3.0: 1446 | version "2.7.0" 1447 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" 1448 | 1449 | negotiator@0.6.1: 1450 | version "0.6.1" 1451 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 1452 | 1453 | node-fetch@^1.0.1, node-fetch@^1.7.3: 1454 | version "1.7.3" 1455 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" 1456 | dependencies: 1457 | encoding "^0.1.11" 1458 | is-stream "^1.0.1" 1459 | 1460 | node-pre-gyp@^0.6.36: 1461 | version "0.6.38" 1462 | resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.38.tgz#e92a20f83416415bb4086f6d1fb78b3da73d113d" 1463 | dependencies: 1464 | hawk "3.1.3" 1465 | mkdirp "^0.5.1" 1466 | nopt "^4.0.1" 1467 | npmlog "^4.0.2" 1468 | rc "^1.1.7" 1469 | request "2.81.0" 1470 | rimraf "^2.6.1" 1471 | semver "^5.3.0" 1472 | tar "^2.2.1" 1473 | tar-pack "^3.4.0" 1474 | 1475 | nopt@^4.0.1: 1476 | version "4.0.1" 1477 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" 1478 | dependencies: 1479 | abbrev "1" 1480 | osenv "^0.1.4" 1481 | 1482 | normalize-path@^2.0.0, normalize-path@^2.0.1: 1483 | version "2.1.1" 1484 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" 1485 | dependencies: 1486 | remove-trailing-separator "^1.0.1" 1487 | 1488 | npmlog@^4.0.2: 1489 | version "4.1.2" 1490 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" 1491 | dependencies: 1492 | are-we-there-yet "~1.1.2" 1493 | console-control-strings "~1.1.0" 1494 | gauge "~2.7.3" 1495 | set-blocking "~2.0.0" 1496 | 1497 | number-is-nan@^1.0.0: 1498 | version "1.0.1" 1499 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" 1500 | 1501 | oauth-sign@~0.8.1: 1502 | version "0.8.2" 1503 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 1504 | 1505 | object-assign@^4.1.0, object-assign@^4.1.1: 1506 | version "4.1.1" 1507 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 1508 | 1509 | object.omit@^2.0.0: 1510 | version "2.0.1" 1511 | resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" 1512 | dependencies: 1513 | for-own "^0.1.4" 1514 | is-extendable "^0.1.1" 1515 | 1516 | on-finished@~2.3.0: 1517 | version "2.3.0" 1518 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 1519 | dependencies: 1520 | ee-first "1.1.1" 1521 | 1522 | once@^1.3.0, once@^1.3.3: 1523 | version "1.4.0" 1524 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 1525 | dependencies: 1526 | wrappy "1" 1527 | 1528 | os-homedir@^1.0.0: 1529 | version "1.0.2" 1530 | resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" 1531 | 1532 | os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: 1533 | version "1.0.2" 1534 | resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" 1535 | 1536 | osenv@^0.1.4: 1537 | version "0.1.4" 1538 | resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" 1539 | dependencies: 1540 | os-homedir "^1.0.0" 1541 | os-tmpdir "^1.0.0" 1542 | 1543 | output-file-sync@^1.1.2: 1544 | version "1.1.2" 1545 | resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76" 1546 | dependencies: 1547 | graceful-fs "^4.1.4" 1548 | mkdirp "^0.5.1" 1549 | object-assign "^4.1.0" 1550 | 1551 | parse-glob@^3.0.4: 1552 | version "3.0.4" 1553 | resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" 1554 | dependencies: 1555 | glob-base "^0.3.0" 1556 | is-dotfile "^1.0.0" 1557 | is-extglob "^1.0.0" 1558 | is-glob "^2.0.0" 1559 | 1560 | parseurl@~1.3.2: 1561 | version "1.3.2" 1562 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" 1563 | 1564 | path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: 1565 | version "1.0.1" 1566 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 1567 | 1568 | path-to-regexp@0.1.7: 1569 | version "0.1.7" 1570 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 1571 | 1572 | performance-now@^0.2.0: 1573 | version "0.2.0" 1574 | resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" 1575 | 1576 | preserve@^0.2.0: 1577 | version "0.2.0" 1578 | resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" 1579 | 1580 | private@^0.1.6, private@^0.1.7: 1581 | version "0.1.8" 1582 | resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" 1583 | 1584 | process-nextick-args@~1.0.6: 1585 | version "1.0.7" 1586 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" 1587 | 1588 | promise@^7.1.1: 1589 | version "7.3.1" 1590 | resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" 1591 | dependencies: 1592 | asap "~2.0.3" 1593 | 1594 | prop-types@^15.6.0: 1595 | version "15.6.0" 1596 | resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" 1597 | dependencies: 1598 | fbjs "^0.8.16" 1599 | loose-envify "^1.3.1" 1600 | object-assign "^4.1.1" 1601 | 1602 | proxy-addr@~2.0.2: 1603 | version "2.0.2" 1604 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.2.tgz#6571504f47bb988ec8180253f85dd7e14952bdec" 1605 | dependencies: 1606 | forwarded "~0.1.2" 1607 | ipaddr.js "1.5.2" 1608 | 1609 | punycode@^1.4.1: 1610 | version "1.4.1" 1611 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 1612 | 1613 | qs@6.5.1: 1614 | version "6.5.1" 1615 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" 1616 | 1617 | qs@~6.4.0: 1618 | version "6.4.0" 1619 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" 1620 | 1621 | randomatic@^1.1.3: 1622 | version "1.1.7" 1623 | resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" 1624 | dependencies: 1625 | is-number "^3.0.0" 1626 | kind-of "^4.0.0" 1627 | 1628 | range-parser@~1.2.0: 1629 | version "1.2.0" 1630 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 1631 | 1632 | raw-body@2.3.2: 1633 | version "2.3.2" 1634 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" 1635 | dependencies: 1636 | bytes "3.0.0" 1637 | http-errors "1.6.2" 1638 | iconv-lite "0.4.19" 1639 | unpipe "1.0.0" 1640 | 1641 | rc@^1.1.7: 1642 | version "1.2.2" 1643 | resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.2.tgz#d8ce9cb57e8d64d9c7badd9876c7c34cbe3c7077" 1644 | dependencies: 1645 | deep-extend "~0.4.0" 1646 | ini "~1.3.0" 1647 | minimist "^1.2.0" 1648 | strip-json-comments "~2.0.1" 1649 | 1650 | react-dom@^16.0.1: 1651 | version "16.0.1" 1652 | resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.0.1.tgz#bd2fc18d2bb81645cb807bb27a03a14d8b796e6d" 1653 | dependencies: 1654 | fbjs "^0.8.16" 1655 | loose-envify "^1.1.0" 1656 | object-assign "^4.1.1" 1657 | prop-types "^15.6.0" 1658 | 1659 | react@^16.0.0: 1660 | version "16.0.0" 1661 | resolved "https://registry.yarnpkg.com/react/-/react-16.0.0.tgz#ce7df8f1941b036f02b2cca9dbd0cb1f0e855e2d" 1662 | dependencies: 1663 | fbjs "^0.8.16" 1664 | loose-envify "^1.1.0" 1665 | object-assign "^4.1.1" 1666 | prop-types "^15.6.0" 1667 | 1668 | readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.4: 1669 | version "2.3.3" 1670 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" 1671 | dependencies: 1672 | core-util-is "~1.0.0" 1673 | inherits "~2.0.3" 1674 | isarray "~1.0.0" 1675 | process-nextick-args "~1.0.6" 1676 | safe-buffer "~5.1.1" 1677 | string_decoder "~1.0.3" 1678 | util-deprecate "~1.0.1" 1679 | 1680 | readdirp@^2.0.0: 1681 | version "2.1.0" 1682 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" 1683 | dependencies: 1684 | graceful-fs "^4.1.2" 1685 | minimatch "^3.0.2" 1686 | readable-stream "^2.0.2" 1687 | set-immediate-shim "^1.0.1" 1688 | 1689 | regenerate@^1.2.1: 1690 | version "1.3.3" 1691 | resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.3.tgz#0c336d3980553d755c39b586ae3b20aa49c82b7f" 1692 | 1693 | regenerator-runtime@^0.10.5: 1694 | version "0.10.5" 1695 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" 1696 | 1697 | regenerator-runtime@^0.11.0: 1698 | version "0.11.0" 1699 | resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" 1700 | 1701 | regenerator-transform@^0.10.0: 1702 | version "0.10.1" 1703 | resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" 1704 | dependencies: 1705 | babel-runtime "^6.18.0" 1706 | babel-types "^6.19.0" 1707 | private "^0.1.6" 1708 | 1709 | regex-cache@^0.4.2: 1710 | version "0.4.4" 1711 | resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" 1712 | dependencies: 1713 | is-equal-shallow "^0.1.3" 1714 | 1715 | regexpu-core@^2.0.0: 1716 | version "2.0.0" 1717 | resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" 1718 | dependencies: 1719 | regenerate "^1.2.1" 1720 | regjsgen "^0.2.0" 1721 | regjsparser "^0.1.4" 1722 | 1723 | regjsgen@^0.2.0: 1724 | version "0.2.0" 1725 | resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" 1726 | 1727 | regjsparser@^0.1.4: 1728 | version "0.1.5" 1729 | resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" 1730 | dependencies: 1731 | jsesc "~0.5.0" 1732 | 1733 | remove-trailing-separator@^1.0.1: 1734 | version "1.1.0" 1735 | resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" 1736 | 1737 | repeat-element@^1.1.2: 1738 | version "1.1.2" 1739 | resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" 1740 | 1741 | repeat-string@^1.5.2: 1742 | version "1.6.1" 1743 | resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" 1744 | 1745 | repeating@^2.0.0: 1746 | version "2.0.1" 1747 | resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" 1748 | dependencies: 1749 | is-finite "^1.0.0" 1750 | 1751 | request@2.81.0: 1752 | version "2.81.0" 1753 | resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" 1754 | dependencies: 1755 | aws-sign2 "~0.6.0" 1756 | aws4 "^1.2.1" 1757 | caseless "~0.12.0" 1758 | combined-stream "~1.0.5" 1759 | extend "~3.0.0" 1760 | forever-agent "~0.6.1" 1761 | form-data "~2.1.1" 1762 | har-validator "~4.2.1" 1763 | hawk "~3.1.3" 1764 | http-signature "~1.1.0" 1765 | is-typedarray "~1.0.0" 1766 | isstream "~0.1.2" 1767 | json-stringify-safe "~5.0.1" 1768 | mime-types "~2.1.7" 1769 | oauth-sign "~0.8.1" 1770 | performance-now "^0.2.0" 1771 | qs "~6.4.0" 1772 | safe-buffer "^5.0.1" 1773 | stringstream "~0.0.4" 1774 | tough-cookie "~2.3.0" 1775 | tunnel-agent "^0.6.0" 1776 | uuid "^3.0.0" 1777 | 1778 | rimraf@2: 1779 | version "2.6.3" 1780 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" 1781 | dependencies: 1782 | glob "^7.1.3" 1783 | 1784 | rimraf@^2.5.1, rimraf@^2.6.1: 1785 | version "2.6.2" 1786 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" 1787 | dependencies: 1788 | glob "^7.0.5" 1789 | 1790 | safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: 1791 | version "5.1.1" 1792 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" 1793 | 1794 | safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: 1795 | version "2.1.2" 1796 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1797 | 1798 | semver@^5.3.0: 1799 | version "5.4.1" 1800 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" 1801 | 1802 | send@0.16.1: 1803 | version "0.16.1" 1804 | resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" 1805 | dependencies: 1806 | debug "2.6.9" 1807 | depd "~1.1.1" 1808 | destroy "~1.0.4" 1809 | encodeurl "~1.0.1" 1810 | escape-html "~1.0.3" 1811 | etag "~1.8.1" 1812 | fresh "0.5.2" 1813 | http-errors "~1.6.2" 1814 | mime "1.4.1" 1815 | ms "2.0.0" 1816 | on-finished "~2.3.0" 1817 | range-parser "~1.2.0" 1818 | statuses "~1.3.1" 1819 | 1820 | serve-static@1.13.1: 1821 | version "1.13.1" 1822 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.1.tgz#4c57d53404a761d8f2e7c1e8a18a47dbf278a719" 1823 | dependencies: 1824 | encodeurl "~1.0.1" 1825 | escape-html "~1.0.3" 1826 | parseurl "~1.3.2" 1827 | send "0.16.1" 1828 | 1829 | set-blocking@~2.0.0: 1830 | version "2.0.0" 1831 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 1832 | 1833 | set-immediate-shim@^1.0.1: 1834 | version "1.0.1" 1835 | resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" 1836 | 1837 | setimmediate@^1.0.5: 1838 | version "1.0.5" 1839 | resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" 1840 | 1841 | setprototypeof@1.0.3: 1842 | version "1.0.3" 1843 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" 1844 | 1845 | setprototypeof@1.1.0: 1846 | version "1.1.0" 1847 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 1848 | 1849 | signal-exit@^3.0.0: 1850 | version "3.0.2" 1851 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" 1852 | 1853 | slash@^1.0.0: 1854 | version "1.0.0" 1855 | resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" 1856 | 1857 | sntp@1.x.x: 1858 | version "1.0.9" 1859 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 1860 | dependencies: 1861 | hoek "2.x.x" 1862 | 1863 | source-map-support@^0.4.15: 1864 | version "0.4.18" 1865 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" 1866 | dependencies: 1867 | source-map "^0.5.6" 1868 | 1869 | source-map@^0.5.6: 1870 | version "0.5.7" 1871 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1872 | 1873 | sshpk@^1.7.0: 1874 | version "1.16.1" 1875 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" 1876 | dependencies: 1877 | asn1 "~0.2.3" 1878 | assert-plus "^1.0.0" 1879 | bcrypt-pbkdf "^1.0.0" 1880 | dashdash "^1.12.0" 1881 | ecc-jsbn "~0.1.1" 1882 | getpass "^0.1.1" 1883 | jsbn "~0.1.0" 1884 | safer-buffer "^2.0.2" 1885 | tweetnacl "~0.14.0" 1886 | 1887 | "statuses@>= 1.3.1 < 2": 1888 | version "1.4.0" 1889 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" 1890 | 1891 | statuses@~1.3.1: 1892 | version "1.3.1" 1893 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" 1894 | 1895 | string-width@^1.0.1, string-width@^1.0.2: 1896 | version "1.0.2" 1897 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" 1898 | dependencies: 1899 | code-point-at "^1.0.0" 1900 | is-fullwidth-code-point "^1.0.0" 1901 | strip-ansi "^3.0.0" 1902 | 1903 | string_decoder@~1.0.3: 1904 | version "1.0.3" 1905 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" 1906 | dependencies: 1907 | safe-buffer "~5.1.0" 1908 | 1909 | stringstream@~0.0.4: 1910 | version "0.0.6" 1911 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" 1912 | 1913 | strip-ansi@^3.0.0, strip-ansi@^3.0.1: 1914 | version "3.0.1" 1915 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 1916 | dependencies: 1917 | ansi-regex "^2.0.0" 1918 | 1919 | strip-json-comments@~2.0.1: 1920 | version "2.0.1" 1921 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 1922 | 1923 | supports-color@^2.0.0: 1924 | version "2.0.0" 1925 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 1926 | 1927 | tar-pack@^3.4.0: 1928 | version "3.4.1" 1929 | resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.1.tgz#e1dbc03a9b9d3ba07e896ad027317eb679a10a1f" 1930 | dependencies: 1931 | debug "^2.2.0" 1932 | fstream "^1.0.10" 1933 | fstream-ignore "^1.0.5" 1934 | once "^1.3.3" 1935 | readable-stream "^2.1.4" 1936 | rimraf "^2.5.1" 1937 | tar "^2.2.1" 1938 | uid-number "^0.0.6" 1939 | 1940 | tar@^2.2.1: 1941 | version "2.2.2" 1942 | resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" 1943 | dependencies: 1944 | block-stream "*" 1945 | fstream "^1.0.12" 1946 | inherits "2" 1947 | 1948 | to-fast-properties@^1.0.3: 1949 | version "1.0.3" 1950 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" 1951 | 1952 | tough-cookie@~2.3.0: 1953 | version "2.3.3" 1954 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" 1955 | dependencies: 1956 | punycode "^1.4.1" 1957 | 1958 | trim-right@^1.0.1: 1959 | version "1.0.1" 1960 | resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" 1961 | 1962 | tunnel-agent@^0.6.0: 1963 | version "0.6.0" 1964 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" 1965 | dependencies: 1966 | safe-buffer "^5.0.1" 1967 | 1968 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 1969 | version "0.14.5" 1970 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 1971 | 1972 | type-is@~1.6.15: 1973 | version "1.6.15" 1974 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" 1975 | dependencies: 1976 | media-typer "0.3.0" 1977 | mime-types "~2.1.15" 1978 | 1979 | ua-parser-js@^0.7.9: 1980 | version "0.7.17" 1981 | resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" 1982 | 1983 | uid-number@^0.0.6: 1984 | version "0.0.6" 1985 | resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" 1986 | 1987 | unpipe@1.0.0, unpipe@~1.0.0: 1988 | version "1.0.0" 1989 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 1990 | 1991 | user-home@^1.1.1: 1992 | version "1.1.1" 1993 | resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" 1994 | 1995 | util-deprecate@~1.0.1: 1996 | version "1.0.2" 1997 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 1998 | 1999 | utils-merge@1.0.1: 2000 | version "1.0.1" 2001 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 2002 | 2003 | uuid@^3.0.0: 2004 | version "3.1.0" 2005 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" 2006 | 2007 | v8flags@^2.1.1: 2008 | version "2.1.1" 2009 | resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" 2010 | dependencies: 2011 | user-home "^1.1.1" 2012 | 2013 | vary@~1.1.2: 2014 | version "1.1.2" 2015 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 2016 | 2017 | verror@1.10.0: 2018 | version "1.10.0" 2019 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" 2020 | dependencies: 2021 | assert-plus "^1.0.0" 2022 | core-util-is "1.0.2" 2023 | extsprintf "^1.2.0" 2024 | 2025 | whatwg-fetch@>=0.10.0: 2026 | version "2.0.3" 2027 | resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" 2028 | 2029 | wide-align@^1.1.0: 2030 | version "1.1.2" 2031 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" 2032 | dependencies: 2033 | string-width "^1.0.2" 2034 | 2035 | wrappy@1: 2036 | version "1.0.2" 2037 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 2038 | -------------------------------------------------------------------------------- /examples/teeing/README.md: -------------------------------------------------------------------------------- 1 | # Basically, Stream Teeing 2 | 3 | By design, streams work on the principle of _one source, one consumer_. So if you have a stream that you'd like to be consumed by two consumers, you need help. Enter [Teeing](https://streams.spec.whatwg.org/#rs-tee). 4 | 5 | Also known as forking streams, teeing refers to splitting one stream into two identical streams to be read by two distinct readers. It's pretty cool. Under the hood, tee() locks the source stream to the fork, creating two identical, unlocked streams ready for consumption. 6 | 7 | [Here's an example](https://tejasq.github.io/basically-streams/examples/teeing/) that, from a source stream, supplies employees in the shape of: 8 | 9 | ```json 10 | { id: 1, name: "Something somethington" } 11 | ``` 12 | 13 | This stream is teed to two different streams: the `leftColumnStream` and the `rightColumnStream`. 14 | 15 | To read from these split streams, press the correponding button. It's that simple! 16 | 17 | Don't forget to check out the [source code](https://github.com/TejasQ/basically-streams/blob/master/examples/teeing/index.js)! 18 | -------------------------------------------------------------------------------- /examples/teeing/index.css: -------------------------------------------------------------------------------- 1 | .examples.examples__streaming { 2 | display: grid; 3 | grid-template-columns: 1fr 1fr; 4 | margin-bottom: calc(var(--spacing) * 2); 5 | } 6 | 7 | .example { 8 | padding: var(--spacing); 9 | } 10 | 11 | .example_right { 12 | background-color: #eee; 13 | } 14 | -------------------------------------------------------------------------------- /examples/teeing/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Basically, Stream Teeing 8 | 9 | 10 |
11 |

Basically, Stream Teeing

12 |

By design, streams work on the principle of one source, one consumer. So if you have a stream that you'd like to be consumed by two consumers, you need help. Enter Teeing.

13 |

Also known as forking streams, teeing refers to splitting one stream into two identical streams to be read by two distinct readers. It's pretty cool.

14 |

Under the hood, tee() locks the source stream to the fork, creating two identical, unlocked streams ready for consumption.

15 |

Below is an example that, from a source stream that supplies employees in the shape of: 16 |

17 | { id: 1, name: "Something somethington" }
18 |         
19 | This stream is teed to two different streams: the `leftColumnStream` and the `rightColumnStream`. 20 |

21 |

To read from these split streams, press the correponding button. It's that simple!

22 |

Don't forget to check out the source code!

23 | 24 |
25 |
26 |
27 |
    28 | 29 |
    30 |
    31 |
      32 | 33 |
      34 |
      35 |
      36 | 37 |
      38 | 39 |
      40 |
      41 |

      More Examples 🎉

      42 | 48 |

      Further Reading: WhatWG Spec 49 |

      50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /examples/teeing/index.js: -------------------------------------------------------------------------------- 1 | // Get the DOM elements to interact with. 2 | const leftColumnStreamTarget = document.querySelector("#left-column-stream") 3 | const rightColumnStreamTarget = document.querySelector("#right-column-stream") 4 | const leftColumnStreamButton = document.querySelector("#left-column-stream-button") 5 | const rightColumnStreamButton = document.querySelector("#right-column-stream-button") 6 | const resetButton = document.querySelector("#reset-button") 7 | 8 | // Here's the dummy stream data. 9 | const employees = [ 10 | { id: 1, name: "Goober Jones" }, 11 | { id: 2, name: "Tejas Indiana" }, 12 | { id: 3, name: "Foo Barrington" }, 13 | { id: 4, name: "Celso Quilala" }, 14 | { id: 5, name: "Magic Mahanake" }, 15 | ] 16 | 17 | // This is the source stream. It's a really simple push-style stream that gives employees. 18 | const sourceStream = new ReadableStream({ 19 | start(controller) { 20 | employees.forEach(employee => controller.enqueue(employee)) 21 | controller.close() 22 | }, 23 | }) 24 | 25 | /* 26 | Here's the fun! We've teed (forked) the sourceStream 27 | into a leftColumnStream and a rightColumnStream 28 | */ 29 | const [leftColumnStream, rightColumnStream] = sourceStream.tee() 30 | 31 | // For fun, we'll have a writeStream for the leftColumnStream 32 | const leftColumnWriteStream = new WritableStream({ 33 | // On write, it'll simply add list items per employee, showing only the ID. 34 | write(data) { 35 | const li = document.createElement("li") 36 | li.innerHTML = data.value.id 37 | leftColumnStreamTarget.appendChild(li) 38 | }, 39 | }) 40 | 41 | /* 42 | Same drill for the right column. 43 | 44 | Notice how the close() is used here, because this stream is piped 45 | to instead of explicitly read(). 46 | */ 47 | const rightColumnWriteStream = new WritableStream({ 48 | write(data) { 49 | const li = document.createElement("li") 50 | li.innerHTML = data.name 51 | rightColumnStreamTarget.appendChild(li) 52 | }, 53 | close() { 54 | rightColumnStreamButton.disabled = true 55 | rightColumnStreamButton.innerHTML = "Stream Closed" 56 | }, 57 | }) 58 | 59 | // So the writer needs a reader to write to it 60 | const leftColumnReader = leftColumnStream.getReader() 61 | 62 | const leftColumnWriter = leftColumnWriteStream.getWriter() 63 | const writeToLeftColumn = async () => { 64 | // First read, 65 | const employee = await leftColumnReader.read() 66 | 67 | // then write 68 | await leftColumnWriter.write(employee) 69 | 70 | /* 71 | ReadableStreamDefaultReader.closed is a promise that resolves when the stream closes. 72 | Let's wait until it closes. 73 | */ 74 | await leftColumnReader.closed 75 | 76 | // Then, disable the button and let the user know it closed. 77 | leftColumnStreamButton.disabled = true 78 | leftColumnStreamButton.innerHTML = "Stream closed" 79 | } 80 | 81 | // Finally, hook everything up by adding event listeners. 82 | leftColumnStreamButton.addEventListener("click", () => writeToLeftColumn()) 83 | rightColumnStreamButton.addEventListener("click", () => rightColumnStream.pipeTo(rightColumnWriteStream)) 84 | 85 | resetButton.addEventListener("click", () => location.reload()) 86 | -------------------------------------------------------------------------------- /index.css: -------------------------------------------------------------------------------- 1 | .examples.examples__streaming { 2 | display: grid; 3 | grid-template-columns: 1fr 1fr; 4 | margin-bottom: calc(var(--spacing) * 2); 5 | } 6 | 7 | .example { 8 | padding: var(--spacing); 9 | } 10 | 11 | .example_right { 12 | background-color: #eee; 13 | } 14 | 15 | h2 { 16 | margin-top: 40px; 17 | } 18 | 19 | .reset-button { 20 | display: none; 21 | } 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Basically, Streams 🌊 8 | 9 | 10 |
      11 |

      Basically, Streams 🌊

      12 |

      So something (relatively) new and fun in browsers, are these two APIs: ReadableStream and WritableStream. 😮

      13 |

      What they allow you to do, is basically send and receive chunks of things progressively (that then combine into the final large thing), as opposed to moving around entire things.

      14 | 15 |

      Below is a living example of streaming vs. conventional transfer:

      16 | 17 |
      18 |
      19 |
      20 |

      I am a bunch of text who lives here, but I'm bored of living here so I want to move trololololololol.

      21 | 22 |
      23 |
      24 |

      25 | 26 |
      27 |
      28 |
      29 | 30 |

      Pretty cool, huh?

      31 | 32 |

      So basically, what's happening is this: 33 |

        34 |
      • Someone clicks "Move me!"
      • 35 |
      • We create a ReadableStream, whose source is the textContent of the left column
      • 36 |
      • The stream removes and queues each letter, one by one, every few milliseconds
      • 37 |
      • This stream is piped into a `receiverStream`, that writes it to the right column.
      • 38 |
      39 |

      40 |

      All of this is visible in the source code (index.js). There's more cool things in the examples folder. Go, explore, have fun! Woooo!

      41 | 42 |

      More Examples 🎉

      43 | 48 | 49 |

      Further Reading: WhatWG Spec 50 |

      51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // Get the source div. 2 | const textToMove = document.querySelector("#text-to-move") 3 | 4 | // Get DOM things to interact with 5 | const moveButton = document.querySelector("#move-button") 6 | const resetButton = document.querySelector("#reset-button") 7 | 8 | const startStream = () => { 9 | /* 10 | "I want to move" -> ["I", " ", "w", "a" ...] 11 | This turns the text into an array 12 | */ 13 | const charactersToMove = textToMove.textContent.split("") 14 | 15 | /* 16 | A function to help us know if we're on the last element of this array. 17 | If we are, we'll enqueue it and then CLOSE THE STREAM! 18 | */ 19 | const isLast = index => length => index === length - 1 20 | 21 | /* 22 | Removes a character from the left column and 23 | moves it (enqueues it) into the stream, to be piped to the 24 | right column. (Thanks, @ricea) 25 | */ 26 | const removeAndEnqueue = (controller, character) => { 27 | textToMove.innerHTML = textToMove.textContent.replace(character, "") 28 | controller.enqueue(character) 29 | } 30 | 31 | /* 32 | Create and return a ReadableStream from our text. 33 | ReadableStreams have methods in them that each get a controller (ReadableStreamController) 34 | that allow you to control your stream. 35 | 36 | Controllers can enqueue things (add them to the stream), 37 | close the stream, throw errors, and get the desired size of the stream. 38 | 39 | It's a nice piece of kit. 40 | */ 41 | return new ReadableStream({ 42 | start(controller) { 43 | /* 44 | For each letter, set a timeout to `enqueue` a piece of data, 45 | in this case a letter, every (index * 50) seconds. 46 | 47 | So, 48 | - I will be enqueued immediately, 49 | - " " will be enqueued after 50ms, 50 | - "w" will be enqueud after 100ms, 51 | - you get the picture. 52 | */ 53 | charactersToMove.forEach((character, index) => 54 | // Set a timeout, 55 | setTimeout( 56 | // With this function that, 57 | () => { 58 | /* 59 | Removes and enqueues a character into the stream (for relocation), 60 | see line 26. 61 | */ 62 | removeAndEnqueue(controller, character) 63 | 64 | // If we're at the last character, 65 | if (isLast(index)(charactersToMove.length)) { 66 | // Close the stream 67 | controller.close() 68 | } 69 | }, 70 | index * 50 // Do this every (`index` * 50) milliseconds 71 | ) 72 | ) 73 | }, 74 | }) 75 | } 76 | 77 | /* 78 | Create a WritableStream that we'll pipe the ReadableStream into! 79 | 80 | The WritableStream also has methods, with a controller (WritableStreamWriter). 81 | This thing can: 82 | - check if the stream has closed 83 | - get the desired size 84 | - get a ready status 85 | - abort the stream 86 | - do something when the stream pouring into it finishes 87 | - release its lock on a ReadableStream 88 | - and of course, do something when it receives data (write()). 89 | 90 | For brevity, we'll check out the write() and close() methods. 91 | */ 92 | const receiverStream = new WritableStream({ 93 | // On Write (when we get data), 94 | write(data) { 95 | // Create a text node, 96 | const textNode = document.createTextNode(data) 97 | 98 | // Add the same character to the other box. 99 | document.querySelector("#text-to-move_target").appendChild(textNode) 100 | }, 101 | 102 | // When the stream is done, 103 | close(data) { 104 | // WOOOO 105 | moveButton.disabled = true 106 | moveButton.innerHTML = "Now I'm lonely :(" 107 | resetButton.style.display = "block" 108 | console.log("%c STREAM CLOSED WOOO!", "color:#0af;font-size:20px") 109 | }, 110 | }) 111 | 112 | /* 113 | Finally, wire it all up by listening for a click on the move button 114 | that starts the stream and pipes it into our receiver WritableStream. 115 | */ 116 | moveButton.addEventListener("click", () => startStream().pipeTo(receiverStream)) 117 | resetButton.addEventListener("click", () => location.reload()) 118 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "basically-streams", 3 | "version": "1.0.0", 4 | "description": "An introductory project to the relatively new stremaing APIs available in modern browsers.", 5 | "main": "index.js", 6 | "repository": "git@github.com:TejasQ/basically-streams.git", 7 | "author": "Tejas Kumar ", 8 | "license": "MIT" 9 | } 10 | --------------------------------------------------------------------------------