├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── index.js ├── package.json └── test ├── fixtures ├── absolute-from.assets.json ├── absolute-from.css ├── absolute-from.expected.css ├── assets │ ├── just-kitty.jpg │ ├── kitty.jpg │ ├── more-kitties.jpg │ └── structure │ │ ├── kitty.jpg │ │ └── nested-kitty.jpg ├── duplicated.assets.json ├── duplicated.css ├── duplicated.expected.css ├── flatten-structure.assets.json ├── flatten-structure.css ├── flatten-structure.expected.css ├── keep-structure.assets.json ├── keep-structure.css ├── keep-structure.expected.css ├── no-assets-path.css ├── no-assets-path.expected.css ├── not-found.css ├── not-found.expected.css ├── postfix.css ├── postfix.expected.css ├── relative-from.assets.json ├── relative-from.css ├── relative-from.expected.css ├── relative-to.assets.json ├── relative-to.css ├── relative-to.expected.css ├── skip.assets.json ├── skip.css ├── skip.expected.css ├── transform-url.assets.json ├── transform-url.css └── transform-url.expected.css └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/fixtures/*.actual.css 3 | test/result 4 | .idea 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # https://docs.travis-ci.com/user/travis-lint 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 4 7 | 8 | install: 9 | - npm install --ignore-scripts 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.0 2 | * use `postcss-value-parser` instead of `reduce-function-call` 3 | * remove plugin name from warnings 4 | * use `blue-tape` as test framework 5 | * colorize test output 6 | ## 0.4.0 7 | * Provide options to save folder structure 8 | ## 0.3.0 9 | * Do not rename assets if they are used multiple times 10 | * use postcss.messages for logging 11 | ## 0.2.1 12 | * Fix url paths for windows 13 | * Handle urls with postfixes 14 | ## 0.2.0 15 | Add renameDuplicates option 16 | ## 0.1.0 17 | Initial release -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Maxime Thirouin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # postcss-assets-rebase [![Build Status](https://travis-ci.org/devex-web-frontend/postcss-assets-rebase.svg)](https://travis-ci.org/devex-web-frontend/postcss-assets-rebase) 2 | 3 | [PostCSS](https://github.com/postcss/postcss) plugin to rebase assets used in project 4 | 5 | Copies all assets used in `.css` to specified folder and rebases all paths. 6 | 7 | Features: 8 | * absolute/relative rebasing path 9 | * saving/flattening assets folder structure 10 | * assets renaming in case of duplication 11 | 12 | If you have any questions of think that some additional options are needed – feel free to create an issue. 13 | 14 | ## Installation 15 | 16 | ```console 17 | $ npm install postcss-assets-rebase 18 | ``` 19 | 20 | ## Usage 21 | 22 | Plugin throws warnings when asset can't be found or duplicate is renamed. 23 | Warnings are thrown using `postcss.messages`. To output warnings in console you can use [postcss-reporter](https://github.com/postcss/postcss-reporter). 24 | ```js 25 | // dependencies 26 | var fs = require("fs") 27 | var postcss = require("postcss") 28 | var rebaser = require("postcss-assets-rebase") 29 | var reporter = require('postcss-reporter'); 30 | 31 | // css to be processed 32 | var css = fs.readFileSync("input.css", "utf8") 33 | 34 | // process css 35 | postcss() 36 | .use(rebaser({ 37 | assetsPath: "assets/imported", // new path for all assets 38 | relative: true // is assetsPath relative to .css position. 39 | //By default its relative to process.cwd() 40 | })) 41 | .use(reporter) 42 | .process(css, { 43 | from: "src/stylesheet/index.css" 44 | to: "dist/index.css" 45 | }) 46 | .then(function (result) { 47 | var output = result.css; 48 | }); 49 | ``` 50 | #### Input `src/stylesheet/index.css`: 51 | ```css 52 | body { 53 | background: url("../../assets/img.jpg"); 54 | background: url(another-assets/another-img.jpg); 55 | background: url("../../assets/not-existing-image.jpg"); 56 | background: url("http://goo.gl/VR2dL6"); 57 | } 58 | ``` 59 | #### Output 60 | ```css 61 | body { 62 | background: url(assets/imported/img.jpg); 63 | background: url(assets/imported/another-img.jpg); 64 | background: url(../../assets/not-existing-image.jpg); 65 | background: url(http://goo.gl/VR2dL6); 66 | } 67 | ``` 68 | 69 | ``` 70 | |-- dist 71 | |-- index.css 72 | |-- assets 73 | |-- imported 74 | |-- another-img.jpg 75 | |-- img.jpg 76 | 77 | ``` 78 | Checkout [tests](test) for more usage examples. 79 | 80 | ### Options 81 | 82 | #### `assetsPath` (required) 83 | Type: `String` 84 | 85 | Path to place assets to 86 | 87 | #### `relative` (optional) 88 | Type: `Boolean` 89 | Default: `False` 90 | 91 | Is assetsPath relative to .css position. By default its relative to process.cwd() 92 | 93 | #### `keepStructure` (optional) 94 | Type: `Boolean` 95 | Default: `False` 96 | 97 | To keep folder structure or not. By default all assets paths are collected flatly in `assetsPath` folder. 98 | If you set this flag to `true`, folder structure relative to process.cwd() would be saved. 99 | 100 | So then example above would generate following files: 101 | ``` 102 | |-- dist 103 | |-- index.css 104 | |-- assets 105 | |-- imported 106 | |-- assets 107 | |-- img.jpg 108 | |-- src 109 | |-- stylesheet 110 | |-- another-assets 111 | |-- another-img.jpg 112 | 113 | ``` 114 | #### `renameDuplicates` (optional) 115 | Type: `Boolean` 116 | Default: `False` 117 | 118 | If there are assets with different paths but same name, they would be renamed using `name_%` pattern. 119 | 120 | By default only first asset would be copied. 121 | 122 | ## [License](LICENSE) 123 | ## [Changelog](CHANGELOG.md) 124 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var parseURL = require('url').parse; 4 | var valueParser = require('postcss-value-parser'); 5 | var mkdirp = require('mkdirp'); 6 | var postcss = require('postcss'); 7 | var postcssResult; 8 | 9 | var rebasedAssets = []; 10 | 11 | module.exports = postcss.plugin('postcss-assets-rebase', function(options) { 12 | return function(css, postcssOptions) { 13 | var to = postcssOptions.opts.to ? path.dirname(postcssOptions.opts.to) : '.'; 14 | postcssResult = postcssOptions; 15 | if (!options || !options.assetsPath) { 16 | return postcssResult.warn('No assets path provided, aborting'); 17 | } 18 | 19 | var instances = getInstances(css, options); 20 | 21 | var promises = instances.map(function(instance) { 22 | return Promise.resolve( 23 | processUrlRebase(instance.base, instance.url, to, options) 24 | ) 25 | .then(function(url) { 26 | instance.node.value = url; 27 | }); 28 | }); 29 | 30 | return Promise.all(promises) 31 | .then(function() { 32 | instances.forEach(function(instance) { 33 | instance.decl.value = String(instance.decl.value); 34 | }); 35 | }); 36 | } 37 | }); 38 | 39 | function getInstances(css, options) { 40 | var instances = []; 41 | 42 | css.walkDecls(function(decl) { 43 | if (!decl.value || decl.value.indexOf('url(') === -1) { 44 | return; 45 | } 46 | 47 | var dirname = process.cwd(); 48 | if ( 49 | options.relative && 50 | decl.source && 51 | decl.source.input && 52 | decl.source.input.file 53 | ) { 54 | dirname = path.dirname(decl.source.input.file); 55 | } 56 | 57 | decl.value = valueParser(decl.value).walk(function(node) { 58 | if ( 59 | node.type !== 'function' || 60 | node.value !== 'url' || 61 | !node.nodes.length 62 | ) { 63 | return; 64 | } 65 | 66 | var url = node.nodes[0].value; 67 | if (isLocalImg(url)) { 68 | instances.push({ 69 | decl: decl, 70 | node: node.nodes[0], 71 | url: url, 72 | base: dirname 73 | }); 74 | } 75 | }); 76 | }); 77 | 78 | return instances; 79 | } 80 | 81 | function normalizeUrl(url) { 82 | return (path.sep === '\\') ? url.replace(/\\/g, '\/') : url; 83 | } 84 | 85 | // checks if file is not local 86 | function isLocalImg(url) { 87 | var notLocal = url.indexOf('data:') === 0 || 88 | url.indexOf('#') === 0 || 89 | /^[a-z]+:\/\//.test(url); 90 | 91 | return !notLocal; 92 | } 93 | 94 | //copy asset and place it to assets folder 95 | function copyAsset(assetPath, contents) { 96 | mkdirp.sync(path.dirname(assetPath)); 97 | if (!fs.existsSync(assetPath)) { 98 | fs.writeFileSync(assetPath, contents); 99 | } 100 | } 101 | 102 | function composeDuplicatedPath(assetPath, index) { 103 | var extname = path.extname(assetPath); 104 | var fileName = path.basename(assetPath, extname); 105 | var dirname = path.dirname(assetPath); 106 | 107 | return path.join(dirname, fileName + '_' + index + extname); 108 | } 109 | 110 | //get asset content 111 | function getAsset(filePath) { 112 | if (fs.existsSync(filePath)) { 113 | return fs.readFileSync(filePath); 114 | } else { 115 | postcssResult.warn('Can\'t read file \'' + filePath + '\', ignoring'); 116 | } 117 | } 118 | 119 | function getPostfix(url) { 120 | var parsedURL = parseURL(url); 121 | var postfix = ''; 122 | 123 | if (parsedURL.search) { 124 | postfix += parsedURL.search; 125 | } 126 | 127 | if (parsedURL.hash) { 128 | postfix += parsedURL.hash; 129 | } 130 | 131 | return postfix; 132 | } 133 | 134 | function getClearUrl(url) { 135 | return parseURL(url).pathname; 136 | } 137 | //compare already rebased asset name with provided and get duplication index 138 | function compareFileNames(rebasedPath, filePath) { 139 | var rebasedExtName = path.extname(rebasedPath); 140 | var fileExtName = path.extname(filePath); 141 | var rebasedBaseName = path.basename(rebasedPath, rebasedExtName); 142 | var fileBaseName = path.basename(filePath, fileExtName); 143 | 144 | var reg = new RegExp('^' + fileBaseName + '_(\\d+)$'); 145 | var executed = reg.exec(rebasedBaseName); 146 | var index = 1; 147 | 148 | if ( 149 | rebasedBaseName !== fileBaseName || 150 | rebasedExtName !== fileExtName 151 | ) { 152 | index = executed ? (parseFloat(executed[1]) + 1) : 0; 153 | } 154 | 155 | return index; 156 | } 157 | 158 | function getAlreadyRebasedPath(filePath) { 159 | for (var i = 0; i < rebasedAssets.length; i++) { 160 | if (rebasedAssets[i].filePath === filePath) { 161 | return { 162 | absolute: rebasedAssets[i].absolute, 163 | relative: rebasedAssets[i].relative 164 | }; 165 | } 166 | } 167 | return null; 168 | } 169 | 170 | function getDuplicationIndex(filePath) { 171 | return rebasedAssets.reduce(function(index, rebasedAsset) { 172 | var newIndex = compareFileNames(rebasedAsset.relative, filePath); 173 | return (newIndex > index) ? newIndex : index; 174 | }, 0); 175 | } 176 | 177 | function resolvePathDuplication(filePath, resolvedPaths) { 178 | var absoluteAssetPath = resolvedPaths.absolute; 179 | var relativeAssetPath = resolvedPaths.relative; 180 | 181 | var alreadyRebasedPath = getAlreadyRebasedPath(filePath); 182 | 183 | if (!!alreadyRebasedPath) { 184 | absoluteAssetPath = alreadyRebasedPath.absolute; 185 | relativeAssetPath = alreadyRebasedPath.relative; 186 | } else { 187 | var duplicationIndex = getDuplicationIndex(absoluteAssetPath); 188 | if (duplicationIndex) { 189 | relativeAssetPath = composeDuplicatedPath(relativeAssetPath, duplicationIndex); 190 | absoluteAssetPath = composeDuplicatedPath(absoluteAssetPath, duplicationIndex); 191 | postcssResult.warn('Duplicated path \'' + filePath + '\' renamed to: ' + relativeAssetPath); 192 | } 193 | } 194 | 195 | rebasedAssets.push({ 196 | filePath: filePath, 197 | absolute: absoluteAssetPath, 198 | relative: relativeAssetPath 199 | }); 200 | 201 | return { 202 | relative: relativeAssetPath, 203 | absolute: absoluteAssetPath 204 | }; 205 | } 206 | 207 | function resolveAssetPaths(options, to, filePath) { 208 | 209 | var fileName = path.basename(filePath); 210 | var keptPath = path.relative(process.cwd(),path.dirname(filePath)); 211 | var relativeAssetPath = ''; 212 | var absoluteAssetPath = '.'; 213 | 214 | if (options.relative) { 215 | absoluteAssetPath = path.resolve(to, options.assetsPath); 216 | relativeAssetPath = options.assetsPath; 217 | } else { 218 | absoluteAssetPath = path.resolve(options.assetsPath); 219 | relativeAssetPath = path.relative(to, absoluteAssetPath); 220 | } 221 | 222 | if (options.keepStructure) { 223 | absoluteAssetPath = path.join(absoluteAssetPath, keptPath); 224 | relativeAssetPath = path.join(relativeAssetPath, keptPath); 225 | } 226 | 227 | return { 228 | absolute: path.join(absoluteAssetPath, fileName), 229 | relative: path.join(relativeAssetPath, fileName) 230 | } 231 | } 232 | function processUrlRebase(dirname, url, to, options) { 233 | 234 | var urlPostfix = getPostfix(url); 235 | var clearUrl = getClearUrl(url); 236 | 237 | var filePath = path.resolve(dirname, clearUrl); 238 | 239 | var assetContents = getAsset(filePath); 240 | var resolvedPaths = resolveAssetPaths(options, to, filePath); 241 | 242 | if (!assetContents) { 243 | return normalizeUrl(url); 244 | } 245 | 246 | if (options.renameDuplicates) { 247 | resolvedPaths = resolvePathDuplication(filePath, resolvedPaths); 248 | } 249 | 250 | copyAsset(resolvedPaths.absolute, assetContents); 251 | 252 | return normalizeUrl(resolvedPaths.relative) + urlPostfix; 253 | } 254 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postcss-assets-rebase", 3 | "version": "0.5.0", 4 | "description": "PostCSS plugin to rebase assets to specified folder.", 5 | "keywords": [ 6 | "css", 7 | "postcss", 8 | "postcss-plugin", 9 | "url", 10 | "rebase", 11 | "assets", 12 | "img", 13 | "image" 14 | ], 15 | "author": "Devexperts", 16 | "license": "MIT", 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/devex-web-frontend/postcss-assets-rebase" 20 | }, 21 | "files": [ 22 | "LICENSE", 23 | "README.md", 24 | "index.js" 25 | ], 26 | "dependencies": { 27 | "mkdirp": "^0.5.1", 28 | "postcss": "^6.0.14", 29 | "postcss-value-parser": "^3.3.0", 30 | "url": "^0.11.0" 31 | }, 32 | "devDependencies": { 33 | "blue-tape": "^1.0.0", 34 | "colortape": "^0.1.2", 35 | "del": "^3.0.0", 36 | "object-assign": "^4.1.1", 37 | "path-exists": "^3.0.0", 38 | "tape": "^4.8.0" 39 | }, 40 | "scripts": { 41 | "test": "colortape test" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/fixtures/absolute-from.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "result/absolute-from/kitty.jpg": "fixtures/assets/kitty.jpg" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/absolute-from.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/absolute-from.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(result/absolute-from/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/assets/just-kitty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devex-web-frontend/postcss-assets-rebase/1ecc60a15ea199cb5cea9a2d4697421d7de2429a/test/fixtures/assets/just-kitty.jpg -------------------------------------------------------------------------------- /test/fixtures/assets/kitty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devex-web-frontend/postcss-assets-rebase/1ecc60a15ea199cb5cea9a2d4697421d7de2429a/test/fixtures/assets/kitty.jpg -------------------------------------------------------------------------------- /test/fixtures/assets/more-kitties.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devex-web-frontend/postcss-assets-rebase/1ecc60a15ea199cb5cea9a2d4697421d7de2429a/test/fixtures/assets/more-kitties.jpg -------------------------------------------------------------------------------- /test/fixtures/assets/structure/kitty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devex-web-frontend/postcss-assets-rebase/1ecc60a15ea199cb5cea9a2d4697421d7de2429a/test/fixtures/assets/structure/kitty.jpg -------------------------------------------------------------------------------- /test/fixtures/assets/structure/nested-kitty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devex-web-frontend/postcss-assets-rebase/1ecc60a15ea199cb5cea9a2d4697421d7de2429a/test/fixtures/assets/structure/nested-kitty.jpg -------------------------------------------------------------------------------- /test/fixtures/duplicated.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixtures/assets/kitty.jpg": "result/duplicated/kitty.jpg", 3 | "fixtures/assets/structure/kitty.jpg": "result/duplicated/kitty_1.jpg" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/duplicated.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/kitty.jpg); 3 | background: url(fixtures/assets/structure/kitty.jpg); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/duplicated.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(result/duplicated/kitty.jpg); 3 | background: url(result/duplicated/kitty_1.jpg); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/flatten-structure.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixtures/assets/structure/nested-kitty.jpg": "result/flatten-structure/nested-kitty.jpg" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/flatten-structure.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/structure/nested-kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/flatten-structure.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(result/flatten-structure/nested-kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/keep-structure.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixtures/assets/structure/nested-kitty.jpg": "result/keep-structure/fixtures/assets/structure/nested-kitty.jpg" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/keep-structure.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/structure/nested-kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/keep-structure.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(result/keep-structure/fixtures/assets/structure/nested-kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/no-assets-path.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/no-assets-path.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/not-found.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/not-found.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/not-found.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/not-found.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/postfix.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url(fixtures/assets/kitty.jpg#iefix); 3 | background: url(fixtures/assets/just-kitty.jpg?some); 4 | background: url(fixtures/assets/structure/nested-kitty.jpg?#blabla); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/postfix.expected.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: url(result/postfix/kitty.jpg#iefix); 3 | background: url(result/postfix/just-kitty.jpg?some); 4 | background: url(result/postfix/nested-kitty.jpg?#blabla); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/relative-from.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixtures/assets/kitty.jpg": "result/relative-from/kitty.jpg" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/relative-from.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(assets/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/relative-from.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(result/relative-from/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/relative-to.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixtures/assets/kitty.jpg": "result/relative-to/kitty.jpg" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/relative-to.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/relative-to.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(../result/relative-to/kitty.jpg); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/skip.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixtures/assets/kitty.jpg": "result/skip/kitty.jpg", 3 | "fixtures/assets/structure/kitty.jpg": "!result/skip/kitty.jpg" 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/skip.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/kitty.jpg); 3 | background: url(fixtures/assets/structure/kitty.jpg); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/skip.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(result/skip/kitty.jpg); 3 | background: url(result/skip/kitty.jpg); 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/transform-url.assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "fixtures/assets/kitty.jpg": "result/transform-url/kitty.jpg", 3 | "fixtures/assets/just-kitty.jpg": "result/transform-url/just-kitty.jpg", 4 | "fixtures/assets/structure/nested-kitty.jpg": "result/transform-url/nested-kitty.jpg" 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/transform-url.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(fixtures/assets/kitty.jpg); 3 | background: url('fixtures/assets/just-kitty.jpg'); 4 | background: url("fixtures/assets/structure/nested-kitty.jpg"); 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/transform-url.expected.css: -------------------------------------------------------------------------------- 1 | a { 2 | background: url(result/transform-url/kitty.jpg); 3 | background: url('result/transform-url/just-kitty.jpg'); 4 | background: url("result/transform-url/nested-kitty.jpg"); 5 | } 6 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var test = require('blue-tape'); 2 | var fs = require('fs'); 3 | var del = require('del'); 4 | var path = require('path'); 5 | var exists = require('path-exists'); 6 | var assign = require('object-assign'); 7 | var postcss = require('postcss'); 8 | var assetsRebase = require('..'); 9 | 10 | process.chdir('test'); 11 | 12 | function readFile(file, encoding) { 13 | return new Promise(function (resolve, reject) { 14 | fs.readFile(file, encoding, function (err, data) { 15 | if (err) { 16 | return reject(err); 17 | } 18 | resolve(data); 19 | }); 20 | }); 21 | } 22 | 23 | function writeFile(file, data) { 24 | return new Promise(function (resolve, reject) { 25 | fs.writeFile(file, data, function (err) { 26 | if (err) { 27 | return reject(err); 28 | } 29 | resolve(); 30 | }); 31 | }); 32 | } 33 | 34 | function compareAssets(t, assetsList) { 35 | var destList = Object.keys(assetsList); 36 | 37 | var promises = destList.map(function (dest) { 38 | var src = assetsList[dest]; 39 | return Promise.all([ 40 | readFile(dest), 41 | readFile(src.replace(/^!/, '')) 42 | ]).then(function (assets) { 43 | var result = Buffer.compare(assets[0], assets[1]); 44 | t.ok(src[0] === '!' ? result !== 0 : result === 0, 'same assets'); 45 | }); 46 | }); 47 | 48 | return Promise.all(promises); 49 | } 50 | 51 | function compareFixtures(t, name, warnings, options, postcssOptions) { 52 | var expectedWarnings = warnings || []; 53 | 54 | var fixtureFile = 'fixtures/' + name + '.css'; 55 | var expectedFile = 'fixtures/' + name + '.expected.css'; 56 | var actualFile = 'fixtures/' + name + '.actual.css'; 57 | var assetsFile = 'fixtures/' + name + '.assets.json'; 58 | 59 | return Promise.all([ 60 | options ? del(options.assetsPath) : null, 61 | readFile(fixtureFile, 'utf-8'), 62 | readFile(expectedFile, 'utf-8'), 63 | exists(assetsFile).then(function (exists) { 64 | if (exists) { 65 | return readFile(assetsFile, 'utf-8'); 66 | } 67 | }) 68 | ]) 69 | .then(function (results) { 70 | return postcss() 71 | .use(assetsRebase(options)) 72 | .process(results[1], postcssOptions) 73 | .then(function (result) { 74 | var warnings = result.warnings(); 75 | var assetsList = results[3] ? JSON.parse(results[3]) : null 76 | t.equal(warnings.length, expectedWarnings.length, 'no warnings'); 77 | warnings.forEach(function (warning, i) { 78 | t.equal(warning.text, expectedWarnings[i], 'same warning'); 79 | }); 80 | t.equal(result.css, results[2], 'same css'); 81 | return Promise.all([ 82 | writeFile(actualFile, result.css), 83 | assetsList ? compareAssets(t, assetsList) : null 84 | ]); 85 | }); 86 | }); 87 | } 88 | 89 | test('should not change css if asstesPath is not specified', function (t) { 90 | return compareFixtures(t, 'no-assets-path', [ 91 | 'No assets path provided, aborting' 92 | ]); 93 | }); 94 | 95 | test('should transform url', function (t) { 96 | return compareFixtures(t, 'transform-url', null, { 97 | assetsPath: 'result/transform-url' 98 | }); 99 | }); 100 | 101 | test('should proper process urls with postfixes', function (t) { 102 | return compareFixtures(t, 'postfix', null, { 103 | assetsPath: 'result/postfix' 104 | }); 105 | }); 106 | 107 | test('should transform url relatively postcss `to` option', function (t) { 108 | return compareFixtures(t, 'relative-to', null, { 109 | assetsPath: 'result/relative-to' 110 | }, { 111 | to: 'fixtures/actual.css' 112 | }); 113 | }); 114 | 115 | test('should use postcss `from` option with enabled `relative` option', function (t) { 116 | return compareFixtures(t, 'relative-from', null, { 117 | assetsPath: 'result/relative-from', 118 | relative: true 119 | }, { 120 | from: 'fixtures/fixture.css' 121 | }); 122 | }); 123 | 124 | test('should not use postcss `from` option to detect assets', function (t) { 125 | return compareFixtures(t, 'absolute-from', null, { 126 | assetsPath: 'result/absolute-from' 127 | }, { 128 | from: 'fixtures/fixture.css' 129 | }); 130 | }); 131 | 132 | test('should not keep directory structure', function (t) { 133 | return compareFixtures(t, 'flatten-structure', null, { 134 | assetsPath: 'result/flatten-structure' 135 | }); 136 | }); 137 | 138 | test('should keep directory structure with enabled option', function (t) { 139 | return compareFixtures(t, 'keep-structure', null, { 140 | assetsPath: 'result/keep-structure', 141 | keepStructure: true 142 | }); 143 | }); 144 | 145 | test('should skip duplicated assets', function (t) { 146 | return compareFixtures(t, 'skip', null, { 147 | assetsPath: 'result/skip' 148 | }); 149 | }); 150 | 151 | test('should rename duplicated assets with enabled option', function (t) { 152 | return compareFixtures(t, 'duplicated', [ 153 | 'Duplicated path \'' + path.resolve('fixtures/assets/structure/kitty.jpg') + '\' renamed to: ' + path.normalize('result/duplicated/kitty_1.jpg') 154 | ], { 155 | assetsPath: 'result/duplicated', 156 | renameDuplicates: true 157 | }); 158 | }); 159 | 160 | test('should warn when file is not found', function (t) { 161 | return compareFixtures(t, 'not-found', [ 162 | 'Can\'t read file \'' + path.resolve('fixtures/assets/not-found.jpg') + '\', ignoring' 163 | ], { 164 | assetsPath: 'result/not-found' 165 | }) 166 | }); 167 | --------------------------------------------------------------------------------