Skip to content
This repository has been archived by the owner on Jun 24, 2024. It is now read-only.

Commit

Permalink
feat: Add bundle method
Browse files Browse the repository at this point in the history
Adds a new `bundle` method to match the API of the JS writer. This allows you to instantiate a
writer a single time, but consume from it multiple times

BREAKING CHANGE: The user must now call `.bundle` before the writer can be consumed
  • Loading branch information
SimenB committed Nov 23, 2017
1 parent fe4e513 commit 5819800
Show file tree
Hide file tree
Showing 5 changed files with 147 additions and 45 deletions.
3 changes: 1 addition & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
"finn-prettier"
],
"env": {
"node": true,
"jest": true
}
}
}
28 changes: 21 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ npm install asset-pipe-css-writer
### Require the writer

```js
const CssWriter = require('asset-pipe-css-writer');
const CssWriter = require('@asset-pipe/css-writer');
```

### Instantiating the writer
Expand All @@ -82,12 +82,12 @@ const writer = new CssWriter([

### Consuming content from the writer

The writer is a readable stream in object mode so in order to access the data
you may register a data handler and listen for objects to be passed to the
handler:
The writer is an event emitter, which has a method called `bundle`, which
returns a readable stream in object mode so in order to access the data you may
register a data handler and listen for objects to be passed to the handler:

```js
writer.on('data', data => {
writer.bundle().on('data', data => {
// { id, name, version, file, content }
});
```
Expand All @@ -96,7 +96,7 @@ You might also pipe the writer into a writeable or transform stream (with input
in object mode):

```js
const { Writable } = require('stream');
const { Writeable } = require('stream');
const consumer = new Writeable({
objectMode: true,
write(chunk, encoding, callback) {
Expand All @@ -106,5 +106,19 @@ const consumer = new Writeable({
},
});

writer.pipe(consumer);
writer.bundle().pipe(consumer);
```

If you want to create a single file output, send `true` as the second argument
when creating the `Writer`.

```js
const writer = new CssWriter(
['/path/to/css/file1.css', '/path/to/css/file2.css'],
true,
);

writer.bundle().on('data', data => {
// the two files bundled together as a single CSS
});
```
69 changes: 48 additions & 21 deletions lib/writer.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,48 @@
'use strict';

const { Readable } = require('stream');
const EventEmitter = require('events');
const { Readable, Transform } = require('stream');
const { identifyCssModule, bundleCssModule } = require('./util');
const { existsSync } = require('fs');
const { isAbsolute } = require('path');
const assert = require('assert');
const { hasher } = require('asset-pipe-common');

module.exports = class Writer extends Readable {
constructor(files = []) {
class Stream extends Readable {
constructor(files) {
super({ objectMode: true });

this.files = files;
}

async _read() {
const file = this.files.shift();
if (!file) {
this.push(null);
return;
}

try {
const [css, meta] = await Promise.all([
bundleCssModule(file),
identifyCssModule(file),
]);
meta.id = hasher(
`${meta.name}|${meta.version}|${meta.file}|${css}`
);
meta.content = css;
this.push(meta);
} catch (err) {
this.emit('error', err);
}
}
}

module.exports = class Writer extends EventEmitter {
constructor(files = [], bundle = false) {
super({ objectMode: true });
this.shouldBundle = bundle;

assert(
Array.isArray(files) || typeof files === 'string',
`Expected 'files' to be of type 'Array' or a 'string', instead got '${typeof files}'`
Expand Down Expand Up @@ -38,25 +70,20 @@ module.exports = class Writer extends Readable {
}
}

async _read() {
const file = this.files.shift();
if (!file) {
this.push(null);
return;
}
bundle() {
const cssStream = new Stream(this.files.slice(0));

try {
const [css, meta] = await Promise.all([
bundleCssModule(file),
identifyCssModule(file),
]);
meta.id = hasher(
`${meta.name}|${meta.version}|${meta.file}|${css}`
);
meta.content = css;
this.push(meta);
} catch (err) {
this.emit('error', err);
if (!this.shouldBundle) {
return cssStream;
}

return cssStream.pipe(
new Transform({
objectMode: true,
transform(chunk, enc, next) {
next(null, chunk.content);
},
})
);
}
};
30 changes: 23 additions & 7 deletions package-lock.json

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

62 changes: 54 additions & 8 deletions test/writer.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict';

/* global test, expect, beforeEach, jest */

const fs = require('fs');
const path = require('path');
const { hasher } = require('asset-pipe-common');
const { identifyCssModule, bundleCssModule } = require('../lib/util.js');
Expand Down Expand Up @@ -64,7 +63,7 @@ test('new Writer(filePath)', done => {
const filePath = path.join(__dirname, 'test-assets/my-module-1/main.css');
const fileRef = 'my-module-1/main.css';

const writer = new Writer(filePath);
const writer = new Writer(filePath).bundle();
const items = [];

writer.on('data', item => {
Expand All @@ -82,6 +81,31 @@ test('new Writer(filePath)', done => {
});
});

test('new Writer(filePath) can be reused', done => {
expect.assertions(2);
const filePath = path.join(__dirname, 'test-assets/my-module-1/main.css');
const dataMock = jest.fn();

const writer = new Writer(filePath);
const bundle1 = writer.bundle();

bundle1.on('data', dataMock);

bundle1.on('end', () => {
expect(dataMock).toHaveBeenCalledTimes(1);

const bundle2 = writer.bundle();

bundle2.on('data', dataMock);

bundle2.on('end', () => {
expect(dataMock).toHaveBeenCalledTimes(2);

done();
});
});
});

test('new Writer(filePath) relative paths throw error', () => {
expect.assertions(1);
const filePath = './test-assets/my-module-1/main.css';
Expand All @@ -96,7 +120,7 @@ test('new Writer([filePath])', done => {
const filePath = path.join(__dirname, 'test-assets/my-module-1/main.css');
const fileRef = 'my-module-1/main.css';

const writer = new Writer([filePath]);
const writer = new Writer([filePath]).bundle();
const items = [];

writer.on('data', item => {
Expand All @@ -122,7 +146,7 @@ test('Writer processes @import statements', done => {
);
const fileRef = 'my-module-3/css/main.css';

const writer = new Writer([filePath]);
const writer = new Writer([filePath]).bundle();
const items = [];

writer.on('data', item => {
Expand Down Expand Up @@ -154,7 +178,7 @@ test('new Writer([filePath1, filePath2]) ensures correct order', done => {
);
const fileRef2 = 'my-module-2/css/main.css';

const writer = new Writer([filePath1, filePath2]);
const writer = new Writer([filePath1, filePath2]).bundle();
const items = [];

writer.on('data', item => {
Expand Down Expand Up @@ -244,7 +268,7 @@ test('writer emits error', done => {
const CssWriter = require('..');
const filePath = path.join(__dirname, 'test-assets/my-module-1/main.css');

const writer = new CssWriter(filePath);
const writer = new CssWriter(filePath).bundle();

writer.on('error', error => {
expect(error).toBeInstanceOf(Error);
Expand All @@ -254,7 +278,7 @@ test('writer emits error', done => {
});

test('new Writer() emits nothing but does not break', done => {
const writer = new Writer();
const writer = new Writer().bundle();
const items = [];

writer.on('data', item => {
Expand All @@ -266,3 +290,25 @@ test('new Writer() emits nothing but does not break', done => {
done();
});
});

test('new Writer(filepath, bundle: true) emits a bundle', done => {
expect.assertions(1);
const filePath = path.join(__dirname, 'test-assets/my-module-1/main.css');

const writer = new Writer([filePath], true).bundle();

writer.on('error', e => {
done.fail(e);
});
const items = [];

writer.on('data', item => {
items.push(item);
});

writer.on('end', () => {
expect(items).toEqual([fs.readFileSync(filePath, 'utf8')]);

done();
});
});

0 comments on commit 5819800

Please sign in to comment.