├── .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 |
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.
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.
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 | 
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 |