diff --git a/README.md b/README.md index e54698b4..cc126f1a 100755 --- a/README.md +++ b/README.md @@ -126,23 +126,6 @@ Default: `[]` A string or an array of paths in where to look for files. -#### `transform` - -Type: `Function` -Default: `null` - -A function to transform the content of imported files. Take one argument (file - content) and should return the modified content or a resolved promise with it. -`undefined` result will be skipped. - -```js -transform: function(css) { - return postcss([somePlugin]).process(css).then(function(result) { - return result.css; - }); -} -``` - #### `plugins` Type: `Array` @@ -163,10 +146,12 @@ files). Type: `Function` Default: `null` -You can overwrite the default path resolving way by setting this option. -This function gets `(id, basedir, importOptions)` arguments and returns full -path, array of paths or promise resolving paths. -You can use [resolve](https://github.com/substack/node-resolve) for that. +You can provide a custom path resolver with this option. This function gets +`(id, basedir, importOptions)` arguments and should return a path, an array of +paths or a promise resolving to the path(s). If you do not return an absolute +path, your path will be resolved to an absolute path using the default +resolver. +You can use [resolve](https://github.com/substack/node-resolve) for this. #### `load` @@ -230,6 +215,13 @@ postcss() }) ``` +### jspm Usage + +postcss-import can `@import` [jspm](http://jspm.io) dependencies if +[`pkg-resolve`](https://www.npmjs.com/package/pkg-resolve) is installed by the +user. Run `npm install pkg-resolve` to install it. postcss-import should then be +able to import from jspm dependencies without further configuration. + ## `dependency` Message Support `postcss-import` adds a message to `result.messages` for each `@import`. Messages are in the following format: diff --git a/index.js b/index.js index e8a7deb8..4d3e945a 100755 --- a/index.js +++ b/index.js @@ -4,6 +4,7 @@ var postcss = require("postcss") var joinMedia = require("./lib/join-media") var resolveId = require("./lib/resolve-id") var loadContent = require("./lib/load-content") +var processContent = require("./lib/process-content") var parseStatements = require("./lib/parse-statements") var promiseEach = require("promise-each") @@ -62,6 +63,14 @@ function AtImport(options) { typeof options.addDependencyTo === "object" && typeof options.addDependencyTo.addDependency === "function" ) { + console.warn([ + "addDependencyTo is deprecated in favor of", + "result.messages.dependency; postcss-loader >= v1.0.0 will", + "automatically add your imported files to webpack's file watcher.", + "For more information, see", + "https://github.com/postcss/postcss-import\ + #dependency-message-support", + ].join("\n")) Object.keys(state.importedFiles) .forEach(options.addDependencyTo.addDependency) } @@ -227,11 +236,18 @@ function resolveImportId( : options.root return Promise.resolve(options.resolve(stmt.uri, base, options)) - .then(function(resolved) { - if (!Array.isArray(resolved)) { - resolved = [ resolved ] + .then(function(paths) { + if (!Array.isArray(paths)) { + paths = [ paths ] } + return Promise.all(paths.map(function(file) { + // Ensure that each path is absolute: + if (!path.isAbsolute(file)) return resolveId(file, base, options) + return file + })) + }) + .then(function(resolved) { // Add dependency messages: resolved.forEach(function(file) { result.messages.push({ @@ -261,6 +277,7 @@ function resolveImportId( }, []) }) .catch(function(err) { + if (err.message.indexOf("Failed to find") !== -1) throw err result.warn(err.message, { node: atRule }) }) } @@ -291,15 +308,6 @@ function loadImportContent( } return Promise.resolve(options.load(filename, options)) - .then(function(content) { - if (typeof options.transform !== "function") { - return content - } - return Promise.resolve(options.transform(content, filename, options)) - .then(function(transformed) { - return typeof transformed === "string" ? transformed : content - }) - }) .then(function(content) { if (content.trim() === "") { result.warn(filename + " is empty", { node: atRule }) @@ -314,11 +322,12 @@ function loadImportContent( return } - return postcss(options.plugins).process(content, { - from: filename, - syntax: result.opts.syntax, - parser: result.opts.parser, - }) + return processContent( + result, + content, + filename, + options + ) .then(function(importedResult) { var styles = importedResult.root result.messages = result.messages.concat(importedResult.messages) diff --git a/lib/process-content.js b/lib/process-content.js new file mode 100644 index 00000000..5a63a20b --- /dev/null +++ b/lib/process-content.js @@ -0,0 +1,61 @@ +var path = require("path") +var postcss = require("postcss") +var sugarss + +module.exports = function processContent( + result, + content, + filename, + options +) { + var plugins = options.plugins + var ext = path.extname(filename) + + var parserList = [] + + // SugarSS support: + if (ext === ".sss") { + if (!sugarss) { + try { + sugarss = require("sugarss") + } + catch (e) { + // Ignore + } + } + if (sugarss) return runPostcss(content, filename, plugins, [ sugarss ]) + } + + // Syntax support: + if (result.opts.syntax && result.opts.syntax.parse) { + parserList.push(result.opts.syntax.parse) + } + + // Parser support: + if (result.opts.parser) parserList.push(result.opts.parser) + // Try the default as a last resort: + parserList.push(null) + + return runPostcss(content, filename, plugins, parserList) +} + +function runPostcss( + content, + filename, + plugins, + parsers, + index +) { + if (!index) index = 0 + return postcss(plugins).process(content, { + from: filename, + parser: parsers[index], + }) + .catch(function(err) { + // If there's an error, try the next parser + index++ + // If there are no parsers left, throw it + if (index === parsers.length) throw err + return runPostcss(content, filename, plugins, parsers, index) + }) +} diff --git a/package.json b/package.json index 71a3963b..fe12ffa6 100644 --- a/package.json +++ b/package.json @@ -30,10 +30,8 @@ "eslint": "^1.10.3", "eslint-config-i-am-meticulous": "^2.0.0", "npmpub": "^3.0.1", - "postcss-scss": "^0.1.3" - }, - "optionalDependencies": { - "pkg-resolve": "^0.1.7" + "postcss-scss": "^0.1.3", + "sugarss": "^0.2.0" }, "jspm": { "name": "postcss-import", diff --git a/test/custom-resolve.js b/test/custom-resolve.js index 974bde58..b4453f67 100644 --- a/test/custom-resolve.js +++ b/test/custom-resolve.js @@ -1,5 +1,7 @@ import test from "ava" import compareFixtures from "./helpers/compare-fixtures" +import postcss from "postcss" +import atImport from ".." import path from "path" test.serial("should accept file", t => { @@ -43,3 +45,23 @@ test.serial("should accept promised array of files", t => { }, }) }) + +test( + "should apply default resolver when custom doesn't return an absolute path", + function(t) { + return postcss() + .use(atImport({ + resolve: path => { + return path.replace("foo", "imports/bar") + }, + load: p => { + t.is(p, path.resolve("fixtures/imports", "bar.css")) + return "/* comment */" + }, + })) + .process(`@import "foo.css";`, { from: "fixtures/custom-resolve-file" }) + .then(result => { + t.is(result.css, "/* comment */") + }) + } +) diff --git a/test/custom-syntax-parser.js b/test/custom-syntax-parser.js new file mode 100644 index 00000000..c14589e6 --- /dev/null +++ b/test/custom-syntax-parser.js @@ -0,0 +1,43 @@ +import test from "ava" +import scss from "postcss-scss" +import sugarss from "sugarss" +import compareFixtures from "./helpers/compare-fixtures" +import compareFixturesExt from "./helpers/compare-fixtures-ext" + +test("should process custom syntax", t => { + return compareFixtures(t, "scss-syntax", null, { + syntax: scss, + }) +}) + +test("should process custom syntax by parser", t => { + return compareFixtures(t, "scss-parser", null, { + parser: scss, + }) +}) + +test(".css importing .sss should work", t => { + return compareFixtures(t, "import-sss") +}) + +test(".sss importing .sss should work", t => { + return compareFixturesExt(t, "sugar", ".sss", null, { + parser: sugarss, + }) +}) + +test(".sss importing .css should work", t => { + return compareFixturesExt(t, "sugar-import-css", ".sss", null, { + parser: sugarss, + }) +}) + +test(".css importing .sss importing .css should work", t => { + return compareFixtures(t, "import-sss-css") +}) + +test(".sss importing .css importing .sss should work", t => { + return compareFixturesExt(t, "import-css-sss", ".sss", null, { + parser: sugarss, + }) +}) diff --git a/test/fixtures/import-css-sss.expected.css b/test/fixtures/import-css-sss.expected.css new file mode 100644 index 00000000..13de4796 --- /dev/null +++ b/test/fixtures/import-css-sss.expected.css @@ -0,0 +1,9 @@ +.sugarbar{ + color: blue +} + +import.sugarbar{} + +.sugar{ + color: white +} diff --git a/test/fixtures/import-css-sss.sss b/test/fixtures/import-css-sss.sss new file mode 100644 index 00000000..1af8c35a --- /dev/null +++ b/test/fixtures/import-css-sss.sss @@ -0,0 +1,4 @@ +@import "import-sugarbar.css" + +.sugar + color: white diff --git a/test/fixtures/import-sss-css.css b/test/fixtures/import-sss-css.css new file mode 100644 index 00000000..c9b32274 --- /dev/null +++ b/test/fixtures/import-sss-css.css @@ -0,0 +1 @@ +@import "foo-recursive.sss"; diff --git a/test/fixtures/import-sss-css.expected.css b/test/fixtures/import-sss-css.expected.css new file mode 100644 index 00000000..333f411a --- /dev/null +++ b/test/fixtures/import-sss-css.expected.css @@ -0,0 +1,5 @@ +bar{} + +foo.recursive{ + color: red +} diff --git a/test/fixtures/import-sss.css b/test/fixtures/import-sss.css new file mode 100644 index 00000000..52e99911 --- /dev/null +++ b/test/fixtures/import-sss.css @@ -0,0 +1 @@ +@import "sugarbar.sss"; diff --git a/test/fixtures/import-sss.expected.css b/test/fixtures/import-sss.expected.css new file mode 100644 index 00000000..f9db5d72 --- /dev/null +++ b/test/fixtures/import-sss.expected.css @@ -0,0 +1,3 @@ +.sugarbar { + color: blue +} diff --git a/test/fixtures/imports/foo-recursive.sss b/test/fixtures/imports/foo-recursive.sss new file mode 100644 index 00000000..0e24a764 --- /dev/null +++ b/test/fixtures/imports/foo-recursive.sss @@ -0,0 +1,4 @@ +@import "bar.css" + +foo.recursive + color: red diff --git a/test/fixtures/imports/import-sugarbar.css b/test/fixtures/imports/import-sugarbar.css new file mode 100644 index 00000000..32cbada9 --- /dev/null +++ b/test/fixtures/imports/import-sugarbar.css @@ -0,0 +1,3 @@ +@import "sugarbar.sss"; + +import.sugarbar{} diff --git a/test/fixtures/imports/sugarbar.sss b/test/fixtures/imports/sugarbar.sss new file mode 100644 index 00000000..602386df --- /dev/null +++ b/test/fixtures/imports/sugarbar.sss @@ -0,0 +1,2 @@ +.sugarbar + color: blue diff --git a/test/fixtures/sugar-import-css.expected.css b/test/fixtures/sugar-import-css.expected.css new file mode 100644 index 00000000..20df4744 --- /dev/null +++ b/test/fixtures/sugar-import-css.expected.css @@ -0,0 +1,4 @@ +bar{} +.sugar{ + color: white +} diff --git a/test/fixtures/sugar-import-css.sss b/test/fixtures/sugar-import-css.sss new file mode 100644 index 00000000..e9d794f5 --- /dev/null +++ b/test/fixtures/sugar-import-css.sss @@ -0,0 +1,3 @@ +@import "bar.css" +.sugar + color: white diff --git a/test/fixtures/sugar.expected.css b/test/fixtures/sugar.expected.css new file mode 100644 index 00000000..1e6583a1 --- /dev/null +++ b/test/fixtures/sugar.expected.css @@ -0,0 +1,6 @@ +.sugarbar { + color: blue +} +.sugar { + color: white +} diff --git a/test/fixtures/sugar.sss b/test/fixtures/sugar.sss new file mode 100644 index 00000000..58815ae3 --- /dev/null +++ b/test/fixtures/sugar.sss @@ -0,0 +1,3 @@ +@import "sugarbar.sss" +.sugar + color: white diff --git a/test/fixtures/transform-content.css b/test/fixtures/transform-content.css deleted file mode 100644 index 0a2e522a..00000000 --- a/test/fixtures/transform-content.css +++ /dev/null @@ -1 +0,0 @@ -@import "foo" diff --git a/test/fixtures/transform-content.expected.css b/test/fixtures/transform-content.expected.css deleted file mode 100644 index 44fe46f6..00000000 --- a/test/fixtures/transform-content.expected.css +++ /dev/null @@ -1 +0,0 @@ -transformed-content {} diff --git a/test/fixtures/transform-undefined.css b/test/fixtures/transform-undefined.css deleted file mode 100644 index 0a2e522a..00000000 --- a/test/fixtures/transform-undefined.css +++ /dev/null @@ -1 +0,0 @@ -@import "foo" diff --git a/test/fixtures/transform-undefined.expected.css b/test/fixtures/transform-undefined.expected.css deleted file mode 100644 index d2d19a3e..00000000 --- a/test/fixtures/transform-undefined.expected.css +++ /dev/null @@ -1 +0,0 @@ -foo{} diff --git a/test/helpers/compare-fixtures-ext.js b/test/helpers/compare-fixtures-ext.js new file mode 100644 index 00000000..de9a4291 --- /dev/null +++ b/test/helpers/compare-fixtures-ext.js @@ -0,0 +1,32 @@ +var fs = require("fs") +var postcss = require("postcss") +var assign = require("object-assign") +var atImport = require("../..") + +function read(name, ext) { + if (!ext) ext = ".css" + return fs.readFileSync("fixtures/" + name + ext, "utf8") +} + +module.exports = function(t, name, ext, opts, postcssOpts, warnings) { + opts = assign({ path: "fixtures/imports" }, opts) + return postcss(atImport(opts)) + .process(read(name, ext), postcssOpts || {}) + .then(function(result) { + var actual = result.css + var expected = read(name + ".expected") + // handy thing: checkout actual in the *.actual.css file + fs.writeFile("fixtures/" + name + ".actual.css", actual) + t.is(actual, expected) + if (!warnings) { + warnings = [] + } + result.warnings().forEach(function(warning, index) { + t.is( + warning.text, + warnings[index], + "unexpected warning: \"" + warning.text + "\"" + ) + }) + }) +} diff --git a/test/import-events.js b/test/import-events.js index 42468a2e..c6f6dbb7 100644 --- a/test/import-events.js +++ b/test/import-events.js @@ -25,34 +25,6 @@ test("should have a callback that returns an object" + }) }) -test("should have a callback shortcut for webpack", t => { - var files = [] - var webpackMock = { - addDependency: file => { - files.push(file) - }, - } - - return postcss() - .use(atImport({ - path: "fixtures/imports", - addDependencyTo: webpackMock, - })) - .process(readFileSync("fixtures/media-import.css"), { - from: "fixtures/media-import.css", - }) - .then(() => { - t.deepEqual( - files, - [ - resolve("fixtures/media-import.css"), - resolve("fixtures/imports/media-import-level-2.css"), - resolve("fixtures/imports/media-import-level-3.css"), - ] - ) - }) -}) - test("should add dependency message for each import", t => { return postcss() .use(atImport({ diff --git a/test/import.js b/test/import.js index 857ae69a..1c07717b 100644 --- a/test/import.js +++ b/test/import.js @@ -47,18 +47,14 @@ test("should not fail with absolute and local import", t => { }) }) -test("should output readable trace", t => { +test("should error when file not found", t => { + t.plan(1) var file = "fixtures/imports/import-missing.css" return postcss() .use(atImport()) .process(readFileSync(file), { from: file }) - .then(result => { - t.is( - result.warnings()[0].text, - /* eslint-disable max-len */ - "Failed to find 'missing-file.css'\n in [ \n " + path.resolve("fixtures/imports") + "\n ]" - /* eslint-enabme max-len */ - ) + .catch(err => { + t.truthy(err) }) }) diff --git a/test/plugins.js b/test/plugins.js index 040bdc69..55dea662 100644 --- a/test/plugins.js +++ b/test/plugins.js @@ -1,6 +1,5 @@ import test from "ava" import postcss from "postcss" -import scss from "postcss-scss" import atImport from ".." import compareFixtures from "./helpers/compare-fixtures" @@ -51,15 +50,3 @@ test("should remain silent when value is an empty array", () => { })) .process("") }) - -test("should process custom syntax", t => { - return compareFixtures(t, "scss-syntax", null, { - syntax: scss, - }) -}) - -test("should process custom syntax by parser", t => { - return compareFixtures(t, "scss-parser", null, { - parser: scss, - }) -}) diff --git a/test/transform.js b/test/transform.js deleted file mode 100644 index 5db74405..00000000 --- a/test/transform.js +++ /dev/null @@ -1,26 +0,0 @@ -import test from "ava" -import compareFixtures from "./helpers/compare-fixtures" - -test.serial("should accept content", t => { - return compareFixtures(t, "transform-content", { - transform: () => "transformed-content {}", - }) -}) - -test.serial("should accept promised content", t => { - return compareFixtures(t, "transform-content", { - transform: () => Promise.resolve("transformed-content {}"), - }) -}) - -test.serial("should ignore returned undefined", t => { - return compareFixtures(t, "transform-undefined", { - transform: () => undefined, - }) -}) - -test.serial("should ignore promised undefined", t => { - return compareFixtures(t, "transform-undefined", { - transform: () => Promise.resolve(undefined), - }) -})