Skip to content
Permalink
Browse files

feat: Allow plugin exports via "processing" lifecycle hook (#404)

Fixes #401
  • Loading branch information...
tivac committed Feb 21, 2018
1 parent 2a16d07 commit 72a89ded1dd40313aa641481a43de334dd2fe938
@@ -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
});
@@ -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.
@@ -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`
@@ -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.
@@ -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) {
@@ -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 || []);

@@ -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;
});
@@ -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; }
@@ -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",
}
`;

@@ -6,9 +6,7 @@ Object {
"wooga",
"a",
],
"simple": Array [
"\\"./simple.css\\"",
],
"simple": "\\"./simple.css\\"",
}
`;

@@ -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", () => {

0 comments on commit 72a89de

Please sign in to comment.
You can’t perform that action at this time.