Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow plugin exports via "processing" lifecycle hook #404

Merged
merged 3 commits into from
Feb 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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