Skip to content

Commit

Permalink
feat: Allow plugin exports via "processing" lifecycle hook (#404)
Browse files Browse the repository at this point in the history
Fixes #401
  • Loading branch information
tivac authored Feb 21, 2018
1 parent 2a16d07 commit 72a89de
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 53 deletions.
90 changes: 53 additions & 37 deletions docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

## Usage

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

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

Expand Down Expand Up @@ -40,40 +40,6 @@ Promise.all([

## Options

### `before`

Specify an array of PostCSS plugins to be run against each file before it is processed.

```js
new Processor({
before : [ require("postcss-import") ]
});
```

### `after`

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.

**Default**: `[]`

: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.

```js
new Processor({
after : [ require("postcss-someplugin") ]
});
```

### `done`

Specify an array of PostCSS plugins to be run against the complete combined CSS.

```js
new Processor({
done : [ require("cssnano")()]
});
```

### `rewrite`

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.
Expand Down Expand Up @@ -205,6 +171,56 @@ new Processor({
*/
```

### Lifecycle Hooks

For more information on the intended uses of the Lifecycle Hooks, see [Extending `modular-css`](extending.md).

#### `before`

Specify an array of PostCSS plugins to be run against each file before it is processed. Plugin will be passed a `from` option.

```js
new Processor({
before : [ require("postcss-import") ]
});
```

#### `processing`

Specify an array of PostCSS plugins to be run against each file during processing. Plugin will be passed a `from` option.

```js
new Processor({
processing : [ require("postcss-import") ]
});
```

Plugins run during the `processing` stage can manipulate the object exported by the CSS file, see the [extending docs](#processing) for an example.

#### `after`

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.

**Default**: `[]`

: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.

```js
new Processor({
after : [ require("postcss-someplugin") ]
});
```

#### `done`

Specify an array of PostCSS plugins to be run against the complete combined CSS. Plugin will be passed a `to` option.

```js
new Processor({
done : [ require("cssnano")()]
});
```

## Properties

### `.files`
Expand Down
73 changes: 73 additions & 0 deletions docs/extending.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Extending `modular-css`

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.

The lifecycle hooks are:

1. [`before`](#before)
1. [`processing`](#processing)
1. [`after`](#after)
1. [`done`](#done)

## `before`

[API](api.md#before)

---

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.

## `processing`

[API](api.md#processing)

---

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.

This works by having the plugin push an object onto the `result.messages` array. Here's a very simple example:

```js
new Processor({
processing : [
(css, result) => {
result.messages.push({
plugin : "modular-css-exporter",
exports : {
a : true,
b : false
}
});
}
]
})
```

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.

## `after`

[API](api.md#after)

---

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.

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`.

```css
@import "bootstrap.css";

/* Will export as "btn .abc123_button" */
.button {
composes: global(btn);
}
```

## `done`

[API](api.md#done)

---

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.
6 changes: 5 additions & 1 deletion packages/core/lib/output.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ var map = require("lodash.mapvalues"),
relative = require("./relative.js");

exports.join = function(output) {
return map(output, (classes) => classes.join(" "));
return map(output, (classes) => (
Array.isArray(classes) ?
classes.join(" ") :
classes.toString()
));
};

exports.compositions = function(cwd, processor) {
Expand Down
14 changes: 11 additions & 3 deletions packages/core/processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ function Processor(opts) {
require("./plugins/externals.js"),
require("./plugins/composition.js"),
require("./plugins/keyframes.js")
]);
].concat(this._options.processing || []));

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

Expand Down Expand Up @@ -134,8 +134,16 @@ Processor.prototype = {
return file.processed.then((result) => {
file.exports = Object.assign(
Object.create(null),
mapValues(file.values, (obj) => [ obj.value ]),
message(result, "classes")
// export @value entries
mapValues(file.values, (obj) => obj.value),

// export classes
message(result, "classes"),

// Export anything from plugins named "modular-css-export*"
result.messages
.filter((msg) => msg.plugin.indexOf("modular-css-export") === 0)
.reduce((prev, curr) => Object.assign(prev, curr.exports), Object.create(null))
);
file.result = result;
});
Expand Down
17 changes: 17 additions & 0 deletions packages/core/test/__snapshots__/options.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ exports[`/processor.js options lifecycle options done should work with cssnano (

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

exports[`/processor.js options lifecycle options processing should include exports from 'modular-css-export' modules 1`] = `
Object {
"a": true,
"b": false,
}
`;

exports[`/processor.js options lifecycle options processing should run async postcss plugins processing processing 1`] = `
"/* packages/core/test/specimens/async-processing.css */
a {}"
`;

exports[`/processor.js options lifecycle options processing should run sync postcss plugins processing processing 1`] = `
"/* packages/core/test/specimens/sync-processing.css */
a {}"
`;

exports[`/processor.js options map should generate source maps 1`] = `
"/* packages/core/test/specimens/folder/folder.css */
.folder { margin: 2px; }
Expand Down
12 changes: 3 additions & 9 deletions packages/core/test/__snapshots__/values.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,9 @@ Object {
"fooga": Array [
"fooga",
],
"local": Array [
"'./local.css'",
],
"o": Array [
"red",
],
"one": Array [
"red",
],
"local": "'./local.css'",
"o": "red",
"one": "red",
}
`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ Object {
"wooga",
"a",
],
"simple": Array [
"\\"./simple.css\\"",
],
"simple": "\\"./simple.css\\"",
}
`;

Expand Down
51 changes: 51 additions & 0 deletions packages/core/test/options.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,57 @@ describe("/processor.js", () => {
.then((result) => expect(result.css).toMatchSnapshot());
});
});

describe("processing", () => {
it("should run sync postcss plugins processing processing", () => {
var processor = new Processor({
namer,
processing : [ sync ]
});

return processor.string(
"packages/core/test/specimens/sync-processing.css",
""
)
.then(() => processor.output({ from : "packages/core/test/specimens/sync-processing.css" }))
.then((result) => expect(result.css).toMatchSnapshot());
});

it("should run async postcss plugins processing processing", () => {
var processor = new Processor({
namer,
processing : [ async ]
});

return processor.string(
"packages/core/test/specimens/async-processing.css",
""
)
.then(() => processor.output({ from : "packages/core/test/specimens/sync-processing.css" }))
.then((result) => expect(result.css).toMatchSnapshot());
});

it("should include exports from 'modular-css-export' modules", () => {
var processor = new Processor({
namer,
processing : [ (css, result) => {
result.messages.push({
plugin : "modular-css-exporter",
exports : {
a : true,
b : false
}
});
} ]
});

return processor.string(
"packages/core/test/specimens/async-processing.css",
""
)
.then((file) => expect(file.exports).toMatchSnapshot());
});
});

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

0 comments on commit 72a89de

Please sign in to comment.