Skip to content

Commit 72a89de

Browse files
authored
feat: Allow plugin exports via "processing" lifecycle hook (#404)
Fixes #401
1 parent 2a16d07 commit 72a89de

File tree

8 files changed

+214
-53
lines changed

8 files changed

+214
-53
lines changed

docs/api.md

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
## Usage
99

10-
Instantiate a new `Processor` instance, call it's `.file(<path>)` or `.string(<name>, <contents>)` methods, and then use the returned Promise to get access to the results/output.
10+
Instantiate a new `Processor` instance, call `.file(<path>)` or `.string(<name>, <contents>)` methods, and then use the returned Promise to get access to the results/output.
1111

1212
```js
13-
var Processor = require("modular-css"),
14-
processor = new Processor({
13+
const Processor = require("modular-css");
14+
const processor = new Processor({
1515
// See "API Options" for valid options to pass to the Processor constructor
1616
});
1717

@@ -40,40 +40,6 @@ Promise.all([
4040

4141
## Options
4242

43-
### `before`
44-
45-
Specify an array of PostCSS plugins to be run against each file before it is processed.
46-
47-
```js
48-
new Processor({
49-
before : [ require("postcss-import") ]
50-
});
51-
```
52-
53-
### `after`
54-
55-
Specify an array of PostCSS plugins to be run after files are processed, but before they are combined. Plugin will be passed a `to` and `from` option.
56-
57-
**Default**: `[]`
58-
59-
:warning: [`postcss-url`](https://www.npmjs.com/package/postcss-url) automatically runs after any plugins defined in the `after` hook. To disable it use the [`rewrite`](#rewrite) option.
60-
61-
```js
62-
new Processor({
63-
after : [ require("postcss-someplugin") ]
64-
});
65-
```
66-
67-
### `done`
68-
69-
Specify an array of PostCSS plugins to be run against the complete combined CSS.
70-
71-
```js
72-
new Processor({
73-
done : [ require("cssnano")()]
74-
});
75-
```
76-
7743
### `rewrite`
7844

7945
Enable or disable the usage of [`postcss-url`](https://www.npmjs.com/package/postcss-url) to correct any URL references within the CSS. The value of `rewrite` will be passed to `postcss-url` to allow for configuration of the plugin.
@@ -205,6 +171,56 @@ new Processor({
205171
*/
206172
```
207173

174+
### Lifecycle Hooks
175+
176+
For more information on the intended uses of the Lifecycle Hooks, see [Extending `modular-css`](extending.md).
177+
178+
#### `before`
179+
180+
Specify an array of PostCSS plugins to be run against each file before it is processed. Plugin will be passed a `from` option.
181+
182+
```js
183+
new Processor({
184+
before : [ require("postcss-import") ]
185+
});
186+
```
187+
188+
#### `processing`
189+
190+
Specify an array of PostCSS plugins to be run against each file during processing. Plugin will be passed a `from` option.
191+
192+
```js
193+
new Processor({
194+
processing : [ require("postcss-import") ]
195+
});
196+
```
197+
198+
Plugins run during the `processing` stage can manipulate the object exported by the CSS file, see the [extending docs](#processing) for an example.
199+
200+
#### `after`
201+
202+
Specify an array of PostCSS plugins to be run after files are processed, but before they are combined. Plugin will be passed a `to` and `from` option.
203+
204+
**Default**: `[]`
205+
206+
:warning: [`postcss-url`](https://www.npmjs.com/package/postcss-url) automatically runs after any plugins defined in the `after` hook. To disable it use the [`rewrite`](#rewrite) option.
207+
208+
```js
209+
new Processor({
210+
after : [ require("postcss-someplugin") ]
211+
});
212+
```
213+
214+
#### `done`
215+
216+
Specify an array of PostCSS plugins to be run against the complete combined CSS. Plugin will be passed a `to` option.
217+
218+
```js
219+
new Processor({
220+
done : [ require("cssnano")()]
221+
});
222+
```
223+
208224
## Properties
209225

210226
### `.files`

docs/extending.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Extending `modular-css`
2+
3+
There are 4 built-in ways to extend the functionality of `modular-css`, the lifecycle hooks. They all can be used to add any number of [PostCSS Plugins](https://github.com/postcss/postcss/blob/master/docs/plugins.md) to `modular-css` at specific points in the processing cycle.
4+
5+
The lifecycle hooks are:
6+
7+
1. [`before`](#before)
8+
1. [`processing`](#processing)
9+
1. [`after`](#after)
10+
1. [`done`](#done)
11+
12+
## `before`
13+
14+
[API](api.md#before)
15+
16+
---
17+
18+
The `before` hook is run before a CSS file is ever processed by `modular-css`, so it provides access to rewrite files if they aren't actually CSS or contain non-standard syntax. Plugins like [`postcss-nested`](https://github.com/postcss/postcss-nested) go well here.
19+
20+
## `processing`
21+
22+
[API](api.md#processing)
23+
24+
---
25+
26+
The `processing` hook is run after `modular-css` has parsed the file, but before any response to [`processor.string`](api.md#string) or [`processor.file`](api.md#file) is returned. Plugins in this hook have a special power: they can change the exports of the file.
27+
28+
This works by having the plugin push an object onto the `result.messages` array. Here's a very simple example:
29+
30+
```js
31+
new Processor({
32+
processing : [
33+
(css, result) => {
34+
result.messages.push({
35+
plugin : "modular-css-exporter",
36+
exports : {
37+
a : true,
38+
b : false
39+
}
40+
});
41+
}
42+
]
43+
})
44+
```
45+
46+
The `plugin` field must begin with "modular-css-export", and the `exports` field should be the object to be mixed into the exports of the CSS file. It will be added last, so it can be used to override the default exports if desired.
47+
48+
## `after`
49+
50+
[API](api.md#after)
51+
52+
---
53+
54+
The `after` hook is run once the output location for the CSS is known, but before all the files are combined. By default it will run [`postcss-url`](https://github.com/postcss/postcss-url) to rebase file references based on the final output location, but this can be disabled using the [`rewrite`](api.md#rewrite) option.
55+
56+
Since all manipulations on the file are complete at this point it is a good place to run plugins like [`postcss-import`](https://github.com/postcss/postcss-import) to inline `@import` rules. The rules inlined in this way won't be scoped so it's a convenient way to pull in 3rd party code which can be included in the selector heirarchy via `composes`.
57+
58+
```css
59+
@import "bootstrap.css";
60+
61+
/* Will export as "btn .abc123_button" */
62+
.button {
63+
composes: global(btn);
64+
}
65+
```
66+
67+
## `done`
68+
69+
[API](api.md#done)
70+
71+
---
72+
73+
The `done` hook is run after all of the constituent files are combined into a single stylesheet. This makes it a good place to add tools like [`cssnano`](http://cssnano.co/) that need access to the entire stylesheet to be able to accurately optimize the CSS.

packages/core/lib/output.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ var map = require("lodash.mapvalues"),
55
relative = require("./relative.js");
66

77
exports.join = function(output) {
8-
return map(output, (classes) => classes.join(" "));
8+
return map(output, (classes) => (
9+
Array.isArray(classes) ?
10+
classes.join(" ") :
11+
classes.toString()
12+
));
913
};
1014

1115
exports.compositions = function(cwd, processor) {

packages/core/processor.js

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ function Processor(opts) {
8787
require("./plugins/externals.js"),
8888
require("./plugins/composition.js"),
8989
require("./plugins/keyframes.js")
90-
]);
90+
].concat(this._options.processing || []));
9191

9292
this._after = postcss(this._options.after || []);
9393

@@ -134,8 +134,16 @@ Processor.prototype = {
134134
return file.processed.then((result) => {
135135
file.exports = Object.assign(
136136
Object.create(null),
137-
mapValues(file.values, (obj) => [ obj.value ]),
138-
message(result, "classes")
137+
// export @value entries
138+
mapValues(file.values, (obj) => obj.value),
139+
140+
// export classes
141+
message(result, "classes"),
142+
143+
// Export anything from plugins named "modular-css-export*"
144+
result.messages
145+
.filter((msg) => msg.plugin.indexOf("modular-css-export") === 0)
146+
.reduce((prev, curr) => Object.assign(prev, curr.exports), Object.create(null))
139147
);
140148
file.result = result;
141149
});

packages/core/test/__snapshots__/options.test.js.snap

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ exports[`/processor.js options lifecycle options done should work with cssnano (
6161

6262
exports[`/processor.js options lifecycle options done should work with cssnano (no preset) 1`] = `".folder{margin:2px}.booga{background:green}"`;
6363

64+
exports[`/processor.js options lifecycle options processing should include exports from 'modular-css-export' modules 1`] = `
65+
Object {
66+
"a": true,
67+
"b": false,
68+
}
69+
`;
70+
71+
exports[`/processor.js options lifecycle options processing should run async postcss plugins processing processing 1`] = `
72+
"/* packages/core/test/specimens/async-processing.css */
73+
a {}"
74+
`;
75+
76+
exports[`/processor.js options lifecycle options processing should run sync postcss plugins processing processing 1`] = `
77+
"/* packages/core/test/specimens/sync-processing.css */
78+
a {}"
79+
`;
80+
6481
exports[`/processor.js options map should generate source maps 1`] = `
6582
"/* packages/core/test/specimens/folder/folder.css */
6683
.folder { margin: 2px; }

packages/core/test/__snapshots__/values.test.js.snap

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,9 @@ Object {
4141
"fooga": Array [
4242
"fooga",
4343
],
44-
"local": Array [
45-
"'./local.css'",
46-
],
47-
"o": Array [
48-
"red",
49-
],
50-
"one": Array [
51-
"red",
52-
],
44+
"local": "'./local.css'",
45+
"o": "red",
46+
"one": "red",
5347
}
5448
`;
5549

packages/core/test/issues/__snapshots__/issue-24.test.js.snap

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,7 @@ Object {
66
"wooga",
77
"a",
88
],
9-
"simple": Array [
10-
"\\"./simple.css\\"",
11-
],
9+
"simple": "\\"./simple.css\\"",
1210
}
1311
`;
1412

packages/core/test/options.test.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,57 @@ describe("/processor.js", () => {
243243
.then((result) => expect(result.css).toMatchSnapshot());
244244
});
245245
});
246+
247+
describe("processing", () => {
248+
it("should run sync postcss plugins processing processing", () => {
249+
var processor = new Processor({
250+
namer,
251+
processing : [ sync ]
252+
});
253+
254+
return processor.string(
255+
"packages/core/test/specimens/sync-processing.css",
256+
""
257+
)
258+
.then(() => processor.output({ from : "packages/core/test/specimens/sync-processing.css" }))
259+
.then((result) => expect(result.css).toMatchSnapshot());
260+
});
261+
262+
it("should run async postcss plugins processing processing", () => {
263+
var processor = new Processor({
264+
namer,
265+
processing : [ async ]
266+
});
267+
268+
return processor.string(
269+
"packages/core/test/specimens/async-processing.css",
270+
""
271+
)
272+
.then(() => processor.output({ from : "packages/core/test/specimens/sync-processing.css" }))
273+
.then((result) => expect(result.css).toMatchSnapshot());
274+
});
275+
276+
it("should include exports from 'modular-css-export' modules", () => {
277+
var processor = new Processor({
278+
namer,
279+
processing : [ (css, result) => {
280+
result.messages.push({
281+
plugin : "modular-css-exporter",
282+
exports : {
283+
a : true,
284+
b : false
285+
}
286+
});
287+
} ]
288+
});
289+
290+
return processor.string(
291+
"packages/core/test/specimens/async-processing.css",
292+
""
293+
)
294+
.then((file) => expect(file.exports).toMatchSnapshot());
295+
});
296+
});
246297

247298
describe("after", () => {
248299
it("should use postcss-url by default", () => {

0 commit comments

Comments
 (0)