├── .gitattributes ├── test ├── fixtures │ ├── no-sourcemaps │ │ ├── src │ │ │ └── styles.css │ │ └── expected │ │ │ └── styles.css │ ├── use-absolute-paths │ │ ├── src │ │ │ ├── foo.css │ │ │ └── style.css │ │ └── expected │ │ │ ├── foo.css │ │ │ └── style.css │ ├── inline-sourcemaps │ │ ├── src │ │ │ └── styles.css │ │ └── expected │ │ │ └── styles.css │ └── external-sourcemaps │ │ ├── src │ │ └── styles.css │ │ └── expected │ │ ├── styles.css │ │ └── styles.css.map ├── .eslintrc.yml └── index.js ├── .gitignore ├── .travis.yml ├── .eslintrc.yml ├── LICENSE ├── package.json ├── index.js └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /test/fixtures/no-sourcemaps/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: peachpuff; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/use-absolute-paths/src/foo.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/inline-sourcemaps/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: peachpuff; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/no-sourcemaps/expected/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: peachpuff; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/use-absolute-paths/expected/foo.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 1rem; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/external-sourcemaps/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: peachpuff; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/use-absolute-paths/src/style.css: -------------------------------------------------------------------------------- 1 | @import "foo.css"; 2 | 3 | body { 4 | color: peachpuff; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/external-sourcemaps/expected/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: peachpuff; 3 | } 4 | 5 | /*# sourceMappingURL=styles.css.map */ -------------------------------------------------------------------------------- /test/fixtures/use-absolute-paths/expected/style.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | font-size: 1rem; 3 | } 4 | 5 | body { 6 | color: peachpuff; 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/external-sourcemaps/expected/styles.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["styles.css"],"names":[],"mappings":"AAAA;EACE,iBAAiB;CAClB","file":"styles.css","sourcesContent":["body {\n color: peachpuff;\n}\n"]} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | *.DS_Store 10 | *.swp 11 | 12 | pids 13 | logs 14 | results 15 | 16 | node_modules 17 | components 18 | test/fixtures/**/build 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | cache: 4 | directories: 5 | - node_modules 6 | - $NVM_DIR 7 | 8 | node_js: 9 | - "6" 10 | - "8" 11 | - "10" 12 | 13 | script: 14 | - npm outdated || true 15 | - npm run lint 16 | - npm test 17 | -------------------------------------------------------------------------------- /test/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | mocha: true 3 | extends: 'eslint:recommended' 4 | rules: 5 | indent: 6 | - error 7 | - 2 8 | linebreak-style: 9 | - off # enforcing of linebreaks is done through .gitattributes 10 | quotes: 11 | - error 12 | - single 13 | semi: 14 | - error 15 | - always 16 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | node: true 3 | es6: true 4 | extends: 'eslint:recommended' 5 | rules: 6 | indent: 7 | - error 8 | - 2 9 | linebreak-style: 10 | - off # enforcing of linebreaks is done through .gitattributes 11 | quotes: 12 | - error 13 | - single 14 | semi: 15 | - error 16 | - always 17 | -------------------------------------------------------------------------------- /test/fixtures/inline-sourcemaps/expected/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | color: peachpuff; 3 | } 4 | 5 | /*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0eWxlcy5jc3MiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7RUFDRSxpQkFBaUI7Q0FDbEIiLCJmaWxlIjoic3R5bGVzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbImJvZHkge1xuICBjb2xvcjogcGVhY2hwdWZmO1xufVxuIl19 */ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 AXA Versicherungen AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metalsmith-postcss", 3 | "version": "4.2.0", 4 | "description": "A Metalsmith plugin that sends your CSS through any PostCSS plugins.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/.bin/mocha test --harmony", 8 | "lint:js": "eslint . *.js --max-warnings 0", 9 | "lint:security": "nsp check", 10 | "lint": "npm run lint:js && npm run lint:security" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/axa-ch/metalsmith-postcss" 15 | }, 16 | "keywords": [ 17 | "metalsmith", 18 | "postcss", 19 | "css", 20 | "preprocessor" 21 | ], 22 | "author": "AXA Versicherungen AG", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/axa-ch/metalsmith-postcss/issues" 26 | }, 27 | "homepage": "https://github.com/axa-ch/metalsmith-postcss", 28 | "dependencies": { 29 | "minimatch": "^3.0.4" 30 | }, 31 | "peerDependencies": { 32 | "postcss": "^5.0.4 || ^6.0.0" 33 | }, 34 | "devDependencies": { 35 | "assert-dir-equal": "^1.0.1", 36 | "eslint": "^5.0.0", 37 | "metalsmith": "^2.1.0", 38 | "mocha": "^5.0.0", 39 | "nsp": "^3.1.0", 40 | "postcss-import": "^11.0.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fixture = path.resolve.bind(path, __dirname, 'fixtures'); 3 | var equal = require('assert-dir-equal'); 4 | var Metalsmith = require('metalsmith'); 5 | var postcss = require('..'); 6 | 7 | describe('metalsmith-postcss', function () { 8 | 9 | describe('sourcemaps', function () { 10 | 11 | it('should not add sourcemaps at all', function (done) { 12 | var metalsmith = Metalsmith(fixture('no-sourcemaps')); 13 | metalsmith 14 | .use(postcss({ 15 | plugins: {} 16 | })) 17 | .build(function (err) { 18 | if (err) return done(err); 19 | equal(fixture('no-sourcemaps/build'), fixture('no-sourcemaps/expected')); 20 | done(); 21 | }); 22 | }); 23 | 24 | it('should add inline sourcemaps', function (done) { 25 | var metalsmith = Metalsmith(fixture('inline-sourcemaps')); 26 | metalsmith 27 | .use(postcss({ 28 | plugins: {}, 29 | map: true 30 | })) 31 | .build(function (err) { 32 | if (err) return done(err); 33 | equal(fixture('inline-sourcemaps/build'), fixture('inline-sourcemaps/expected')); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('should add external sourcemap files', function (done) { 39 | var metalsmith = Metalsmith(fixture('external-sourcemaps')); 40 | metalsmith 41 | .use(postcss({ 42 | plugins: {}, 43 | map: { 44 | inline: false 45 | } 46 | })) 47 | .build(function (err) { 48 | if (err) return done(err); 49 | equal(fixture('external-sourcemaps/build'), fixture('external-sourcemaps/expected')); 50 | done(); 51 | }); 52 | }); 53 | 54 | it('should pass absolute paths to postcss', function (done) { 55 | var metalsmith = Metalsmith(fixture('use-absolute-paths')); 56 | metalsmith 57 | .use(postcss({ 58 | plugins: { 59 | 'postcss-import': {} 60 | } 61 | })) 62 | .build(function (err) { 63 | if (err) return done(err); 64 | equal(fixture('use-absolute-paths/build'), fixture('use-absolute-paths/expected')); 65 | done(); 66 | }); 67 | }); 68 | 69 | it('should be able to use arrays as a way to define plugins', function (done) { 70 | var metalsmith = Metalsmith(fixture('use-absolute-paths')); 71 | metalsmith 72 | .use(postcss({ 73 | plugins: [{ 74 | 'postcss-import': {}, 75 | }], 76 | })) 77 | .build(function (err) { 78 | if (err) return done(err); 79 | equal(fixture('use-absolute-paths/build'), fixture('use-absolute-paths/expected')); 80 | done(); 81 | }); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var postcss = require('postcss'); 2 | var minimatch = require('minimatch'); 3 | var path = require('path'); 4 | 5 | module.exports = main; 6 | 7 | function main(options) { 8 | 9 | options = options || {}; 10 | var pluginsConfig = Array.isArray(options.plugins) ? options.plugins : [options.plugins]; 11 | var plugins = []; 12 | 13 | // Require each plugin, pass it it’s options 14 | // and add it to the plugins array. 15 | pluginsConfig.forEach(function (pluginsObject) { 16 | if (typeof pluginsObject === 'string') { 17 | plugins.push(require(pluginsObject)({})); 18 | } else { 19 | Object.keys(pluginsObject).forEach(function (pluginName) { 20 | var value = pluginsObject[pluginName]; 21 | if (value === false) return; 22 | var pluginOptions = value === true ? {} : value; 23 | plugins.push(require(pluginName)(pluginOptions)); 24 | }); 25 | } 26 | }); 27 | 28 | var map = normalizeMapOptions(options.map); 29 | 30 | var processor = postcss(plugins); 31 | 32 | return function (files, metalsmith, done) { 33 | var styles = Object.keys(files).filter(minimatch.filter(options.pattern || '*.css', { matchBase: true })); 34 | 35 | if(styles.length == 0) { 36 | done(); 37 | return; 38 | } 39 | 40 | var promises = []; 41 | 42 | styles.forEach(function (file) { 43 | var contents = files[file].contents.toString(); 44 | var absolutePath = path.resolve(metalsmith.source(), file); 45 | 46 | var promise = processor 47 | .process(contents, { 48 | from: absolutePath, 49 | to: absolutePath, 50 | map: map 51 | }) 52 | .then(function (result) { 53 | files[file].contents = new Buffer(result.css); 54 | 55 | if (result.map) { 56 | files[file + '.map'] = { 57 | contents: new Buffer(JSON.stringify(result.map)), 58 | mode: files[file].mode, 59 | stats: files[file].stats 60 | }; 61 | } 62 | }); 63 | 64 | promises.push(promise); 65 | }); 66 | 67 | Promise.all(promises) 68 | .then(function() { 69 | done(); 70 | }) 71 | .catch(function(error) { 72 | // JSON.stringify on an actual error object yields 0 key/values 73 | if (error instanceof Error) { 74 | return done(error); 75 | } 76 | done(new Error('Error during postcss processing: ' + JSON.stringify(error))); 77 | }); 78 | 79 | }; 80 | } 81 | 82 | function normalizeMapOptions(map) { 83 | if (!map) return undefined; 84 | 85 | return { 86 | inline: map === true ? true : map.inline, 87 | prev: false, // Not implemented yet 88 | sourcesContent: undefined, // Not implemented yet 89 | annotation: undefined // Not implemented yet 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # metalsmith-postcss 2 | 3 | [![Build Status](https://travis-ci.org/axa-ch/metalsmith-postcss.svg?branch=master)](https://travis-ci.org/axa-ch/metalsmith-postcss) 4 | [![Greenkeeper badge](https://badges.greenkeeper.io/axa-ch/metalsmith-postcss.svg)](https://greenkeeper.io/) 5 | [![Known Vulnerabilities](https://snyk.io/test/github/axa-ch/metalsmith-postcss/badge.svg)](https://snyk.io/test/github/axa-ch/metalsmith-postcss) 6 | [![NSP Status](https://nodesecurity.io/orgs/axa-ch/projects/394d1209-83a5-48f4-ae04-19dde4db3763/badge)](https://nodesecurity.io/orgs/axa-ch/projects/394d1209-83a5-48f4-ae04-19dde4db3763) 7 | 8 | > A Metalsmith plugin that sends your CSS 9 | > through any [PostCSS](https://github.com/postcss/postcss) plugins. 10 | 11 | ## Installation 12 | 13 | ```sh 14 | npm install metalsmith-postcss 15 | ``` 16 | 17 | ## Getting Started 18 | 19 | If you haven't checked out [Metalsmith](http://metalsmith.io/) before, 20 | head over to their website and check out the documentation. 21 | 22 | ## JavaScript API 23 | 24 | Using the JavaScript api for Metalsmith, 25 | just add the postcss package name, optionally with it’s 26 | options, to your `.use()` directives. Here is an example 27 | using `postcss-pseudoelements` and `postcss-nested` to 28 | transform your source files. 29 | 30 | ```js 31 | var postcss = require('metalsmith-postcss'); 32 | 33 | metalsmith.use(postcss({ 34 | plugins: { 35 | 'postcss-pseudoelements': {} 36 | 'postcss-nested': {} 37 | } 38 | })); 39 | ``` 40 | 41 | By default, files with `.css` extension will be parsed. This may be overridden 42 | by providing a custom pattern e.g. 43 | 44 | ```js 45 | metalsmith.use(postcss({ 46 | pattern: '*.postcss', 47 | plugins: { ... } 48 | })); 49 | ``` 50 | 51 | ## Metalsmith CLI 52 | 53 | Using the Metalsmith CLI, just add the postcss package name, 54 | optionally with it’s options, to your `metalsmith.json` config. 55 | Here is an example using `postcss-pseudoelements` and `postcss-nested` 56 | to transform your source files. 57 | 58 | ```js 59 | "metalsmith-postcss": { 60 | "plugins": { 61 | "postcss-pseudoelements": {}, 62 | "postcss-nested": {} 63 | }, 64 | "map": true 65 | } 66 | ``` 67 | 68 | ## Alternative plugin definition syntax 69 | 70 | Sometime in PostCSS, plugins need to be defined in a certain order and JavaScript 71 | Objects cannot guarantee the order of keys in an object. Therefore, you are able 72 | to specify PostCSS plugins using an array of objects(which can guarantee the order 73 | of loading). 74 | 75 | ```js 76 | "metalsmith-postcss": { 77 | "plugins": [ 78 | "postcss-pseudoelements", 79 | { 80 | "postcss-nested": { 81 | "some": "config" 82 | } 83 | } 84 | ] 85 | } 86 | ``` 87 | 88 | ## Sourcemaps 89 | 90 | This plugin doesn't generate sourcemaps by default. However, you 91 | can enable them using several ways. 92 | 93 | ### Inline sourcemaps 94 | 95 | Add `map: true` to the `options` argument to get your 96 | sourcemaps written into the source file. 97 | 98 | ```js 99 | metalsmith.use(postcss({ 100 | plugins: {}, 101 | map: true 102 | })); 103 | ``` 104 | 105 | Behind the scenes, this resolves to the following: 106 | 107 | ```js 108 | metalsmith.use(postcss({ 109 | plugins: {}, 110 | map: { 111 | inline: true 112 | } 113 | })); 114 | ``` 115 | 116 | ### External sourcemaps 117 | 118 | If you don't want to have your files polluted with sourcemaps, 119 | just set `inline: false`. Using that, you'll get `.map` files 120 | generated beside your sources. 121 | 122 | ```js 123 | metalsmith.use(postcss({ 124 | plugins: {}, 125 | map: { 126 | inline: false 127 | } 128 | })); 129 | ``` 130 | 131 | ## Test 132 | 133 | To run the tests use: 134 | 135 | ```sh 136 | npm test 137 | ``` 138 | --------------------------------------------------------------------------------