Git Product home page Git Product logo

postcss-copy's Introduction

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

postcss-copy's People

Contributors

alexander-akait avatar dpaez avatar greenkeeperio-bot avatar tinchoz49 avatar trysound avatar wbinnssmith avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

postcss-copy's Issues

Hash function is called before transformation

If you want to use special transforms on file like image minification in your example, hash function will be used on original buffer, not on buffer which has been transformed (i.e. minified in this example).

Returning file meta with hash property changed also doesn’t do anything.

Is this by design? Am I doing something wrong?

multiple src with similar folder structure

So I have a setup like this

app/
|__ project/
|    |__assets/
|       |__ imgs/
|       |__ css/
|           |__ app.scss
|    core/
|    |__assets/
|      |__ fonts/ <--- fonts are here
|      |__ imgs/
|
|__ dist/
src: ['app/core/assets', 'app/project/assets'],
dest: 'dist/assets/project',
template: '[path]/[name]-[hash].[ext]',
@font-face {
  font-family: 'font name';
  src: url('../fonts/font-name.woff2') format('woff2');
}

It fails to copy the fonts from the directory and it complains that it's not inside the projectname/assets/fonts although in the postcss-copy config it's one of the src.

Spaces in path replaced with %20

I'm having an issue with fonts that have space in the path:

@font-face {
    font-family:'Brandon Text Bold';
    src: url('../fonts/Brandon Text Bold.eot');
    src: url('../fonts/Brandon Text Bold.eot?#iefix') format('embedded-opentype'),
    url('../fonts/Brandon Text Bold.woff2') format('woff2'),
    url('../fonts/Brandon Text Bold.woff') format('woff'),
    url('../fonts/Brandon Text Bold.ttf') format('truetype'),
    url('../fonts/Brandon Text Bold.otf') format('opentype'),
    url('../fonts/Brandon Text Bold.svg#Brandon Text Bold') format('svg');
    font-weight: 450;
    font-style: normal;
    font-stretch: normal;
}

I keep getting:

postcss-copy: ...\resources\assets\sass\app.css:3:3: Can't read the file in ...\resources\assets\fonts\Brandon%20Text%20Bold.eot
postcss-copy: ...\resources\assets\sass\app.css:4:3: Can't read the file in ...\resources\assets\fonts\Brandon%20Text%20Bold.eot
postcss-copy: ...\resources\assets\sass\app.css:4:3: Can't read the file in ...\resources\assets\fonts\Brandon%20Text%20Bold.woff2
postcss-copy: ...\resources\assets\sass\app.css:4:3: Can't read the file in ...\resources\assets\fonts\Brandon%20Text%20Bold.woff
postcss-copy: ...\resources\assets\sass\app.css:4:3: Can't read the file in ...\resources\assets\fonts\Brandon%20Text%20Bold.ttf
postcss-copy: ...\resources\assets\sass\app.css:4:3: Can't read the file in ...\resources\assets\fonts\Brandon%20Text%20Bold.otf
postcss-copy: ...\resources\assets\sass\app.css:4:3: Can't read the file in ...\resources\assets\fonts\Brandon%20Text%20Bold.svg

It replaces spaces with %20 and then it cant find the file.

handling multiple references to the same source

Hi! :)

I am experimenting with the transform hook and noticed that sometimes the same file is being called. i.e.

real world example (see fontawesome-webfont.eot is referenced twice)

https://github.com/FortAwesome/Font-Awesome/blob/c6e79ae1a1e7bef4e7290e5026b704d24f4fa590/css/font-awesome.css#L9-L10

the same asset could occur in many places

// buttons.css
.btn-save {
  background-image: url(assets/save.png)
}

// menu.css
.menu-save {
  background: url(assets/save.png) no-repeat
}

it seems that

  1. is it correct that postcss-copy only reads the file 1 time?
    form the docs
  1. Read the asset file (using a cache buffer if exists)
  1. As far as I see tranform is called for each occurrence/reference in the stylesheet.
    therefore using custom optimization (i.e. imagemin) would do duplicated processing

Chaning tranform on a filebase would not be 100% backwards compatible as some people may do different optimization depending on the querystrnig/hash.. altough that might be a rare case.

What would be the best strategy to avoid transforming the same asset multiple times?

  1. Would you consider adding a tranformSrc hook that works on filebase?

documentation imagemin example in readme is out of date

Hi agian

I believe the imagemin api changed. Imagemin can accept a buffer and does return a promise.

return imagemin.buffer(fileMeta.contents, {
    plugins: [
      imageminPngquant(),
      imageminJpegtran()
    ]
  })
.then(() => console.log('done! now return fileMeta'))
.then(() => fileMeta)

Should I create a PR?

Missing asset quits postcss process

Hi,

Great plugin by the way! 😝

If postcss-copy can't found an asset file it throws an Error cancelling out the whole postcss task.

If I comment the line then the process continue and a new css file is generated normally. Also the missing asset path is maintained. This allows scenarios like assets in different levels (eg: outside the project, hosted anywhere else).

Please let me know if there is something else I can do.

Thanks in advance.

deka

sometimes 1 of 4 urls is not resolved correctly

Hi, I am trying to setup a project that imports material-design-icons from node_module/material-design-icons/iconfont/material-icons.css to dist/fonts. The fonts are always copied and the filenames have the correct format specified by template.

Unfortunately "sometimes" one of the url does not point to the correct file. It is always the second last. Expected: url(../fonts/MaterialIcons-Regular.448ac5fa421c0e61.woff) format('woff') sometimes: url(MaterialIcons-Regular.woff) format('woff')
It happens at least every 1 in 5 times, sometimes 2 times in a row. For some reason it is always the woff url, the second last.

I tried to reduce the setup to a minimum working bug.

CSS
@import "../../node_modules/material-design-icons/iconfont/material-icons.css";
JS
const postcss = require('postcss');
const importCSS = require('postcss-import');
const copyCSS = require('postcss-copy');
const fildes = require('fildes');
const readFile = fildes.readFile
const writeFile = fildes.writeFile;

const input = 'lib/styles/bug.css';
const output = 'dist/styles/bug.css';

readFile(input)
.then(css => {
  return postcss([
    importCSS(),
    copyCSS({
      'src': ['node_modules/material-design-icons/iconfont'],
      'dest': 'dist/fonts',
      'template': '[name].[hash].[ext]',
      'keepRelativePath': false
    }),
  ])
  .process(css, {
    'from': input,
    'to': output,
    'map': {
      'inline': false
    }
  })
})
.then(result => {
  return Promise.all([
    readFile('lib/index.html')
    .then(file => writeFile('dist/index.html', file)),
    writeFile(output, result.css),
    writeFile(output + '.map', result.map)
  ]);
})
.catch(error => {
  console.log(error);
});
cli
npm run clean && node conf/bug.js && cat dist/styles/bug.css

Cannot read property 's' of undefined

PostCSS - Error
Cannot read property 's' of undefined

I got this error and it was a waste of time before I figured out that if postcss-copy cannot access a file mentioned in some of imported CSS, it don't tell me where it was failed.

I have to check all my imports manually to fix all paths.

Uh-oh.

Feature Request: easy way to disable file renaming

First off, thanks for the great plugin. I really love working with it.

One minor issue for me is that I don't want asset files renamed. In order to accomplish that I'm currently defining the following template function:

meta => meta.name + '.' + meta.ext + meta.query

This works, but, in my opinion, it would be a lot cleaner and more future proof to have a simple switch to disable file renaming. Maybe you could just pass false to the hashFunction setting?

Border-image syntax gets cut off by plugin.

It appears postcss-copy somehow gets the syntax of border-image wrong, and cuts off anything after the url() bit.

Example:

.foo {
  border-image: url(image.svg) 0 0 10 0 stretch repeat;
}

...becomes...

.foo {
  border-image: url(image.svg);
}

Tested this with gulp 3.9.4, postcss 6.0.0 and postcss-copy 2.2.1.

Need a way to modify css asset path

Hi, would it be possible to introduce a custom css path resolver? I'm using this plugin with gulp and I can't get the path to css correctly.

I have:

.pipe(postCss([
            postCssCopy({
                basePath: '../dashboard/assets',
                dest: '../public/assets/',
                template: '[name].[hash].[ext]',
            }),
        ]))

Everything is copied correctly, but the path in css is not assets/[name].[hash].[ext] but css/[name].[hash].[ext].

If I add to:

.pipe(postCss([
            postCssCopy({
                basePath: '../dashboard/assets',
                dest: '../public/assets/',
                template: '[name].[hash].[ext]',
            }),
        ], {
            to: '../public/css/limitless.css',
        }))

Then everything is referenced correctly, but sourcemaps are broken.

Skip overwrite existing file?

If I’m creating output filename based on hashed image buffer and if I have same output filename from two input CSS files, it is possible you can get into racing condition when writing file to disk and reading it’s contents for purposes of inlining.

Maybe there should be an option to skip overwriting same file on the disk if it exists?

What do you think? Does this make sense?

regression relative url path

Just updated from 2.6.3 to 5.0.1

something changed that the url( used to contain the "dest" path (I believe).

input

@import "material-design-icons/iconfont/material-icons.css";

postcss

postcss([
  importCSS(),
  copyCSS({
      'src': ['node_modules/material-design-icons/iconfont'],
      'dest': 'dist/fonts',
      'template': '[name].[hash].[ext]',
      'keepRelativePath': false
  })

output

@font-face {
  font-family: 'Material Icons';
  font-style: normal;
  font-weight: 400;
  src: url(MaterialIcons-Regular.26fb8cecb5512223.eot); /* For IE6-8 */
  src: local('Material Icons'),
       local('MaterialIcons-Regular'),
       url(MaterialIcons-Regular.09963592e8c953cc.woff2) format('woff2'),
       url(MaterialIcons-Regular.c6c953c2ccb2ca9a.woff) format('woff'),
       url(MaterialIcons-Regular.fc05de31234e0090.ttf) format('truetype');
}

.material-icons {
  font-family: 'Material Icons';
  font-weight: normal;
  ...

expected

url(../fonts/MaterialIcons-Regular.26fb8cecb5512223.eot)

Assets not copied when using node

I can't find a way to have postcss-copy works with pure node. Here is my code:

let postcss = require('postcss');

postcss({
    plugins: [
        require('postcss-copy')({
            dest: 'dist',
            basePath: '.'
        })
    ]
}).process('.foo{background-image: url(./foo.png);}').then(function(data) {
    console.warn(data);
});

foo.png is present in '.' and is not copied to 'dist', Plus, postcss returns a warning in its output:

[ Warning {
       type: 'warning',
       text: 'Path must be a string. Received undefined',
       line: 1,
       column: 6,
       node: [Object],
       plugin: 'postcss-copy' } ],

What am I doing wrong?

Error: Cannot read property 'input' of undefined

@tinchoz49, @TrySound this line is preventing me from upgrading from v4.0.2 to v5.0.1+. It's because decl.source is undefined, so I'm getting this error:

<css input>: Cannot read property 'input' of undefined

Turns out it was an issue with postcss-sprites, but it begs the question – shouldn't we display a more helpful error message in this case so that people know the sources are being lost from another plugin? It took me a while to find/fix this issue.

wrong url

@tinchoz49
With simple configuration:

postcssCopy({
                    src: [
                        'src/' + bundle,
                        'node_modules'
                    ],
                    dest: 'public/assets/' + bundle,
                    get template() {
                        return '[path]/[name].[ext]';
                    },
                    keepRelativePath: true
                })

Rebase url from node_modules wrong (from local files all ok), i get each url assets from node_modules as public/assets/theme/default-skin.png, without [path]. But copy all ok(public/assets/theme/photoswipe/dist/default-skin/default-skin.png), i think it is bug. I want relative url in my local files and from node_modules.

Maybe settable this https://github.com/geut/postcss-copy/blob/master/src/index.js#L290, because after testing:
dirname.replace(fileMeta.src, opts.dest) => /some/path/public/assets/theme/photoswipe/dist/default-skin
fileMeta.resultAbsolutePath => /some/path/public/assets/theme/photoswipe/dist/default-skin/default-skin.png
And we get in /some/path/public/assets/theme/app/css/app.css: ... url(default-skin.png) ..., but real path default-skin.png (copy work OK!) /some/path/public/assets/theme/photoswipe/dist/default-skin/default-skin.png.
I think dirname.replace(fileMeta.src, opts.dest) need return relative path to main css file (where importing) - /some/path/public/assets/theme/css/app.css

More testing get result that rebase work good, if my structure:
/some/path/site/css/app.css (include bootstrap)
/some/path/site/css/photoswipe/**
OR
/some/path/site/css/app/app.css (include bootstrap)
/some/path/site/css/photoswipe/**
BUT NOT WORK :(
/some/path/site/css/app/app.css (include bootstrap)
/some/path/node_modules/photoswipe/**

How to change the input path?

My CSS is being built in an intermediate folder and postcss-copy resolves the name of the assets by using the following call:

path.dirname(decl.source.input.file)

The input file being into the "build" folder and not into the src folder, postcss-copy is unable to find the assets and end up doing nothing.

Version 6 of the module was doing this:

opts.inputPath(decl);

It gave us the chance to pass a custom inputPath function to postcss-copy to tell hom "OK, my assets have to be resolved from this folder instead of relatively to the CSS file".

How can it be done with postcss-copy@7? Every serious build-tool is not building the CSS into the same place as the SASS sources, so without such a way to override the input path, postcss-copy is useless.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.