diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..8978b44 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# editorconfig.org +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +indent_size = 2 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7ab649f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +test/fixtures/*.actual.css diff --git a/.jscsrc b/.jscsrc new file mode 100644 index 0000000..29720b3 --- /dev/null +++ b/.jscsrc @@ -0,0 +1,130 @@ +{ + "excludeFiles": [ + "node_modules/**" + ], + "fileExtensions": [ + ".js" + ], + "requireCurlyBraces": [ + "if", + "else", + "for", + "while", + "do", + "try", + "catch" + ], + "requireSpaceAfterKeywords": [ + "if", + "else", + "for", + "while", + "do", + "switch", + "return", + "try", + "catch" + ], + "requireSpaceBeforeBlockStatements": true, + "requireParenthesesAroundIIFE": true, + "requireSpacesInConditionalExpression": { + "afterTest": true, + "beforeConsequent": true, + "afterConsequent": true, + "beforeAlternate": true + }, + "requireSpacesInFunctionExpression": { + "beforeOpeningCurlyBrace": true + }, + "disallowSpacesInFunctionExpression": { + "beforeOpeningRoundBrace": true + }, + "disallowMultipleVarDecl": true, + "requireBlocksOnNewline": 1, + "disallowPaddingNewlinesInBlocks": true, + "disallowEmptyBlocks": true, + "disallowSpacesInsideObjectBrackets": true, + "disallowSpacesInsideArrayBrackets": true, + "disallowSpacesInsideParentheses": true, + "disallowQuotedKeysInObjects": "allButReserved", + "disallowSpaceAfterObjectKeys": true, + "requireCommaBeforeLineBreak": true, + "requireOperatorBeforeLineBreak": [ + "?", + "+", + "-", + "/", + "*", + "=", + "==", + "===", + "!=", + "!==", + ">", + ">=", + "<", + "<=" + ], + "disallowSpaceAfterPrefixUnaryOperators": [ + "++", + "--", + "+", + "-", + "~", + "!" + ], + "disallowSpaceBeforePostfixUnaryOperators": [ + "++", + "--" + ], + "requireSpaceBeforeBinaryOperators": [ + "+", + "-", + "/", + "*", + "=", + "==", + "===", + "!=", + "!==" + ], + "requireSpaceAfterBinaryOperators": [ + "+", + "-", + "/", + "*", + "=", + "==", + "===", + "!=", + "!==" + ], + "disallowImplicitTypeConversion": [ + "numeric", + "boolean", + "binary", + "string" + ], + "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", + "disallowKeywords": [ + "with" + ], + "disallowMultipleLineStrings": true, + "validateQuoteMarks": "\"", + "validateIndentation": 2, + "disallowMixedSpacesAndTabs": true, + "disallowTrailingWhitespace": true, + "requireKeywordsOnNewLine": [ + "else" + ], + "requireLineFeedAtFileEnd": true, + "requireCapitalizedConstructors": true, + "safeContextKeyword": "that", + "requireDotNotation": true, + "validateJSDoc": { + "checkParamNames": true, + "checkRedundantParams": true, + "requireParamTypes": true + }, + "requireSpaceAfterLineComment": true +} diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..9f268f7 --- /dev/null +++ b/.jshintrc @@ -0,0 +1,9 @@ +{ + "newcap": false, + "undef": true, + "unused": true, + "asi": true, + "esnext": true, + "node": true, + "browser": true +} diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..587bd3e --- /dev/null +++ b/.travis.yml @@ -0,0 +1 @@ +language: node_js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100755 index 0000000..4da6464 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# 1.0.0 - 2014-08-24 + +First release diff --git a/LICENSE b/LICENSE new file mode 100755 index 0000000..9abe4f5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2014 "MoOx" Maxime Thirouin + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100755 index 0000000..66bba0e --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +# postcss-url [![Build Status](https://travis-ci.org/postcss/postcss-url.png)](https://travis-ci.org/postcss/postcss-url) + +> [PostCSS](https://github.com/postcss/postcss) plugin to rebase or inline on url(). + +## Installation + + $ npm install postcss-url + +## Usage + +```js +// dependencies +var fs = require("fs") +var postcss = require("postcss") +var url = require("postcss-url") + +// css to be processed +var css = fs.readFileSync("input.css", "utf8") + +// process css +var output = postcss() + .use(url({ + url: "rebase" // or "inline" + })) + .process(css, { + // "rebase" mode need at least one of those options + // "inline" mode might need `from` option only + from: "src/stylesheet/index.css" + to: "dist/index.css" + }) + .css +``` + +Checkout [tests](test) for examples. + +### Options + +#### `url` (default: `"rebase"`) + +##### `url: "rebase"` + +Allow you to fix `url()` according to postcss `to` and/or `from` options (rebase to `to` first if available, otherwise `from` or `process.cwd()`). + +##### `url: "inline"` + +Allow you to inline assets using base64 syntax. Can use postcss `from` option to find ressources. + +--- + +## Contributing + +Work on a branch, install dev-dependencies, respect coding style & run tests before submitting a bug fix or a feature. + + $ git clone https://github.com/postcss/postcss-url.git + $ git checkout -b patch-1 + $ npm install + $ npm test + +## [Changelog](CHANGELOG.md) + +## [License](LICENSE) diff --git a/index.js b/index.js new file mode 100755 index 0000000..2a4c623 --- /dev/null +++ b/index.js @@ -0,0 +1,106 @@ +/** + * Module dependencies. + */ +var fs = require("fs") +var path = require("path") +var reduceFunctionCall = require("reduce-function-call") +var base64 = require("js-base64").Base64 +var mime = require("mime") + +/** + * Fix url() according to source (`from`) or destination (`to`) + * + * @param {Object} options + */ +module.exports = function fixUrl(options) { + options = options || {} + var mode = options.url !== undefined ? options.url : "rebase" + + return function(styles, postcssOptions) { + var from = postcssOptions.from ? path.dirname(postcssOptions.from) : "." + var to = postcssOptions.to ? path.dirname(postcssOptions.to) : from + + styles.eachDecl(function checkUrl(decl) { + if (!decl.value) { + return + } + + if (decl.value.indexOf("url(") > -1) { + var dirname = path.dirname(decl.source.file) + decl.value = reduceFunctionCall(decl.value, "url", function(value) { + // save quote style + var quote = getQuote(value) + value = unquote(value, quote) + + // ignore absolute url + if (value.indexOf("/") === 0) { + return value + } + + var newPath = value + + if (mode === "rebase") { + if (dirname !== from) { + newPath = path.relative(from, dirname + path.sep + newPath) + } + newPath = path.resolve(from, newPath) + newPath = path.relative(to, newPath) + } + else if (mode === "inline") { + var file = path.resolve(from, dirname !== from ? dirname + path.sep + value : value) + if (!fs.existsSync(file)) { + console.warn("Can't read file '" + file + "', ignoring") + } + else { + var mimeType = mime.lookup(file) + if (!mimeType) { + console.warn("Unable to find asset mime-type for " + file) + } + else { + newPath = "data:" + mimeType + ";base64," + base64.encode(file) + } + } + } + else { + throw new Error("Unknow mode for postcss-url: " + mode) + } + + return "url(" + quote + newPath + quote + ")" + }) + } + }) + } +} + +/** + * remove quote around a string + * + * @param {String} string + * @param {String} quote + * @return {String} unquoted string + */ +function unquote(string, quote) { + if (quote) { + return string.substr(1, string.length - 2) + } + + return string +} + + +/** + * return quote type + * + * @param {String} string quoted (or not) value + * @return {String} quote if any, or empty string + */ +function getQuote(string) { + var quote = "" + Array("\"", "'").forEach(function(q) { + if (string.charAt(0) === q && string.charAt(string.length - 1) === q) { + quote = q + } + }) + + return quote +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..2ff7827 --- /dev/null +++ b/package.json @@ -0,0 +1,46 @@ +{ + "name": "postcss-url", + "version": "0.0.0", + "description": "PostCSS plugin to rebase or inline on url().", + "keywords": [ + "css", + "postcss", + "postcss-plugins", + "url", + "rebase", + "inline", + "base64", + "assets" + ], + "author": "MoOx", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/postcss/postcss-url.git" + }, + "files": [ + "CHANGELOG.md", + "LICENSE", + "README.md", + "index.js" + ], + "dependencies": { + "js-base64": "^2.1.5", + "mime": "^1.2.11", + "reduce-function-call": "^1.0.1" + }, + "devDependencies": { + "jscs": "^1.5.9", + "jshint": "^2.5.2", + "jshint-stylish": "^0.4.0", + "postcss": "^2.2.1", + "postcss-import": "^1.0.0", + "tap-colorize": "^1.2.0", + "tape": "^2.14.0" + }, + "scripts": { + "jscs": "jscs *.js **/*.js", + "jshint": "jshint . --exclude node_modules --reporter node_modules/jshint-stylish/stylish.js", + "test": "npm run jscs && npm run jshint && tape test | tap-colorize" + } +} diff --git a/test/fixtures/cant-inline.css b/test/fixtures/cant-inline.css new file mode 100644 index 0000000..d888c50 --- /dev/null +++ b/test/fixtures/cant-inline.css @@ -0,0 +1,3 @@ +body { + background: url("./one"); +} diff --git a/test/fixtures/cant-inline.expected.css b/test/fixtures/cant-inline.expected.css new file mode 100644 index 0000000..d888c50 --- /dev/null +++ b/test/fixtures/cant-inline.expected.css @@ -0,0 +1,3 @@ +body { + background: url("./one"); +} diff --git a/test/fixtures/cant-rebase.css b/test/fixtures/cant-rebase.css new file mode 100644 index 0000000..0193b4d --- /dev/null +++ b/test/fixtures/cant-rebase.css @@ -0,0 +1,3 @@ +body { + background: url("./test"), url("./wot"); +} diff --git a/test/fixtures/cant-rebase.expected.css b/test/fixtures/cant-rebase.expected.css new file mode 100644 index 0000000..d5da8ce --- /dev/null +++ b/test/fixtures/cant-rebase.expected.css @@ -0,0 +1,3 @@ +body { + background: url("test"), url("wot"); +} \ No newline at end of file diff --git a/test/fixtures/imported/index.css b/test/fixtures/imported/index.css new file mode 100644 index 0000000..f513b71 --- /dev/null +++ b/test/fixtures/imported/index.css @@ -0,0 +1,3 @@ +.imported { + background: url("pixel.png"), url("../pixel.gif") +} diff --git a/test/fixtures/imported/pixel.png b/test/fixtures/imported/pixel.png new file mode 100644 index 0000000..e32226b Binary files /dev/null and b/test/fixtures/imported/pixel.png differ diff --git a/test/fixtures/inline-from.css b/test/fixtures/inline-from.css new file mode 100644 index 0000000..a67df6e --- /dev/null +++ b/test/fixtures/inline-from.css @@ -0,0 +1,3 @@ +body { + background: url("pixel.gif") +} diff --git a/test/fixtures/inline-from.expected.css b/test/fixtures/inline-from.expected.css new file mode 100644 index 0000000..468692b --- /dev/null +++ b/test/fixtures/inline-from.expected.css @@ -0,0 +1,3 @@ +body { + background: url("data:image/gif;base64,L1VzZXJzL01vT3gvU3luYy9EZXZlbG9wbWVudC9wb3N0Y3NzL3VybC90ZXN0L2ZpeHR1cmVzL3BpeGVsLmdpZg==") +} diff --git a/test/fixtures/inline-imported.css b/test/fixtures/inline-imported.css new file mode 100644 index 0000000..091c712 --- /dev/null +++ b/test/fixtures/inline-imported.css @@ -0,0 +1 @@ +@import "imported/index.css"; diff --git a/test/fixtures/inline-imported.expected.css b/test/fixtures/inline-imported.expected.css new file mode 100644 index 0000000..d3bc29c --- /dev/null +++ b/test/fixtures/inline-imported.expected.css @@ -0,0 +1,3 @@ +.imported { + background: url("data:image/png;base64,L1VzZXJzL01vT3gvU3luYy9EZXZlbG9wbWVudC9wb3N0Y3NzL3VybC90ZXN0L2ZpeHR1cmVzL2ltcG9ydGVkL3BpeGVsLnBuZw=="), url("data:image/gif;base64,L1VzZXJzL01vT3gvU3luYy9EZXZlbG9wbWVudC9wb3N0Y3NzL3VybC90ZXN0L2ZpeHR1cmVzL3BpeGVsLmdpZg==") +} diff --git a/test/fixtures/pixel.gif b/test/fixtures/pixel.gif new file mode 100644 index 0000000..46a2cf0 Binary files /dev/null and b/test/fixtures/pixel.gif differ diff --git a/test/fixtures/rebase-all-url-syntax.css b/test/fixtures/rebase-all-url-syntax.css new file mode 100644 index 0000000..b38debd --- /dev/null +++ b/test/fixtures/rebase-all-url-syntax.css @@ -0,0 +1,3 @@ +body { + background: url("./one"), url('./two'), url(./three), url(./four); +} diff --git a/test/fixtures/rebase-all-url-syntax.expected.css b/test/fixtures/rebase-all-url-syntax.expected.css new file mode 100644 index 0000000..3d05461 --- /dev/null +++ b/test/fixtures/rebase-all-url-syntax.expected.css @@ -0,0 +1,3 @@ +body { + background: url("test/fixtures/one"), url('test/fixtures/two'), url(test/fixtures/three), url(test/fixtures/four); +} \ No newline at end of file diff --git a/test/fixtures/rebase-imported.css b/test/fixtures/rebase-imported.css new file mode 100644 index 0000000..091c712 --- /dev/null +++ b/test/fixtures/rebase-imported.css @@ -0,0 +1 @@ +@import "imported/index.css"; diff --git a/test/fixtures/rebase-imported.expected.css b/test/fixtures/rebase-imported.expected.css new file mode 100644 index 0000000..1c03f95 --- /dev/null +++ b/test/fixtures/rebase-imported.expected.css @@ -0,0 +1,3 @@ +.imported { + background: url("imported/pixel.png"), url("pixel.gif") +} diff --git a/test/fixtures/rebase-to-from.css b/test/fixtures/rebase-to-from.css new file mode 100755 index 0000000..e0be252 --- /dev/null +++ b/test/fixtures/rebase-to-from.css @@ -0,0 +1,3 @@ +body { + background: url("./one"), url("./two"); +} diff --git a/test/fixtures/rebase-to-from.expected.css b/test/fixtures/rebase-to-from.expected.css new file mode 100755 index 0000000..eb86a3f --- /dev/null +++ b/test/fixtures/rebase-to-from.expected.css @@ -0,0 +1,3 @@ +body { + background: url("one"), url("two"); +} \ No newline at end of file diff --git a/test/fixtures/rebase-to-to-without-from.css b/test/fixtures/rebase-to-to-without-from.css new file mode 100755 index 0000000..188f349 --- /dev/null +++ b/test/fixtures/rebase-to-to-without-from.css @@ -0,0 +1,3 @@ +body { + background: url("./test/fixtures/one"), url("./test/fixtures/two"); +} diff --git a/test/fixtures/rebase-to-to-without-from.expected.css b/test/fixtures/rebase-to-to-without-from.expected.css new file mode 100755 index 0000000..a84e85e --- /dev/null +++ b/test/fixtures/rebase-to-to-without-from.expected.css @@ -0,0 +1,3 @@ +body { + background: url("test/fixtures/one"), url("test/fixtures/two"); +} diff --git a/test/fixtures/rebase-to-to.css b/test/fixtures/rebase-to-to.css new file mode 100755 index 0000000..e0be252 --- /dev/null +++ b/test/fixtures/rebase-to-to.css @@ -0,0 +1,3 @@ +body { + background: url("./one"), url("./two"); +} diff --git a/test/fixtures/rebase-to-to.expected.css b/test/fixtures/rebase-to-to.expected.css new file mode 100755 index 0000000..a84e85e --- /dev/null +++ b/test/fixtures/rebase-to-to.expected.css @@ -0,0 +1,3 @@ +body { + background: url("test/fixtures/one"), url("test/fixtures/two"); +} diff --git a/test/index.js b/test/index.js new file mode 100755 index 0000000..42e63cb --- /dev/null +++ b/test/index.js @@ -0,0 +1,49 @@ +var test = require("tape") + +var fs = require("fs") + +var url = require("..") +var postcss = require("postcss") + +function read(name) { + return fs.readFileSync("test/" + name + ".css", "utf8").trim() +} + +function compareFixtures(t, name, msg, opts, postcssOpts, plugin) { + opts = opts || {} + var pcss = postcss() + if (plugin) { + pcss.use(plugin()) + } + pcss.use(url(opts)) + var actual = pcss.process(read("fixtures/" + name), postcssOpts).css + var expected = read("fixtures/" + name + ".expected") + + // handy thing: checkout actual in the *.actual.css file + fs.writeFile("test/fixtures/" + name + ".actual.css", actual) + + t.equal(actual, expected, msg) +} + +test("rebase", function(t) { + var opts = {} + compareFixtures(t, "cant-rebase", "shouldn't rebase url if not info available") + compareFixtures(t, "rebase-to-from", "should rebase url to dirname(from)", opts, {from: "test/fixtures/transform.css"}) + compareFixtures(t, "rebase-to-to-without-from", "should rebase url to dirname(to)", opts, {to: "transform.css"}) + compareFixtures(t, "rebase-to-to", "should rebase url to dirname(to) even if from given", opts, {from: "test/fixtures/transform.css", to: "transform.css"}) + compareFixtures(t, "rebase-all-url-syntax", "should rebase url even if there is differentes types of quotes", opts, {from: "test/fixtures/transform.css", to: "transform.css"}) + + compareFixtures(t, "rebase-imported", "should rebase url of imported files", opts, {from: "test/fixtures/transform.css"}, require("postcss-import")) + + t.end() +}) + +test("inline", function(t) { + var opts = {url: "inline"} + compareFixtures(t, "cant-inline", "shouldn't inline url if not info available", opts) + compareFixtures(t, "inline-from", "should inline url from dirname(from)", opts, {from: "test/fixtures/transform.css"}) + + compareFixtures(t, "inline-imported", "should inline url of imported files", opts, {from: "test/fixtures/transform.css"}, require("postcss-import")) + + t.end() +})