diff --git a/.babelrc b/.babelrc index 073bc2d..af12ba4 100644 --- a/.babelrc +++ b/.babelrc @@ -1,8 +1,5 @@ { "presets": [ - ["es2015", { "loose": true }] - ], - "plugins": [ - "add-module-exports" + ["@babel/preset-env", { "loose": true, "targets": { "node": "12" } }] ] } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fc2c3b2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: CI +on: + - push + - pull_request +jobs: + Test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: + - 12 + - 16 + steps: + - name: Clone repository + uses: actions/checkout@v2 + + - name: Use Node ${{ matrix.node-version }} + uses: actions/setup-node@v2 + with: + node-version: ${{ matrix.node-version }} + + - name: Install dependencies + run: npm install + + - name: Test + run: npm test diff --git a/.gitignore b/.gitignore index 2c2b14b..bdcfdb8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules/ test/build/ lib/ +package-lock.json diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index ea1830f..0000000 --- a/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -sudo: false -language: node_js -node_js: - - stable - - "4" - - "0.12" -branches: - only: - - master diff --git a/CHANGELOG.md b/CHANGELOG.md index 5b0d557..47dc73a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [5.0.0] - 2021-11-08 +### Changed +- Support PostCSS >= 8 +- Support Node >= 12 +- Dependencies upgrade +- Default export is now inside `default` (follows default Babel implementation) + ## [4.2.1] - 2017-02-07 ### Fixed - Preserve source of original declarations - See #90 diff --git a/README.md b/README.md index 96585f0..9f44233 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# postcss-sprites [![Build Status](https://travis-ci.org/2createStudio/postcss-sprites.svg?branch=master)](https://travis-ci.org/2createStudio/postcss-sprites) [![npm version](https://badge.fury.io/js/postcss-sprites.svg)](http://badge.fury.io/js/postcss-sprites) +# postcss-sprites [![Build Status](https://github.com/niksy/postcss-sprites/workflows/CI/badge.svg?branch=postcss-8-support)](https://github.com/niksy/postcss-sprites/actions?query=workflow%3ACI) [![npm version](https://badge.fury.io/js/@niksy%2Fpostcss-sprites.svg)](https://badge.fury.io/js/@niksy%2Fpostcss-sprites) > [PostCSS](https://github.com/postcss/postcss) plugin that generates spritesheets from your stylesheets. @@ -21,7 +21,7 @@ ```javascript var fs = require('fs'); var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var { default: sprites } = require('@niksy/postcss-sprites'); var css = fs.readFileSync('./css/style.css', 'utf8'); var opts = { @@ -265,7 +265,7 @@ Every filter or group function will be called with an ``Image`` object. Pull requests are welcome. ``` -$ git clone git@github.com:2createStudio/postcss-sprites.git +$ git clone git@github.com:niksy/postcss-sprites.git $ npm install $ npm run watch ``` diff --git a/examples/filter-by.md b/examples/filter-by.md index 5f58e7a..7b6328c 100644 --- a/examples/filter-by.md +++ b/examples/filter-by.md @@ -10,7 +10,7 @@ ```javascript var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var sprites = require('@niksy/postcss-sprites'); var opts = { stylesheetPath: './css', spritePath: './css/images/', diff --git a/examples/group-by.md b/examples/group-by.md index 64563e6..0cf6e6b 100644 --- a/examples/group-by.md +++ b/examples/group-by.md @@ -12,7 +12,7 @@ ```javascript var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var sprites = require('@niksy/postcss-sprites'); var opts = { stylesheetPath: './css', spritePath: './css/images/', diff --git a/examples/output-dimensions.md b/examples/output-dimensions.md index 2935eda..3c9eb2d 100644 --- a/examples/output-dimensions.md +++ b/examples/output-dimensions.md @@ -10,7 +10,7 @@ ```javascript var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var sprites = require('@niksy/postcss-sprites'); var updateRule = require('postcss-sprites/lib/core').updateRule; var opts = { stylesheetPath: './css', diff --git a/examples/relative-to-rule.md b/examples/relative-to-rule.md index 114ccdb..df73e4b 100644 --- a/examples/relative-to-rule.md +++ b/examples/relative-to-rule.md @@ -16,7 +16,7 @@ ```javascript var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var sprites = require('@niksy/postcss-sprites'); var opts = { stylesheetPath: './css', spritePath: './css/images/', diff --git a/examples/responsive-spritesheets.md b/examples/responsive-spritesheets.md index ca5617f..06531ef 100644 --- a/examples/responsive-spritesheets.md +++ b/examples/responsive-spritesheets.md @@ -10,7 +10,7 @@ ```javascript var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var sprites = require('@niksy/postcss-sprites'); var opts = { stylesheetPath: './css', spritePath: './css/images/', diff --git a/examples/skip-prefix.md b/examples/skip-prefix.md index 3428ec2..fb6ff3f 100644 --- a/examples/skip-prefix.md +++ b/examples/skip-prefix.md @@ -13,7 +13,7 @@ There [used to be a `skipPrefix` plugin option](https://github.com/2createStudio ```js var path = require('path'); var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var sprites = require('@niksy/postcss-sprites'); var opts = { stylesheetPath: './css', spritePath: './css/images/', diff --git a/examples/webpack-hot-load.md b/examples/webpack-hot-load.md index 5dc72e6..e540788 100644 --- a/examples/webpack-hot-load.md +++ b/examples/webpack-hot-load.md @@ -10,7 +10,7 @@ If you want to hot load image assets as they are introduced or edited, you can c ```js var path = require('path'); var postcss = require('postcss'); -var sprites = require('postcss-sprites'); +var sprites = require('@niksy/postcss-sprites'); var revHash = require('rev-hash'); module.exports = function loadPostcssPlugins() { diff --git a/package.json b/package.json index d05c091..e72c01d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "postcss-sprites", - "version": "4.2.1", + "name": "@niksy/postcss-sprites", + "version": "5.0.0", "description": "Generate spritesheets from stylesheets", "main": "lib/index.js", "keywords": [ @@ -14,31 +14,48 @@ "svg" ], "scripts": { - "test": "./node_modules/.bin/ava test/*.js --verbose --serial", - "build": "./node_modules/.bin/babel src/ --out-dir lib/", - "watch": "./node_modules/.bin/babel --watch src/ --out-dir lib/", - "prepublish": "npm test", - "pretest": "npm run build" + "test": "ava test/*.js --verbose --serial", + "build": "babel src/ --out-dir lib/", + "watch": "babel --watch src/ --out-dir lib/", + "prepublishOnly": "npm run build", + "pretest": "npm run build", + "release": "np" }, - "repository": "2createStudio/postcss-sprites", + "repository": "niksy/postcss-sprites", "author": "2createStudio", "license": "MIT", - "bugs": "https://github.com/2createStudio/postcss-sprites/issues", - "homepage": "https://github.com/2createStudio/postcss-sprites#readme", + "bugs": "https://github.com/niksy/postcss-sprites/issues", + "homepage": "https://github.com/niksy/postcss-sprites#readme", "devDependencies": { - "ava": "^0.15.2", - "babel-cli": "^6.4.0", - "babel-plugin-add-module-exports": "^0.2.1", - "babel-preset-es2015": "^6.3.13" + "@ava/babel": "^2.0.0", + "@babel/cli": "^7.16.0", + "@babel/preset-env": "^7.16.0", + "ava": "^3.15.0", + "np": "^6.5.0", + "postcss": "^8.1.2" }, "dependencies": { - "bluebird": "^3.1.1", - "debug": "^2.6.0", - "fs-extra": "^0.26.4", + "debug": "^4.3.2", + "fs-extra": "^10.0.0", "lodash": "^4.0.0", - "postcss": "^5.0.14", + "p-each-series": "^2.2.0", + "p-filter": "^2.1.0", + "p-map": "^4.0.0", + "p-reduce": "^2.1.0", "spritesmith": "^3.0.1", "svg-sprite": "^1.3.5" }, - "typings": "./typings.d.ts" + "peerDependencies": { + "postcss": "^8.1.2" + }, + "typings": "./typings.d.ts", + "engines": { + "node": ">=12" + }, + "ava": { + "babel": true + }, + "publishConfig": { + "access": "public" + } } diff --git a/src/core.js b/src/core.js index 08579a6..bf1722e 100644 --- a/src/core.js +++ b/src/core.js @@ -1,16 +1,14 @@ import path from 'path'; import fs from 'fs-extra'; -import Promise from 'bluebird'; import _ from 'lodash'; import debug from 'debug'; +import pFilter from 'p-filter'; +import pReduce from 'p-reduce'; +import pMap from 'p-map'; +import pEach from 'p-each-series'; import RasterFactory from './factories/raster'; import VectorFactory from './factories/vector'; -/** - * Wrap with promises. - */ -Promise.promisifyAll(fs); - /** * Plugin constants. */ @@ -61,7 +59,7 @@ export const defaults = { shape: { id: { generator(name, file) { - return new Buffer(file.path).toString('base64'); + return Buffer.from(file.path).toString('base64'); } } }, @@ -86,7 +84,7 @@ export function prepareFilterBy(opts, result) { // Filter non existing images opts.filterBy.unshift(image => { - return fs.statAsync(image.path) + return fs.stat(image.path) .catch(() => { const message = `Skip ${image.url} because doesn't exist.`; @@ -201,8 +199,8 @@ export function extractImages(root, opts, result) { export function applyFilterBy(opts, images) { opts.logger('Applying the filters...'); - return Promise.reduce(opts.filterBy, (images, filterFn) => { - return Promise.filter(images, (image) => { + return pReduce(opts.filterBy, (images, filterFn) => { + return pFilter(images, (image) => { return filterFn(image) .then(() => true) .catch(() => false); @@ -219,8 +217,8 @@ export function applyFilterBy(opts, images) { export function applyGroupBy(opts, images) { opts.logger('Applying the groups...'); - return Promise.reduce(opts.groupBy, (images, groupFn) => { - return Promise.map(images, (image) => { + return pReduce(opts.groupBy, (images, groupFn) => { + return pMap(images, (image) => { return groupFn(image) .then(group => { image.groups.push(group); @@ -344,7 +342,7 @@ export function runSpritesmith(opts, images) { export function saveSpritesheets(opts, images, spritesheets) { opts.logger('Saving the spritesheets...'); - return Promise.each(spritesheets, (spritesheet) => { + return pEach(spritesheets, (spritesheet) => { return ( _.isFunction(opts.hooks.onSaveSpritesheet) ? Promise.resolve(opts.hooks.onSaveSpritesheet(opts, spritesheet)) : @@ -363,7 +361,7 @@ export function saveSpritesheets(opts, images, spritesheets) { spritesheet.path = spritesheet.path.replace(/\\/g, '/'); - return fs.outputFileAsync(spritesheet.path, spritesheet.image); + return fs.outputFile(spritesheet.path, spritesheet.image); }); }).then(spritesheets => { return [opts, images, spritesheets]; diff --git a/src/factories/raster.js b/src/factories/raster.js index 2e14d0e..b201772 100644 --- a/src/factories/raster.js +++ b/src/factories/raster.js @@ -1,5 +1,5 @@ import Spritesmith from 'spritesmith'; -import Promise from 'bluebird'; +import { promisify } from 'util'; import _ from 'lodash'; /** @@ -27,7 +27,7 @@ export default function run(opts, images) { } } - return Promise.promisify(Spritesmith.run, { context: Spritesmith })(config) + return promisify(Spritesmith.run.bind(Spritesmith))(config) .then((spritesheet) => { spritesheet.extension = 'png'; diff --git a/src/factories/vector.js b/src/factories/vector.js index 72a432a..59a0241 100644 --- a/src/factories/vector.js +++ b/src/factories/vector.js @@ -1,6 +1,6 @@ import fs from 'fs'; import SVGSpriter from 'svg-sprite'; -import Promise from 'bluebird'; +import { promisify } from 'util'; import _ from 'lodash'; /** @@ -17,10 +17,15 @@ export default function run(opts, images) { spriter.add(path, null, fs.readFileSync(path, { encoding: 'utf-8' })); }); - return Promise.promisify(spriter.compile, { - context: spriter, - multiArgs: true - })().spread((result, data) => { + return new Promise((resolve, reject) => { + spriter.compile((error, ...result) => { + if (error) { + reject(error); + return + } + resolve(result); + }) + }).then(([result, data]) => { const spritesheet = {}; spritesheet.extension = 'svg'; @@ -33,7 +38,7 @@ export default function run(opts, images) { data.css.shapes.forEach((shape) => { - spritesheet.coordinates[new Buffer(shape.name, 'base64').toString()] = { + spritesheet.coordinates[Buffer.from(shape.name, 'base64').toString()] = { width: shape.width.outer, height: shape.height.outer, x: shape.position.absolute.x, diff --git a/src/index.js b/src/index.js index 49e424b..1eae758 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ -import postcss from 'postcss'; import _ from 'lodash'; import { defaults, @@ -18,34 +17,40 @@ import { /** * Plugin registration. */ -export default postcss.plugin('postcss-sprites', (options = {}) => { - return (css, result) => { - // Extend defaults - const opts = _.merge({}, defaults, options); +const plugin = (options = {}) => { + return { + postcssPlugin: 'postcss-sprites', + Once: (css, { result }) => { + // Extend defaults + const opts = _.merge({}, defaults, options); - // Setup the logger - opts.logger = createLogger(opts.verbose); + // Setup the logger + opts.logger = createLogger(opts.verbose); - // Prepare filter & group functions - prepareFilterBy(opts, result); - prepareGroupBy(opts); + // Prepare filter & group functions + prepareFilterBy(opts, result); + prepareGroupBy(opts); - // Process it - return extractImages(css, opts, result) - .spread((opts, images) => applyFilterBy(opts, images)) - .spread((opts, images) => applyGroupBy(opts, images)) - .spread((opts, images) => setTokens(css, opts, images)) - .spread((root, opts, images) => runSpritesmith(opts, images)) - .spread((opts, images, spritesheets) => saveSpritesheets(opts, images, spritesheets)) - .spread((opts, images, spritesheets) => mapSpritesheetProps(opts, images, spritesheets)) - .spread((opts, images, spritesheets) => updateReferences(css, opts, images, spritesheets)) - .spread((root, opts, images, spritesheets) => { - opts.logger(`${spritesheets.length} ${spritesheets.length > 1 ? 'spritesheets' : 'spritesheet'} generated.`); - }) - .catch((err) => { - console.error(`postcss-sprites: An error occurred while processing files - ${err.message}`); - console.error(err.stack); - throw err; - }); + // Process it + return extractImages(css, opts, result) + .then(([opts, images]) => applyFilterBy(opts, images)) + .then(([opts, images]) => applyGroupBy(opts, images)) + .then(([opts, images]) => setTokens(css, opts, images)) + .then(([root, opts, images]) => runSpritesmith(opts, images)) + .then(([opts, images, spritesheets]) => saveSpritesheets(opts, images, spritesheets)) + .then(([opts, images, spritesheets]) => mapSpritesheetProps(opts, images, spritesheets)) + .then(([opts, images, spritesheets]) => updateReferences(css, opts, images, spritesheets)) + .then(([root, opts, images, spritesheets]) => { + opts.logger(`${spritesheets.length} ${spritesheets.length > 1 ? 'spritesheets' : 'spritesheet'} generated.`); + }) + .catch((err) => { + console.error(`postcss-sprites: An error occurred while processing files - ${err.message}`); + console.error(err.stack); + throw err; + }); + } } -}); +}; +plugin.postcss = true; + +export default plugin; diff --git a/test/04-apply-filter-by.js b/test/04-apply-filter-by.js index a614ef1..9089198 100644 --- a/test/04-apply-filter-by.js +++ b/test/04-apply-filter-by.js @@ -1,6 +1,5 @@ import test from 'ava'; import postcss from 'postcss'; -import Promise from 'bluebird'; import _ from 'lodash'; import { defaults, prepareFilterBy, extractImages, applyFilterBy } from '../lib/core'; diff --git a/test/05-apply-group-by.js b/test/05-apply-group-by.js index 2ccb955..80e41c5 100644 --- a/test/05-apply-group-by.js +++ b/test/05-apply-group-by.js @@ -1,6 +1,5 @@ import test from 'ava'; import postcss from 'postcss'; -import Promise from 'bluebird'; import _ from 'lodash'; import { defaults, prepareGroupBy, extractImages, applyGroupBy } from '../lib/core'; diff --git a/test/07-run-spritesmith.js b/test/07-run-spritesmith.js index c42b983..00f64fe 100644 --- a/test/07-run-spritesmith.js +++ b/test/07-run-spritesmith.js @@ -1,19 +1,16 @@ import test from 'ava'; import postcss from 'postcss'; import _ from 'lodash'; -import Promise from 'bluebird'; -import fs from 'fs'; +import fs from 'fs-extra'; import { defaults, extractImages, prepareGroupBy, applyGroupBy, runSpritesmith } from '../lib/core'; -const readFileAsync = Promise.promisify(fs.readFile); - test.beforeEach((t) => { t.context.opts = _.merge({ logger() {} }, defaults); }); test('should generate spritesheets', async (t) => { - const cssContents = await readFileAsync('./fixtures/basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts; [ opts, images ] = await extractImages(ast, t.context.opts); @@ -24,8 +21,8 @@ test('should generate spritesheets', async (t) => { }); test('should generate SVG spritesheets', async (t) => { - const cssContents = await readFileAsync('./fixtures/svg-basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/svg-basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/svg-basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/svg-basic/style.css' }); let images, spritesheets, opts; prepareGroupBy(t.context.opts); @@ -38,8 +35,8 @@ test('should generate SVG spritesheets', async (t) => { }); test('should generate spritesheets by groups', async (t) => { - const cssContents = await readFileAsync('./fixtures/retina/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/retina/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/retina/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/retina/style.css' }); let images, spritesheets, opts; t.context.opts.retina = true; diff --git a/test/08-save-spritesheets.js b/test/08-save-spritesheets.js index b792999..5cf5e2c 100644 --- a/test/08-save-spritesheets.js +++ b/test/08-save-spritesheets.js @@ -1,7 +1,6 @@ import test from 'ava'; import postcss from 'postcss'; import _ from 'lodash'; -import Promise from 'bluebird'; import fs from 'fs-extra'; import path from 'path'; import { @@ -13,33 +12,31 @@ import { saveSpritesheets } from '../lib/core'; -const readFileAsync = Promise.promisify(fs.readFile); - test.beforeEach((t) => { t.context.opts = _.merge({ logger() {} }, defaults); }); test('should save spritesheets', async (t) => { - const cssContents = await readFileAsync('./fixtures/basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/basic'; + t.context.opts.spritePath = './test/build/basic'; [ opts, images ] = await extractImages(ast, t.context.opts); [ opts, images, spritesheets ] = await runSpritesmith(t.context.opts, images); [ opts, images, spritesheets ] = await saveSpritesheets(t.context.opts, images, spritesheets); - t.deepEqual(spritesheets[0].path, 'build/basic/sprite.png'); - t.truthy(fs.statAsync('./build/basic/sprite.png')); + t.deepEqual(spritesheets[0].path, 'test/build/basic/sprite.png'); + t.truthy(fs.stat('./test/build/basic/sprite.png')); }); test('should save SVG spritesheets', async (t) => { - const cssContents = await readFileAsync('./fixtures/svg-basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/svg-basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/svg-basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/svg-basic/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/svg-basic'; + t.context.opts.spritePath = './test/build/svg-basic'; prepareGroupBy(t.context.opts); [ opts, images ] = await extractImages(ast, t.context.opts); @@ -47,16 +44,16 @@ test('should save SVG spritesheets', async (t) => { [ opts, images, spritesheets ] = await runSpritesmith(t.context.opts, images); [ opts, images, spritesheets ] = await saveSpritesheets(t.context.opts, images, spritesheets); - t.deepEqual(spritesheets[0].path, 'build/svg-basic/sprite.svg'); - t.truthy(fs.statAsync('./build/svg-basic/sprite.svg')); + t.deepEqual(spritesheets[0].path, 'test/build/svg-basic/sprite.svg'); + t.truthy(fs.stat('./test/build/svg-basic/sprite.svg')); }); test('should save spritesheets by groups', async (t) => { - const cssContents = await readFileAsync('./fixtures/retina/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/retina/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/retina/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/retina/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/retina'; + t.context.opts.spritePath = './test/build/retina'; t.context.opts.retina = true; prepareGroupBy(t.context.opts); @@ -66,18 +63,18 @@ test('should save spritesheets by groups', async (t) => { [ opts, images, spritesheets ] = await runSpritesmith(t.context.opts, images); [ opts, images, spritesheets ] = await saveSpritesheets(t.context.opts, images, spritesheets); - t.deepEqual(spritesheets[0].path, 'build/retina/sprite.png'); - t.deepEqual(spritesheets[1].path, 'build/retina/sprite.@2x.png'); - t.truthy(fs.statAsync('./build/retina/sprite.png')); - t.truthy(fs.statAsync('./build/retina/sprite.@2x.png')); + t.deepEqual(spritesheets[0].path, 'test/build/retina/sprite.png'); + t.deepEqual(spritesheets[1].path, 'test/build/retina/sprite.@2x.png'); + t.truthy(fs.stat('./test/build/retina/sprite.png')); + t.truthy(fs.stat('./test/build/retina/sprite.@2x.png')); }); test('should use path provided by book', async (t) => { - const cssContents = await readFileAsync('./fixtures/basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/on-save-hook/'; + t.context.opts.spritePath = './test/build/on-save-hook/'; t.context.opts.hooks.onSaveSpritesheet = (pluginOpts, spritesheetGroups) => { return path.join(pluginOpts.spritePath, 'custom-name.png'); } @@ -86,16 +83,16 @@ test('should use path provided by book', async (t) => { [ opts, images, spritesheets ] = await runSpritesmith(t.context.opts, images); [ opts, images, spritesheets ] = await saveSpritesheets(t.context.opts, images, spritesheets); - t.deepEqual(spritesheets[0].path, 'build/on-save-hook/custom-name.png'); - t.truthy(fs.statAsync('./build/on-save-hook/custom-name.png')); + t.deepEqual(spritesheets[0].path, 'test/build/on-save-hook/custom-name.png'); + t.truthy(fs.stat('./test/build/on-save-hook/custom-name.png')); }); test('should throw error if path is empty', async (t) => { - const cssContents = await readFileAsync('./fixtures/basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/on-save-hook/'; + t.context.opts.spritePath = './test/build/on-save-hook/'; t.context.opts.hooks.onSaveSpritesheet = (pluginOpts, spritesheetGroups) => { return ''; } @@ -103,15 +100,15 @@ test('should throw error if path is empty', async (t) => { [ opts, images ] = await extractImages(ast, t.context.opts); [ opts, images, spritesheets ] = await runSpritesmith(t.context.opts, images); - t.throws(saveSpritesheets(t.context.opts, images, spritesheets)); + return t.throwsAsync(() => saveSpritesheets(t.context.opts, images, spritesheets)); }); test('should use Promise result provided by book', async (t) => { - const cssContents = await readFileAsync('./fixtures/basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/on-save-hook/'; + t.context.opts.spritePath = './test/build/on-save-hook/'; t.context.opts.hooks.onSaveSpritesheet = (pluginOpts, spritesheetGroups) => { return new Promise(( resolve ) => setTimeout(() => resolve(Promise.resolve(path.join(pluginOpts.spritePath, 'custom-name.png'))), 0)); } @@ -120,6 +117,6 @@ test('should use Promise result provided by book', async (t) => { [ opts, images, spritesheets ] = await runSpritesmith(t.context.opts, images); [ opts, images, spritesheets ] = await saveSpritesheets(t.context.opts, images, spritesheets); - t.deepEqual(spritesheets[0].path, 'build/on-save-hook/custom-name.png'); - t.truthy(fs.statAsync('./build/on-save-hook/custom-name.png')); + t.deepEqual(spritesheets[0].path, 'test/build/on-save-hook/custom-name.png'); + t.truthy(fs.stat('./test/build/on-save-hook/custom-name.png')); }); diff --git a/test/09-map-spritesheet-props.js b/test/09-map-spritesheet-props.js index 3261559..560b634 100644 --- a/test/09-map-spritesheet-props.js +++ b/test/09-map-spritesheet-props.js @@ -1,7 +1,6 @@ import test from 'ava'; import postcss from 'postcss'; import _ from 'lodash'; -import Promise from 'bluebird'; import fs from 'fs-extra'; import { defaults, @@ -13,34 +12,32 @@ import { applyGroupBy } from '../lib/core'; -const readFileAsync = Promise.promisify(fs.readFile); - test.beforeEach((t) => { t.context.opts = _.merge({ logger() {} }, defaults); }); test('should add coords & spritePath to every image', async (t) => { - const cssContents = await readFileAsync('./fixtures/basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/basic'; + t.context.opts.spritePath = './test/build/basic'; [ opts, images ] = await extractImages(ast, t.context.opts); [ opts, images, spritesheets ] = await runSpritesmith(t.context.opts, images); [ opts, images, spritesheets ] = await saveSpritesheets(t.context.opts, images, spritesheets); [ opts, images, spritesheets ] = await mapSpritesheetProps(t.context.opts, images, spritesheets); - t.deepEqual(images[0].spritePath, 'build/basic/sprite.png'); + t.deepEqual(images[0].spritePath, 'test/build/basic/sprite.png'); t.deepEqual(images[0].coords, { x: 0, y: 0, height: 25, width: 25 }); }); test('should add coords & spritePath to every SVG image', async (t) => { - const cssContents = await readFileAsync('./fixtures/svg-basic/style.css'); - const ast = postcss.parse(cssContents, { from: './fixtures/svg-basic/style.css' }); + const cssContents = await fs.readFile('./test/fixtures/svg-basic/style.css'); + const ast = postcss.parse(cssContents, { from: './test/fixtures/svg-basic/style.css' }); let images, spritesheets, opts; - t.context.opts.spritePath = './build/svg-basic'; + t.context.opts.spritePath = './test/build/svg-basic'; prepareGroupBy(t.context.opts); [ opts, images ] = await extractImages(ast, t.context.opts); @@ -49,6 +46,6 @@ test('should add coords & spritePath to every SVG image', async (t) => { [ opts, images, spritesheets ] = await saveSpritesheets(t.context.opts, images, spritesheets); [ opts, images, spritesheets ] = await mapSpritesheetProps(t.context.opts, images, spritesheets); - t.deepEqual(images[0].spritePath, 'build/svg-basic/sprite.svg'); - t.deepEqual(images[0].coords, { x: 0, y: 0, height: 600, width: 600 }); + t.deepEqual(images[0].spritePath, 'test/build/svg-basic/sprite.svg'); + t.deepEqual(images[0].coords, { x: -0, y: -0, height: 600, width: 600 }); }); diff --git a/test/10-update-references.js b/test/10-update-references.js index 46b040f..cfef5fc 100644 --- a/test/10-update-references.js +++ b/test/10-update-references.js @@ -1,7 +1,6 @@ import test from 'ava'; import postcss from 'postcss'; import _ from 'lodash'; -import Promise from 'bluebird'; import fs from 'fs-extra'; import { defaults, @@ -14,20 +13,18 @@ import { updateRule } from '../lib/core'; -const readFileAsync = Promise.promisify(fs.readFile); - test.beforeEach((t) => { t.context.opts = _.merge({ logger() {} }, defaults); }); test('should update CSS declarations', async (t) => { - const input = await readFileAsync('./fixtures/basic/style.css'); - const expected = await readFileAsync('./expectations/basic/style.css', 'utf8'); - const ast = postcss.parse(input, { from: './fixtures/basic/style.css' }); + const input = await fs.readFile('./test/fixtures/basic/style.css'); + const expected = await fs.readFile('./test/expectations/basic/style.css', 'utf8'); + const ast = postcss.parse(input, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts, root; - t.context.opts.spritePath = './build/basic'; - t.context.opts.stylesheetPath = './build/basic'; + t.context.opts.spritePath = './test/build/basic'; + t.context.opts.stylesheetPath = './test/build/basic'; [ opts, images ] = await extractImages(ast, t.context.opts); [ root, opts, images ] = await setTokens(ast, t.context.opts, images); @@ -40,12 +37,12 @@ test('should update CSS declarations', async (t) => { }); test('should update CSS declarations with relative paths', async (t) => { - const input = await readFileAsync('./fixtures/relative/style.css'); - const expected = await readFileAsync('./expectations/relative/style.css', 'utf8'); - const ast = postcss.parse(input, { from: './fixtures/relative/style.css' }); + const input = await fs.readFile('./test/fixtures/relative/style.css'); + const expected = await fs.readFile('./test/expectations/relative/style.css', 'utf8'); + const ast = postcss.parse(input, { from: './test/fixtures/relative/style.css' }); let images, spritesheets, opts, root; - t.context.opts.spritePath = './build/relative'; + t.context.opts.spritePath = './test/build/relative'; [ opts, images ] = await extractImages(ast, t.context.opts); [ root, opts, images ] = await setTokens(ast, t.context.opts, images); @@ -58,13 +55,13 @@ test('should update CSS declarations with relative paths', async (t) => { }); test('should use function provided by onUpdateRule hook', async (t) => { - const input = await readFileAsync('./fixtures/basic/style.css'); - const expected = await readFileAsync('./expectations/basic-on-update-rule-hook/style.css', 'utf8'); - const ast = postcss.parse(input, { from: './fixtures/basic/style.css' }); + const input = await fs.readFile('./test/fixtures/basic/style.css'); + const expected = await fs.readFile('./test/expectations/basic-on-update-rule-hook/style.css', 'utf8'); + const ast = postcss.parse(input, { from: './test/fixtures/basic/style.css' }); let images, spritesheets, opts, root; - t.context.opts.spritePath = './build/basic-on-update-rule-hook'; - t.context.opts.stylesheetPath = './build/basic-on-update-rule-hook'; + t.context.opts.spritePath = './test/build/basic-on-update-rule-hook'; + t.context.opts.stylesheetPath = './test/build/basic-on-update-rule-hook'; t.context.opts.hooks.onUpdateRule = (rule, commentNode, image) => { const backgroundColorDecl = postcss.decl({ prop: 'background-color', diff --git a/test/11-e2e.js b/test/11-e2e.js index 0f40ac2..128ddd4 100644 --- a/test/11-e2e.js +++ b/test/11-e2e.js @@ -1,15 +1,12 @@ import test from 'ava'; import postcss from 'postcss'; import fs from 'fs-extra'; -import Promise from 'bluebird'; import path from 'path'; import plugin from '../lib'; -Promise.promisifyAll(fs); - async function run(inputPath, expectedPath, opts, t) { - const input = await fs.readFileAsync(inputPath, 'utf8'); - const expected = await fs.readFileAsync(expectedPath, 'utf8'); + const input = await fs.readFile(inputPath, 'utf8'); + const expected = await fs.readFile(expectedPath, 'utf8'); const processor = postcss([plugin(opts)]); const result = await processor.process(input, { from: inputPath }); @@ -17,46 +14,46 @@ async function run(inputPath, expectedPath, opts, t) { } test('throws error', async (t) => { - const inputPath = './fixtures/error/style.css'; + const inputPath = './test/fixtures/error/style.css'; const opts = { - stylesheetPath: './build/example-error/', - spritePath: './build/example-error/', + stylesheetPath: './test/build/example-error/', + spritePath: './test/build/example-error/', }; - const input = await fs.readFileAsync(inputPath, 'utf8'); + const input = await fs.readFile(inputPath, 'utf8'); const processor = postcss([plugin(opts)]); - t.throws(processor.process(input, { from: inputPath })); + return t.throwsAsync(() => processor.process(input, { from: inputPath })); }); test('basic', async (t) => { - const inputPath = './fixtures/basic/style.css'; - const expectedPath = './expectations/basic/style.css'; + const inputPath = './test/fixtures/basic/style.css'; + const expectedPath = './test/expectations/basic/style.css'; const opts = { - stylesheetPath: './build/basic/', - spritePath: './build/basic/' + stylesheetPath: './test/build/basic/', + spritePath: './test/build/basic/' }; return run(inputPath, expectedPath, opts, t); }); test('basic SVG', async (t) => { - const inputPath = './fixtures/svg-basic/style.css'; - const expectedPath = './expectations/svg-basic/style.css'; + const inputPath = './test/fixtures/svg-basic/style.css'; + const expectedPath = './test/expectations/svg-basic/style.css'; const opts = { - stylesheetPath: './build/svg-basic/', - spritePath: './build/svg-basic/' + stylesheetPath: './test/build/svg-basic/', + spritePath: './test/build/svg-basic/' }; return run(inputPath, expectedPath, opts, t); }); test('retina', async (t) => { - const inputPath = './fixtures/retina/style.css'; - const expectedPath = './expectations/retina/style.css'; + const inputPath = './test/fixtures/retina/style.css'; + const expectedPath = './test/expectations/retina/style.css'; const opts = { - stylesheetPath: './build/retina/', - spritePath: './build/retina/', + stylesheetPath: './test/build/retina/', + spritePath: './test/build/retina/', retina: true }; @@ -64,44 +61,44 @@ test('retina', async (t) => { }); test('color', async (t) => { - const inputPath = './fixtures/color/style.css'; - const expectedPath = './expectations/color/style.css'; + const inputPath = './test/fixtures/color/style.css'; + const expectedPath = './test/expectations/color/style.css'; const opts = { - stylesheetPath: './build/color/', - spritePath: './build/color/' + stylesheetPath: './test/build/color/', + spritePath: './test/build/color/' }; return run(inputPath, expectedPath, opts, t); }); test('absolute path', async (t) => { - const inputPath = './fixtures/absolute/css/style.css'; - const expectedPath = './expectations/absolute/style.css'; + const inputPath = './test/fixtures/absolute/css/style.css'; + const expectedPath = './test/expectations/absolute/style.css'; const opts = { - basePath: './fixtures/absolute/', - stylesheetPath: './build/absolute/', - spritePath: './build/absolute/' + basePath: './test/fixtures/absolute/', + stylesheetPath: './test/build/absolute/', + spritePath: './test/build/absolute/' }; return run(inputPath, expectedPath, opts, t); }); test('relative path', async (t) => { - const inputPath = './fixtures/relative/style.css'; - const expectedPath = './expectations/relative/style.css'; + const inputPath = './test/fixtures/relative/style.css'; + const expectedPath = './test/expectations/relative/style.css'; const opts = { - spritePath: './build/relative/' + spritePath: './test/build/relative/' }; return run(inputPath, expectedPath, opts, t); }); test('filter by', async (t) => { - const inputPath = './fixtures/filter-by/style.css'; - const expectedPath = './expectations/filter-by/style.css'; + const inputPath = './test/fixtures/filter-by/style.css'; + const expectedPath = './test/expectations/filter-by/style.css'; const opts = { - stylesheetPath: './build/filter-by/', - spritePath: './build/filter-by/', + stylesheetPath: './test/build/filter-by/', + spritePath: './test/build/filter-by/', filterBy: (image) => { if (image.url.indexOf('square') === -1) { return Promise.reject(); @@ -115,11 +112,11 @@ test('filter by', async (t) => { }); test('group by', async (t) => { - const inputPath = './fixtures/group-by/style.css'; - const expectedPath = './expectations/group-by/style.css'; + const inputPath = './test/fixtures/group-by/style.css'; + const expectedPath = './test/expectations/group-by/style.css'; const opts = { - stylesheetPath: './build/group-by/', - spritePath: './build/group-by/', + stylesheetPath: './test/build/group-by/', + spritePath: './test/build/group-by/', groupBy: (image) => { if (image.url.indexOf('square') === -1 && image.url.indexOf('circle') === -1) { return Promise.reject(); @@ -133,14 +130,14 @@ test('group by', async (t) => { }); test('hooks', async (t) => { - const inputPath = './fixtures/hooks/style.css'; - const expectedPath = './expectations/hooks/style.css'; + const inputPath = './test/fixtures/hooks/style.css'; + const expectedPath = './test/expectations/hooks/style.css'; const opts = { - stylesheetPath: './build/hooks/', - spritePath: './build/hooks/', + stylesheetPath: './test/build/hooks/', + spritePath: './test/build/hooks/', hooks: { - onSaveSpritesheet: (opts, groups) => { - return path.join(opts.spritePath, ['shapes', ...groups, 'png'].join('.')); + onSaveSpritesheet: (opts, spritesheet) => { + return path.join(opts.spritePath, ['shapes', ...spritesheet.groups, 'png'].join('.')); }, onUpdateRule: (rule, token, image) => { rule.insertAfter(token, postcss.decl({ diff --git a/test/12-examples.js b/test/12-examples.js index 21da3e0..1220381 100644 --- a/test/12-examples.js +++ b/test/12-examples.js @@ -1,16 +1,13 @@ import test from 'ava'; import postcss from 'postcss'; import fs from 'fs-extra'; -import Promise from 'bluebird'; import path from 'path'; import plugin from '../lib'; import { updateRule } from '../lib/core'; -Promise.promisifyAll(fs); - async function run(inputPath, expectedPath, opts, t) { - const input = await fs.readFileAsync(inputPath, 'utf8'); - const expected = await fs.readFileAsync(expectedPath, 'utf8'); + const input = await fs.readFile(inputPath, 'utf8'); + const expected = await fs.readFile(expectedPath, 'utf8'); const processor = postcss([plugin(opts)]); const result = await processor.process(input, { from: inputPath }); @@ -18,11 +15,11 @@ async function run(inputPath, expectedPath, opts, t) { } test('filter by', async (t) => { - const inputPath = './fixtures/example-filter-by/style.css'; - const expectedPath = './expectations/example-filter-by/style.css'; + const inputPath = './test/fixtures/example-filter-by/style.css'; + const expectedPath = './test/expectations/example-filter-by/style.css'; const opts = { - stylesheetPath: './build/example-filter-by/', - spritePath: './build/example-filter-by/', + stylesheetPath: './test/build/example-filter-by/', + spritePath: './test/build/example-filter-by/', filterBy: (image) => { if (!/\.png$/.test(image.url)) { return Promise.reject(); @@ -36,11 +33,11 @@ test('filter by', async (t) => { }); test('group by', async (t) => { - const inputPath = './fixtures/example-group-by/style.css'; - const expectedPath = './expectations/example-group-by/style.css'; + const inputPath = './test/fixtures/example-group-by/style.css'; + const expectedPath = './test/expectations/example-group-by/style.css'; const opts = { - stylesheetPath: './build/example-group-by/', - spritePath: './build/example-group-by/', + stylesheetPath: './test/build/example-group-by/', + spritePath: './test/build/example-group-by/', groupBy: (image) => { if (image.url.indexOf('shapes') === -1) { return Promise.reject(); @@ -54,11 +51,11 @@ test('group by', async (t) => { }); test('output dimensions', async (t) => { - const inputPath = './fixtures/example-output-dimensions/style.css'; - const expectedPath = './expectations/example-output-dimensions/style.css'; + const inputPath = './test/fixtures/example-output-dimensions/style.css'; + const expectedPath = './test/expectations/example-output-dimensions/style.css'; const opts = { - stylesheetPath: './build/example-output-dimensions/', - spritePath: './build/example-output-dimensions/', + stylesheetPath: './test/build/example-output-dimensions/', + spritePath: './test/build/example-output-dimensions/', hooks: { onUpdateRule: (rule, token, image) => { updateRule(rule, token, image); @@ -77,11 +74,11 @@ test('output dimensions', async (t) => { }); test('responsive spritesheets', async (t) => { - const inputPath = './fixtures/example-responsive-spritesheets/style.css'; - const expectedPath = './expectations/example-responsive-spritesheets/style.css'; + const inputPath = './test/fixtures/example-responsive-spritesheets/style.css'; + const expectedPath = './test/expectations/example-responsive-spritesheets/style.css'; const opts = { - stylesheetPath: './build/example-responsive-spritesheets/', - spritePath: './build/example-responsive-spritesheets/', + stylesheetPath: './test/build/example-responsive-spritesheets/', + spritePath: './test/build/example-responsive-spritesheets/', hooks: { onUpdateRule: (rule, token, image) => { let backgroundSizeX = (image.spriteWidth / image.coords.width) * 100; @@ -120,11 +117,11 @@ test('responsive spritesheets', async (t) => { }); test('skip prefix', async (t) => { - const inputPath = './fixtures/example-skip-prefix/style.css'; - const expectedPath = './expectations/example-skip-prefix/style.css'; + const inputPath = './test/fixtures/example-skip-prefix/style.css'; + const expectedPath = './test/expectations/example-skip-prefix/style.css'; const opts = { - stylesheetPath: './build/example-skip-prefix/', - spritePath: './build/example-skip-prefix/', + stylesheetPath: './test/build/example-skip-prefix/', + spritePath: './test/build/example-skip-prefix/', hooks: { onSaveSpritesheet: (opts, groups) => { return path.join(opts.spritePath, 'shapes.png'); diff --git a/typings.d.ts b/typings.d.ts index c6235f8..e73c197 100644 --- a/typings.d.ts +++ b/typings.d.ts @@ -1 +1,179 @@ -export declare function sprites(): any; +import { Plugin, AnyNode, Comment } from 'postcss'; + +interface Spritesheet { + path: string, + image: string, + groups: string[], + extension: string, + coordinates: { width: number, height: number, x: number, y: number }, + properties: { width: number, height: number } +} + +interface Image { + /** + * An absolute path to the stylesheet. + */ + styleFilePath: string, + + /** + * An absolute path to the image. + */ + path: string, + + /** + * The url found in your stylesheet including the query params. + */ + originalUrl: string, + + /** + * A normalized version of the original url. + */ + url: string, + + /** + * The retina ratio of your image. + */ + ratio: number, + + /** + * Indicates whenever your image is retina. + */ + retina: boolean, + + /** + * The groups associated with the image. + */ + groups: string[], + + /** + * The string used as reference in your stylesheet. + */ + token: string, + + /** + * The position & dimensions of image in generated spritesheet. + */ + coords: { width: number, height: number, x: number, y: number }, + + /** + * A relative path to the generated spritesheet. + */ + spritePath: string, + + /** + * A CSS url to the generated spritesheet. + */ + spriteUrl: string, + + /** + * The total width of the spritesheet. + */ + spriteWidth: number, + + /** + * The total height of the spritesheet. + */ + spriteHeight: number +} + +interface Hooks { + /** + * Hook that allows to rewrite the data of produced spritesheet. + */ + onSaveSpritesheet?: (options: Options, spritesheet: Spritesheet) => string|object|Promise, + + /** + * Hook that allows to rewrite the CSS output for an image. + */ + onUpdateRule?: (rule: AnyNode, token: Comment, image: Image) => void +} + +interface Spritesmith { + + /** + * The [engine](https://github.com/Ensighten/spritesmith#engines). + */ + engine?: string, + + /** + * The [algorithm](https://github.com/Ensighten/spritesmith#algorithms). + */ + algorithm?: 'top-down' | 'left-right' | 'diagonal' | 'alt-diagonal' | 'binary-tree', + + /** + * The space between images in spritesheet. + */ + padding?: number, + + /** + * The configuration of the [engine](https://github.com/Ensighten/spritesmith#engines). + */ + engineOpts?: object, + + /** + * The export options of the [engine](https://github.com/Ensighten/spritesmith#engines). + */ + exportOpts?: object, +} + +interface Options { + /** + * Relative path to the folder that will keep your output stylesheet(s). If it's null the path of CSS file will be used. + */ + stylesheetPath?: string, + + /** + * Relative path to the folder that will keep your output spritesheet(s). + */ + spritePath: string, + + /** + * Your base path that will be used for images with absolute CSS urls. + */ + basePath?: string, + + /** + * Indicates whether the url should be relative against current CSS context or original CSS stylesheet file. + */ + relativeTo?: string, + + /** + * Defines filter functions that will manipulate the list of images founded in your stylesheet(s). + */ + filterBy?: (image: Image) => Promise, + + /** + * Defines group functions that will manipulate the list of images founded in your stylesheet(s). + */ + groupBy?: (image: Image) => Promise, + + /** + * Defines whether or not to search for retina mark in the filename. + */ + retina?: boolean, + + /** + * Process hooks. + */ + hooks?: Hooks, + + /** + * A [spritesmith](https://github.com/Ensighten/spritesmith) configuration. + */ + spritesmith?: Spritesmith, + + /** + * A [svg-sprite](https://github.com/jkphl/svg-sprite#configuration-basics) configuration. + */ + svgsprite?: object, + + /** + * Prints the plugin output to the console. + */ + verbose?: boolean + +} + +declare function sprites(options: Options): Plugin; + +export = sprites;