Skip to content

Commit

Permalink
Support cjs
Browse files Browse the repository at this point in the history
- Updates to Readme
- Rollup dep
- Bump version
- Examples cleanup
  • Loading branch information
grommett committed Dec 29, 2021
1 parent 23a9846 commit 3d769e2
Show file tree
Hide file tree
Showing 5 changed files with 171 additions and 10 deletions.
29 changes: 23 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,35 @@
<a href="https://github.com/grommett/fastify-chunk-view/actions/workflows/ci.yaml"><img src="https://github.com/grommett/fastify-chunk-view/workflows/CI/badge.svg" /></a>
![Known Vulnerabilities](https://snyk.io/test/github/grommett/fastify-chunk-view/badge.svg)



A Fastify plugin for sending HTML content to the client as soon as it becomes available.


## Why

This plugin is ideal for situations where you have static content that can be served immediatley and async content that will arrive later. For example, a product list page where the header, styles and hero image can be sent right away while you grab the products from an API.

## Install
```js
npm i fastify-chunk-view --save
```
## Usage
`fastify-chunk-view` supports ESM and CommonJS
### ESM
```js
import fastifyChunkView from 'fastify-chunk-view';
```

The `chunk-view` plugin takes an array of chunks and sends them to the client in order. A chunk in this sense can be a `string`, a `function` that returns a `string`, an `async function` that returns a `string` or a `Readable` stream.
### Common JS
```js
const fastifyChunkView = require('fastify-chunk-view');
```
The `fastify-chunk-view` plugin takes an array of chunks and sends them to the client in that order. A chunk can be:
- A `string`
- A `function` that returns a `string`
- An `async function` that returns a `string`
- A `Readable` stream

Have a look or run the `examples` folder in this repo for mre details, but here's a silly example to illustate:
Have a look or run the `examples` folder in this repo for more details. Here's a silly example to illustate:

```js
fastify.get('/', (_req, reply) => {
Expand All @@ -33,8 +49,9 @@ An example of the ecommerce use case mentioned above:
```js
fastify.get('/', (_req, reply) => {
reply.chunkView([
'<html><head></head><body>',
productList,
// Sent to client immediately
'<html><head></head><body><img src="a-big-marketing-image.jpg">',
productList, // Sent after the list is retrieved
footer,
'</body></html>'
]);
Expand Down
111 changes: 111 additions & 0 deletions cjs/index.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
'use strict';

var stream = require('stream');
var fastifyPlugin = require('fastify-plugin');

function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }

var fastifyPlugin__default = /*#__PURE__*/_interopDefaultLegacy(fastifyPlugin);

const NO_STRATEGY = `<!-- No strategy found for chunk -->`;

function fastifyChunkView(fastify, _opts, next) {
fastify.decorateReply('chunkView', chunkView);
next();
}

/**
* Creates and sends the response to the client.
* Given an array of chunks will push each to the response stream in given order
* @param {(string|() => string| () => Promise<string>)[]} chunks
* @returns {void}
*/
async function chunkView(chunks) {
const responseStream = getReadStream();
this.header('content-type', 'text/html');
this.header('transfer-encoding', 'chunked');
this.send(responseStream);

try {
for (const chunk of chunks) {
let result;
const nextChunk = getChunk(chunk, responseStream);
if (nextChunk.then) {
result = await nextChunk;
} else {
result = nextChunk;
}
responseStream.push(result);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
}

responseStream.push(null);
}

/**
* Returns the chunk
* @param {Readable|string|Function} chunk
* @param {Readable} responseStream
* @returns {string|Promise<string>}
*/
function getChunk(chunk, responseStream) {
const strategy = getChunkStrategy(chunk, responseStream);
return strategy ? strategy() : NO_STRATEGY;
}

/**
* Evaluates the chunk type and returns the strategy to call for this type of chunk
* @param {Readable|string|() => string} chunk A Readable Stream to be pushed into the response stream,
* a string to push into the response stream or function that returns a string
* @param {Readable} responseStream A Readable Stream
* @returns {() => string|() => Promise<string>}
*/
function getChunkStrategy(chunk, responseStream) {
if (typeof chunk === 'string') return () => chunk;
if (typeof chunk === 'function') return chunk;
if (chunk instanceof stream.Readable) return handleReadStream(responseStream, chunk);
return () => NO_STRATEGY;
}

/**
* Pushes a read stream into the response read stream
* Resolves once the readStream is complete
* @param {Readable} responseStream
* @param {Readable} readStream
* @returns {() => Promise<string>}
*/
function handleReadStream(responseStream, readStream) {
return () =>
new Promise((resolve, reject) => {
readStream.on('data', data => {
responseStream.push(data);
});
readStream.on('end', () => {
resolve('');
});
readStream.on('error', error => {
reject(error);
});
});
}

/**
* Gets a Readable stream setting its `_read` method
* @returns {Readable} Readable
*/
function getReadStream() {
const stream$1 = new stream.Readable();
// eslint-disable-next-line no-empty-function
stream$1._read = () => {};
return stream$1;
}

var index = fastifyPlugin__default["default"](fastifyChunkView, {
fastify: '3.x',
name: 'chunk-view',
});

module.exports = index;
3 changes: 2 additions & 1 deletion examples/simple/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fastify.get('/', (req, reply) => {
return `<h1>Hello chunked view</h1>`;
},
function () {
return new Promise((resolve, _reject) => {
return new Promise(resolve => {
setTimeout(() => {
resolve(`<p>This is async content</p>`);
}, 1000);
Expand All @@ -24,5 +24,6 @@ fastify.get('/', (req, reply) => {

fastify.listen(3000, err => {
if (err) throw err;
// eslint-disable-next-line no-console
console.log(`server listening on ${fastify.server.address().port}`);
});
27 changes: 26 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
{
"name": "fastify-chunk-view",
"version": "1.0.0",
"version": "1.1.0",
"description": "A Fastify plugin that chunks content as it becomes available.",
"type": "module",
"main": "index.js",
"exports": {
".": {
"require": "./cjs/index.cjs"
}
},
"scripts": {
"test": "tap",
"lint": "eslint"
"lint": "eslint",
"cjs": "rollup index.js --file cjs/index.cjs --format cjs --exports auto --external stream,fastify-plugin"
},
"keywords": [
"fastify",
Expand All @@ -22,6 +28,7 @@
"devDependencies": {
"eslint": "^8.5.0",
"fastify": "^3.25.2",
"rollup": "^2.62.0",
"simple-get": "^4.0.0",
"tap": "^15.1.5"
}
Expand Down

0 comments on commit 3d769e2

Please sign in to comment.