/postcss-copy

An async postcss plugin to copy all assets referenced in CSS files to a custom destination folder and updating the URLs.

Primary LanguageJavaScriptMIT LicenseMIT

postcss-copy

Build Status Build status Coverage Status Dependency Status devDependency Status

An async postcss plugin to copy all assets referenced in CSS files to a custom destination folder and updating the URLs.

Sections
Install
Quick Start
Options
Custom Hash Function
Transform
Using postcss-import
About lifecyle and the fileMeta object
Roadmap
Credits

Install

With npm do:

$ npm install postcss-copy

Quick Start

// postcss.config.js
module.exports = {
    plugins: [
        require('postcss-copy')({
            dest: 'dist'
        })
    ]
};
$ postcss src/index.css

Using Gulp

var gulp = require('gulp');
var postcss = require('gulp-postcss');
var postcssCopy = require('postcss-copy');

gulp.task('buildCss', function () {
    var processors = [
        postcssCopy({
            basePath: ['src', 'otherSrc']
            dest: 'dist'
        })
    ];

    return gulp
        .src(['src/**/*.css', 'otherSrc/**/*.css'])
        .pipe(postcss(processors))
        .pipe(gulp.dest('dist'));
});

Options

basePath ({string|array} default = process.cwd())

Define one/many base path for your CSS files.

dest ({string} required)

Define the dest path of your CSS files and assets.

template ({string | function} default = '[hash].[ext][query]')

Define a template name for your final url assets.

  • string template
    • [hash]: Let you use a hash name based on the contents of the file.
    • [name]: Real name of your asset.
    • [path]: Original relative path of your asset.
    • [ext]: Extension of the asset.
    • [query]: Query string.
    • [qparams]: Query string params without the ?.
    • [qhash]: Query string hash without the #.
  • function template
var copyOpts = {
    ...,
    template(fileMeta) {
        return 'assets/custom-name-' + fileMeta.name + '.' + fileMeta.ext;
    }
}

preservePath ({boolean} default = false)

Flag option to notify to postcss-copy that your CSS files destination are going to preserve the directory structure. It's helpful if you are using postcss-cli with the --base option or gulp-postcss with multiple files (e.g: gulp.src('src/**/*.css'))

ignore ({string | string[] | function} default = [])

Option to ignore assets in your CSS file.

Using the ! key in your CSS:
.btn {
    background-image: url('!images/button.jpg');
}
.background {
    background-image: url('!images/background.jpg');
}
Using a string or array with micromatch support to ignore files:
// ignore with string
var copyOpts = {
    ...,
    ignore: 'images/*.jpg'
}
// ignore with array
var copyOpts = {
    ...,
    ignore: ['images/button.+(jpg|png)', 'images/background.jpg']
}
Using a custom function:
// ignore function
var copyOpts = {
    ...,
    ignore(fileMeta, opts) {
        return (fileMeta.filename.indexOf('images/button.jpg') ||
                fileMeta.filename.indexOf('images/background.jpg'));
    }
}

hashFunction

Define a custom function to create the hash name.

var copyOpts = {
    ...,
    hashFunction(contents) {
        // borschik
        return crypto.createHash('sha1')
            .update(contents)
            .digest('base64')
            .replace(/\+/g, '-')
            .replace(/\//g, '_')
            .replace(/=/g, '')
            .replace(/^[+-]+/g, '');
    }
};

transform

Extend the copy method to apply a transform in the contents (e.g: optimize images).

IMPORTANT: The function must return the fileMeta (modified) or a promise using resolve(fileMeta).

var Imagemin = require('imagemin');
var imageminJpegtran = require('imagemin-jpegtran');
var imageminPngquant = require('imagemin-pngquant');

var copyOpts = {
    ...,
    transform(fileMeta) {
        if (['jpg', 'png'].indexOf(fileMeta.ext) === -1) {
            return fileMeta;
        }
        return Imagemin.buffer(fileMeta.contents, {
            plugins: [
                imageminPngquant(),
                imageminJpegtran({
                    progressive: true
                })
            ]
        })
        .then(result => {
            fileMeta.contents = result;
            return fileMeta; // <- important
        });
    }
};

Using copy with postcss-import

postcss-import is a great plugin that allow us work our css files in a modular way with the same behavior of CommonJS.

One thing more... postcss-import has the ability of load files from node_modules. If you are using a custom basePath and you want to track your assets from node_modules you need to add the node_modules folder in the basePath option:

myProject/
|-- node_modules/
|-- dest/
|-- src/

Full example

var gulp = require('gulp');
var postcss = require('gulp-postcss');
var postcssCopy = require('postcss-copy');
var postcssImport = require('postcss-import');
var path = require('path');

gulp.task('buildCss', function () {
    var processors = [
        postcssImport(),
        postcssCopy({
            basePath: ['src', 'node_modules'],
            preservePath: true,
            dest: 'dist'
        })
    ];

    return gulp
        .src('src/**/*.css')
        .pipe(postcss(processors, {to: 'dist/css/index.css'}))
        .pipe(gulp.dest('dist/css'));
});

About lifecyle and the fileMeta object

The fileMeta is a literal object with meta information about the copy process. Its information grows with the progress of the copy process.

The lifecyle of the copy process is:

  1. Detect the url in the CSS files

  2. Validate url

  3. Initialize the fileMeta:

    {
        sourceInputFile, // path to the origin CSS file
        sourceValue, // origin asset value taked from the CSS file
        filename, // filename normalized without query string
        absolutePath, // absolute path of the asset file
        fullName, // name of the asset file
        path, // relative path of the asset file
        name, // name without extension
        ext, // extension name
        query, // full query string
        qparams, // query string params without the char '?'
        qhash, // query string hash without the char '#'
        basePath // basePath found
    }
  4. Check ignore function

  5. Read the asset file (using a cache buffer if exists)

  6. Add content property in the fileMeta object

  7. Execute custom transform

  8. Create hash name based on the custom transform

  9. Add hash property in the fileMeta object

  10. Define template for the new asset

  11. Add resultAbsolutePath and extra properties in the fileMeta object

  12. Write in destination

  13. Write the new URL in the PostCSS node value.

On roadmap

nothing for now :)

Credits

  • Thanks to @conradz and his rework plugin rework-assets my inspiration in this plugin.
  • Thanks to @MoOx for let me create the copy function in his postcss-url plugin.
  • Thanks to @webpack, i take the idea of define templates from his awesome file-loader
  • Huge thanks to @TrySound for his work in this project.

License

MIT